B4A Library [B4X] NinjaData - Easy-to-use data structure with JSON input/output

wonder

Expert
Licensed User
TL;DR: Please test this B4X Class. Let me know if you find any bugs.

I've just finished (work-in-progress) developing a B4X data storage class (for my own needs).
It's a JSON-like object container that's loosely based on Python lists and dictionaries.

Can you guys give it a test drive? :)
GitLab repository: https://gitlab.com/brunowonder/ninjadata
Direct link: https://gitlab.com/brunowonder/ninjadata/-/archive/master/ninjadata-master.zip

How does it work:
- Initialize it with a data source (text or object)
- There are 4 main methods: get(), set(), push() and pop()
- Navigate the object like you would navigate a file system

Limitations:
- While you can add custom types (given by the Type keyword), class based objects are not supported (with the exception of NinjaData itself)
- Arrays (as opposed to Lists) of custom types are not supported (see section J in the example code).
- It's still kinda buggy (work-in-progress)

Comparison:
B4X:
'*** Vanilla B4X ***
'Example A
Dim permissions As List
permissions.Initialize
permissions.Add("read")
permissions.Add("write")
permissions.Add("execute")
Dim john As Map
john.Initialize
john.Put("Permissions", permissions)
Dim users As Map
users.Initialize
users.Put("John", john)
Dim system As Map
system.Initialize
system.Put("Users", users)
system.Put("Name", "Workstation")

