Simbey's C++ JSON Library

When I first encountered XML, I didn't find it that interesting.  Eventually I realized its value, but I was never overly excited about XML.  SOAP didn't even phase me.  It all seemed somehow mundane and boring. In the best case scenario, it was limited; in the worst case scenario, it was bloated.

Then I met JSON...  Even though both can accomplish the same tasks, JSON's structure and syntax were much more appealing to me.  I actually find JSON interesting, and I was excited to write a parser for it.  Yes, I did look at pre-existing parsers, but none worked quite the way I wanted.

My parser isn't going to work for everyone.  If you're looking for something that's cross-platform compatible using the STL, this isn't it.  Instead, I've built a Windows specific library that's COM-like: the library itself is a DLL, only interfaces and 'C' style APIs are exported, and all calls are cross-DLL safe.  Everything is reference counted, including the strings!

namespace JSON
{
	enum Type
	{
		String,
		Object,
		Boolean,
		Integer,
		LongInteger,
		Double,
		Float,
		Array
	};
};

namespace AddFromObject
{
	enum Options
	{
		Default = 0,
		Optional = 1,
		NotNull = 2,
		NoOverwrite = 4
	};
}

interface IPreProcessorReportError
{
	virtual VOID ReportError (INT nLine, PCWSTR pcwzFile, PCWSTR pcwzError) = 0;
};

interface __declspec(uuid("C1CC5B0E-1210-481a-BC08-B56B77B5A421")) IJSONObject : IUnknown
{
	virtual sysint Count (VOID) = 0;
	virtual HRESULT GetValueName (sysint nValue, __out RSTRING* prstrName) = 0;
	virtual HRESULT SetValueName (sysint nValue, RSTRING rstrName) = 0;
	virtual HRESULT GetValueByIndex (sysint nValue, __deref_out_opt IJSONValue** ppValue) = 0;
	virtual HRESULT SetValueByIndex (sysint nValue, IJSONValue* pValue) = 0;
	virtual HRESULT AddValue (RSTRING rstrName, __in_opt IJSONValue* pValue) = 0;
	virtual HRESULT AddValueW (PCWSTR pcwzName, __in_opt IJSONValue* pValue) = 0;
	virtual HRESULT RemoveValue (RSTRING rstrName, __deref_opt_out_opt IJSONValue** ppValue = NULL) = 0;
	virtual HRESULT RemoveValueW (PCWSTR pcwzName, __deref_opt_out_opt IJSONValue** ppValue = NULL) = 0;
	virtual HRESULT FindValue (RSTRING rstrName, __deref_out_opt IJSONValue** ppValue) const = 0;
	virtual HRESULT FindValueW (PCWSTR pcwzName, __deref_out_opt IJSONValue** ppValue) const = 0;
	virtual HRESULT FindNonNullValue (RSTRING rstrName, __deref_out IJSONValue** ppValue) const = 0;
	virtual HRESULT FindNonNullValueW (PCWSTR pcwzName, __deref_out IJSONValue** ppValue) const = 0;
	virtual HRESULT Compact (VOID) = 0;
	virtual bool HasField (RSTRING rstrName) const = 0;
};

interface __declspec(uuid("ACAA6168-DEB5-4d80-8BE5-A3622F5383C2")) IJSONArray : IUnknown
{
	virtual sysint Count (VOID) = 0;
	virtual HRESULT Add (__in_opt IJSONValue* pvItem) = 0;
	virtual HRESULT Insert (sysint idxInsert, __in_opt IJSONValue* pvItem) = 0;
	virtual HRESULT Remove (sysint nItem, __deref_out_opt IJSONValue** ppvItem = NULL) = 0;
	virtual HRESULT Replace (sysint nItem, __in_opt IJSONValue* pvItem) = 0;
	virtual HRESULT GetValue (sysint nItem, __deref_out_opt IJSONValue** ppvItem) = 0;
	virtual HRESULT GetObject (sysint nItem, __deref_out IJSONObject** ppObject) = 0;
	virtual HRESULT GetString (sysint nItem, __deref_out_opt RSTRING* prstrValue) = 0;
	virtual HRESULT Clear (VOID) = 0;
	virtual HRESULT Compact (VOID) = 0;
};

interface __declspec(uuid("817FAB75-C7A0-4894-B221-3132B34AC5AB")) IJSONValue : IUnknown
{
	virtual JSON::Type GetType (VOID) = 0;
	virtual HRESULT GetString (__out RSTRING* prstrValue) = 0;
	virtual HRESULT GetStringAsGuid (__out GUID& guid) = 0;
	virtual HRESULT GetObject (__deref_out IJSONObject** ppObject) = 0;
	virtual HRESULT GetArray (__deref_out IJSONArray** ppArray) = 0;
	virtual HRESULT GetArrayLength (__out sysint* pcArray) = 0;
	virtual HRESULT GetArrayItem (sysint nItem, __deref_out_opt IJSONValue** ppValue) = 0;
	virtual HRESULT GetBoolean (__out bool* pfValue) = 0;
	virtual HRESULT GetInteger (__out int* pnValue) = 0;
	virtual HRESULT GetLongInteger (__out __int64* pnLongValue) = 0;
	virtual HRESULT GetDouble (__out double* pdblValue) = 0;
	virtual HRESULT GetFloat (__out float* pfltValue) = 0;
	virtual HRESULT GetDWord (__out DWORD* pdwValue) = 0;
};

