Raylib 3
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 

789 lines
24 KiB

//Author: Tony Wilk
//Source: https://www.codeproject.com/Articles/885389/jRead-An-in-place-JSON-Element-Reader
//LICENSE: The Code Project Open License (CPOL) 1.02
//see provided file ICENSE_jReadJWrite.html or https://www.codeproject.com/info/cpol10.aspx
// jRead.cpp
// Version 1v6
//
// jRead - an in-place JSON element reader
// =======================================
//
// Instead of parsing JSON into some structure, this maintains the input JSON as unaltered text
// and allows queries to be made on it directly.
//
// e.g. with the simple JSON:
// {
// "astring":"This is a string",
// "anumber":42,
// "myarray":[ "one", 2, {"description":"element 3"}, null ],
// "yesno":true,
// "HowMany":"1234",
// "foo":null
// }
//
// calling:
// jRead( json, "{'myarray'[0", &jElem );
//
// would return:
// jElem.dataType= JREAD_STRING;
// jElem.elements= 1
// jElem.bytelen= 3
// jElem.pValue -> "one"
//
// or you could call the helper functions:
// jRead_string( json, "{'astring'", destString, MAXLEN );
// jRead_int( json, "{'anumber'", &myint );
// jRead_string( json, "{'myarray'[3", destString, MAXLEN );
// etc.
//
// Note that the helper functions do type coersion and always return a value
// (on error an empty string is returned or value of zero etc.)
//
// The query string simply defines the route to the required data item
// as an arbitary list of object or array specifiers:
// object element= "{'keyname'"
// array element= "[INDEX"
//
// The jRead() function fills a jReadElement structure to describe the located element
// this can be used to locate any element, not just terminal values
// e.g.
// jRead( json, "{'myarray'", &jElem );
//
// in this case jElem would contain:
// jElem.dataType= JSON_ARRAY
// jElem.elements= 4
// jElem.bytelen= 46
// jElem.pValue -> [ "one", 2, {"descripton":"element 3"}, null ] ...
//
// allowing jRead to be called again on the array:
// e.g.
// jRead( jElem.pValue, "[3", &jElem ); // get 4th element - the null value
//
// .oO! see main.c runExamples() for a whole bunch of examples !Oo.
// -------------------------------------------------------
//
// Note that jRead never modifies the source JSON and does not allocate any memory.
// i.e. elements are returned as pointer and length into the source text.
//
// Functions
// =========
// Main JSON reader:
// int jRead( char * JsonSource, char *query, jReadElement &pResult );
//
// Extended function using query parameters for indexing:
// int jRead( char * JsonSource, char *query, jReadElement &pResult, int *queryParams );
//
// Function to step thru JSON arrays instead of indexing:
// char *jReadArrayStep( char *pJsonArray, struct jReadElement *pResult );
//
// Optional Helper functions:
// long jRead_long( char *pJson, char *pQuery );
// int jRead_int( char *pJson, char *pQuery );
// double jRead_double( char *pJson, char *pQuery );
// int jRead_string( char *pJson, char *pQuery, char *pDest, int destlen );
//
// Optional String output Functions
// char * jReadTypeToString( int dataType ); // string describes dataType
// char * jReadErrorToString( int error ); // string descibes error code
//
// *NEW* in 1v2
// - "{NUMBER" returns the "key" value at that index within an object
// - jReadParam() adds queryParams which can be used as indexes into arrays (or into
// objects to return key values) by specifying '*' in the query string
// e.g. jReadParam( pJson, "[*", &result, &index )
// *NEW in 1v4
// - fixed a couple of error return values
// - added #define JREAD_DOUBLE_QUOTE_IN_QUERY
// *NEW* in 1v5 (11mar2015)
// - fixed null ptr if '[*' used when null param passed
// *NEW* in 1v6 (24sep2016)
// - fixed handling of empty arrays and objects
//
// TonyWilk, 24sep2016
// mail at tonywilk . co .uk
//
// License: "Free as in You Owe Me a Beer"
// - actually, since some people really worry about licenses, you are free to apply
// whatever licence you want.
//
// Note: jRead_atol() and jRead_atof() are modified from original routines
// fast_atol() and fast_atof() 09-May-2009 Tom Van Baak (tvb) www.LeapSecond.com
//
// You may want to replace the use of jRead_atol() and jRead_atof() in helper functions
// of your own. Especially note that my atof does not handle exponents.
//
//
#include <stdio.h>
#include "jRead.h"
// By default we use single quote in query strings so it's a lot easier
// to type in code i.e. "{'key'" instead of "{\"key\""
//
#ifdef JREAD_DOUBLE_QUOTE_IN_QUERY
#define QUERY_QUOTE '\"'
#else
#define QUERY_QUOTE '\''
#endif
//------------------------------------------------------
// Internal Functions
char * jReadSkipWhitespace( char *sp );
char * jReadFindTok( char *sp, int *tokType );
char * jReadGetString( char *pJson, struct jReadElement *pElem, char quote );
int jReadTextLen( char *pJson );
int jReadStrcmp( struct jReadElement *j1, struct jReadElement *j2 );
char * jReadCountObject( char *pJson, struct jReadElement *pResult, int keyIndex );
char * jReadCountArray( char *pJson, struct jReadElement *pResult );
char * jRead_atoi( char *p, unsigned int *result );
char * jRead_atol( char *p, long *result );
char * jRead_atof( char *p, double *result);
//=======================================================
char *jReadSkipWhitespace( char *sp )
{
while( (*sp != '\0') && (*sp <= ' ') )
sp++;
return sp;
};
// Find start of a token
// - returns pointer to start of next token or element
// returns type via tokType
//
char *jReadFindTok( char *sp, int *tokType )
{
char c;
sp= jReadSkipWhitespace(sp);
c= *sp;
if( c == '\0' ) *tokType= JREAD_EOL;
else if((c == '"') || (c == QUERY_QUOTE))*tokType= JREAD_STRING;
else if((c >= '0') && (c <= '9')) *tokType= JREAD_NUMBER;
else if( c == '-') *tokType= JREAD_NUMBER;
else if( c == '{') *tokType= JREAD_OBJECT;
else if( c == '[') *tokType= JREAD_ARRAY;
else if( c == '}') *tokType= JREAD_EOBJECT;
else if( c == ']') *tokType= JREAD_EARRAY;
else if((c == 't') || (c == 'f')) *tokType= JREAD_BOOL;
else if( c == 'n') *tokType= JREAD_NULL;
else if( c == ':') *tokType= JREAD_COLON;
else if( c == ',') *tokType= JREAD_COMMA;
else if( c == '*') *tokType= JREAD_QPARAM;
else *tokType= JREAD_ERROR;
return sp;
};
// jReadGetString
// - assumes next element is "string" which may include "\" sequences
// - returns pointer to -------------^
// - pElem contains result ( JREAD_STRING, length, pointer to string)
// - pass quote = '"' for Json, quote = '\'' for query scanning
//
// returns: pointer into pJson after the string (char after the " terminator)
// pElem contains pointer and length of string (or dataType=JREAD_ERROR)
//
char * jReadGetString( char *pJson, struct jReadElement *pElem, char quote )
{
short skipch;
pElem->dataType= JREAD_ERROR;
pElem->elements= 1;
pElem->bytelen= 0;
pJson= jReadSkipWhitespace( pJson );
if( *pJson == quote )
{
pJson++;
pElem->pValue= pJson; // -> start of actual string
pElem->bytelen=0;
skipch= 0;
while( *pJson != '\0' )
{
if( skipch )
skipch= 0;
else if( *pJson == '\\' ) // "\" sequence
skipch= 1;
else if( *pJson == quote )
{
pElem->dataType= JREAD_STRING;
pJson++;
break;
}
pElem->bytelen++;
pJson++;
};
};
return pJson;
};
// jReadTextLen
// - used to identify length of element text
// - returns no. of chars from pJson upto a terminator
// - terminators: ' ' , } ]
//
int jReadTextLen( char *pJson )
{
int len= 0;
while( (*pJson > ' ' ) && // any ctrl char incl '\0'
(*pJson != ',' ) &&
(*pJson != '}' ) &&
(*pJson != ']' ) )
{
len++;
pJson++;
}
return len;
}
// compare two json elements
// returns: 0 if they are identical strings, else 1
//
int jReadStrcmp( struct jReadElement *j1, struct jReadElement *j2 )
{
int i;
if( (j1->dataType != JREAD_STRING) ||
(j2->dataType != JREAD_STRING) ||
(j1->bytelen != j2->bytelen ) )
return 1;
for( i=0; i< j1->bytelen; i++ )
if( ((char *)(j1->pValue))[i] != ((char *)(j2->pValue))[i] )
return 1;
return 0;
}
// read unsigned int from string
char * jRead_atoi( char *p, unsigned int *result )
{
unsigned int x = 0;
while (*p >= '0' && *p <= '9') {
x = (x*10) + (*p - '0');
++p;
}
*result= x;
return p;
}
// read long int from string
//
char * jRead_atol( char *p, long *result )
{
long x = 0;
int neg = 0;
if (*p == '-') {
neg = 1;
++p;
}
while (*p >= '0' && *p <= '9') {
x = (x*10) + (*p - '0');
++p;
}
if (neg) {
x = -x;
}
*result= x;
return p;
}
#define valid_digit(c) ((c) >= '0' && (c) <= '9')
// read double from string
// *CAUTION* does not handle exponents
//
//
char * jRead_atof( char *p, double *result)
{
double sign, value;
// Get sign, if any.
sign = 1.0;
if (*p == '-') {
sign = -1.0;
p += 1;
} else if (*p == '+') {
p += 1;
}
// Get digits before decimal point or exponent, if any.
for (value = 0.0; valid_digit(*p); p += 1) {
value = value * 10.0 + (*p - '0');
}
// Get digits after decimal point, if any.
if (*p == '.') {
double pow10 = 10.0;
p += 1;
while (valid_digit(*p)) {
value += (*p - '0') / pow10;
pow10 *= 10.0;
p += 1;
}
}
*result= sign * value;
return p;
}
// read element into destination buffer and add '\0' terminator
// - always copies element irrespective of dataType (unless it's an error)
// - destBuffer is always '\0'-terminated (even on zero lenght returns)
// - returns pointer to destBuffer
//
char *jRead_strcpy( char *destBuffer, int destLength, struct jReadElement *pElement )
{
int i;
int len= pElement->bytelen;
char *pdest= destBuffer;
char *psrc= (char *)pElement->pValue;
if( pElement->error == 0 )
{
if( len >= destLength )
len= destLength;
for( i=0; i<destLength; i++ )
*pdest++= *psrc++;
}
*pdest= '\0';
return destBuffer;
}
// jReadCountObject
// - used when query ends at an object, we want to return the object length
// - on entry pJson -> "{... "
// - used to skip unwanted values which are objects
// - keyIndex normally passed as -1 unless we're looking for the nth "key" value
// in which case keyIndex is the index of the key we want
//
char * jReadCountObject( char *pJson, struct jReadElement *pResult, int keyIndex )
{
struct jReadElement jElement;
int jTok;
char *sp;
pResult->dataType= JREAD_OBJECT;
pResult->error= 0;
pResult->elements= 0;
pResult->pValue= pJson;
sp= jReadFindTok( pJson+1, &jTok ); // check for empty object
if( jTok == JREAD_EOBJECT )
{
pJson= sp+1;
}else
{
while( 1 )
{
pJson= jReadGetString( ++pJson, &jElement, '\"' );
if( jElement.dataType != JREAD_STRING )
{
pResult->error= 3; // Expected "key"
break;
}
if( pResult->elements == keyIndex ) // if passed keyIndex
{
*pResult= jElement; // we return "key" at this index
pResult->dataType= JREAD_KEY;
return pJson;
}
pJson= jReadFindTok( pJson, &jTok );
if( jTok != JREAD_COLON )
{
pResult->error= 4; // Expected ":"
break;
}
pJson= jRead( ++pJson, "", &jElement );
if( pResult->error )
break;
pJson= jReadFindTok( pJson, &jTok );
pResult->elements++;
if( jTok == JREAD_EOBJECT )
{
pJson++;
break;
}
if( jTok != JREAD_COMMA )
{
pResult->error= 6; // Expected "," in object
break;
}
}
}
if( keyIndex >= 0 )
{
// we wanted a "key" value - that we didn't find
pResult->dataType= JREAD_ERROR;
pResult->error= 11; // Object key not found (bad index)
}else{
pResult->bytelen= pJson - (char *)pResult->pValue;
}
return pJson;
}
// jReadCountArray
// - used when query ends at an array, we want to return the array length
// - on entry pJson -> "[... "
// - used to skip unwanted values which are arrays
//
char * jReadCountArray( char *pJson, struct jReadElement *pResult )
{
struct jReadElement jElement;
int jTok;
char *sp;
pResult->dataType= JREAD_ARRAY;
pResult->error= 0;
pResult->elements= 0;
pResult->pValue= pJson;
sp= jReadFindTok( pJson+1, &jTok ); // check for empty array
if( jTok == JREAD_EARRAY )
{
pJson= sp+1;
}else
{
while( 1 )
{
pJson= jRead( ++pJson, "", &jElement ); // array value
if( pResult->error )
break;
pJson= jReadFindTok( pJson, &jTok ); // , or ]
pResult->elements++;
if( jTok == JREAD_EARRAY )
{
pJson++;
break;
}
if( jTok != JREAD_COMMA )
{
pResult->error= 9; // Expected "," in array
break;
}
}
}
pResult->bytelen= pJson - (char *)pResult->pValue;
return pJson;
}
// jReadArrayStep()
// - reads one value from an array
// - assumes pJsonArray points at the start of an array or array element
//
char *jReadArrayStep( char *pJsonArray, struct jReadElement *pResult )
{
int jTok;
pJsonArray= jReadFindTok( pJsonArray, &jTok );
switch( jTok )
{
case JREAD_ARRAY: // start of array
case JREAD_COMMA: // element separator
return jRead( ++pJsonArray, "", pResult );
case JREAD_EARRAY: // end of array
pResult->error= 13; // End of array found
break;
default: // some other error
pResult->error= 9; // expected comma in array
break;
}
pResult->dataType= JREAD_ERROR;
return pJsonArray;
}
// jRead
// - reads a complete JSON <value>
// - matches pQuery against pJson, results in pResult
// returns: pointer into pJson
//
// Note: is recursive
//
char * jRead( char *pJson, char *pQuery, struct jReadElement *pResult )
{
return jReadParam( pJson, pQuery, pResult, NULL );
}
char * jReadParam( char *pJson, char *pQuery, struct jReadElement *pResult, int *queryParams )
{
int qTok, jTok, bytelen;
unsigned int index, count;
struct jReadElement qElement, jElement;
pJson= jReadFindTok( pJson, &jTok );
pQuery= jReadFindTok( pQuery, &qTok );
pResult->dataType= jTok;
pResult->bytelen= pResult->elements= pResult->error= 0;
pResult->pValue= pJson;
if( (qTok != JREAD_EOL) && (qTok != jTok) )
{
pResult->error= 1; // JSON does not match Query
return pJson;
}
switch( jTok )
{
case JREAD_ERROR: // general error, eof etc.
pResult->error= 2; // Error reading JSON value
break;
case JREAD_OBJECT: // "{"
if( qTok == JREAD_EOL )
return jReadCountObject( pJson, pResult, -1 ); // return length of object
pQuery= jReadFindTok( ++pQuery, &qTok ); // "('key'...", "{NUMBER", "{*" or EOL
if( qTok != JREAD_STRING )
{
index= 0;
switch( qTok )
{
case JREAD_NUMBER:
pQuery= jRead_atoi( (char *)pQuery, &index ); // index value
break;
case JREAD_QPARAM:
pQuery++;
index= (queryParams != NULL) ? *queryParams++ : 0; // substitute parameter
break;
default:
pResult->error= 12; // Bad Object key
return pJson;
}
return jReadCountObject( pJson, pResult, index );
}
pQuery= jReadGetString( pQuery, &qElement, QUERY_QUOTE ); // qElement = query 'key'
//
// read <key> : <value> , ... }
// loop 'til key matched
//
while( 1 )
{
pJson= jReadGetString( ++pJson, &jElement, '\"' );
if( jElement.dataType != JREAD_STRING )
{
pResult->error= 3; // Expected "key"
break;
}
pJson= jReadFindTok( pJson, &jTok );
if( jTok != JREAD_COLON )
{
pResult->error= 4; // Expected ":"
break;
}
// compare object keys
if( jReadStrcmp( &qElement, &jElement ) == 0 )
{
// found object key
return jReadParam( ++pJson, pQuery, pResult, queryParams );
}
// no key match... skip this value
pJson= jRead( ++pJson, "", pResult );
pJson= jReadFindTok( pJson, &jTok );
if( jTok == JREAD_EOBJECT )
{
pResult->error= 5; // Object key not found
break;
}
if( jTok != JREAD_COMMA )
{
pResult->error= 6; // Expected "," in object
break;
}
}
break;
case JREAD_ARRAY: // "[NUMBER" or "[*"
//
// read index, skip values 'til index
//
if( qTok == JREAD_EOL )
return jReadCountArray( pJson, pResult ); // return length of object
index= 0;
pQuery= jReadFindTok( ++pQuery, &qTok ); // "[NUMBER" or "[*"
if( qTok == JREAD_NUMBER )
{
pQuery= jRead_atoi( pQuery, &index ); // get array index
}else if( qTok == JREAD_QPARAM )
{
pQuery++;
index= (queryParams != NULL) ? *queryParams++ : 0; // substitute parameter
}
count=0;
while( 1 )
{
if( count == index )
return jReadParam( ++pJson, pQuery, pResult, queryParams ); // return value at index
// not this index... skip this value
pJson= jRead( ++pJson, "", &jElement );
if( pResult->error )
break;
count++;
pJson= jReadFindTok( pJson, &jTok ); // , or ]
if( jTok == JREAD_EARRAY )
{
pResult->error= 10; // Array element not found (bad index)
break;
}
if( jTok != JREAD_COMMA )
{
pResult->error= 9; // Expected "," in array
break;
}
}
break;
case JREAD_STRING: // "string"
pJson= jReadGetString( pJson, pResult, '\"' );
break;
case JREAD_NUMBER: // number (may be -ve) int or float
case JREAD_BOOL: // true or false
case JREAD_NULL: // null
bytelen= jReadTextLen( pJson );
pResult->dataType= jTok;
pResult->bytelen= bytelen;
pResult->pValue= pJson;
pResult->elements= 1;
pJson += bytelen;
break;
default:
pResult->error= 8; // unexpected character (in pResult->dataType)
}
// We get here on a 'terminal value'
// - make sure the query string is empty also
pQuery= jReadFindTok( pQuery, &qTok );
if( !pResult->error && (qTok != JREAD_EOL) )
pResult->error= 7; // terminal value found before end of query
if( pResult->error )
{
pResult->dataType= JREAD_ERROR;
pResult->elements= pResult->bytelen= 0;
pResult->pValue= pJson; // return pointer into JSON at error point
}
return pJson;
}
//--------------------------------------------------------------------
// Optional helper functions
// - simple routines to extract values from JSON
// - does coercion of types where possible
// - always returns a value (e.g. 0 or "" on error)
//
// Note: by default, pass NULL for queryParams
// unless you are using '*' in the query for indexing
//
// jRead_long
// - reads signed long value from JSON
// - returns number from NUMBER or STRING elements (if possible)
// returns 1 or 0 from BOOL elements
// otherwise returns 0
//
long jRead_long( char *pJson, char *pQuery, int *queryParams )
{
struct jReadElement elem;
long result;
jReadParam( pJson, pQuery, &elem, queryParams );
if( (elem.dataType == JREAD_ERROR) || (elem.dataType == JREAD_NULL))
return 0;
if( elem.dataType == JREAD_BOOL )
return *((char *)elem.pValue)=='t' ? 1 : 0;
jRead_atol( (char *)elem.pValue, &result );
return result;
}
int jRead_int( char *pJson, char *pQuery, int *queryParams )
{
return (int)jRead_long( pJson, pQuery, queryParams );
}
// jRead_double
// - returns double from JSON
// - returns number from NUMBER or STRING elements
// otherwise returns 0.0
//
double jRead_double( char *pJson, char *pQuery, int *queryParams )
{
struct jReadElement elem;
double result;
jReadParam( pJson, pQuery, &elem, queryParams );
if( elem.dataType == JREAD_ERROR )
return 0.0;
jRead_atof( (char *)elem.pValue, &result );
return result;
}
// jRead_string
// Copy string to pDest and '\0'-terminate it (upto destlen total bytes)
// returns: character length of string (excluding '\0' terminator)
//
// Note: any element can be returned as a string
//
int jRead_string( char *pJson, char *pQuery, char *pDest, int destlen, int *queryParams )
{
struct jReadElement elem;
int i;
*pDest= '\0';
jReadParam( pJson, pQuery, &elem, queryParams );
if( elem.dataType == JREAD_ERROR )
return 0;
for( i=0; (i<elem.bytelen) && (i<destlen-1); i++ )
*pDest++ = ((char *)elem.pValue)[i];
*pDest= '\0';
return elem.bytelen;
}
//-------------------------------------------------
// Optional String output Functions
//
char *jReadTypeStrings[]={
"Error", // 0
"Object", // 1
"Array", // 2
"String", // 3
"Number", // 4
"Bool", // 5
"null", // 6
"Object key", // 7
"colon", // 8
"eol", // 9
"comma", // 10
"}", // 11
"]", // 12
"* parameter" // 13
};
char *jReadTypeToString( int dataType )
{
return jReadTypeStrings[ dataType ];
};
char * jReadErrorStrings[]={
"Ok", // 0
"JSON does not match Query", // 1
"Error reading JSON value", // 2
"Expected \"key\"", // 3
"Expected ':'", // 4
"Object key not found", // 5
"Expected ',' in object", // 6
"Terminal value found before end of query", // 7
"Unexpected character", // 8
"Expected ',' in array", // 9
"Array element not found (bad index)", // 10
"Object key not found (bad index)", // 11
"Bad object key", // 12
"End of array found", // 13
"End of object found" // 14
};
char * jReadErrorToString( int error )
{
if( (error >=0 ) && (error <= 14))
return jReadErrorStrings[ error ];
return "Unknown error";
};
// end of jRead.c