Native Application Dev Tip - Tutorial of JSON parser
PUBLISHED
1 Introduction
JSON (JavaScript Object Notation) is lightweight data-interchange format and easy to understand for developer and machine. This document provides the guideline for developer who wants to make a Tizen native application which works with JSON-GLIB library. And through sample code, it explain how to parse JSON tree which is loaded from file and from buffer.
Followings are the preconditions and environments of sample application
- Target SDK: Tizen SDK 2.3 Rev2
- Target Platform: Tizen Mobile
- Target OS: Windows 7 64bit
- Target Device: Samsung Z1
2 JSON Parser
To use JSON parser in your code, include this header file. As Tizen 2.3 support JSON-GLIB version 0.10.4 natively, you don’t need manually download this library and install it on your project. And you can visit web site(https://developer.gnome.org/json-glib/) of JSON-GLIB as API guide is provided.
#include <json-glib.h> |
Next step is to create a parser instance as following:
JsonParser *jsonParser = NULL; GError *error = NULL; jsonParser = json_parser_new (); |
And there are several way to load data in JSON parser as following and Sample code of this document get the data from File and Buffer.
From file |
gboolean json_parser_load_from_file (JsonParser *parser, const gchar *filename, GError **error); |
From buffer |
gboolean json_parser_load_from_data (JsonParser *parser, const gchar *data, gssize length, GError **error); |
From stream |
gboolean json_parser_load_from_stream (JsonParser *parser, GInputStream *stream, GCancellable *cancellable, GError **error); |
When error occurs during loading JSON stream, you should free the allocated memory using g_error_free(). Otherwise you can retrieve the top level node by following way:
if (error) { g_error_free (error); } else { JsonNode *root; root = json_parser_get_root (jsonParser); if (NULL != root) { int level =1; Eina_List *jsonList = NULL; jsonList = ParseJsonEntity(root,false); printParsedList(jsonList,level); } } |
2.1 Structure of Eina List for JSON
As we store object and array including fundamental value using JSON, Linked list is convenient for storing/loading it danamically.
Eina is one of EFL core libraries which can be used in Tizen native application. This is a libray for data types and useful tools. Eina support List data type and it is double linked list. We’re using three default node to store Key and value to present JSON structure. In case of Object type of JSON data, we create new Eina list inside to store it.
Following is data of array in the sample (\jsonParser\src\jasonparser.c).
{ "array" : [ false, "foo" ], "object" : { "foo" : true } }", { "type" : "ClutterGroup", " "width" : 1, " "children" : [ { "type" : "ClutterRectangle", "children" : [ { "type" : "ClutterText", "text" : "hello there" } ] }, { "type" : "ClutterGroup", "width" : 1, "children" : [ { "type" : "ClutterText", "text" : "hello" } ] } ] } |
Following is data of file in the sample (\jsonParser\shared\res\ jsonSample.json).
{"List": { "debug": "on", "window": { "title": "Is an Icon List", "name": "main_window", "width": 500, "height": 500 }, "image": { "src": "Images/fun.png", "name": "fun1" }, "text": { "data": "Click Here", "size": 36, "style": "bold", "name": "text1" }, "properties" : [ {"alignment" : "center"}, {"transparency":"80"} ], "datatypes":{ "bool": false, "double": 23432543.56546, "int": 357685, "boolean": true } } } |
Here is the defined structure of Eina List for JSON in the sample.
- Simple pair ‘key: value’
EinaList1[0] ->Key
EinaList1[1] ->EinaList2[0]->Value
- EinaList with ‘object’ or ‘array with keys’
EinaList1[0] ->Key
EinaList1[1] ->EinaList2[0]->Key
EinaList2[1]->EinaList3[0]->Value
EinaList2[2]->Key
EinaList2[3]->EinaList4[0]->Value
- EinaList with array without keys
EinaList1[0] ->Key
EinaList1[1] ->EinaList2[0]->NULL
EinaList2[1]->EinaList3[0]->Value
EinaList2[2]->NULL
EinaList2[3]->EinaList4[0]->Value
2.2 Main Loop in sample: ParseJsonEntity
ParseJsonEntity() in the sample code is the main loop to execute JSON parsing. As JSON has data as three type like Object, Array, Value, the main loop intenally detect it and parse its content.
If the current node is ‘object’ or ‘array’ type, it continue to parse JSON tree by ParseJsonEntity() recursively. but in case of ‘value’ type, it extract values by ExtractValue().
Eina_List* ParseJsonEntity(JsonNode *root, bool isArrayParsing) { Eina_List *jsonList = NULL; |
2.2.1 Case of ‘object’
If the current note is ’object’, it can be represented like three type : {key:value}, {key:[array]} or {key:{object}}.
Next is the step for objects:
- We take a pointer of object by json_node_get_object() and size of json object is returned by json_object_get_size(object).
- Keys are loaded into GList * keysList by json_object_get_members(). Please note that g_list_free() should be called in the end. But Memory for keys are allocated dynamically too.
- Values (it can be ‘object’,‘array’ or simple string ‘value’ ) are loaded into GList * valList via json_object_get_values(). Please note that g_list_free() should be called in the end also.
- Both lists are parsed inside the loop and Keys are added into Eina_List at once. Value is parsed with recursivelly call of ParseJsonEntity(). If the type of node is JSON_NODE_VALUE, it is parsed by ExtractValue(). Otherwise it is parsed as ‘object’ and ‘array’ into loops again.
- Eina_List which has parsed data is returned.
if(JSON_NODE_TYPE (root) == JSON_NODE_OBJECT) { JsonObject *object = NULL; object = json_node_get_object (root); if (object != NULL) { GList * keysList = NULL; GList * valList = NULL; guint size; size = json_object_get_size (object); keysList = json_object_get_members (object); valList = json_object_get_values (object); JsonNode *tmpNode; gchar *keyName; for(int j=0;j<size;j++) { if (keysList) { keyName = malloc( (strlen(keysList->data) +1)*sizeof(gchar) ); sprintf (keyName, "%s", (gchar*)(keysList->data)); jsonList = eina_list_append(jsonList,keyName); } if (valList) { tmpNode=(JsonNode*)(valList->data); } Eina_List *l= ParseJsonEntity(tmpNode,false); jsonList = eina_list_append(jsonList,l); keysList=g_list_next(keysList); valList=g_list_next(valList); } if (keysList != NULL) g_list_free(keysList); if (valList != NULL) g_list_free(valList); } } |
2.2.2 Case of ‘array’
If the current node is ’array’, we perform next actions:
- We take a pointer of object by json_node_get_object() and size of json object is returned by json_object_get_size(object).
- Every element (node) is taken by json_array_get_element(array,i) and is delivered into ParseJsonEntity(arrayElem, true).If array contains the pair [key:value], it is parsed as ‘object. so key is added at first, after ParseJsonEntity() is called again to extract the value. If array contains only [value], it is parsed one time and NULL is added for node of ‘key’.
- Eina_List of child is merged to previous list.
else if (JSON_NODE_TYPE (root) == JSON_NODE_ARRAY) { JsonArray* array = json_node_get_array(root); guint arraySize = json_array_get_length (array); JsonNode *arrayElem; for(guint i=0;i<arraySize;i++) { Eina_List *jsonArrElemList=NULL; arrayElem = json_array_get_element(array,i); jsonArrElemList=ParseJsonEntity(arrayElem, true); jsonList= eina_list_merge(jsonList,jsonArrElemList); } } |
2.2.3 Case of ‘value’
If the current node contains value (key is already stored in Eina_List before), the value is extracted by ExtractValue(root) and it it added to Eina_List (list contains one item only).
If value is part of array and there are no ‘keys’ (it was not parsed as object), NULL is added for every node of ‘key’.
else if (JSON_NODE_TYPE (root) == JSON_NODE_VALUE) { jsonList = eina_list_append(jsonList,ExtractValue(root)); if(isArrayParsing) { jsonList = eina_list_append(jsonList,NULL); } } return jsonList; } |
2.2.4 Parsing of value
If the node type is JSON_NODE_VALUE, it is parsed by ExtractValue(). At first we should detect value type this way:
GType valueType=json_node_get_value_type(node); |
After this, if value is ’string’, we copy it into allocated memory by json_node_dup_string():
case G_TYPE_STRING: value = json_node_dup_string(node); break; |
If it is ‘double’, ‘int’ or ‘bool’ types, we copy it into allocated memory by this way:
case G_TYPE_DOUBLE: value= malloc(20*sizeof(gchar)); sprintf (value, "%d", (int)json_node_get_int(node)); break; |
For ‘int’ and ‘bool’ types, json_node_get_int() & json_node_get_boolean() are used respectively.
2.3 To avoid memory leak
As parsed JSON data ( keys & values) are dynamically added into allocated memory and it should be freed manually. To avoid memory leak, you should use g_object_unref() after parsing is done. And it can release all the memory allocated for the JSON parser.
3 How to run sample code
As Sample code has data file internally, you just build the sample code to see the result. And if you input "JSONPARSER" in Log view by Tag, you will see the result. Whenever click the button "Parse from File" or "Parse from Array", JSON tree will be represented in Log View.
Following is screenshot of sample.
Following is a result of ”Parse from File” menu:
Following is a result of ”Parse from Array” menu:
4 Summary
As JSON is commoly used nowadays, parsor is necessary module in the application. This tutorial will be helpful to understand how to use JSON-GLIB library in your native application.