interface __declspec(uuid("0BD194B5-E22A-4f6e-935B-4328C8B7C67F")) IJSONDictionary : IUnknown
{
	virtual sysint Length (VOID) = 0;
	virtual HRESULT Get (sysint idx, RSTRING* prstrItem) = 0;
	virtual bool Has (RSTRING rstrItem) = 0;
	virtual HRESULT Add (RSTRING rstrItem) = 0;
};

// Core JSON parsing, serialization, and creation functions
HRESULT JSONParse (__in_opt IPreProcessorReportError* pReport, PCWSTR pcwzJSON, INT cchJSON,
	__deref_out_opt IJSONValue** ppvJSON);
HRESULT JSONParseWithDictionary (__in_opt IJSONDictionary* pJSONDictionary,
	__in_opt IPreProcessorReportError* pReport, PCWSTR pcwzJSON, INT cchJSON,
	__deref_out_opt IJSONValue** ppvJSON);
HRESULT JSONCreateDictionary (__deref_out IJSONDictionary** ppJSONDictionary);
HRESULT JSONSerialize (__in_opt IJSONValue* pvJSON, __out ISequentialStream* pstmJSON);
HRESULT JSONSerializeObject (IJSONObject* pObject, __out ISequentialStream* pstmJSON);
HRESULT JSONSerializeArray (IJSONArray* pArray, __out ISequentialStream* pstmJSON);
INT JSONCompareObjects (IJSONObject* pObjectA, IJSONObject* pObjectB);
INT JSONCompareArrays (IJSONArray* pArrayA, IJSONArray* pArrayB);
INT JSONCompare (__in_opt IJSONValue* pvA, __in_opt IJSONValue* pvB);
HRESULT JSONClone (__in_opt IJSONValue* pvJSON, __deref_out IJSONValue** ppvClone, BOOL fDeepClone);
HRESULT JSONCloneObject (IJSONObject* pObject, __deref_out IJSONValue** ppvClone, BOOL fDeepClone);
HRESULT JSONCloneArray (IJSONArray* pArray, __deref_out IJSONValue** ppvClone, BOOL fDeepClone);
HRESULT JSONWrapObject (IJSONObject* pObject, __deref_out IJSONValue** ppvJSON);
HRESULT JSONWrapArray (IJSONArray* pArray, __deref_out IJSONValue** ppvJSON);
HRESULT JSONCreateObject (__deref_out IJSONObject** ppObject);
HRESULT JSONCreateArray (__deref_out IJSONArray** ppArray);
HRESULT JSONCreateString (RSTRING rstrString, __deref_out IJSONValue** ppvString);
HRESULT JSONCreateStringW (PCWSTR pcwzString, INT cchString, __deref_out IJSONValue** ppvString);
HRESULT JSONCreateInteger (int nValue, __deref_out IJSONValue** ppvInteger);
HRESULT JSONCreateLongInteger (__int64 nValue, __deref_out IJSONValue** ppvLongInteger);
HRESULT JSONCreateFloat (float fltValue, __deref_out IJSONValue** ppvFloat);
HRESULT JSONCreateDouble (double dblValue, __deref_out IJSONValue** ppvDouble);
HRESULT JSONFindArrayObjectIndirect (IJSONValue* pvArray, RSTRING rstrField, RSTRING rstrValue,
	__deref_out IJSONObject** ppObject, __out_opt sysint* pnIndex);
HRESULT JSONFindArrayObject (IJSONArray* pArray, RSTRING rstrField, RSTRING rstrValue,
	__deref_out IJSONObject** ppObject, __out_opt sysint* pnIndex);
HRESULT JSONMergeObject (IJSONObject* pTarget, IJSONObject* pSource);
HRESULT JSONAddFromObject (IJSONObject* pTarget, RSTRING rstrTarget, IJSONObject* pSource,
	RSTRING rstrSource, AddFromObject::Options eOptions);
HRESULT JSONAddStringWToObject (IJSONObject* pTarget, RSTRING rstrField, PCWSTR pcwzText, INT cchText);

// Auxiliary path parsing JSON functions
HRESULT JSONGetObject (IJSONValue* pvRoot, PCWSTR pcwzPath, INT cchPath, BOOL fEnsureExists,
	__deref_out IJSONObject** ppObject, __in_opt IPreProcessorReportError* pReport = NULL);