'Example B:
Dim users As Map = system.Get("Users")
Dim john As Map = users.Get("John")
Dim permissions As List = john.Get("Permissions")
permissions.add("modify")
** VS. **
B4X:
'*** NinjaData ***
'Example A:
Dim System as NinjaData
system.Initialize($"
    {
        "Name": "Workstation",
        "Users": {
            "John": {
                "Permissions": [
                    "read",
                    "write",
                    "execute"
                ]
            }
        }
    }
"$)

'Example B:
system.Push("/Users/John/Permissions", "modify")

Let's have a look at the test module and its output for understanding what it does:
B4X:
'Non-UI application (console / server application)
#Region Project Attributes
    #CommandLineArgs:
    #MergeLibraries: True
#End Region

Sub Process_Globals
    Type Person(name As String, age As Int)
End Sub

Sub AppStart (Args() As String)
    'https://json.org/example.html
    Dim json_str = $"
    {
        "glossary": {
            "title": "example glossary",
            "GlossDiv": {
                "title": "S",
                "GlossList": {
                    "GlossEntry": {
                        "ID": "SGML",
                        "SortAs": "SGML",
                        "GlossTerm": "Standard Generalized Markup Language",
                        "Acronym": "SGML",
                        "Abbrev": "ISO 8879:1986",
                        "GlossDef": {
                            "para": "A meta-markup language, used to create markup languages such as DocBook.",
                            "GlossSeeAlso": ["GML", "XML"]
                        },
                        "GlossSee": "markup"
                    }
                }
            }
        }
    } "$ As String

    Dim src As NinjaData
    Dim dst As NinjaData

    'Load the object from a JSON string
    Log("A ===============================================")
    src.Initialize(json_str)
    Log(src.PrettyJSON)

    'Deep copy the 'src' object using the get command
    Log("B ===============================================")
    dst.Initialize(src.Get("/"))
    Log(dst.PrettyJSON)

    'Destroy the content of the 'src' object using the set command
    Log("C ===============================================")
    src.Set("/", Null)
    Log(src.PrettyJSON)

    'Deep copy the 'dst' object without using the get command
    Log("D ===============================================")
    src.Initialize(dst)
    Log(src.PrettyJSON)

    'Destroy the 'src' object
    Log("E ===============================================")
    src = Null
    Log(dst.PrettyJSON) 'The 'dst' object isn't affected

    'Let's have some fun
    Log("F ===============================================")
    dst.ShowWarnings = True
    Log(dst.Pop ("/glossary/GlossDiv/GlossList/GlossEntry/GlossDef/GlossSeeAlso/0"))
    Log(dst.Push("/glossary/GlossDiv/GlossList/GlossEntry/GlossDef/GlossSeeAlso", "EXTRA ITEM 1"))
    Log(dst.Push("/glossary/GlossDiv/GlossList/GlossEntry/GlossDef/GlossSeeAlso", Array As Float(1, 2, 3)))
    Log(dst.Get("/glossary/GlossDiv/GlossList/GlossEntry/GlossDef/GlossSeeAlso/2/0"))
    Log(dst.Pop("/glossary/GlossDiv/GlossList/GlossEntry/GlossDef/GlossSeeAlso/2/0"))
    Log(dst.Push("/glossary/GlossDiv/GlossList/GlossEntry/GlossDef/GlossSeeAlso/2", "EXTRA ITEM 2"))
    Log(dst.Set("/glossary/GlossDiv/GlossList/GlossEntry/GlossDef/GlossSeeAlso/2", "EXTRA ITEM 3"))
    Log(dst.Set("/glossary/GlossDiv/GlossList/GlossEntry/ID", "NEW VALUE 1"))
    Log(dst.Get("/glossary/GlossDiv/GlossList/GlossEntry/Bananas"))
    Log(dst.Pop("/glossary/GlossDiv/GlossList/GlossEntry/Bananas"))

    'Trigger some errors
    Log("G ===============================================")
    dst.IgnoreErrors = True
    Log(dst.Push("/glossary/GlossDiv/GlossList/GlossEntry/GlossDef/GlossSeeAlso/2/ok", "THIS WILL FAIL"))
    Log(dst.Push("/glossary/GlossDiv/GlossList/GlossEntry/GlossDef/", "THIS WILL FAIL"))
    Log(dst.Set("/glossary/GlossDiv/GlossList/GlossEntry/GlossDef/GlossSeeAlso/23/", "THIS WILL FAIL"))
    Log(dst.Get("/glossary/GlossDiv/GlossList/GlossEntry/GlossDef/GlossSeeAlso/23/"))
    Log(dst.PrettyJSON)

    'Use a custom type as an input source
    Log("H ===============================================")
    Dim p1 As Person
    p1.Initialize
    p1.name = "John"
    p1.age = 25

    Dim myObj As NinjaData
    myObj.Initialize(p1)
    Log(myObj.PrettyJSON)

    'Data complexity is preserved
    Log("I ===============================================")
    Dim p2 As Person
    p2.Initialize
    p2.name = "Kate"
    p2.age = 30

    Dim myObj As NinjaData
    myObj.Initialize("{}")
    myObj.Set("user", p2)
    Log(myObj.PrettyJSON)
    Log(GetType(myObj.Get("/user"))) 'Data type is preserved ($_person)
    Dim p3 = myObj.Get("/user") As Person
    Log(p3.name) 'Kate

    'Destroy data complexity
    myObj.ReloadAsJSONString 'Object is converted to JSON string and reloaded
    Log(GetType(myObj.Get("/user"))) 'Data type is destroyed (converted to Map)
    Try
        Dim p4 = myObj.Get("/user") As Person
        Log(p4.name) 'Error
    Catch
        LogError(LastException)
    End Try

    'Arrays of objects are NOT supported
    Log("J ===============================================")
    Dim arr(2) As Person
    arr(0) = p1
    arr(1) = p2

    'This doesn't work...
    Dim arr_persons As NinjaData
    arr_persons.Initialize(arr)
    Log(arr_persons.PrettyJSON)

    '...but this does!
    Dim lst_persons As NinjaData
    lst_persons.Initialize("[]")
    For Each p As Person In arr
        lst_persons.Push("/", p)
    Next
    Log(lst_persons.PrettyJSON)

    'Remove using pop
    Log("J ===============================================")
    Log(lst_persons.Pop("1"))
    Log(lst_persons.PrettyJSON)

    Log(dst.Pop("/glossary/GlossDiv"))
    Log(dst.PrettyJSON)

    Log(dst.Pop("/"))
    Log(dst.PrettyJSON)
End Sub

'Return true to allow the default exceptions handler to handle the uncaught exception.
Sub Application_Error (Error As Exception, StackTrace As String) As Boolean
    Return True
End Sub
Expected output:
B4X:
A ===============================================
{
    "glossary": {
        "title": "example glossary",
        "GlossDiv": {
            "GlossList": {
                "GlossEntry": {
                    "GlossTerm": "Standard Generalized Markup Language",
                    "GlossSee": "markup",
                    "SortAs": "SGML",
                    "GlossDef": {
                        "para": "A meta-markup language, used to create markup languages such as DocBook.",
                        "GlossSeeAlso": [
                            "GML",
                            "XML"
                        ]
                    },
                    "ID": "SGML",
                    "Acronym": "SGML",
                    "Abbrev": "ISO 8879:1986"
                }
            },
            "title": "S"
        }
    }
}
B ===============================================
{
    "glossary": {
        "title": "example glossary",
        "GlossDiv": {
            "GlossList": {
                "GlossEntry": {
                    "GlossTerm": "Standard Generalized Markup Language",
                    "GlossSee": "markup",
                    "SortAs": "SGML",
                    "GlossDef": {
                        "para": "A meta-markup language, used to create markup languages such as DocBook.",
                        "GlossSeeAlso": [
                            "GML",
                            "XML"
                        ]
                    },
                    "ID": "SGML",
                    "Acronym": "SGML",
                    "Abbrev": "ISO 8879:1986"
                }
            },
            "title": "S"
        }
    }
}
C ===============================================
null
D ===============================================
{
    "glossary": {
        "title": "example glossary",
        "GlossDiv": {
            "GlossList": {
                "GlossEntry": {
                    "GlossTerm": "Standard Generalized Markup Language",
                    "GlossSee": "markup",
                    "SortAs": "SGML",
                    "GlossDef": {
                        "para": "A meta-markup language, used to create markup languages such as DocBook.",
                        "GlossSeeAlso": [
                            "GML",
                            "XML"
                        ]
                    },
                    "ID": "SGML",
                    "Acronym": "SGML",
                    "Abbrev": "ISO 8879:1986"
                }
            },
            "title": "S"
        }
    }
}
E ===============================================
{
    "glossary": {
        "title": "example glossary",
        "GlossDiv": {
            "GlossList": {
                "GlossEntry": {
                    "GlossTerm": "Standard Generalized Markup Language",
                    "GlossSee": "markup",
                    "SortAs": "SGML",
                    "GlossDef": {
                        "para": "A meta-markup language, used to create markup languages such as DocBook.",
                        "GlossSeeAlso": [
                            "GML",
                            "XML"
                        ]
                    },
                    "ID": "SGML",
                    "Acronym": "SGML",
                    "Abbrev": "ISO 8879:1986"
                }
            },
            "title": "S"
        }
    }
}
F ===============================================
GML
[XML, EXTRA ITEM 1]
[XML, EXTRA ITEM 1, [F@1f32e575]
1.0
1.0
[2.0, 3.0, EXTRA ITEM 2]
[XML, EXTRA ITEM 1, EXTRA ITEM 3]
{GlossTerm=Standard Generalized Markup Language, GlossSee=markup, SortAs=SGML, GlossDef={para=A meta-markup language, used to create markup languages such as DocBook., GlossSeeAlso=[XML, EXTRA ITEM 1, EXTRA ITEM 3]}, ID=NEW VALUE 1, Acronym=SGML, Abbrev=ISO 8879:1986}
null
[WARNING] NinjaData: Key 'Bananas' not found - Null object returned.
[WARNING] NinjaData: Key 'Bananas' not found - Null object returned.
null
G ===============================================
[ERROR] NinjaData: Target object (String) is not a List!
null
[ERROR] NinjaData: Target object (Map) is not a List!
null
[ERROR] NinjaData: Index #23 out-of-bounds (list size is 3)!
[XML, EXTRA ITEM 1, EXTRA ITEM 3]
[WARNING] NinjaData: Index #23 out-of-bounds (list size is 3) - Null object returned.
null
{
    "glossary": {
        "title": "example glossary",
        "GlossDiv": {
            "GlossList": {
                "GlossEntry": {
                    "GlossTerm": "Standard Generalized Markup Language",
                    "GlossSee": "markup",
                    "SortAs": "SGML",
                    "GlossDef": {
                        "para": "A meta-markup language, used to create markup languages such as DocBook.",
                        "GlossSeeAlso": [
                            "XML",
                            "EXTRA ITEM 1",
                            "EXTRA ITEM 3"
                        ]
                    },
                    "ID": "NEW VALUE 1",
                    "Acronym": "SGML",
                    "Abbrev": "ISO 8879:1986"
                }
            },
            "title": "S"
        }
    }
}
H ===============================================
{
    "name": "John",
    "IsInitialized": "true",
    "age": "25"
}
I ===============================================
{
    "user": {
        "name": "Kate",
        "IsInitialized": "true",
        "age": "30"
    }
}
b4j.example.main$_person
Kate
anywheresoftware.b4a.objects.collections.Map$MyMap
(ClassCastException) java.lang.ClassCastException: anywheresoftware.b4a.objects.collections.Map$MyMap cannot be cast to b4j.example.main$_person
J ===============================================
[
    "ERROR: Unsupported array type (_person)"
]
[
    {
        "name": "John",
        "IsInitialized": "true",
        "age": "25"
    },
    {
        "name": "Kate",
        "IsInitialized": "true",
        "age": "30"
    }
]
J ===============================================
[IsInitialized=true, name=Kate, age=30
]
[
    {
        "name": "John",
        "IsInitialized": "true",
        "age": "25"
    }
]
{GlossList={GlossEntry={GlossTerm=Standard Generalized Markup Language, GlossSee=markup, SortAs=SGML, GlossDef={para=A meta-markup language, used to create markup languages such as DocBook., GlossSeeAlso=[XML, EXTRA ITEM 1, EXTRA ITEM 3]}, ID=NEW VALUE 1, Acronym=SGML, Abbrev=ISO 8879:1986}}, title=S}
{
    "glossary": {
        "title": "example glossary"
    }
}
{glossary={title=example glossary}}
null
 
Last edited:

sorex

Expert
Licensed User
I don't really get what there is to crack as the output is still plain text?
Maybe I'm missing the point of it.

Your other 'impossible to crack' thingy was more interesting than this.
 

wonder

Expert
Licensed User
@sorex, by "breaking" I meant "crash it and/or find any bugs" with it... :D :) Sorry for the misunderstanding.

So in other words, can you guys give it a "test drive" and see if it's stable enough? It's very easy to miss the corner cases in this kind of library...
 

wonder

Expert
Licensed User
Hi!

i have a question, why like this:

B4X:
Select True
        Case t == "[i"
and not like this:

B4X:
Select t
        Case "[i"
If that’s where I’m thinking of, then the reason was probably a copy-paste error and being too sleepy to realize it. :D
Thanks!! I’ll fix it soon. :)
 

wonder

Expert
Licensed User
I have been working with JSON for a few months now. What use is the library?
Maybe I need the library if I understand what I can use it for. 😅
I created this library for myself because I work a lot with JSON in Python.
It makes data more "pythonic" to work with. 😂
In the end, it's a matter of syntax, it doesn't add anything to B4X.
 
Top