![]() |
Argo
1.0
A C++ library for handling JSON.
|
Well, coders always think we can do better, don't we? I orginally wrote this a few years back as part of another project when there weren't any C++ JSON libraries I could find that didn't leave me banging my head on the desk in frustration. Since then many good options have come along and Milo Yip has done an outstanding job of testing and cataloging many of them.
Anyway, this one is, I think, nice to use and the combination of performance and error checking is strong so I think it's worth publishing.
All of these examples can be found in json_example.cpp. json_test.cpp also includes a lot of example code.
Read a JSON message from one file and write it back out to another.
Create an object with some scalar values in it:
Given the following JSON in a file named foo.json:
Extract the value of the zeroeth element of the foo array:
The Argo library provides a set of C++ classes for manipulating JSON structures as defined by RFC7159. It was designed with the following goals in mind:
Argo has a number of features:
The library has no external dependencies beyond the C++ standard libraries. It does however make use of several C++11 features and therefore needs a compiler that supports them. The code was developed using GCC 4.8 but also built and tested with Visual Studio 2017.
If you want to build the documentation you'll also need doxygen installed.
JSON messages can contain unicode characters (encoded as UTF-8) directly or in the form of \u sequences. E.g.
When reading, Argo handles translates everything into UTF-8. When writing it encodes all unicode characaters using \u sequences.
JSON has just numbers. C++, on the other hand, has ints and doubles. When parsing messages Argo handles this as follows:
An option is provided for programs that need to handle larger numbers using code of their own whereby numbers are syntax checked only and returned as a string for conversion by the caller.
Turning floating point numbers into strings is harder than it looks. Argo take the approach of writing doubles to the full available precision (17 digits) which means that, in string terms, what comes out will not always match what comes out of a Python or Javascript (or other) program but, in numeric terms once parsed and held in an IEEE floating point number, will. If you care about the exact formatting of floating point numbers in your output file then use the json(json::number_double_e, "123.5678") constructor to control the exact output format. To produce optimal number strings (broadly the shortest string that produces the exact same floating point number when converted back) from C++ code I recommend Google's double conversion library.
Argo offers the option to parse JSON messages without converting strings into UTF-8 and without converting numbers into ints or doubles. This allows calling code to handle its own coversions in the case where string values may be invalid (e.g. formatting issues or invalid unicode code points) or where numbers may be too large to fit into the standard types. String and number raw values (created using the json(type, raw_value) constructor) behave differently to normally constructed objects in the following ways:
- Cast operators will throw an exception. - Comparison operators (== != < > <= >=) will throw an exception.
If you need to compare them then you must handle your own conversion on the string returned by get_raw_value().
Doubles and ints may be cast to each other and the usual C/C++ conventions of type promotion are followed by all the relevant operators. Ints, but not doubles, may be cast to booleans.
The default maximum length of a lexical token (e.g. a string, a number, a keyword) is defined in parser.hpp and is set to 100K. This is almost certainly enough for 99.99% of all known applications but the option exists to specify a different number at run time via parameters to the parse class constructor.
The library uses only thread safe system calls and has no global variables that are not also constant. This means that multiple threads can safely parse and uparse messages at the same time and that multiple threads can safely call const methods on a shared json instance. Simultaneous update/read or update/update operations on a json instance are not thread safe. This is the same thread safety model implemented by STL containers.
The Argo json class comes with a copy constructor and an assignment operator. For small objects they're convient to use and don't come with too much of a performance hit. E.g. this is probably fine unless you're doing a very large number of times in a tight loop:
However, if you're handling large structures then you need to put a more effort in to ensure efficiency. The parser() class returns unique_ptrs to instances for this reason and there are specific versions of the json.append and json.insert methods that also work with pointers. In addition there is a json move constructor and a json move assignment operator which can remove the need for explicit pointer operations in many cases.
The simplest way to install Argo is to include the code in your own project and then build it along with everything else. All the .hpp files and all the .cpp files are needed except for documentation.hpp, json_test.cpp and json_example.cpp.
The -std=c++11 g++ flag is required for compilation with gcc.
Alternatively, use the included cmake config file to build a static library and link to that:
cd directory-where-you-downloaded-argo cmake . make
Then in your own build config on the compile line:
-Idirectory-where-you-downloaded-argo
And on the link line:
-Ldirectory-where-you-downloaded-argo-largo
Open Argo.vcxproj using Visual Studio 2017 (community edition works fine) and build it. If you want to incorporate it into an existing project you'll need to configure your build not to use precompiled headers for the Argo files.
Note that the Windows version does not provide the file description based read and write options that the Unix version does. On Windows you are limited to string, stdio and iostreams.
JSON vs json - I've tried to stick to the convention of using JSON to refer to bits of text in the JSON format and json to refer to the Argo json class.
AVRO - the library doesn't have support for this right now but I do plan to add it at some point as I need it for another project.
SAX style parser - I'm not sure about this one. Does anyone really use JSON to represent anything so large and/or complex that it's a genuinely useful approach? I get why the monster than XML has become needs such a thing but I'm not convinced of its usefulness for JSON, at least not from the use cases I've observed or considered. Please tell me otherwise if you disagree.
This software is distributed under the MIT open source license.
Copyright (c) 2017 Andrew Haisley
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.