HRESULT JSONGetValue (IJSONValue* pvRoot, PCWSTR pcwzPath, INT cchPath, __deref_out_opt IJSONValue**
	ppvJSON, __in_opt IPreProcessorReportError* pReport = NULL);
HRESULT JSONSetValue (IJSONValue* pvRoot, PCWSTR pcwzPath, INT cchPath, __in_opt IJSONValue* pvJSON,
	__in_opt IPreProcessorReportError* pReport = NULL);
HRESULT JSONRemoveValue (IJSONValue* pvRoot, PCWSTR pcwzPath, INT cchPath, __deref_opt_out_opt IJSONValue**
	ppvJSON, __in_opt IPreProcessorReportError* pReport = NULL);

// Formatting for readability
HRESULT ReformatJSON (PCWSTR pcwzJSON, INT cchJSON, __out ISequentialStream* pstmFormatted);

// Legacy adapter for IObjectCollectionEx
HRESULT JSONCreateCollectionWrapper (IJSONArray* pArray, __deref_out IObjectCollectionEx** ppCollection);

You can download the 32-bit library here.  I haven't had the need yet to produce a 64-bit version, but I can produce one on demand if asked.

To use the library, just pass JSON text into JSONParse().  If successful, an object of type IJSONValue will be returned.  Most of the library should be fairly self explanatory.  JSONSerialize() formats JSON values back into text.  JSONCompare() compares two JSON values and returns -1, 0, or 1 depending on equivalence.  JSONClone() clones (makes a copy of) a value.  For non-deep clone requests, only the first level object or array is cloned.  For deep clones, all objects and arrays are cloned in the tree.  Values and strings are never copied, just reference counted.  JSONWrapObject() creates an IJSONValue wrapper for a provided object.  You'll need this when manually constructing objects.  JSONCreateObject() creates an empty object.

Path Parsing APIs

The JSONGetObject(), JSONGetValue(), JSONSetValue(), and JSONRemoveValue() APIs are convenience functions that use a path string to navigate to a location in the JSON tree.  The lexer used internally for these functions is the same lexer that's used for JSONParse().  Paths are names of fields separated by colons.  Each field name can be enclosed in quotes or not.  When a field name refers to an array, square brackets are used immediately after the field name to indicate which element of the array.  If the provided value is an array, then the search path will begin with an open square bracket.  Array elements can be specified with literal values or using "name:value" pairs if the array contains objects.  The latter syntax is useful when the index is not already known.

Consider this JSON:

[
	{
		food: "fruit",
		types: [ "apple", "banana", "mango" ]
	},
	{
		food: "meat",
		types: [ "beef", "chicken", "pork" ]
	}
]

A path to find the second type of "meat" would look like this:

[food:"meat"]:types[1]

RSTRING APIs

Finally, RSTRING is probably new to you.  See RSTRING.h (in the download) for the full API listing.

You can almost think of RSTRING as a BSTR with reference counting.  Unlike a BSTR, which is always UTF-16 and can be referenced like PWSTR directly, RSTRING can be either UTF-8 or UTF-16 and cannot be referenced directly without adjusting its pointer.  It can also wrap static strings without reference counting.  It does this using the first two bits of the pointer value itself.  Use the RStrIsStatic and RStrIsManaged macros to determine whether the memory is static or reference counted, and use the RStrIsAnsi and RStrIsWide macros to determine whether the memory is UTF-8 or UTF-16 (perhaps RStrIsAnsi should have been named RStrIsUTF8 because it must be treated as UTF-8 text).

Finally, since RSTRING is typed as const BYTE*, you must treat RSTRING memory as immutable, and you cannot directly cast this memory to PSTR or PWSTR.  Instead, you should use the RStrToAnsi and RStrToWide macros to access the read-only memory.  The value NULL is a valid RSTRING value and indicates a static UTF-8 string that's NULL.  All non-NULL values used with RSTRING are zero terminated.  Like a BSTR, values can contain additional NIL characters, so you should always use RStrLen() to obtain the length of an RSTRING.  Finally, use the RStrAddRef() and RStrRelease() APIs on RSTRING values using COM rules.  When the reference falls to zero, the memory, which is owned by SimbeyCore.DLL (included in the download), is freed.

Production Use

If you're wondering whether this library has ever been used professionally, I can proudly tell you it has!  I successfully used this JSON library in the Coca-Cola Freestyle's Consumer Engagement pilot program.  After that, it was further integrated into the Gulfstream's code base for the next six years!

This JSON library is also used by several of my personal projects, including QuadooScript, where it manages the JSON objects and arrays for the scripting language.  In turn, the post-build scripts for Coca-Cola's Freestyle, Gulfstream platform, were written in QuadooScript for the last three years of its life!