B4J Question [SOLVED] Altered order when generating json from a map

mc73

Well-Known Member
Licensed User
Longtime User
Hi, I'm generating a json from a map, however the final json though absolutely valid, loses the order of the items either in or out an object. This can be a bit problematic if a user decides to manually edit the json file, so it would be nice to have a solution. I will implement a function to re-organise the json, however in the mean time I'm really curious about why this may be happening.

Can anyone with a better vision on this, present the cause?

Here's the log of the original map:


[CODE lang="b4x" title="Map"](MyMap) {Description=test description, Input Port=BMT 1, Output Port=BMT 1, Sequences=[{Description=test desc, Active=Yes, Channel=0, Message Type=CC, Control Number=10, Value From=0, Value To=127, Diff From=0, Diff To=1, Operator=Or, Steps=[{Type=Keystrokes, Active=Yes, Keys=t e s t }]}]}[/CODE]

And here's the json generated by the JSONGenerator:
[CODE lang="json" title="JSON"]{
"Sequences": [
{
"Operator": "Or",
"Steps": [
{
"Type": "Keystrokes",
"Active": "Yes",
"Keys": "t e s t "
}
],
"Active": "Yes",
"Diff From": "0",
"Description": "test desc",
"Value To": "127",
"Diff To": "1",
"Channel": "0",
"Value From": "0",
"Control Number": "10",
"Message Type": "CC"
}
],
"Description": "test description",
"Input Port": "BMT 1",
"Output Port": "BMT 1"
}[/CODE]
 
Hi, I'm generating a json from a map, however the final json though absolutely valid, loses the order of the items either in or out an object. This can be a bit problematic if a user decides to manually edit the json file, so it would be nice to have a solution. I will implement a function to re-organise the json, however in the mean time I'm really curious about why this may be happening.

Can anyone with a better vision on this, present the cause?

Here's the log of the original map:


[CODE lang="b4x" title="Map"](MyMap) {Description=test description, Input Port=BMT 1, Output Port=BMT 1, Sequences=[{Description=test desc, Active=Yes, Channel=0, Message Type=CC, Control Number=10, Value From=0, Value To=127, Diff From=0, Diff To=1, Operator=Or, Steps=[{Type=Keystrokes, Active=Yes, Keys=t e s t }]}]}[/CODE]

And here's the json generated by the JSONGenerator:
[CODE lang="json" title="JSON"]{
"Sequences": [
{
"Operator": "Or",
"Steps": [
{
"Type": "Keystrokes",
"Active": "Yes",
"Keys": "t e s t "
}
],
"Active": "Yes",
"Diff From": "0",
"Description": "test desc",
"Value To": "127",
"Diff To": "1",
"Channel": "0",
"Value From": "0",
"Control Number": "10",
"Message Type": "CC"
}
],
"Description": "test description",
"Input Port": "BMT 1",
"Output Port": "BMT 1"
}[/CODE]
Take a look at this post from Erel here
 
Upvote 0
Take a look at this post from Erel here
Thank you very much Walter.
I don't like inserting another level of complexity in the json, so either I'll leave it as is, or try a manual generator. I will update if I come up with a good one :)
 
Upvote 0
So, I've made a custom generator, it works for me, however I haven't thoroughly tested it.
[CODE lang="b4x" title="Custom JSON Generator for keeping the order of objects"]Sub customJson(myMap As Map) As String
Return appendMap(myMap)
End Sub

Sub appendMap(myMap As Map) As String
Private json As StringBuilder
json.Initialize
json.Append("{"&CRLF)
For Each key As String In myMap.Keys
Private myObject As Object=myMap.Get(key)
json.Append(appendObject(key,myObject))
Next
Private jsonString As String=json.ToString
If jsonString.EndsWith(","&CRLF) Then
jsonString=jsonString.SubString2(0,jsonString.Length-2)
End If
jsonString=jsonString&CRLF&"}"
Return jsonString
End Sub

Sub appendList(myList As List) As String
Private json As StringBuilder
json.Initialize
json.Append("["&CRLF)
For Each myObject As Object In myList
json.Append(appendObject("",myObject))
Next
Private jsonString As String=json.ToString
If jsonString.EndsWith(","&CRLF) Then
jsonString=jsonString.SubString2(0,jsonString.Length-2)
End If
jsonString=jsonString&CRLF&"]"
Return jsonString
End Sub

Sub appendObject(key As String,myObject As Object) As String
Private json As StringBuilder
json.Initialize
If key.Length>0 Then
json.Append(escapeJSONChars(key)&": ")
End If
If myObject Is List Then
Private lst As List=myObject
json.Append(appendList(lst))
Else if myObject Is Map Then
Private anotherMap As Map=myObject
json.Append(appendMap(anotherMap))
Else
json.Append(escapeJSONChars(myObject))
End If
json.Append(","&CRLF)
Return json.ToString
End Sub

