B4J Question How to know if string is parseable as JSON "list" or "Map"

jmon

Well-Known Member
Licensed User
Longtime User
Hi,

I would like to be able to determine if a JSON string will be parseable as a LIST or as a MAP before processing it.

Am I missing something, or is the only way to know is to test the first character?
"[" = LIST
"{" = MAP

code:
Dim json As JSONParser
json.Initialize(StdOut)

If json.IsInitialized Then
    If StdOut.StartsWith("[") Then
        For Each Program As Map In json.NextArray
            'processing as a list of map
        Next

    Else
        Dim Program As Map = json.NextObject
        'processing as a map

    End If        
End If

Any other idea?

Thanks
 

Magma

Expert
Licensed User
Longtime User
It's a way (selecting between "[" or "{")... if you have only between those two to "select"..

B4X:
Dim list1 As List
list1.Initialize
list1.Add("AAA")
list1.Add("BBB")

Dim map1 As Map
map1.Initialize
map1.Put("field1","aaa")
map1.Put("field1","bbb")

Dim list2 As List
list2.Initialize
list2.Add(map1)

Log("this is the list")
Log(list1.As(JSON).ToString)
Log("this is the map")
Log(map1.As(JSON).ToString)
Log("this is the 2nd list")
Log(list2.As(JSON).ToString)

ps: I ve create this to remember - how starting...? :)

I am sure that you asking because you want to pass back the values from string to list or map... right ?
 
Last edited:
Upvote 0

Magma

Expert
Licensed User
Longtime User
...you can turn this to a sub...

B4X:
Dim aaa As String=map1.As(JSON).ToString

Dim tryit As Boolean=False

Try
    Dim listormap1 As List=aaa.As(JSON).tolist
    Log ("it is a list...")
Catch
    tryit=True
    Log ("it isn't list...")
End Try
If tryit=True Then
    Try
        tryit=False
        Dim listormap2 As Map=aaa.As(JSON).Tomap
        Log ("it is a map...")
    Catch
        tryit=True
        Log ("it isn't map...")
    End Try
End If
If tryit=True Then Log("It is something different")
 
Upvote 0

aeric

Expert
Licensed User
Longtime User
B4X:
Sub AppStart (Args() As String)
    Dim str1 As String = $"{"a": 3}"$
    Log( "str1 is Map? " & IsMap(str1) )
    Log( "str1 is List? " & IsList(str1) )
    
    Dim str2 As String = $"["a", 3]"$
    Log( "str2 is Map? " & IsMap(str2) )
    Log( "str2 is List? " & IsList(str2) )
    
    Dim str3 As String = $"{"x": ["a", 3]}"$
    Log( "str3 is Map? " & IsMap(str3) )
    Log( "str3 is List? " & IsList(str3) )
    
    Dim str4 As String = $"[{"a": 2}, {"3": "y"}]"$
    Log( "str4 is Map? " & IsMap(str4) )
    Log( "str4 is List? " & IsList(str4) )
End Sub

Sub IsMap (str As String) As Boolean
    Try
        Dim M As Map = str.As(JSON).ToMap
        If M.IsInitialized Then
            'Log("Map initialized")
            'Log(M.Size)
            Return True
        End If
    Catch
        'Log(LastException)
        Return False
    End Try
    Return False
End Sub

Sub IsList (str As String) As Boolean
    Try
        Dim L As List = str.As(JSON).ToList
        If L.IsInitialized Then
            'Log("List initialized")
            'Log(L.Size)
            Return True
        End If
    Catch
        'Log(LastException)
        Return False
    End Try
    Return False
End Sub

Bash:
str1 is Map? true
str1 is List? false
str2 is Map? false
str2 is List? true
str3 is Map? true
str3 is List? false
str4 is Map? false
str4 is List? true
 
Upvote 0

aeric

Expert
Licensed User
Longtime User
A cleaner version:

B4X:
Sub IsMap (str As String) As Boolean
    Try
        Dim M As Map = str.As(JSON).ToMap
        Return M.IsInitialized
    Catch
        Return False
    End Try
End Sub

Sub IsList (str As String) As Boolean
    Try
        Dim L As List = str.As(JSON).ToList
        Return L.IsInitialized
    Catch
        Return False
    End Try
End Sub

B4X:
Dim str5 As String = "{}"
Log( "str5 is Map? " & IsMap(str5) )
Log( "str5 is List? " & IsList(str5) )

Dim str6 As String = "[]"
Log( "str6 is Map? " & IsMap(str6) )
Log( "str6 is List? " & IsList(str6) )

Bash:
str5 is Map? true
str5 is List? false
str6 is Map? false
str6 is List? true
 
Last edited:
Upvote 0

aeric

Expert
Licensed User
Longtime User
B4X:
    Dim str7 As String = "{,}"
    Log( "str7 is Map? " & IsMap(str7) )
    Dim str8 As String = "{:}"
    Log( "str8 is Map? " & IsMap(str8) )
    
    Dim str9 As String = "[,]"
    Log( "str9 is List? " & IsList(str9) )
    Dim str10 As String = "[:]"
    Log( "str10 is List? " & IsList(str10) )

Bash:
str7 is Map? false
str8 is Map? false
str9 is List? false
str10 is List? false
 
Upvote 0

jmon

Well-Known Member
Licensed User
Longtime User
Thanks for your answers. I always thought it was a bad idea to have many try/catches as it was inefficient?

The code that I'm running is a jshell that executes a PowerShell every 5 seconds (gets a list of processes). The output of this PowerShell is always a JSON but sometimes it's a list of maps because the PowerShell found many items, and sometimes it's a map because it found only a single item.

Because it's running every 5 seconds I was looking for the most efficient way of coding that.

