Version 1.0.0.16
https://www.dropbox.com/scl/fi/16rzprk6dhnr6204ya5vu/PRO_JSON-1.0.0.16.library?rlkey=vohd00hny0uvm9i6a1avf16vq&dl=0
Changes:
1. changes to JSONVAR.FB_Init (Simon Dreyer)
1. line 8: delete all names first before populating the array
2. line 25: when copying the string, ensure that no more than GPL_JSON.MAX_NAME_SIZE are copied, to leave a null character at the end
2. changes from discussion at https://github.com/stefandreyer/JSON-Library/issues/1
1. line 129 (End previous object): added second case for when offset < 0 (struct of struct)
3. (Stefan Dreyer) added GPL_JSON.APPLICATION_NAME for when the Application has another name than ‘Application’
4. STRUCT_TO_JSON:
1. added check for ApplicationLevel = 0, misconfigured application name
2. reconfigured error checking and added ErrorMsg
5. JSON_TO_STRUCT
1. added check for ApplicationLevel = 0, misconfigured application name
2. reconfigured error checking and added ErrorMsg
6. STRUCT_TO_JSON: added error and message if JSONString is too small
7. JSONVAR_FB_Init: New array, added a check for the end of an array on the previous var for the case when two arrays are adjacent to each other in a struct
8. added JSONVARS_TABLE_VISU and JSONVAR_TAVLE_VISU for testing and troubleshooting
9. added some points to JSON_TO_STRUCT_VISU and STRUCT_TO_JSON_VISU
10. (Stefan Dreyer) changed STRUCT_TO_JSON line 132 closeLevel:= MaxLevel (was closeLevel:= 1) to fix bug where closing bracket was not added when MaxLevel was defined
11. changed the ASCII enum to make it a bit more readable when looking at the JSONString arrays
12. Restructuring of JSON_TO_STRUCT.Name and Value methods - some length checks added, more efficient and readable
13. Several minor stylistic changes
Version 1.0.0.15
https://www.dropbox.com/s/9w2p2yrgncalixp/PRO_JSON%201.0.0.15.library?dl=0
Changes:
1. refactoring of JSON_TO_STRUCT
1. added MatchAllLevels method to make the code more understandable
2. moved some variables, changed some names
2. added JSON_TO_STRUCT.ClearAll method which resets the value of all JSONVARS to null
3. added STRUCT_TO_JSON.ClearString method which clears the output string
4. added JSON_TO_STRUCT_VISU - mostly for testing, but I guess you could use it anywhere
5. added STRUCT_TO_JSON_VISU
6. deleted copyright and copying files. I don’t care about licensing. This library is released under the terms of "it's on the internet so people can do whatever the heck they want with it". Use it, modify it, copy it, put it in your own projects, contribute to it, credit me. Or not. Whatever you like. But it's free, so don't cry to me if it breaks something.
I realize I'm bypassing the structure of the Forge platform by simply posting links to the .library files. I don't have time to make this site work with Schneider Machine Expert, which is what I primarily use for development. If you want to contribute, comment below or send me an email.
This as an open source library to compose and parse JSON objects. For more information about JSON, have a look at json.org. It is a popular language used as part of many other protocols, like REST.
The usage is simple. You can describe a JSON file with a special structure format, which then can be stored or loaded from or to a file. Actually instead of a file, you are also able to use an HTTP Client library or s.th. similar. The example below is in the library, along with several others.
//All variables must be of type JSONVAR. They can include other structures or put into arrays, as needed.
TYPE EXAMPLEJSONSTRUCT :
STRUCT
var1: JSONVAR;
var2: JSONVAR;
var3: JSONVAR;
obj: EXAMPLEJSONSTRUCT_1;
arrayobj: ARRAY[1..3] OF JSONVAR;
END_STRUCT
END_TYPE
TYPE EXAMPLEJSONSTRUCT_1 :
STRUCT
var4: JSONVAR;
var5: JSONVAR;
var6: JSONVAR;
var7: ARRAY[1..3] OF JSONVAR;
END_STRUCT
END_TYPE
PROGRAM ExampleJSON_PRG
VAR
ExampleJSONObj: EXAMPLEJSONSTRUCT;
ComposeJSON: STRUCT_TO_JSON;
JSONString: STRING(1000);
NewJSONObj: EXAMPLEJSONSTRUCT;
ParseJSON: JSON_TO_STRUCT;
JSONString2: STRING(1000);
END_VAR
//JSONVAR variable values can be set in a number of different ways
ExampleJSONObj.var1.Boolean:= TRUE;
ExampleJSONObj.var2.Number:= 34.8756;
ExampleJSONObj.var3.CharString:= 'teststring';
ExampleJSONObj.obj.var4.Number:= 22;
ExampleJSONObj.obj.var5.AsString:= 'FALSE'; //JSONVAR will guess at type, in this case, boolean
ExampleJSONObj.obj.var6.CharString:= 'qwer';
//NOTE: AsString will convert the value to/from STRING, and guess at a type, CharString will not convert, but assume it's a JSON string type.
ExampleJSONObj.obj.var7[1].Number:= 123.234;
ExampleJSONObj.obj.var7[2].AsString:= '55.46'; //JSONVAR will guess at type, in this case, a number
ExampleJSONObj.obj.var7[3].Number:= 985;
ExampleJSONObj.arrayobj[1].Number:= 1;
ExampleJSONObj.arrayobj[2].Number:= 2;
ExampleJSONObj.arrayobj[3].Number:= 3;
//create a JSON string from example object
ComposeJSON(
JSONString:= ADR(JSONString),
JSONStringSize:= SIZEOF(JSONString),
JSONVars:= ADR(ExampleJSONObj),
NumberOfVars:= SIZEOF(ExampleJSONObj) / SIZEOF(JSONVAR)
);
//fill another example object with values from the created JSON string
ParseJSON(
JSONString:= ADR(JSONString2),
JSONStringSize:= SIZEOF(JSONString2),
JSONVars:= ADR(NewJSONObj),
NumberOfVars:= SIZEOF(NewJSONObj) / SIZEOF(JSONVAR)
);
It's cleared.
Last edit: svenjotec 2019-12-03
Hello Guys,
I believe there's an issue with nested structs on all verions. For example:
TYPE stDebugJson :
STRUCT
timestamp : JSONVAR;
messageType : JSONVAR;
//message : JSONVAR; //works fine with setting MaxLevel to 1
message : stDebugMessageDetails; // missing end bracket
END_STRUCT
END_TYPE
stDebugMessageDetails is just another type:
TYPE stDebugMessageDetails :
STRUCT
message : JSONVAR;
END_STRUCT
END_TYPE
The output looks like:
{"debugMessageJson":{"timestamp":"2020-03-06T23:27:22.966Z","messageType":"DEBUG","message":{"message":"test22"}}
Last edit: nothinrandom 2020-03-06
I'll see if I can find some time to test this out.
Hi,
you should add (GPL_JSON.MAX_VALUE_SIZE) on this string.
Because if bigger value sizes it runs into faulty parse.
TYPE JSONNAMEVALUE :
STRUCT
Value: STRING(GPL_JSON.MAX_VALUE_SIZE); //value of the object at this level
END_STRUCT
END_TYPE
Greetings
good catch, I'll add that to future versions
Hello, I have a problem parsing a JSON string and I need help.
The string I receive:
'[{"device_id":"1","pgt_id":"2","usertag":"3","hgl":"4","ls":"5","rs":"6","erg":"7"}]'
The struct I devined:
TYPE strPGTdataSet:
STRUCT
device_id :JSONVAR;
pgt_id :JSONVAR;
usertag :JSONVAR;
hgl :JSONVAR;
ls :JSONVAR;
rs :JSONVAR;
erg :JSONVAR;
END_STRUCT
END_TYPE
The FB I call:
ParseJSON(
Execute:=xParseJSON,
JSONString:= ADR(hTTPClient_0.httpResult.sContent),
JSONStringSize:= SIZEOF(hTTPClient_0.httpResult.sContent),
JSONVars:= ADR(NewJSONObj),
NumberOfVars:= SIZEOF(NewJSONObj) / SIZEOF(JSONVAR)
);
The NewJSONObj always remains empty. I also tried to define the NewJSONObj as an array of struct like this:
TYPE strPGToutputSet:
STRUCT
recordset :ARRAY [0..0] OF strPGTdataSet;
END_STRUCT
END_TYPE
I don't know if you need more information to help. Also I'm new to CODESYS programming.
Thank you!
Hello, have you managed to solve this case? I am struggling with the same issue.
Thanks.
I am aware that this thread is three years old, but I ran into the same issue and could not resolve it. Does anyone know what can cause this?
Thanks in advance!
Edit:
After some more tinkering, I found the issue preventing proper parsing of the JSON string. For this the library function JSON_TO_STRUCT (FB) has to be adjusted from row 100. The original implementation is as follows:
This checks if a key in the struct is identical to the key currently being processed. However, as the EXIT statement is set in case of no matching name, the loop continues after a match and in the next cycle, the variable foundname will be overwritten again. Changing the code accordingly did the trick for me:
If this library is still being moderated, it would be interesting to know if this was an error in the code or if I am simply using it wrong?
Last edit: manuknecht 2023-10-19
I haven't had a lot of time to do anything with this for several months, but thanks for the input. It does look like a bug, I'll try to take a closer look at it next week.
If you will tackle it again, here's another issue I discovered. The JSON string I want to parse contains some nested arrays and objects. It came to my attention, that proper parsing only works when the key-value pairs on the lowest level (inside arrays) are sorted alphabetically. Otherwise, the values can be assigned to the wrong keys. I didn't check the code further to find what causes this, but maybe you will have an idea?
Otherwise it seems to do what it does, so thanks a lot for sharing!
I think I have reproduced something like this, but it would be helpful if you can provide a test program, or at least a JSON string that demonstrates it.
I'll attach a project to this message. I am using the HTTPClient library to read a JSON string through a HTTP request. I left you a sample string that I want to parse in the code to demonstrate the issue. You can see that the key-value pairs do not match up between the string and the JSON struct.
Hope that helps.
Last edit: manuknecht 2023-10-31
I've released v 1.0.0.15 (link above). I tested your project as well as several of mine, with various different orders of the variables and it's working for me. The code on line 100 was correct, as far as how the FOR loop/EXIT worked, but I refactored some of that to make it easier to understand.
Thanks a lot for the update! So my project worked with the old version of the library for you? That is very strange. Need to check it from my side then.
Hi, I haven't had time to test it, but have you tried recordset :ARRAY [1..1] OF strPGTdataSet;
Will you please rerelease this under MIT or MPL2?
GPL is a cancer.
@bnewman as far as I'm concerned, it's released under the terms of "it's on the internet so people can do whatever the heck they want with it". I think I had to pick a license when I put it on here, but I don't actually know anything about GPL, nor do I care. Use it, modify it, copy it, put it in your own projects, contribute to it, credit me. Or not. Whatever you like.
I just changed the licensing to both MIT and Unlicense. I honestly don't know the difference, but I hope that makes it easier for you.
Last edit: tvm 2023-10-27
awesome! thank you sir.
Hello. I have an error in JSON_TO_STRUCT when trying to get JSON from string: 'Check application name in GPL_JSON.APPLICATION_NAME'. But I didn't change my application name, it's still "Application". What's wrong?
Check any of your JSONVARs. The Names variable contains an array of the variable name divided by level. For example, if the variable path is Application.Test_SingleVar_PRG.colour, then the array will contain
[3] 'Application'
[2] 'Test_SingleVar_PRG'
[1] 'colour'
The ApplicationLevel property should be the array index which contains 'Application', in this case 3. This is all figured out in the JSONVAR.FB_Init method.
The other place to check is JSONVAR.VarName, which is the full instance path of the variable as a string. The string length is set in GPL_JSON.MAX_VAR_NAME, by default 150 characters. Maybe your path is longer than 150 characters and something is getting cut off?
SOLVED SEE REPLIES
NOT A BUG, Different thinking approach
Hello,
There is still a bug within the Json_TO_Struct FB. Within the method MatchAllLevels:
The EXIT Statement was in the wrong place within the for loop. As the loop needs to be exited when a match is found. The bug caused the matchalllevels method to always feedback the value false due to the exit statement.
When placing the exit after match is set to true the values are parsed into the struct.
--------------------------------------OLD-----------------------------------------------------------
FOR i:= 1 TO obj_level DO
//Match name and array index. Array index will be 0 if the current name:value is not part of an array
IF MATCH_NAMES(i_JSONVar.Names[i], NameValue[obj_level-(i-1)]) THEN
MatchAllLevels:= TRUE; //name in JSON string matches the local variable name
ELSE
MatchAllLevels:= FALSE; //name in JSON string does not match the local variable name, go to next name
EXIT;
END_IF
END_FOR
--------------------------------------NEW-----------------------------------------------------------
FOR i:= 1 TO obj_level DO
//Match name and array index. Array index will be 0 if the current name:value is not part of an array
IF MATCH_NAMES(i_JSONVar.Names[i], NameValue[obj_level-(i-1)]) THEN
MatchAllLevels:= TRUE; //name in JSON string matches the local variable name
EXIT;
ELSE
MatchAllLevels:= FALSE; //name in JSON string does not match the local variable name, go to next name
END_IF
END_FOR
Last edit: maxkemmeren 2024-05-02
The EXIT is in the correct place in the original, this is not a bug. The loop returns FALSE and exits on the first occurrence of a mismatch. If there are any mismatches, there is no point in evaluating the rest of the array. All levels must match for this method to return TRUE.
OKay but then there must be another error, as after the change the functionblock read all the values into my local struct. Without the fix my local struct stayed empty all the time.
If you can post an example I can test it here.
sFileContentsWrite_Struct : ST_TEST;
sFileContentsWrite_Struct_Json : ST_TEST_Json;
sFileContentsRead_Struct_Json : ST_TEST_Json;
sFileContentsRead_Struct : ST_TEST;
sFileContentsWrite_Struct_Json.fReal.Number := sFileContentsWrite_Struct.fReal;
sFileContentsWrite_Struct_Json.nInteger.Integer := sFileContentsWrite_Struct.nInteger;
sFileContentsWrite_Struct_Json.sText.CharString := sFileContentsWrite_Struct.sText;
sFileContentsWrite_Struct_Json.fLreal.Number :=sFileContentsWrite_Struct.fLreal;
s_fbJsonParser(
Execute := Execute,
Abort := Abort,
JSONString := ADR(JsonString),
JSONStringSize := SIZEOF(JsonString),
JSONVars := ADR(sFileContentsWrite_Struct_Json),
NumberOfVars := SIZEOF(sFileContentsWrite_Struct_Json)/SIZEOF(JSONVAR),
VariableArraySize := TRUE,
Format := false,
Busy => Busy,
Done => Done,
Aborted => Aborted,
Error => Error,
ErrorPosition => ErrorPosition,
ComposeTime => ComposeTime);
s_fbStructParser(
Execute := rExecute,
Abort := rAbort,
JSONString := ADR(JsonString),
JSONStringSize := SIZEOF(JsonString),
JSONVars := ADR(sFileContentsRead_Struct_Json),
NumberOfVars := SIZEOF(sFileContentsRead_Struct_Json)/SIZEOF(JSONVAR),
Busy => rBusy,
Done => rDone,
Aborted => rAborted,
Error => rError,
ErrorPosition => rErrorPosition,
ParseTime => rComposeTime);
sFileContentsRead_Struct.fReal := sFileContentsRead_Struct_Json.fReal.Number;
sFileContentsRead_Struct.nInteger := TO_INT(sFileContentsRead_Struct_Json.nInteger.Integer);
sFileContentsRead_Struct.sText := sFileContentsRead_Struct_Json.sText.CharString;
sFileContentsRead_Struct.fLreal := sFileContentsRead_Struct_Json.fLreal.Number;
TYPE ST_TEST :
STRUCT
END_STRUCT
END_TYPE
TYPE ST_TEST_Json :
STRUCT
thisisnew : JSONVAR;
sText : JSONVAR;
nInteger : JSONVAR;
fReal : JSONVAR;
fLreal :JSONVAR;
END_STRUCT
END_TYPE
This is using only your blocks writing the json string and then parsing back the json string to the same struct. Without the exit change the read struct stayed empty