Sub escapeJSONChars (text As String) As String
Return Chr(34)&text.Replace("\","\\").Replace(Chr(34),"\"&Chr(34)).Replace(CRLF,"\n").Replace(Chr(13),"\r").Replace(TAB,"\t").Replace("/","\/")&Chr(34)
End Sub[/CODE]
 
Upvote 0
Hey don't think your code guarantees that it will save in the correct order (for maps). May be try with an orderedmap

 
Upvote 0
You can use this if you have a single level Map e.g. a config file.

B4X:
Sub AppStart (Args() As String)   
    Dim Config As Map
    Config.Initialize
    
    Config.Put("audit_web",True)
    Config.Put("ts_host","localhost")
    Config.Put("web_ignore_ext",".js|.css|.aspx")
    Config.Put("commas","1,2,3")
    Config.Put("audit_ptl", True)
    Config.Put("ts_port", 9090)   
    
    Log(MapToSortedJSONString(Config))
End Sub


Sub MapToSortedJSONString(Input As Map) As String   
    Dim Cleaned As String = Input.As(JSON).ToString
    
    Cleaned = Cleaned.SubString2(1,Cleaned.Length-1) ' Remove braces
    Cleaned = Cleaned.Trim 'Trim any empty space
    
    Dim Values As List
    Values.Initialize
    
    For Each Value As String In Regex.split(","&Chr(10),Cleaned) ' (JSON).ToString uses Chr(10) for new lines and not CRLF
        Values.Add(Value.Trim) ' Trim each line
    Next
    
    Values.Sort(True)
        
    Dim Glue As String = ","&CRLF
    Dim Indent As String = "    "
    
    Dim sb As StringBuilder
    sb.Initialize
    For Each s As String In Values
        sb.Append(Indent).Append(s).Append(Glue)
    Next
    If sb.Length > 0 Then sb.Remove(sb.Length - Glue.Length, sb.Length)
    
    Return $"{${CRLF}${sb.ToString}${CRLF}}"$
End Sub
 
Upvote 0
Hey don't think your code guarantees that it will save in the correct order (for maps). May be try with an orderedmap

Hi, in this particular project the forEach in map.keys works well without the need of a b4xOrdersMap, but yes, it's true that if one tries to use this code in a more general project it may fail.
 
Upvote 0
You can use this if you have a single level Map e.g. a config file.

B4X:
Sub AppStart (Args() As String)  
    Dim Config As Map
    Config.Initialize
   
    Config.Put("audit_web",True)
    Config.Put("ts_host","localhost")
    Config.Put("web_ignore_ext",".js|.css|.aspx")
    Config.Put("commas","1,2,3")
    Config.Put("audit_ptl", True)
    Config.Put("ts_port", 9090)  
   
    Log(MapToSortedJSONString(Config))
End Sub


Sub MapToSortedJSONString(Input As Map) As String  
    Dim Cleaned As String = Input.As(JSON).ToString
   
    Cleaned = Cleaned.SubString2(1,Cleaned.Length-1) ' Remove braces
    Cleaned = Cleaned.Trim 'Trim any empty space
   
    Dim Values As List
    Values.Initialize
   
    For Each Value As String In Regex.split(","&Chr(10),Cleaned) ' (JSON).ToString uses Chr(10) for new lines and not CRLF
        Values.Add(Value.Trim) ' Trim each line
    Next
   
    Values.Sort(True)
       
    Dim Glue As String = ","&CRLF
    Dim Indent As String = "    "
   
    Dim sb As StringBuilder
    sb.Initialize
    For Each s As String In Values
        sb.Append(Indent).Append(s).Append(Glue)
    Next
    If sb.Length > 0 Then sb.Remove(sb.Length - Glue.Length, sb.Length)
   
    Return $"{${CRLF}${sb.ToString}${CRLF}}"$
End Sub
Hi, thank you. Unfortunately the map I work on has more levels inside.
 
Upvote 0
Note that in B4X CRLF is in fact the chr(10).
Thanks for pointing this out, I had never noticed this.

I always assumed it was Chr(13) + Chr(10) since the name is CRLF

The reason I usually replace Chr(10) with Chr(13) & Chr(10) is that some versions of Notepad on Windows wont show the new lines correctly.

I never noticed this (as I use Notepad++) until a customer pointed out that it was difficult to update a config file as it was all on one line.

B4X:
ConfigString = ConfigString.Replace(Chr(10), Chr(13) & Chr(10))
 
Upvote 0
Back
Top