We will learn how to use the wxJSON library by examining some simple examples. JSON is a data format; it is not suitable for describing complex documents or to store images, audio and video streams. For these purposes, there are many other formats which are by far more appropriate than JSON.
If you are new to JSON, it is better that you first read the following page, which describe the JSON syntax, its advantages and its disadvantages:
/*************************** This is a C-style comment ***************************/ { // this is a comment line in C++ style "wxWidgets" : { "Description" : "Cross-platform GUI framework", "License" : "wxWidgets", "Version" : { "Major" : 2, "Minor" : 8, "Stable" : true }, "Languages" : [ "C++", "Phyton", "Perl", "C#/Net" ] } }
We can retrieve the values using several access methods as explained in the following code fragment:
// the JSON text, stored in a wxString object wxString document( _T( "/************\n This is a ...... ")); // construct the JSON root object wxJSONValue root; // construct a JSON parser wxJSONReader reader; // now read the JSON text and store it in the 'root' structure // check for errors before retreiving values... int numErrors = reader.Parse( document, &root ); if ( numErrors > 0 ) { cout << "ERROR: the JSON document is not well-formed" << endl; const wxArrayString& errors = reader.GetErrors(); // now print the errors array ... return; } // get the 'License' value wxString license = root["wxWidgets"]["License"].AsString(); // check if a 'Version' value is present bool hasMember = root["wxWidgets"].HasMember( "Version" ); // get the major version value as an integer int majorVer = root["wxWidgets"]["Version"]["Major"].AsInt(); // get the minor version; if the value does not exists, the // default value of ZERO is returned wxJSONValue defaultValue( 0 ); int minorVer = root["wxWidgets"]["Version"].Get( "Minor", defaultValue).AsInt(); // the same as above, but directly constructing the default value int minorVer = root["wxWidgets"]["Version"].Get( "Minor", wxJSONValue( 0)).AsInt(); // now retrive the array of supported languages wxJSONValue languages = root["wxWidgets"]["Languages"]; // before obtaining the array of strings, we check that the type // of the 'language' object is an array // NOTE: this is not strictly necessary. bool isArray = languages.IsArray(); wxArrayString supportedLanguages; for ( int i = 0; i < languages.Size() i++ ) { supportedLanguages.Add( languages[i].AsString()); } // finally, we get an array of all member's names of the 'wxWidgets' // item. The string array will contain (maybe not in this order): // // Description // License // Version // Languages // wxArrayString memberNames = root["wxWidgets"].GetMemberNames();
The wxJSONReader class's constructor has some parameters that control how much error-tolerant should the parser be. By default, the parser is very tolerant about non fatal errors which are reported as warnings. For more information see the wxJSONReader class's description. In addition to reading from a wxString object, you can also read the JSON text from a wxInputStream object. The difference is that the text read from a stream must be encoded in UTF-8 format while the text stored in a string object is encoded in different ways depending on the platform and the build mode: in Unicode builds, strings are stored in UCS-2 format on Windows and UCS-4 on GNU/Linux; in ANSI builds, the string object contains one-byte, locale dependent characters. To know more about Unicode / ANSI read Unicode support in wxJSON
Adding new values or changing the value of existing JSON-value objects is also very simple. The following code fragment shows some examples:
// upgrade the minor version root["wxWidgets"]["Version"]["Minor"] = 9; // create the new 'URL' item root["wxWidgets"]["URL"] = "http://www.wxwidgets.org"; // append a new supported language in the 'Language' array root["wxWidgets"]["Languages"].Append( "Java" ); // creates the new 'Authors' array. // creating an array is just as simple as using the 'Append()' // member function. root["wxWidgets"]["Authors"].Append( "J. Smart" ); root["wxWidgets"]["Authors"].Append( "V. Zeitling" ); root["wxWidgets"]["Authors"].Append( "R. Roebling" ); ... and many others... // you can also use subscripts to obtain the same result: root["wxWidgets"]["Authors"][0] = "J. Smart"; root["wxWidgets"]["Authors"][1] = "V. Zeitling"; root["wxWidgets"]["Authors"][2] = "R. Roebling"; ... and many others... // after the changes, we have to write the JSON object back // to its text representation wxJSONWriter writer; wxString str; writer.Write( root, str ); // if you use the default writer constructor the JSON text // output is human-readable (indented) but does not contain // the comment lines // if you want to keep the comment lines you have to pass // some parameters to the wxJSONWriter constructor wxJSONWriter writer2( wxJSONWRITER_STYLED | wxJSONWRITER_WRITE_COMMENTS ); wxString str2; writer2.Write( root, str2 );
The writer class's constructor has some parameters that allow you to control the style of the output. By default, the writer produces human-readable output with a three-space indentation for objects / arrays sub-items (as shown in the example text above) but it does not write comment lines. You can suppress indentation if, for example, the JSON text has to be sent over a network connection.
Also note that in order to actually have comment lines written back to the JSON text document, you also have to store comments when reading the JSON text document. By default, the parser is error-tolerant and recognizes C/C++ comments but it ignores them. This means that you cannot rewrite them back regardless the flags used in the writer class. To know more about comment lines in JSONvalue objects, see Using comment lines in wxJSON.
In addition to writing to a wxString object, you can also write to a wxOutputStream object. The difference is that the text written to streams is always encoded in UTF-8 format in both Unicode and ANSI builds while the text written to a string object is encoded in different ways depending on the platform and the build mode: in Unicode builds, strings are stored in UCS-2 format on Windows and UCS-4 on GNU/Linux; in ANSI builds, the string object contains one-byte, locale dependent characters. To know more about Unicode / ANSI read Unicode support in wxJSON
Also note that the wxJSONWriter::Write() function does not return a status code. This is OK for writing to a string object but when writing to streams, you have to check for errors. Because the wxJSON writer does not return error codes, you have to check for errors using the stream's memberfunctions, as in the following example code:
// construct the JSON value object and add values to it wxJSONValue root; root["key1"] = "some value"; // write to a stream wxMemoryOutputStream mem; wxJSONWriter writer; writer.Write( root, mem ); // use the stream's 'GetLastError()' function to know if the // write operation was successfull or not wxStreamError err = mem.GetLastError(); if ( err != wxSTREAM_NO_ERROR ) { MessageBox( _T("ERROR: cannot write the JSON text output")); }
I only would like to let you know how much simple is wxJSON: the subscript operators used to access JSON values returns a reference to the JSON value itself thus allowing to have multiple subscripts. Moreover, if the accessed value does not exists, it will be created and a reference to the newly created value is returned. This feature lets you use constructs such as the following:
wxJSONValue value; value["key-1"]["key-2"]["key-3"][4] = 12;
Because value
does not contain any of the specified keys (for objects) and elements (for the array), they will be created. The JSON text output of the code fragment seen above is as follows:
{ "key-1" : { "key-2" : { "key-3" : [ null, null, null, null, 12 ] } } }
In this example we use JSON to store the configuration data of a simple web server application. If you take a look at the Apache config file you will notice that our example looks very similar (but much more human readable).
Our server is a neverending application and it is not interactive: it reads its configuration at startup and when a signal is sent to it. Using JSON for the configuration data is a good choice because it is easy for humans to write the JSON text document. Below we find our webserver's configuration file:
{ // global configuration "Global" : { "DocumentRoot" : "/var/www/html", "MaxClients" : 250, "ServerPort" : 80, "ServerAddress" : 0.0.0.00 "MaxRequestsPerClient" : 1000 } // an array of objects that describes the modules that has to // be loaded at startup "Modules" : [ { "Name" : "auth_basic", "File" : "modules/mod_auth_basic.so", "OnStart" : true }, { "Name" : "auth_digest", "File" : "modules/mod_auth_digest.so", "OnStart" : true }, { "Name" : "auth_file", "File" : "modules/mod_auth_file.so", "OnStart" : false }, ] // Main server configuration "Server" : { "Admin" : "root@localhost.localdomain" "Name" : "www.example.com" }, // The description of directories and their access permissions "Diretory" : [ { "Path" : "/var/www/html", "AllowFrom" : "ALL", "DenyFrom" : null, "Options" : { "Multiviews" : false, "Indexes" : true, "FollowSymLink" : false } } ] }
I think that the file is self-explanatory. I do not want to write the code of the whole web-server application: I only want to show you how to read the configuration data.
When the application starts, it calls a function that reads the configuration file and returns ZERO if there was no error or an exit status code if the file is not correct. The function may be similar to the following one:
int ReadConfig( wxInputStream& jsonStream ) { // comment lines are recognized by the wxJSON library and // can also be stored in the JSON value objects they refer to // but this is not needed by our application because the // config file is written by hand by the website admin // so we use the default ctor of the parser which recognizes // comment lines but do not store them wxJSONReader reader; wxJSONvalue root; int numErrors = reader.Parse( jsonStream, root ); if ( numErrors > 0 ) { // if there are errors in the JSON document, print the // errors and return a non-ZERO value const wxArrayString& errors = reader.GetErrors(); for ( int i = 0; i < numErrors; i++ ) { cout << errors[i] << endl; } return 1; } // if the config file is syntactically correct, we retrieve // the values and store them in application's variables gs_docRoot = root["Global"]["DocumentRoot"].AsString(); // we use the Get() memberfunction to get the port on which // the server listens. If the parameter does not exist in the // JSON value, the default port 80 is returned wxJSONvalue defaultPort = 80; gs_serverPort = root["Global"].Get( "ServerPort", defaultPort ).AsInt(); // the array of modules is processed in a different way: for // every module we print its name and the 'OnStart' flag. // if the flag is TRUE, we load it. wxJSONValue modules = root["Modules"]; // check that the 'Modules' value is of type ARRAY if ( !modules.IsArray() ) { cout << "ERROR: \'modules\' must be a JSON array" << endl; return 1; } for ( int i = 0; i < modules.Size(); i++ ) { cout << "Processing module: " << modules[i]["Name"].AsString() << endl; bool load = modules[i]["OnStart"].AsBool(); cout << "Load module? " << ( load ? "YES" : "NO" ) << endl; if ( load ) { LoadModule( modules[i]["File"].Asstring()); } } // return a ZERO value: it means success. return 0; }
It is not hard to write a similar panel: we only need to implement some basic data that will be passed as parameters to the panel window:
The answer could be: use a JSON formatted wxString object. We define a JSON object that contains two main objects:
{ "Border" : 1, "Columns" : [ { "Name" : "City", "Width" : 50, "Unit" : "Percentage" }, { "Name" : "Temperature", "Width" : 20, "Unit" : "Percentage" }, { "Name" : "Date", "Width" : 30, "Unit" : "Percentage" "Alignment" : "center" } ] "Rows" : [ [ "Baltimora", 20, "20 july" ], [ "New York", 25, "23 july" ], [ "Los Angeles", 29, "25 july" ] ] }
Note that there is no need to specify the type of the data contained in each column because the JSON value object already carries it.
The code for displaying a table that is described in the above JSON text is similar to this one:
void DisplayTable( const wxString& jsonText ) { wxJSONReader reader; wxJSONvalue root; int numErrors = reader.Parse( jsonText, root ); if ( numErrors > 0 ) { // if there are errors in the JSON document return return; { // now display the column names wxJSONvalue columns = root["Columns"]; int border = root["Border"].AsInt(); int width; string align; for ( int i = 0; i < columns.Size(); i++ ) { width = columns[i]["Width"].AsInt(); DisplayColName( columns[i]["Name"].AsString(), width ); } // and now we display the data in the rows // note that we use a predefined alignment for data // unless a specific alignment is set: // // left for strings // right for numbers // the bidimensional array wxJSONValue rows = root["Rows"]; // the string that has to be displayed in the table's cell string valueString; // the default alignment: it is set depending on the data type wxJSONValue defaultAlign; // for all rows ... for ( int x = 0; x < rows.Size(); x++ ) { // .. and for all columns in the row for ( int y = 0; y < rows[x].Size(); y++ ) { // get the width of the column width = columns[y]["Width"].AsInt(); // get the value object wxJSONValue value = rows[x][y]; // check the type of the data wxJSONValue::wxJSONType type = value.GetType(); switch ( type ) { case wxJSONTYPE_NULL : // display an empty string valueString.clear();; break; case wxJSONTYPE_INT : case wxJSONTYPE_UINT : case wxJSONTYPE_DOUBLE : // numeric values are right-aligned defaulAlign = "right"; align = columns[y].Get( "Align", defaultAlign ).AsString(); valueString = value.AsString(); break; case wxJSONTYPE_STRING : case wxJSONTYPE_CSTRING : defaulAlign = "left"; align = columns[y].Get( "Align", defaultAlign ).AsString(); valueString = value.AsString(); break; case wxJSONTYPE_BOOL : defaulAlign = "center"; align = columns[y].Get( "Align", defaultAlign ).AsString(); valueString = value.AsString(); break; } // now that we have the alignment, the column's width and the // value of the data as a string: // note that numeric data are converted to a decimal string // and boolean values are converted to 'TRUE' or 'FALSE' when you // use the wxJSONValue::AsString() memberfunction DisplayValue( valueString, width, align ); } // continue for all columns } // continue for all rows }
JSON format is very flexible: in future we can add new features to the application. For example we may decide that our general-purpose table-viewer panel will let the user to change the values in the table rows but only for some specific columns.
We add a new item in the Columns array descriptors: the Editable flag which is a boolean type. Example:
"Columns" : [ { "Name" : "Temperature", "Width" : 50, "Unit" : "Percentage", "Editable" : true },
Note that this new format of our table description is compatible in both directions: it is normal that a new version of the application can read and handle old-style data but it is not very easy to maintain the old application compatible with a new data format that was introduced in a new version.
In our example, the simplicity and flexibility of JSON make the old application capable of reading the new format of JSON data. Of course, the data are not editable because the old application does not permit this operation. The old version of the application simply ignores the existance of the new Editable flag so that the JSON text can be read and processed as in the previous version.
In older 0.x versions you can get a value stored in the JSON value object in a type that is different from the one that is actually stored provided that the two types are compatible. For example, if a value contains an integer type of value -1, you can get the value as an integer or as a double, or as a string:
wxJSONValue v( -1 ); int i = v.AsInt(); // returns -1 double d = v.AsDouble(); // returns -1.0 wxString s = v.AsString(); // returns "-1"
This is no longer supported in new 1.x versions: all AsXxxxxx() functions return the stored value without reinterpreting the bits stored in memory so when you call the AsDouble() function on a value object that contains the integer -1 (all bits set), you get a NaN because all bits set in a double type represent a NaN:
wxJSONValue v( -1 ); int i = v.AsInt(); // returns -1 double d = v.AsDouble(); // returns NaN wxString s = v.AsString(); // returns "-1"
The only exceptions to this rule are the functions:
wxJSONValue v; if ( v.IsInt() ) { int i = v.AsInt(); } else if ( v.IsDouble() ) { double d = v.AsDouble(); } else ...
You can also use the wxJSONValue::GetType() function to get directly the type of the value which returns a wxJSONType type that can be used in a switch statement:
wxJSONValue value( 100 ); wxJSONType type = value.GetType(); switch ( type ) { case wxJSONTYPE_INT : case wxJSONTYPE_SHORT : case wxJSONTYPE_LONG : case wxJSONTYPE_INT64 : wxInt64 i64 = value.AsInt64(); case wxJSONTYPE_UINT : case wxJSONTYPE_USHORT : case wxJSONTYPE_ULONG : case wxJSONTYPE_UINT64 : wxUint64 i64 = value.AsUInt64(); case wxJSONTYPE_DOUBLE : double d = value.AsDouble(); ... always specify ALL the possible types ... default : wxFAIL_MSG( _T("Unexpected value type")); }
What happens if I get the wrong type?
As explained earlier, the wxJSONValue::AsXxxxxx()
functions just return the content of the memory area as it apears without trying to promote the stored type to another type even when they are compatible. The conseguence is that the function returns a wrong value. Example:
wxJSONValue v( -1 ); // returns -1 (OK): the IsInt() returns TRUE int i = v.AsInt(); // returns NaN (wrong): IsDouble() returns FALSE double d = v.AsDouble(); // returns 4294967295 (wrong) IsUInt() returns FALSE unsigned u = v.AsUInt(); // returns 65535 (wrong) IsUShort() returns FALSE unsigned short h = v.AsUShort(); // returns "-1" (OK) IsString() returns FALSE; this is an exception wxString s = v.AsString(); // returns a NULL pointer IsCString() returns FALSE wxChar* c = v.AsCString();
However, in debug builds all AsXxxxxx() functions ASSERT that the corresponding IsXxxxx fucntions return TRUE so avoiding type mismatch errors. The ASSERTION failures does not occur in release builds.
Starting from version 0.5 the wxJSON library supports 64-bits integers on platforms that have native support for this. If the library is compiled with the 64-bits support enabled, the JSON value class defines functions in order to let the user know the storage needed by the integer value (32-bits or 64-bits). To know more about this topic read 64-bits and 32-bits integers.
Why should we use comments in JSON formatted text?
There are several reasons: in an application's configuration file like the one we have seen in Example 2: a configuration file comments are very usefull to help the user to understand the meaning of each configuration option.
On the other hand, if a data structure is sent over a network connection, it is most likely that comments are not really needed but they may still be usefull for debugging purposes or for explaining the value, as in the following example:
{ "Person" : { { "Name" : "John Smith", "Height" : 190, // expressed in centimeters "Birthday" : { "Year" : 1965, "Month" : 8, "Day" : 18 } } }
position
parameter are:
{ // comment before 'key-1' "key-1" : "value-1", "key-2" : "value-2", // comment inline 'key-2' "key-3" : "value-3" // comment after 'key-3' }
To get the above output use the following code fragment:
wxJSONValue root; root["key-1"] = "value-1"; root["key-2"] = "value-2"; root["key-3"] = "value-3"; root["key-1"].AddComment( "// comment before", wxJSONVALUE_COMMENT_BEFORE ); root["key-2"].AddComment( "// comment inline", wxJSONVALUE_COMMENT_INLINE ); root["key-3"].AddComment( "// comment after", wxJSONVALUE_COMMENT_AFTER );
You have to note that comment lines are kept in an array of strings in a data member of the wxJSONValue object: this means that you can add more than one comment line to a JSON value object but remember that there is only one data member for storing the position of all comment lines. In other words, the position at which the comment lines are written in the JSON output text in the same position as the one specified in the last call to the wxJSONValue::AddComment() function.
In order to prevent that the comment's position have to be set in the last call to the AddComment()
function, you can specify the wxJSONVALUE_COMMENT_DEFAULT
constant as the position parameter. This constant causes the function to not modify the actual position value. If you use this constant in the first call to the AddComment()
function, it is interpreted as wxJSONVALUE_COMMENT_BEFORE
. Below you find an example:
wxJSONValue root; root.AddComment( "// comment for root (line 1)", wxJSONVALUE_COMMENT_BEFORE ); // no need to specify the comment position in subsequent calls to AddComment() // the old position is not modified root.AddComment( "// comment for root (line 2)" ); // set the value for 'key-1' root["key-1"] = "value1"; // now we add a comment line for 'key-1'. We do not specify the comment // position so it defaults to wxJSONVALUE_COMMENT_DEFAULT which cause // the AddCommnen() function to maintan the old position. // As the comment position was never set before, the wxJSONVALUE_COMMENT_BEFORE // will be set root["key-1"].AddComment( "// comment before key-1" ); // set the value of 'key-4' an an empty object. // note that we cannot use the default wxJSONValue constructor to get an // empty object type: the default ctor constructs a NULL value object. root["key-4"] = wxJSONValue( wxJSONTYPE_OBJECT ); // now we add an inline comment to 'key-4' root["key-4"].AddComment( "// comment inline key-4", wxJSONVALUE_COMMENT_INLINE ); // now we write the JSON 'root' value to a JSON formatted string // object. Note that we have to specify some flags in the wxJSONWriter // constructor wxJSONWriter writer( wxJSONWRITER_STYLED | wxJSONWRITER_WRITE_COMMENTS ); wxString jsonText; writer.Write( root, jsonText );
Below is the output text:
// comment for root (line 1) // comment for root (line 2) { // comment before 'key2' "key-1" : "value1", "key-4" : { // comment inline key-4 } }
wxJSONValue root; root["key-1"] = "value1"; root["key-1"].AddComment( " // comment inline (1)", wxJSONVALUE_COMMENT_INLINE ); root["key-1"].AddComment( " // comment inline (2)" ); root["key-1"].AddComment( " // comment inline (3)" ); // this is the JSON formatted output: { "key-1" : "value1", // comment inline (1) // comment inline (2) // comment inline (3) }
Note that only the first line is really printed inline. The other two lines are printed after the value they refer to and without indentation: this is not very readable. For this reason, you should use inline comments only when you have only one line of comments. If you need more than one line of comment use the before or the after comment's position.
wxJSONValue v1( 10 ); v1.AddComment( "// A C++ comment line\n" ); // this is OK v1.AddComment( "// Another C++ comment line" ); // this is OK v1.AddComment( "/* A C-style comment */"); // OK wxJSONValue v2( 20 ); v2.AddComment( "A C++ comment line\n" ); // Error: does not start with '//' v2.AddComment( "/ A C++ comment line\n" ); // Error: does not start with '//' v2.AddComment( "/*** comment **" ); // Error: the close-comment is missing // the following is OK: new-line characters may follow // the end-comment characters of a C-style comment wxJSONValue v3( 30 ); v2.AddComment( "/*** C comment ***/\n\n\n" );
Note that the function cannot trap all possible errors because the checks that are done by the function are very simple:
// the following is not correct: the AddComment() function only // appends the final LF char wxJSONValue v1( 10 ); v1.AddComment( "// Line 1\nLine2" ); // this is the JSON output (it is not OK) ... // Line 1 Line 2 10 ...
You would have to write:
wxJSONValue v1( 10 ); v1.AddComment( "// Line 1" ); v1.AddComment( "// Line 2" );
Nested C-style comments are not handled correctly by the wxJSON parser:
wxJSONValue v2( 20 ); v2.AddComment( "/* comment1 /* comment2 */ */" ); // this is the JSON text output: ... /* comment1 /* comment2 */ */ 20 ...
The parser will report an error when it reads the last close-comment characters because when a C-style comment starts, all characters until the first close-comment chars are ignored by the parser.
// this ctor is error tolerant and stores comments wxJSONReader reader1( wxJSONREADER_TOLERANT | wxJSONREADER_STORE_COMMENTS ); // this ctor is not error tolerant: wxJSON extensions are off // the parser does not recognize comments: they are reported as errors wxJSONReader reader2( wxJSONREADER_STRICT ); // this ctor is error tolerant but does not store comments wxJSONReader reader3; // this ctor recognizes all wxJSON extensions except the // 'multiline string' feature which is reported as an error // the parser also stores comments wxJSONReader reader1( wxJSONREADER_ALLOW_COMMENTS | wxJSONREADER_CASE | wxJSONREADER_MISSING | wxJSONREADER_STORE_COMMENTS ); // parser is tolerant and stores comments but comments apear AFTER // the value they refer to wxJSONReader reader1( wxJSONREADER_TOLERANT | wxJSONREADER_STORE_COMMENTS ); | wxJSONREADER_COMMENTS_AFTER );
See the wxJSONReader class's description for more informations about the wxJSON parser's extensions. Also note that the constructor's flags related to comments are only meaningfull if the main flags are also specified. In other words, the wxJSONREADER_STORE_COMMENTS
flag is only meaningfull if wxJSONREADER_ALLOW_COMMENTS
is also set (or the wxJSONREADER_TOLERANT
constant which includes it). Also, the wxJSONREADER_COMMENTS_AFTER
is only meaningfull if wxJSONREADER_STORE_COMMENTS
is also set: if comments are not stored, there is no need for the parser to know the position of the comments with respect to the value.
Below you find a JSON text with many comment lines and the description of which value the comments refer to. The parser is constructed with the wxJSONREADER_STORE_COMMENT
flag set, thus the parser assumes that comments apear before the value they refer to.
// comment for root (line 1) // comment for root (line 2) { "key-1" : "value1", // comment before 'key2' "key-2" : "value2", // comment before 'key3' (1) // comment before 'key3' (2) "key-3" : { "key3-1" : "value3-1", // comment before key3-2 "key3-2" : "value3-2" }, "key-4" : { // comment inline key4 // this comment does not refer to anything } "key-5" : [ // comment inline key5 // comment before item 5-1 "item5-1", "item5-2", // comment inline 5-2 "item5-3" // comment inline 5-3 // this comment does not refer to anything ], "key-6" : // comment inline key-6 "value", "key-7" : { "key-7-1" : "value-7-1" }, // comment inline key-7 "key-8" // comment inline key-8(1) : // comment inline key-8(2) "value", // comment inline key-8(3) "key-9" : { "key9-1" : 91, "key9-2" : 92 } "key-10" : [ ] // comment inline key-10 // this comment does not refer to anything } // this comment does not refer to anything // if comments apear before the value This non-JSON text is ignored by the parser because it apears after the top-level close-object character
At time of writing (january 2008), the wxWidgets community is about to release version 3.0 of the GUI which will introduce a major change in unicode support. I read in the forum that the new version will no more support ANSI builds: wxWidgets will only compile in Unicode mode. From the point of view of the wxJSON library this change does not have side effects: the only conseguence is that what I wrote for ANSI builds will no more apply.
The JSON syntax states that JSON string values are stored in Unicode format and the encoding of a JSON text is by default UTF-8; UCS-2 and UCS-4 are also allowed. The wxJSON library follows this rules but because wxJSON (and wxWidgets itself) may be compiled in two different modes (by now) we have to distinguish two situations:
Similarly, if the JSON text is read from a stream, the wxJSON parser reads one character at a time from the stream and then converts it to a wide character thus obtaining the same result. Note that I said that the parser reads one character at a time and not one byte: UTF-8 format may require one or more bytes to store one Unicode character. The parser knows how many bytes it has to read from the stream in order to get one UTF-8 character.
Just like reading, writing a JSON value structure to a JSON text representation has the same choices: you can write to a string object or to a stream object. If you write to a wxString object, the text is encoded in the platform dependent format: UCS-2 on Windows and UCS-4 on Linux. You can then convert the string object to whatever format you want. Writing to streams always produces UTF-8 encoded text.
When using Unicode builds you can directly hardcode JSON values in all character sets as, for example:
wxJSONValue value; value[_T("us-ascii")] = _T(""abcABC"); value[_T("latin1")] = _T("àèì©®"); value[_T("greek")] = _T("αβγδ"); value[_T("cyrillic")] = _T("ФХЦЧ"); wxMemoryOutputStream os; wxJSONWriter writer; writer.Write( value, os );
The above code fragment contains characters from various european languages which are incompatible in a locale depended environment. The output memory stream contains a UTF-8 encoded text. WARNING: the possibility to directly hardcode Unicode strings depends on your editor which has to be able to save the source file in a format that support Unicode (for example, UTF-8). Also, your compiler must be able to read and compile UTF-8 sources.
Note that when writing to a stream object, you cannot use an encoding format other than UTF-8. If you want to encode the JSON text to a different format you have to go through the wxString output object:
wxJSONValue value; value[_T("key")] = 12; // add values to the JSON object wxString jsonText; // write the value to a string object wxJSONWriter writer; // instantiate the writer and write the value writer.Write( value, jsonText ); // instantiate the conversion object: you should check if wxCSConv::IsOk() wxCSConv conv( _T("UCS-4LE")); // compute the length of the needed buffer size_t len = conv.FromWChar( 0, 0, jsonText.wc_str()); // allocate the buffer char* buffer = new char[len + 4]; // do the conversion and finally write the buffer to a file or // whatever you want len = conv.FromWChar( buffer, len + 4, jsonText.wc_str());
Note that the above code fragment should work fine because the selected encoding format has full support for Unicode characters. On the other hand, you cannot convert the wxString object to whatever format you want; in other words, you cannot convert it to a locale dependent charset because the string contains characters from different languages which are encoded in different locale dependent charsets (by the way, the charsets that have to be used are ISO-8859-1 for Latin1, ISO-8859-7 for greek and ISO-8859-5 for cyrillic). For example, the following code does not work:
wxJSONValue value; wxString jsonText; wxJSONWriter writer; // write to a string object writer.Write( value, jsonText ); // instantiate the conversion object: we want to encode in Latin-1 wxCSConv conv( _T("ISO-8859-1")); // compute the length of the needed buffer // what we get is the wxCONV_FAILED error code size_t len = conv.FromWChar( 0, 0, jsonText.wc_str());
The better solution, however, is to get a UTF-8 encoded buffer which is compatible with all other JSON implementations. Also note that UTF-8 has full support for wide character encoding:
wxJSONValue value; wxString jsonText; wxJSONWriter writer; // write to a string object writer.Write( value, jsonText ); // convert to UTF-8 wxCharBuffer buffer = jsonText.utf8_str();
Is there a mean to exchange JSON data from a Unicode mode application to an ANSI one (and viceversa)? The answer is: YES but you cannot use the wxString object as the JSON text output: you have to use streams which produce UTF-8 encoded JSON text (or, if you use strings, you have to encode the output in UTF-8 format using the wxString::utf8_str()
function). If you want to know more about this topic, continue read.
When reading from a string, the parser does not try to convert characters: the wxString object contains one-byte characters that are simply read by the parser and stored in the JSON string value.
The same applies when writing to a wxString object: the JSON output text contains one-byte, locale dependent characters. You can simply write the string data to a file or whatever other object you want but keep in mind that the JSON text is not correctly readable by other JSON implementations which expects UTF-8 encoded text. The only exception to this is when you only use US-ASCII characters whose character code is within 0x00 and 0x7F: the UTF-8 encoding format stores those characters in only one byte. The following is an example:
wxJSONValue value; value["key"] = "a string"; wxString jsonText; wxJSONWriter writer; // write to the string object writer.Write( value, jsonText ); // write the output JSON text to a file in ANSI format: wxFFile f( "myfile", "w" ); bool success = f.Write( jsonText.c_str(), jsonText.Length());
In addition to writing to a string object, you can also write the JSON value object to a stream, as in Unicode builds. When writing to streams, the encoding format is UTF-8 even if the application is built in ANSI mode. This permits the data to be sent to an Unicode version of the same application (or another application built in Unicode mode) or any other JSON implementation.
When reading from JSON text, you have to same options: you can read from a wxString object which may be constructed by directly reading a file: this is exactly as writing the string output to a file. You can also read JSON values from a stream: the difference between reading from a stream and from a string is that the stream must be encoded in UTF-8 format.
What? But we are in ANSI mode and do not have wide char support!!! What happens when the latin1, greek and cyrillic characters are read?
This is the big difference between reading from a string and a stream in ANSI builds: when reading from a stream, the input JSON text is read one char at a time and converted to a wide character. Then, the wide character is converted to the corresponding locale dependent character, if one exists. If the wide character cannot be represented in the current locale dependent charset, than the wxJSON reader will store the unicode escaped sequence of the wide character. For example, consider the following JSON text contained in a file encoded in UTF-8 format:
{ "us-ascii" : "abcABC", "latin1" : "àèì©®", "greek" : "αβγδ", "cyrillic" : "ФХЦЧ" }
We read the UTF-8 file in an ANSI application which is localized in West Europa thus using the ISO-8859-1 (Latin-1) character set. The JSON value object will contain the following values:
{ "us-ascii" : "abcABC", "latin1" : "àèì©®", "greek" : "\u03B1\u03B2\u03B3\u03B4", "cyrillic" : "\u0424\u0425\u0426\u0427" }
As you can see, the greek and cyrillic characters that cannot be represented as latin characters are preserved in their original meaning. Moreover, if the data is resent to a Unicode application, the strings will be correctly reconverted to their original wide character encoding. Also, if the data will be sent to another ANSI application which has a different locale, for example ISO-8859-7 (greek), the latin-1 characters will be stored as unicode escaped sequences while the greek sequences will be converted to the corresponding greek letters.
{ "key1" : "value1", "key2" : { "key2-1" : 12 } }
{ "key1" : "value1", "key2" : { "key2-1" : 12 } }
{"key1" : "value1","key2" : { "key2-1" : 12 }}
Note that the JSON text output is a bit different from that of 0.x versions because in the prevoious versions this style adds LF characters between value. Linefeeds are not added in the new 1.0 version of wxJSON. The JSON output text is syntactically equal in both implementations: they only differ for the LF character. If you rely on the old text output, you can have the same results by specifying the following flags:
which actually produce the same result as the old wxJSONWRITER_NONE flag; adds LF between values but suppress indentation.
wxJSONWriter writer( wxJSONWRITER_STYLED | wxJSONWRITER_NO_INDENTATION | wxJSONWRITER_NO_LINEFEEDS ); // is the same as: wxJSONWriter writer( wxJSONWRITER_NONE );
If the styled flag is not specfified, these two flags are useless and has no effect: it is not an error to specify them because the writer simply ignores them. The following is a text output of a value written using the wxJSONWRITER_STYLED and wxJSONWRITER_NO_LINEFEEDS flags: as you can see, LF are not printed but indentation is performed.
{ "key1" : "value1", "key2" : { "key2-1" : 12 } }
Also note that the LF characters are only suppressed between values and not for comments. If you include C++ comments in the JSON text output, they need LF characters in order to mark the end of the comment. Note that C-style comments do not need a LF character to mark their end so this flag cause the LF character to be suppressed between values. LF characters that are contained in the C-style comment are not suppressed. Example using the wxJSONWRITER_NO_LINEFEEDS and wxJSONWRITER_WRITE_COMMENTS:
{ "key1" : "value1", // C++ comment cause the LF char to be written "key2" : { "key2-1" : 12 }}
Also note that string values may contain line-feeds: they never are suppressed by these flags.
Unlike in older 0.x versions, this flag does not depend on the wxJSONWRITER_STYLED flag. Comments may be added to the JSON text output even if indentation is suppressed. Because C++ comments rely on the LF character to mark their end, a LF character is always added to the end of a C++ comment and ti cannot be suppressed even if you specify the wxJSONWRITER_NO_LINEFEEDS flag.
Example using only this flag:
{ "key1" : "value1", // C++ comment cause the LF char to be written "key2" : { "key2-1" : 12 } /* C-style comment */ }
Comments in JSON text are normally used for debugging purposes and, in general, because a human has to read them so the most common use of this flag is toghether with wxJSONREADER_STYLED:
{ "key1" : "value1", // C++ comment always include a trailing LF "key2" : { "key2-1" : 12 /* C-style comment */ } }
Note that the wxJSON writer only writes one LF character between the first value and the second one. Because wxJSONWRITER_STYLED prints a LF between values and the C++ comment already terminates with a LF, the writer checks that a LF char was already written before writing the final LF that separates values.
The same applies for C-style comments: they do not need a terminating LF character but you can store it in the comment string: moreover, you can store more than one of them. Before writing the LF character that separates the values, the writer checks if the last character written is a LF: it is it, the final LF is not needed so it is omitted.
{ // C++ comment always include a trailing LF "key1" : "value1", "key2" : { /* C-style comment */ "key2-1" : 12 } }
Note that comments that are not written inline are indented using the same number of TABs (or spaces) of the value they refer to.
{ "key" : "A very long string value that cause the user to scroll horizontally in order to actually read the whole line" }
It would be more readable if it was written as:
{ "key" : "A very long string value that cause the user to scroll" " horizontally in order to actually read the whole line" }
This flag forces the wxJSON writer to split string values if the output column number at which the next character would be printed is equal or greater than 75. Note that the writer will split the string value only in correndonce of a space or punctuation character in order to prevent single words to be splitted. Also, strings that exceeds column number 75 are not splitted if:
The limits of column number is hardcoded in the wxJSON library but they are defined
in the include/wx/json_defs.h
header file. If you want to change these limits edit that header file and recompile the library.
wxJSONValue v[ _T("counter")] = (unsigned int) 100; wxJSONWriter writer; wxString jsonText; writer.Write( v, jsonText ); // the output is: { "counter" : 100 }
The reader cannot know that the variable that generated the value was of type unsigned and it stores the value as a signed integer. In order to force the reader use a unsigned integer in this case, the wxJSONWriter prepends a plus sign to the integer value:
wxJSONValue v[ _T("counter")] = (unsigned int) 100; wxJSONWriter writer( wxJSONWRITER_RECOGNIZE_UNSIGNED ); wxString jsonText; writer.Write( v, jsonText ); // the output is: { "counter" : +100 }
Now the wxJSONReader class assigns the value to a unsigned integer data type. Note that this feature is not strict JSON and may be handled incorrectly by other JSON mplementations so, by default, this feature is disabled; you have to use a special wxJSONWriter's flag to get this. Also note that other JSON implementations may fail to read such integers: you should only use the feature if your applications only use the wxJSON library for reading JSON text.