Click here to Skip to main content
14,300,131 members

jWrite - A Really Simple JSON Writer in C

Rate this:
4.91 (12 votes)
Please Sign up or sign in to vote.
4.91 (12 votes)
14 Mar 2018CPOL
A simple set of functions to write C variables out to JSON with no fuss or errors

Introduction

jWrite is a simple way of writing JSON to a char buffer in C, directly from native variables. It manages the output buffer so you don't overrun, it handles all the fiddly quotes, brackets and commas and reports where you have tried to create invalid JSON. There is now a C++ version with demo sketch for Arduino.

You can, of course, write json to a string with sprintf()... but this is miles better.

Background

This is a companion set of functions to "jRead an in-place JSON element reader" (http://www.codeproject.com/Articles/885389/jRead-an-in-place-JSON-element-reader).

The same basic design principles apply: it should be in straight C, have little or no memory overhead, execute fast and be simple to use. It is intended for use in embedded projects where using some nice and big C++ structured solution is just not appropriate.

Several approaches were considered; the 'most automatic' was to define a structure which described the JSON and contained pointers to external variables to get the data - this seemed a good idea since it would then be a single call to 'stringify' everything... the downside was the complexity of API required to define such a structure and keep it all in memory.

For the programmer, it is pointless to make configuration of a JSON writer more complicated than writing the stuff out by hand!

jWrite attempts a happy medium in that it is simple and doesn't seem to do much - you just tell it what to write and it does it.

Using the Code

Jumping straight in:

jwOpen( buffer, buflen, JW_OBJECT, JW_PRETTY );  // open root node as object
jwObj_string( "key", "value" );                  // writes "key":"value"
jwObj_int( "int", 1 );                           // writes "int":1
jwObj_array( "anArray");                         // start "anArray": [...] 
    jwArr_int( 0 );                              // add a few integers to the array
    jwArr_int( 1 );
    jwArr_int( 2 );
jwEnd();                                         // end the array
err= jwClose();                                  // close root object - done

which results in:

{
    "key": "value",
    "int": 1,
    "anArray": [
        0,
        1,
        2
    ]
}

The output is prettyfied (it's an option) and has all the { } [ ] , : " characters in the right place.

Although this looks very straightforward, not a lot different than a load of sprintf()s you may say, but jWrite does a few really useful things: it helps you make the output valid JSON.

You can easily call a sequence of jWrite functions which are invalid, like:

jwOpen( buffer, buflen, JW_OBJECT, JW_PRETTY );  // open root node as object
jwObj_string( "key", "value" );                  // writes "key":"value"
jwObj_int( "int", 1 );                           // writes "int":1
    jwArr_int( 0 );                              // add a few integers to the array
...

Since the JSON root is an object, we must insert "key":"value" pairs, so the call to jwArr_int( 0 ) is not valid at this point... this sets an internal error flag to "tried to write Array value into Object" and ignores subsequent function calls until the ending jwClose() when it reports the error.

When writing a large JSON file, it may be difficult to figure out where you went wrong... in this case, jWrite helps out by giving you the number of the function which caused the error and leaving the partially-constructed JSON in your buffer (with '\0' termianator). In the above case, jwErrorPos() would return 4 because the 4th function in this sequence caused the error (the jwOpen() call is number 1)

Since jWrite handles the JSON formatting, it's easy to create the output programatically (rather than in-line as above) like:

jwOpen( buffer, buflen, JWOBJECT, JW_COMPACT );    // outer JSON is an object, compact format

jwObj_array( "myArray" );                          // contains an array: "myArray":[...]
for( i=0; i<myArrayLen; i++ )
    jwArr_int( myArray[i] );                       // write zero or more array entries
jwEnd();

err= jwClose();

In this example, myArrayLen could be anything (0,1,2...) and jWrite handles the output and puts the array value separator commas in the right places.

Any valid JSON sequence can be created with value types of Object, Array, int, double, bool, null and string. You can also add your own stringified values by inserting them raw, e.g., jwObj_raw( "key", rawtext ).

There are longer examples in main.c and some more information in jWrite.h.

C++ "Arduino" Version

The original jWrite (in straight C) has been converted to a C++ class which can be used on any platform. The jWrite_Demo download includes an Arduino sketch which prints a few examples on the Arduino IDE's Serial Monitor.

Using the C++ version is very similar to the C version, the first example above becomes:

jWrite jw( buffer, buflen );        // Create jWrite instance to use application buffer 
jw.open( JW_OBJECT, JW_PRETTY );    // open root node as object 
jw.obj_string( "key", "value" );    // writes "key":"value" 
jw.obj_int( "int", 1 );             // writes "int":1 
jw.obj_array( "anArray");           // start "anArray": [...] 
   jw.arr_int( 0 );                 // add a few integers to the array 
   jw.arr_int( 1 ); 
   jw.arr_int( 2 ); 
jw.end();                           // end the array 
err= jw.close();                    // close root object - done

The jWrite_Demo.ino sketch shows several longer examples.

Points of Interest (C Version)

The Internal Control Structure

Internally, the jWrite functions keep a stack of the Object/Array depth and at every call check if that would result in an error or not. An almost minor point is that it manages your output buffer - once you pass a buffer and length to jwOpen(), it will not overrun it (it will return you an "output buffer full" error) and it will keep it '\0' terminated.

You may have realised that these functions must store some state information (and the node stack) somewhere...

...yes, there is a struct jWriteControl which keeps track of the internals and is used by all of the functions.

Some of you may say "Oh, ok, fine" and others may say "Wait a minute... that's not GLOBAL is it ?"

Well, yes and no...

To Be, or Not To Be, GLOBAL

For many applications it is a lot simpler to have one global (static) instance of a structure which can be used for jWrite, it makes the API calls easy to type in - you don't have to supply a reference every time.

However, that is not very flexible and does not allow for multiple uses of jWrite functions at the same time, so jWrite allows you to turn off the global by undefining JW_GLOBAL_CONTROL_STRUCT. This causes all the API functions to require a pointer to an application-supplied instance of struct jWriteControl.

The above example with #define JW_GLOBAL_CONTROL_STRUCT commented out looks like:

struct jWriteControl jwc;
jwOpen( &jwc, buffer, buflen, JW_OBJECT, JW_PRETTY );  // open root node as object
jwObj_string( &jwc, "key", "value" );                  // writes "key":"value"
jwObj_int( &jwc, "int", 1 );                           // writes "int":1
jwObj_array( &jwc, "anArray");                         // start "anArray": [...] 
    jwArr_int( &jwc, 0 );                              // add a few integers to the array
    jwArr_int( &jwc, 1 );
    jwArr_int( &jwc, 2 );
jwEnd( &jwc );                                         // end the array
err= jwClose( &jwc );                                  // close root object - done

Which is a lot more to type in and begs to be a C++ class really... which has now been written with an example sketch for Arduino, although the class itself is applicable to any platform.

Conclusion

The jWrite and jRead are simple to use and make handling JSON in C manageable without overhead nor a complicated API to learn, especially in embedded projects where you still need to be careful of memory and processor usage.

The download jWrite_1v2.zip contains the source for jWrite.c/jWrite.h and a Windows commandline main.c which runs a couple of examples. Compiled in VS2010 with the included project files.

The download jWrite_Demo contains the source for jWrite.cpp/jWrite.hpp and the jWrite_Demo.ino sketch which runs a few examples and prints the results to the Arduino IDE's Serial Monitor. It will run on an Arduino UNO and above. The jWrite class is generally applicable to any platform.

jWrite C version is written in C89 and has no dependencies.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

tonywilk
Founder
United Kingdom United Kingdom
Developer in just about anything from 6502 machine code thru C, C++ and now things like PHP and javascript. Used to develop hardware and still dabble with electronics and ham radio when I'm not letting off pyrotechnics, shooting or flying my VPM M16 gyroplane.

Comments and Discussions

 
QuestionString escape Pin
Deathwing2327-Jun-19 4:23
memberDeathwing2327-Jun-19 4:23 
QuestionAuto memory allocation Pin
Mariusz Jasinski8-Aug-18 4:41
memberMariusz Jasinski8-Aug-18 4:41 
AnswerRe: Auto memory allocation Pin
Mariusz Jasinski8-Aug-18 4:42
memberMariusz Jasinski8-Aug-18 4:42 
QuestionNan in JSON Pin
Member 1335113331-May-18 22:18
memberMember 1335113331-May-18 22:18 
AnswerRe: Nan in JSON Pin
tonywilk4-Jun-18 7:28
professionaltonywilk4-Jun-18 7:28 
PraiseVery interesting Pin
rather_b_sailing15-Mar-18 6:22
memberrather_b_sailing15-Mar-18 6:22 
Questionwhere is your github? Pin
skyformat99@gmail.com14-Mar-18 16:30
memberskyformat99@gmail.com14-Mar-18 16:30 
AnswerRe: where is your github? Pin
tonywilk17-Mar-18 8:43
professionaltonywilk17-Mar-18 8:43 
GeneralRe: where is your github? Pin
Deathwing2327-Jun-19 4:21
memberDeathwing2327-Jun-19 4:21 
Questionhow do I? Pin
rdnpro14-Mar-18 14:08
memberrdnpro14-Mar-18 14:08 
AnswerRe: how do I? Pin
tonywilk17-Mar-18 8:42
professionaltonywilk17-Mar-18 8:42 
Questionhow to run with gcc ? Pin
Member 1363248529-Jan-18 0:21
memberMember 1363248529-Jan-18 0:21 
AnswerRe: how to run with gcc ? Pin
tonywilk17-Mar-18 8:46
professionaltonywilk17-Mar-18 8:46 
PraiseGood job Pin
Member 1358039217-Dec-17 7:13
memberMember 1358039217-Dec-17 7:13 
SuggestionProblem in jwObj String Pin
Member 1358039217-Dec-17 7:12
memberMember 1358039217-Dec-17 7:12 
Praisenice job Pin
Member 1349249429-Oct-17 15:16
memberMember 1349249429-Oct-17 15:16 
Bugstack depth is not fully utilized Pin
Haider Miraj M1-Oct-17 22:26
memberHaider Miraj M1-Oct-17 22:26 
QuestionReally very well done. Pin
Member 1282940414-Sep-17 8:24
memberMember 1282940414-Sep-17 8:24 
QuestionNested json object from other files Pin
Member 1281067526-Oct-16 14:27
memberMember 1281067526-Oct-16 14:27 
AnswerRe: Nested json object from other files Pin
tonywilk26-Oct-16 16:57
professionaltonywilk26-Oct-16 16:57 
QuestionQuestion about license. Pin
Member 1151283414-Sep-15 1:32
memberMember 1151283414-Sep-15 1:32 
AnswerRe: Question about license. Pin
tonywilk20-Oct-15 2:24
professionaltonywilk20-Oct-15 2:24 
GeneralRe: Question about license. Pin
Ivan Galabov23-Jun-17 6:04
memberIvan Galabov23-Jun-17 6:04 
QuestionC86 ? Pin
zvx19-Mar-15 4:25
memberzvx19-Mar-15 4:25 
AnswerRe: C86 ? Pin
tonywilk19-Mar-15 23:56
professionaltonywilk19-Mar-15 23:56 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

Article
Posted 17 Mar 2015

Stats

39.6K views
2.2K downloads
26 bookmarked