I think that so far, testing the first character is the simplest, but I'm wondering if it's bullet proof
 
Upvote 0

aeric

Expert
Licensed User
Longtime User
testing the first character is the simplest, but I'm wondering if it's bullet proof
I don’t think so. I used to think the same when I started with B4X and JSON but after long time understanding json I think I was wrong.
You may have string with single { only and many other invalid patterns.
 
Upvote 0

TILogistic

Expert
Licensed User
Longtime User
?
check pattern and length for valid json
B4X:
Public Sub IsJson(Json As String) As Boolean
    Dim Pattern As String = $"[{\[]{1}([,:{}\[\]0-9.\-+Eaeflnr-u \n\r\t]|".*?")+[}\]]{1}"$
    Return Regex.IsMatch(Pattern, Json.Trim) And Json.Length > 2
End Sub

And

and validate the first and last characters of the json string for list or map
or use Try cash
 
Upvote 0

emexes

Expert
Licensed User
The output of this PowerShell is always a JSON but sometimes it's a list of maps because the PowerShell found many items, and sometimes it's a map because it found only a single item.

Does it help to wrap the JSON in brackets? eg:

B4X:
Dim S As String = JsonFromPowerShell
Log(S)

Dim jp As JSONParser
jp.Initialize("[" & S & "]")   

Dim jpl As List = jp.NextArray
Log(jpl)

although maybe that's just kicking the can down the road 🤔

ie sooner or later you're going to have to distinguish between the two styles that can be returned by PowerShell.
 
Upvote 0

emexes

Expert
Licensed User
The output of this PowerShell is always a JSON but sometimes it's a list of maps because the PowerShell found many items, and sometimes it's a map because it found only a single item.

Or how about? :

B4X:
Dim S As String = JsonFromPowerShell
Log(S)

S = S.Trim

If S.Length = 0 Then
    Log("json?  what json?")
ElseIf S.StartsWith("[") and S.EndsWith("]") Then    'to be sure, to be sure  🍻
    'no worries, is already array (containing multiple elements)
ElseIf S.StartsWith("{") And S.EndsWith("}") Then
    S = "[" & S & "]"    'convert single element into array (containing that one element)
Else
    Log("don't look like no JSON to me")
End If

Dim jp As JSONParser
jp.Initialize(S)

Dim jpl As List
jpl.Initialize    'probably not necessary (if S.Length <> 0) but... slow and steady, wins the race 🏆
If S.Length <> 0 Then    'may as well make it work for zero-element case too
    jpl = jp.NextArray
End If
Log(jpl)
 
Last edited:
Upvote 0

emexes

Expert
Licensed User
The output of this PowerShell is always a JSON but sometimes it's a list of maps because the PowerShell found many items, and sometimes it's a map because it found only a single item.

Or even how about? :

https://learn.microsoft.com/en-us/p...y/convertto-json?view=powershell-7.2#-asarray

1665027607200.png


( an old boss of mine would say: fix the problem, not the symptom 🍻 )
 
Upvote 0

TILogistic

Expert
Licensed User
Longtime User
?
1. - Valid Json
B4X:
Public Sub IsJson(Json As String) As Boolean
    Dim Pattern As String = $"[{\[]{1}([,:{}\[\]0-9.\-+Eaeflnr-u \n\r\t]|".*?")+[}\]]{1}"$
    Return Regex.IsMatch(Pattern, Json.Trim) And Json.Length > 2
End Sub
2.- Process the Json (List or Map)
Note:
"[]" = LIST
"{}" = MAP
B4X:
    'This json string when converted is transformed into map
    Dim sJson As String = $"{"Monday": 2, "Thursday": 5, "Friday": 6, "Sunday": 1, "Wednesday": 4, "Tuesday": 3, "Saturday": 7 }"$

    'You can convert to Map or List (Example to List)
    Dim IsLIst As Boolean = IIf(sJson.StartsWith("[") And sJson.EndsWith("]"), True, False)
    Dim lstJson As List = IIf(IsLIst, sJson.As(JSON).ToList, ("[" & sJson & "]").As(JSON).ToList)
    Log(lstJson.As(JSON).ToString)
1665031908583.png
 
Last edited:
Upvote 0

alwaysbusy

Expert
Licensed User
Longtime User
@TILogistic I do like your idea of encapsulating the json string between "[" "]". If you would do it always, no matter the json string (still needs to be valid of course), you are sure that you can always use .NextArray as the first call.

B4X:
Dim sJson As String = $"[{"test": "test"}]"$
 
sJson = "[" & sJson & "]"
 
Dim json As JSONParser
json.Initialize(sJson)
 
dim lst as List = json.NextArray
dim obj as Object = lst.get(0)
if obj is Map then

end if
if obj is List then

end if

EDIT: just read that was already suggested by @emexes
 
Last edited:
Upvote 0

TILogistic

Expert
Licensed User
Longtime User

Valid Errors:
[{"Monday": 2, ..... "Saturday": 7 }
or
{"Monday": 2, "Thursday":, ..... "Saturday": 7 }

B4X:
    Dim sJson As String = $"[{"Monday": 2, "Thursday": 5, "Friday": 6, "Sunday": 1, "Wednesday": 4, "Tuesday": 3, "Saturday": 7 }"$
   
    Try
        'You can convert to Map or List (Example to List)
        Dim IsLIst As Boolean = IIf(sJson.StartsWith("[") And sJson.EndsWith("]"), True, False)
        Dim lstJson As List = IIf(IsLIst, sJson.As(JSON).ToList, ("[" & sJson & "]").As(JSON).ToList)
        Log(lstJson.As(JSON).ToString)
    Catch
        Log("Error: Json Not Valid")
    End Try
1665035918525.png
 
Upvote 0
Top