JSON Transfer and Parsing
The cJsonHttpTransfer class
This class provides an easy mechanism for sending and receiving JSON documents via HTTP. JSON documents are sent and retrieved as JSON objects.
This class depends on the cJsonObject class. It is expected that this class always uses cJsonObject objects in in- and out-parameters. JSON being sent will be created using the cJsonObject class interface and JSON being returned will be analyzed using the cJsonObject class interface.
This class can be used to transfer data in a RESTful manner. Interfaces exist allowing you to use all of the common HTTP verbs: HttpDeleteJson, HttpGetJson, HttpPatchJson, HttpPostJson, and HttpPutJson. An additional method HttpVerbJson can be used where you can pass any verb of your choosing.
Sample
Here is an example of transferring data as JSON objects using HttpPostJson:
// returns the response JSON data. If 0, no data but legal,
// if -1, bad transfer
Function TransferMyData Handle hoReqJson Returns Handle
Handle hoHttp
Handle hoRcvdJson
Boolean bOk
// Create JSON HTTP transfer object.
Get Create (RefClass(cJsonHttpTransfer)) to hoHttp
// post JSON document and receive returned JSON document
Get HttpPostJson of hoHttp "dataaccess.com" ;
"MyApplication/JsonService.asp" hoReqJson (&bOk) to hoRcvdJson
// transfer object no longer needed
Send Destroy of hoHttp
// it’s actually possible for a valid transfer to not return an object
// if a transfer error, we return -1 (remember this is an example)
If not (bOk) Begin
Move -1 to hoRcvdJson
End
Function_Return hoRcvdJson // if >0, this is newly created, and must be destroyed
End_Function
The JSON data objects are passed and returned as UTF-8 data over HTTP. This is the expected encoding for JSON transfers over HTTP.
Transfer errors can be further analyzed by using the peJsonTransferStatus property.
This class allows you to transfer and receive data that is larger than the string size limitations imposed by DataFlex. It does this by augmenting OnDataReceived and uses UChar[] arrays to manipulate the data. You may wish to study the class source code to see how this is done so you can create other cHttpTransfer subclasses that allow you to transfer large amounts of data. Note that the event OnDataReceived is not a public event in this class — you should not change it. A similar class, cXmlHttpTransfer, exists for transferring XML documents using XML.
The JSON parser (cJsonObject)
The JSON parser is represented by a single cJsonObject class that contains the entire API for parsing, generating, analyzing and manipulating JSON. The API can be seen as a simplified DOM where each object represents a single JSON node. JSON can be generated by creating and configuring these objects before calling Stringify to generate the JSON string. JSON can be parsed by calling Parse (or ParseUtf8) and then using the API to analyze/enumerate the object structure.
A special feature is the possibility to generate the JSON object structure based on DataFlex struct or array data in memory. Calling DataTypeToJson will generate cJsonObject objects that match the structure of the data and calling Stringify will fully serialize this data into a string. The reverse is done using JsonToDataType, which converts the JSON object structure into struct/array data. This feature requires the JSON to match the format of the struct/array data or errors will be thrown.
Example: Parse known JSON (extensive)
This sample shows how a JSON string passed in as a UChar[] is parsed into a JSON object structure and values are extracted.
Input:
{ "Value1" : "Test data!", "Value2" : 381 }
Output:
Value 1: Test data!
Value 2: 381
Code:
Procedure ParseJSON UChar[] aData
Handle hoObj hoMem
Boolean bTest bSuccess
String sVal
Integer iVal
Get Create (RefClass(cJsonObject)) to hoObj
Get ParseUtf8 of hoObj aData to bSuccess
If (bSuccess) Begin
Get IsOfJsonType of hoObj jsonTypeObject to bTest
If (bTest) Begin
Get Member of hoObj "Value1" to hoMem
If (hoMem > 0) Begin
If (IsOfJsonType(hoMem, jsonTypeString)) Begin
Get JSONValue of hoMem to sVal
Showln ("Value 1: " + sVal)
End
Send Destroy of hoMem
End
// Get Value2 member
Get Member of hoObj "Value2" to hoMem
If (hoMem > 0) Begin
If (IsOfJsonType(hoMem, jsonTypeInteger)) Begin
Get JSONValue of hoMem to iVal
Showln ("Value 2: " + iVal)
End
Send Destroy of hoMem
End
End
End
Send Destroy of hoObj
End_Procedure
Example: Parse known JSON (optimized)
A simplified version using helper functions IsMemberOfJsonType and MemberValue for direct access:
Procedure ParseJSON UChar[] aData
Handle hoObj
Boolean bTest bSuccess
String sVal
Integer iVal
Get Create (RefClass(cJsonObject)) to hoObj
Get ParseUtf8 of hoObj aData to bSuccess
If (bSuccess and IsOfJsonType(hoObj, jsonTypeObject)) Begin
If (IsMemberOfJsonType(hoObj, "Value1", jsonTypeString)) Begin
Get MemberValue of hoObj "Value1" to sVal
Showln ("Value 1: " + sVal)
End
If (IsMemberOfJsonType(hoObj, "Value2", jsonTypeInteger)) Begin
Get MemberValue of hoObj "Value2" to iVal
Showln ("Value 2: " + iVal)
End
End
Send Destroy of hoObj
End_Procedure
Example: Generate JSON (long)
This example shows how to create a JSON object structure to generate JSON into a string. It reuses the member object by reinitializing it.
Function GenerateJSON String sVal String sVal2 Integer iVal Returns UChar[]
Handle hoObj hoMem
String sResult
Get Create (RefClass(cJsonObject)) to hoObj
Send InitializeJsonType of hoObj jsonTypeObject
Get Create (RefClass(cJsonObject)) to hoMem
Send InitializeJsonType of hoMem jsonTypeString
Set JSONValue of hoMem to sVal
Send SetMember of hoObj "Value1" hoMem
Set JSONValue of hoMem to sVal2
Send SetMember of hoObj "Value2" hoMem
Send InitializeJsonType of hoMem jsonTypeInteger
Set JSONValue of hoMem to iVal
Send SetMember of hoObj "Value3" hoMem
Send Destroy of hoMem
Get Stringify of hoObj to sResult
Send Destroy of hoObj
Function_Return sResult
End_Function
Example: Generate JSON (optimized)
The optimized version, using SetMemberValue:
Function GenerateJSON String sVal Integer iVal Returns String
Handle hoObj
String result
Get Create (RefClass(cJsonObject)) to hoObj
Send InitializeJsonType of hoObj jsonTypeObject
Send SetMemberValue of hoObj "Value1" jsonTypeString sVal
Send SetMemberValue of hoObj "Value2" jsonTypeInteger iVal
Set peWhitespace of hoObj to jpWhitespace_Spaced
Get Stringify of hoObj to result
Send Destroy of hoObj
Function_Return result
End_Function
Example: Generate JSON Array (long)
Function GenerateJSONArray Integer iLen Returns UChar[]
Handle hoObj hoMem
UChar[] result
Integer iCur
Get Create (RefClass(cJsonObject)) to hoObj
Send InitializeJsonType of hoObj jsonTypeArray
Get Create (RefClass(cJsonObject)) to hoMem
Send InitializeJsonType of hoMem jsonTypeInteger
For iCur from 0 to iLen
Set JSONValue of hoMem to iCur
Send AddMember of hoObj hoMem
Loop
Get StringifyUtf8 of hoObj to result
Send Destroy of hoMem
Send Destroy of hoObj
Function_Return result
End_Function
Example: Generate JSON Array (short)
This could be optimized with an AddMemberValue procedure that can be used to add simple typed members directly:
Function GenerateJSONArray Integer iLen Returns UChar[]
Handle hoObj
UChar[] result
Integer iCur
Get Create (RefClass(cJsonObject)) to hoObj
Send InitializeJsonType of hoObj jsonTypeArray
For iCur from 0 to iLen
Send AddMemberValue of hoObj jsonTypeInteger iCur
Loop
Get StringifyUtf8 of hoObj to result
Send Destroy of hoObj
Function_Return result
End_Function
Example: Parse unknown JSON
The following example demonstrates traversing a tree of JSON objects. It handles arrays, objects and other simple types and outputs the structure to the console.
Procedure WriteJSONSimple Handle hoObj
String sVal
Get JSONValue of hoObj to sVal
Showln ("'" + sVal + "'")
End_Procedure
Procedure WriteJSONArray Handle hoObj
Handle hoMem
Integer iCur iTo
Get MemberCount of hoObj to iTo
For iCur from 0 to (iTo - 1)
Get MemberByIndex of hoObj iCur to hoMem
Show ("Member '" + String(iCur) + "': ")
Send WriteJSON hoMem
Loop
End_Procedure
Procedure WriteJSONObject Handle hoObj
Handle hoMem
Integer iCur iTo
String sName
Get MemberCount of hoObj to iTo
For iCur from 0 to (iTo - 1)
Get MemberByIndex of hoObj iCur to hoMem
Get MemberNameByIndex of hoObj iCur to sName
Show ("Member '" + sName + "': ")
Send WriteJSON hoMem
Loop
End_Procedure
Procedure WriteJSON Handle hoObj
Integer eType
Get JsonType of hoObj to eType
Case Begin
Case (eType = jsonTypeObject)
Send WriteJSONObject hoObj
Break
Case (eType = jsonTypeArray)
Send WriteJSONArray hoObj
Break
Case Else
Send WriteJSONSimple hoObj
Case End
End_Procedure
Example: Deserialize JSON into struct
This shows how to deserialize JSON data into a specific struct type:
Function DeserializeFromJson String sData Returns tMyStruct
tMyStruct tResult
Handle hoObj
Boolean bSucces
Get Create (RefClass(cJsonObject)) to hoObj
Get Parse of hoObj sData to bSucces
If (bSucces) Begin
Get JsonToDataType of hoObj to tResult
End
Send Destroy of hoObj
Function_Return tResult
End_Function
Example: Serialize struct into JSON
This shows how to serialize a struct into JSON:
Function SerializeToJson tSimpleStructTest tInput Returns UChar[]
UChar[] uData
Handle hoObj
Get Create (RefClass(cJsonObject)) to hoObj
Set peWhitespace of hoObj to jpWhitespace_Pretty
Set pbUseVariantMetaData of hoObj to True
Send DataTypeToJSON of hoObj tInput
Get StringifyUtf8 of hoObj to uData
Send Destroy of hoObj
Function_Return uData
End_Function
Example: Manipulate JSON Object
Procedure AlterJSON Handle hoObj
Send SetMemberValue of hoObj "Value1" jsonTypeString "Changed Value"
End_Procedure
Implementation
The API wraps the JSON-C APIs and provides an object-oriented API for parsing, enumerating, serializing, deserializing and generating JSON. The JSON-C API works by providing pointers to structs that represent JSON objects. Global functions provide operations that can be performed on those objects. Reference counting is used to manage memory; the documentation explains the ownership methodology. Some functions take ownership of an object; where needed ownership must be taken back by incrementing the reference count (using json_object_get). Releasing is done using json_object_put.
Each DataFlex cJsonObject object basically wraps a pointer to a single JSON-C object. A cJsonObject can represent the different types of objects in a JSON structure (object, array, string, boolean, number). The manipulation functions on the cJsonObject class must ensure that they always keep ownership of the underlying JSON-C object. In some cases that means explicitly calling json_object_get. When releasing an object (for example at Destroy) they will call json_object_put.
cJsonObject (API reference)
The cJsonObject represents a single JSON node within a structure of JSON nodes. A JSON node can represent an object, array, simple type (string, number and boolean) or null value. The class contains the entire API for generating, parsing and enumerating JSON. The JsonToDataType and DataTypeToJson functions can be used to convert between JSON and DataFlex struct/array data.
Parse (ParseString)
Parses a string of JSON into a JSON object structure where the object instance on which it is called represents the outermost JSON object.
- Params:
String sData - Returns:
Boolean
Use ReportParseError or the psParseError property to get parsing errors if False is returned.
ParseUtf8
Parses a UChar[] containing UTF-8 encoded JSON into a JSON object structure where the object instance on which it is called represents the outermost JSON object.
- Params:
UChar[] aData - Returns:
Boolean
Use ReportParseError or the psParseError property to get parsing errors if False is returned.
psParseError
If Parse or ParseUtf8 failed this property contains the error description.
- Returns:
String
ReportParseError
If Parse or ParseUtf8 failed, calling this procedure will trigger an error with the proper description.
Stringify
Generates the JSON String for the object structure being represented by the JSON objects.
- Returns:
String
StringifyUtf8
Generates the JSON string for the object structure and returns the data in a UChar[] as a UTF-8 encoded string.
- Returns:
UChar[]
DataTypeToJson
Converts a native DataFlex variable (usually a struct or array) into a JSON object structure representing that data. Use in combination with Stringify to produce a JSON string.
- Params:
data(struct or array)
JsonToDataType
Converts a JSON object structure into a native DataFlex variable (usually a struct or array). Use after Parse to deserialize JSON into DataFlex types.
- Returns: native DataFlex struct/array
Member
Gets a member by its name or array index.
- Params:
String sJsMember - Returns:
Handleto the member object
JsonType
Returns the type of the object as an integer constant.
- Returns:
Integer
IsOfJsonType
Determines if the object is of a specific type and returns True if it is. Types are passed as constants and can be object, string, integer, number, boolean or null (null is also the initial state of the object).
- Params:
Integer eType - Returns:
Boolean
JsonValue (Get_JsonValue)
Fetches the value. If the object is of type object or array it returns the JSON as a string.
- Returns:
String
Set JsonValue (Set_JsonValue)
Sets the value of the object. The object needs to be initialized as a simple JSON type (jsonTypeBoolean, jsonTypeInteger, jsonTypeString, etc.). To initialize a newly created JSON object use InitializeJsonType.
- Params:
String sValue
MemberValue
Returns the value of a member of the JSON object. The member name (or array index) is passed as the sMember parameter.
- Params:
String sMember - Returns:
String
IsMemberOfJsonType
Checks the JSON type of a specific member (of an array or object) against the passed type. If the type passed matches the type of the member, True will be returned. Note that False will be returned if the member does not exist (no error).
- Params:
String sMemberInteger eType- Returns:
Boolean
MemberJsonType
Determines the JSON type of a specific member of an array or object.
- Params:
String sMember - Returns:
Integer
InitializeJsonType
Initializes the JSON object as a specific type. This completely reinitializes the object allowing the object instance to be reused. If the previous value/object was a member of another JSON object it will continue to exist; otherwise it will be destroyed.
- Params:
Integer eType
HasMember
Checks if a member exists based on the passed member name (or array index). Note that a member of type null will return True.
- Params:
String sMember - Returns:
Boolean
SetMember
Sets the member of an object or array to an initialized JSON object.
- Params:
String sMemberHandle hoJsonObj
SetMemberValue
Sets the member of an object directly to a simple value. The JSON type needs to be passed explicitly.
- Params:
String sMemberInteger eJsonTypeString sValue
AddMember
Appends an initialized JSON object to an array.
- Params:
Handle hoJsonObj
AddMemberValue
Appends a simple value to an array.
- Params:
Integer eJsonTypeString sValue
RemoveMember
Removes a member from a JSON object or array. Removing from an array will shift all members after this member one place to the left.
- Params:
String sMember
MemberCount
Returns the number of members of an array or an object.
- Returns:
Integer(number of members)
MemberByIndex
Gets a member object by its index. Can be used for enumerating object properties and to access array items.
- Params:
Integer iIndex - Returns: JSON object
Handle
MemberNameByIndex
Gets the name of an object member by its index. Used for enumerating members of a JSON object.
- Params:
Integer iIndex - Returns:
Stringwith the member name
peWhitespace
This property controls if/how whitespace is added to the generated JSON when calling the Stringify function. It should be set on the object where Stringify is called; setting it on parent objects does not affect the output.
- Default:
jpWhitespace_Plain
Options:
jpWhitespace_Plain— No whitespace is added. The most compact setting.jpWhitespace_Spaced— Spaces are added between property names and their values.jpWhitespace_Pretty— The most readable format with line feeds and indentation spaces.
pbUseVariantMetaData
This property determines how variants are serialized. Variants can contain several types of values and when serialized to JSON without adding metadata they cannot always be properly deserialized with the proper typed value. For example, when a variant containing a struct value is serialized with pbUseVariantMetaData set to False, it serializes the struct but when deserializing it doesn't know what struct type was used and so cannot reinitialize the proper struct.
Setting this property to True will serialize variants into JSON objects with a property vt indicating the variant type and, in the case of a struct, a property st with the name of the struct that was used. In the case of an array, a property at will be added indicating the array type.
When the JSON is used to serialize data from memory for storage, this property can be enabled to provide proper support for variants. It needs to be set on the object on which DataTypeToJson and JsonToDataType are being called.
pbRequireAllMembers
This property determines if JsonToDataType will raise errors if a struct member does not exist in the JSON structure. If set to False, a missing member in the JSON will not cause an error and leaves the struct member uninitialized.
DataFlex class definition (codesense / metadata)
The code below shows the DataFlex definition of the class as used by the metadata system to feed codesense:
Class cJsonObject is a cObject
{ MethodType=Property }
Function peWhitespace Returns Integer
End_Function
{ MethodType=Property }
{ EnumList="jpWhitespace_Plain,jpWhitespace_Spaced,jpWhitespace_Pretty" }
{ InitialValue=jpWhitespace_Plain }
Procedure Set peWhitespace Integer eValue
End_Procedure
{ MethodType=Property }
Function pbUseVariantMetaData Returns Boolean
End_Function
{ MethodType=Property }
{ InitialValue=False }
Procedure Set pbUseVariantMetaData Boolean bValue
End_Procedure
Function Parse String sData Returns Boolean
End_Function
Function ParseUtf8 UChar[] sData Returns Boolean
End_Function
Function Stringify Returns String
End_Function
Function StringifyUtf8 Returns UChar[]
End_Function
Procedure DataTypeToJson Struct data
End_Procedure
Function JsonToDataType Returns Struct
End_Function
Function Member String sMember Returns Handle
End_Function
Function JsonType Returns Integer
End_Function
Function IsOfJsonType Integer eType Returns Boolean
End_Function
{ MethodType=Property }
Function JsonValue Returns String
End_Function
{ MethodType=Property }
Procedure Set JsonValue String sValue
End_Procedure
Function MemberValue String sMember Returns String
End_Function
Function IsMemberOfJsonType String sMember Integer eType Returns Boolean
End_Function
Function MemberJsonType String sMember Returns Integer
End_Function
Procedure InitializeJsonType Integer eType
End_Procedure
Procedure SetMember String sMember Handle hoJsonObj
End_Procedure
Procedure SetMemberValue String sMember Integer eJsonType String sValue
End_Procedure
Procedure AddMember Handle hoJsonObj
End_Procedure
Procedure AddMemberValue Integer eJsonType String sValue
End_Procedure
Function MemberCount Returns Integer
End_Function
Function MemberByIndex Integer iIndex Returns Handle
End_Function
Function MemberNameByIndex Integer iIndex Returns String
End_Function
End_Class