Home AMX User Forum NetLinx Studio

Released: JSON Library

Ground up build of a JSON all-in-one library. Read it, modify it, write it. Heavily commented, completely configurable in scale.
https://github.com/sentry07/netlinx-libJSON

Releasing this to help anyone out that needs to get as close to full JSON integration as possible, and also in hopes that people will contribute to it to make a better library for everybody. Feel free to use, update, suggest changes, etc. If you have any problems with it, please let me know. I've been using it for a while now and it's been pretty solid. Testing on an NI-3100 and it's a pretty low CPU hit.

Running JSON_ParseObject on a JSON string will validate it first, then parse it into a _JSON_Object structure variable. The structure has an array of Key:Value pairs that also give you the type of value (string, number, array, etc). JSON_ObjectToString takes a _JSON_Object and returns it as a string for writing to files or whatever. And there's a load of other supporting functions in there for setting values, deleting keys, and what not.

Comments

  • JohnMichnrJohnMichnr Posts: 279

    OK - I've been going through the various JSON libraries on the forums, for parsing that is, and I have a simple question. How do you know that you have a complete JSON object to send it up to the various parsing functions.

    I'm working with a Barco projector, over serial not HTTP so I don't have a content length header to parse and tell me how long the string is.

    It seems like all I can do is go through the buffer, counting the number of curly braces, and when the number of "}" equals the number of "{" then my string is complete.

    Is that what people are doing?

  • viningvining Posts: 4,368
    edited March 2019

    Sounds like your only option. I would just advance the start seach pointer on every find string. Having the http header content length would be easier but you need a function that finds matching braces anyway.

  • sentry07sentry07 Posts: 77

    I have a JSON_Validate function in my library for that purpose. It goes over the input string and will count all the open and close brackets and make sure the string is valid JSON. The JSON_ParseObject function calls it first before starting the parse. If it reaches the end of the string and doesn't have a zero sum on everything, it returns FALSE and the reason why will be returned in the cCleaned parameter. The problem is if it has a whole object and half of another, it would fail.

    It's possible to write a function that would search over a buffer and spit back complete JSON strings, removing them from the buffer. It would basically be the same as the JSON_Validate function, except at the end of each loop, it would check to see if all the counts equal zero, do a REMOVE_STRING and return TRUE. You could call it in a WHILE in your STRING: event or a timeline event.

  • sentry07sentry07 Posts: 77

    Ok, if you check the Github repo again, I've updated the library. I changed JSON_ParseObject to JSON_ParseValidObject and removed the JSON_Validate call from that function so that we can save some CPU usage. There's a new JSON_ParseObject that calls JSON_Validate first and then JSON_ParseValidObject.

    I added JSON_FindObjectInString. That will traverse a string and if it finds a valid JSON object, it will remove it from the input string and return it. Since this function is basically a copy of JSON_Validate, we know the JSON is valid and then you can call JSON_ParseValidString with the return. Here's an example string event:

    STRING:
    {
        STACK_VAR CHAR cJSONString[500]               // Object found in buffer
        STACK_VAR _JSON_Object jObject                // Parsed JSON object
        STACK_VAR INTEGER F1
    
        // cJSONBuffer is a global variable
        cJSONBuffer = "cJSONBuffer,DATA.TEXT"
        SEND_STRING 0,"cJSONBuffer"
    
        WHILE (JSON_FindObjectInString(cJSONBuffer,cJSONString))
        {
            JSON_ParseValidObject(cJSONString,jObject)
            FOR (F1 = 1; F1 <= jObject.Count; F1++)
            {
                SEND_STRING 0,"'Key [',ITOA(F1),']: ',jObject.KV[F1].K,'; Value: ',jObject.KV[F1].V"
            }
        }
    }
    
  • KenKen Posts: 19

    Is this library able to handle reading in nested json arrays into a format that can be input into a structure?

    Example usage: room config

    {
        "uRoom": [
            {
                "sName": "B48",
                "lTimeout": 108000,
                "nWarmupTimer": 90,
                "nCoolDownTimer": 60,
                "Display1": [
                    {
                        "sName": "NEC P451W",
                        "sDefaultSource": "HDMI,1",
                        "IpComms": [
                            {
                                "sIPAddress": "192.168.0.100",
                                "nPort:" 23
                                "nProtocal": 1
                            }
                        ]
                    }
                ]
            }
        ]
    }
    

    I played around a little but couldn't figure out a clear way.

  • sentry07sentry07 Posts: 77

    There's not really a good way to handle this in Netlinx. You can parse all the information out of that JSON object manually and put it in a structure. I'm going this in a job I'm working on now. I parse the JSON object, do a FOR loop for every key in the object, and then handle the data based on what the key is.

    Something that might work is to add another field to the KV structure that stores the location of the data and do a full parse of the JSON object. Then have a function search for something like 'uRoom[1].nWarmupTimer'.

  • sentry07sentry07 Posts: 77

    Here is my code for parsing a pretty complicated JSON file for system presets.

    DEFINE_FUNCTION INTEGER ReadSystemConfig (CHAR cFileName[])
    {
        STACK_VAR INTEGER F1
        STACK_VAR INTEGER F2
        STACK_VAR _JSON_Object jConfig
        STACK_VAR _JSON_Object jTempAudio
        STACK_VAR _JSON_Object jTempMics
        STACK_VAR _JSON_Object jTempMic
        STACK_VAR _JSON_Object jTempSpeakers
        STACK_VAR _JSON_Object jTempSpeaker
        STACK_VAR _JSON_Object jTempCameras
        STACK_VAR _JSON_Object jTempCamera
        STACK_VAR CHAR cTemp[5000]
        STACK_VAR CHAR cMic[20]
        STACK_VAR _JSON_Array jSwitcherConfig
        STACK_VAR _JSON_Array jMicCrosspoints
        STACK_VAR _JSON_Array jQuads
    
        ReadFileToString("'/presets/',cFileName,'.json'",cTemp)
        IF (JSON_ParseObject(cTemp,jConfig))
        {
            IF (JSON_HasKey(jConfig,'switcher'))                                            // Load switcher settings
            {
                SEND_STRING 0,"'Switcher'"
                JSON_ParseArray(JSON_GetValue(jConfig,'switcher'),jSwitcherConfig)
                FOR (F1 = 1; F1 <= jSwitcherConfig.Count; F1++)
                {
                    _SWITCH(1,ATOI(jSwitcherConfig.List[F1].Value),F1)
                }
            }
            IF (JSON_HasKey(jConfig,'audio'))                                               // Load audio settings
            {
                SEND_STRING 0,"'Audio'"
                JSON_ParseObject(JSON_GetValue(jConfig,'audio'),jTempAudio)
                IF (JSON_HasKey(jTempAudio,'mics'))                                         // Load mic settings
                {
                    SEND_STRING 0,"'Mics'"
                    JSON_ParseObject(JSON_GetValue(jTempAudio,'mics'),jTempMics)
                    _TesiraRecallPreset(1002)           // Resets the mixer
                    FOR (F1 = 1; F1 <= jTempMics.Count; F1++)                               // Get each mic in the object
                    {
                        cMic = jTempMics.KV[F1].K
                        JSON_ParseObject(jTempMics.KV[F1].V,jTempMic)
                        IF (JSON_HasKey(jTempMic,'vol'))                                    // Set the mic volume
                        {
                            _TesiraSetVolumePercent(Room1,F1,ATOI(JSON_GetValue(jTempMic,'vol')))
                        }
                        IF (JSON_HasKey(jTempMic,'mute'))                                   // Set the mic mute
                        {
                            IF (JSON_GetValue(jTempMic,'mute') == 'true')
                            {
                                ON[daTPDSP_Tesira[Room1],nTesiraMuteChannels[F1]]
                            }
                            ELSE IF (JSON_GetValue(jTempMic,'mute') == 'false')
                            {
                                OFF[daTPDSP_Tesira[Room1],nTesiraMuteChannels[F1]]
                            }
                        }
                        IF (JSON_HasKey(jTempMic,'crosspoints'))                            // Set the mic crosspoints
                        {
                            JSON_ParseArray(JSON_GetValue(jTempMic,'crosspoints'),jMicCrosspoints)
                            FOR (F2 = 1; F2 <= jMicCrosspoints.Count; F2++)
                            {
                                IF (jMicCrosspoints.List[F2].Value == '1')
                                {
                                    SetAudioCrosspoint(F1,F2,ATOI(jMicCrosspoints.List[F2].Value))
                                }
                            }
                        }
                    }
                }
                IF (JSON_HasKey(jTempAudio,'speakers'))
                {
                    SEND_STRING 0,"'Speakers'"
                    JSON_ParseObject(JSON_GetValue(jTempAudio,'speakers'),jTempSpeakers)
                    FOR (F1 = 1; F1 <= jTempSpeakers.Count; F1++)
                    {
                        JSON_ParseObject(jTempSpeakers.KV[F1].V,jTempSpeaker)
                        IF (JSON_HasKey(jTempSpeaker,'vol'))
                        {
                            _TesiraSetVolumePercent(Room1,FindStringInArray(SpeakerOutputJSONID,jTempSpeakers.KV[F1].K,FALSE),ATOI(JSON_GetValue(jTempSpeaker,'vol')))
                        }
                        IF (JSON_HasKey(jTempSpeaker,'mute'))
                        {
                            IF (JSON_GetValue(jTempSpeaker,'mute') == 'true')
                            {
                                ON[daTPDSP_Tesira[Room1],nTesiraMuteChannels[FindStringInArray(SpeakerOutputJSONID,jTempSpeakers.KV[F1].K,FALSE)]]
                            }
                            ELSE IF (JSON_GetValue(jTempSpeaker,'mute') == 'true')
                            {
                                OFF[daTPDSP_Tesira[Room1],nTesiraMuteChannels[FindStringInArray(SpeakerOutputJSONID,jTempSpeakers.KV[F1].K,FALSE)]]
                            }
                        }
                    }
                }
            }
            IF (JSON_HasKey(jConfig,'cameras'))
            {
                SEND_STRING 0,"'Cameras'"
                JSON_ParseObject(JSON_GetValue(jConfig,'cameras'),jTempCameras)
                IF (JSON_HasKey(jTempCameras,'camera1_pos'))
                {
                    Camera_GotoPTZ(devCamera[1],JSON_GetValue(jTempCameras,'camera1_pos'))
                }
                IF (JSON_HasKey(jTempCameras,'camera2_pos'))
                {
                    Camera_GotoPTZ(devCamera[2],JSON_GetValue(jTempCameras,'camera2_pos'))
                }
                IF (JSON_HasKey(jTempCameras,'camera3_pos'))
                {
                    Camera_GotoPTZ(devCamera[3],JSON_GetValue(jTempCameras,'camera3_pos'))
                }
                IF (JSON_HasKey(jTempCameras,'camera4_pos'))
                {
                    Camera_GotoPTZ(devCamera[4],JSON_GetValue(jTempCameras,'camera4_pos'))
                }
                IF (JSON_HasKey(jTempCameras,'camera5_pos'))
                {
                    Camera_GotoPTZ(devCamera[5],JSON_GetValue(jTempCameras,'camera5_pos'))
                }
                IF (JSON_HasKey(jTempCameras,'camera6_pos'))
                {
                    Camera_GotoPTZ(devCamera[6],JSON_GetValue(jTempCameras,'camera6_pos'))
                }
                IF (JSON_HasKey(jTempCameras,'camera7_pos'))
                {
                    Camera_GotoPTZ(devCamera[7],JSON_GetValue(jTempCameras,'camera7_pos'))
                }
                IF (JSON_HasKey(jTempCameras,'camera8_pos'))
                {
                    Camera_GotoPTZ(devCamera[8],JSON_GetValue(jTempCameras,'camera8_pos'))
                }
            }
            IF (JSON_HasKey(jConfig,'quads'))
            {
                JSON_ParseArray(JSON_GetValue(jConfig,'quads'),jQuads)
                FOR (F1 = 1; F1 <= 3; F1++)
                {
                    Quad_SetMode(F1,jQuads.List[F1].Value)
                }
            }
        }
        ELSE
        {
            SEND_STRING 0,"'Error parsing JSON file ',cFileName"
        }
    }
    
  • sentry07sentry07 Posts: 77

    And here's the JSON file it's parsing:

    {
        "audio": {
        "mics": {
        "w_mic1": {
        "vol": 75,
        "mute": true,
        "crosspoints": [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]
    },
        "w_mic2": {
        "vol": 75,
        "mute": true,
        "crosspoints": [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]
    },
        "w_mic3": {
        "vol": 75,
        "mute": true,
        "crosspoints": [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]
    },
        "w_mic4": {
        "vol": 75,
        "mute": true,
        "crosspoints": [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]
    },
        "d_mic1": {
        "vol": 82,
        "mute": false,
        "crosspoints": [0,0,0,1,0,0,0,0,0,0,0,0,0,0,0]
    },
        "d_mic2": {
        "vol": 35,
        "mute": false,
        "crosspoints": [0,0,0,0,0,1,1,1,1,0,0,0,0,0,0]
    },
        "d_mic3": {
        "vol": 70,
        "mute": false,
        "crosspoints": [0,0,0,0,0,0,0,0,0,0,0,0,0,0,1]
    },
        "sim_ceiling": {
        "vol": 60,
        "mute": false,
        "crosspoints": [0,0,0,0,1,0,0,0,0,0,0,0,0,0,0]
    },
        "seq_ceiling": {
        "vol": 75,
        "mute": false,
        "crosspoints": [0,0,0,0,1,0,0,0,0,0,0,0,0,0,0]
    }
    },
        "speakers": {
        "recorder1": {
        "vol": 75,
        "mute": false
    },
        "recorder2": {
        "vol": 75,
        "mute": false
    },
        "recorder3": {
        "vol": 75,
        "mute": false
    },
        "sim_overhead": {
        "vol": 75,
        "mute": false
    },
        "seq_overhead": {
        "vol": 75,
        "mute": false
    }
    }
    },
        "quads": [1,1,1],
        "switcher": [0,0,8,17,18,17,18,19,20,3,3,3,1,1,1,11,11,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
        "cameras": {
        "camera1_pos": "0F0F000C000D000B:02020605",
        "camera2_pos": "0F070903000B0C0B:020A010D",
        "camera3_pos": "0F0F0F0E0F0F0F0E:00000000",
        "camera4_pos": "0F0F0F0E0F0F0F0E:00000000",
        "camera5_pos": "0F0F0F0E0F0F0F0E:00000000",
        "camera6_pos": "0F0F0F0E0F0F0F0E:00000000",
        "camera7_pos": "0F0E00030E0B0909:01060A00",
        "camera8_pos": "0E0A020B0F000006:00000000"
    }
    }
    
  • KenKen Posts: 19

    Amazing work Sentry. I will try it out later.

    Thank you for your contributions to the community. :)

  • Apropos of nothing - online resource for messing around with json

    https://jsonplaceholder.typicode.com/

Sign In or Register to comment.