tiny-json/tiny-json.c

455 lines
16 KiB
C
Raw Normal View History

2016-10-12 21:08:38 +00:00
/*
2016-10-20 14:45:06 +00:00
* Developed by Rafa Garcia <rafagarcia77@gmail.com>
*
2016-10-12 21:08:38 +00:00
* tiny-json.c is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* tiny-json.c is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
2016-10-12 22:19:55 +00:00
*
2016-10-12 21:08:38 +00:00
* You should have received a copy of the GNU General Public License
2017-01-20 13:51:52 +00:00
* along with tiny-json.c. If not, see <http://www.gnu.org/licenses/>.
2016-10-12 22:19:55 +00:00
*
2016-10-12 21:08:38 +00:00
*/
#include <string.h>
#include "tiny-json.h"
/** Structure to handle a heap of JSON properties. */
typedef struct jsonPool_s {
json_t* const mem; /**< Pointer to array of json properties. */
unsigned int const qty; /**< Length of the array of json properties. */
unsigned int nextFree; /**< The index of the next free json property. */
} jsonPool_t;
/* Search a property by its name in a JSON object. */
json_t const* json_getProperty( json_t const* obj, char const* property ) {
json_t const* sibling;
for( sibling = obj->u.c.child; sibling; sibling = sibling->sibling )
2016-10-12 21:08:38 +00:00
if ( sibling->name && !strcmp( sibling->name, property ) )
return sibling;
return 0;
}
2017-04-03 08:12:11 +00:00
/* Search a property by its name in a JSON object and return its value. */
char const* json_getPropertyValue( json_t const* obj, char const* property ) {
json_t const* field = json_getProperty( obj, property );
if ( !field ) return 0;
2017-04-05 00:25:17 +00:00
jsonType_t type = json_getType( field );
if ( JSON_ARRAY >= type ) return 0;
2017-04-03 08:12:11 +00:00
return json_getValue( field );
}
2016-10-12 21:08:38 +00:00
/* Internal prototypes: */
2017-04-03 23:37:49 +00:00
static char* goBlank( char* str );
static bool isNum( unsigned char ch );
static char* goNum( char* str );
static json_t* poolInit( jsonPool_t* pool );
static json_t* poolNew( jsonPool_t* pool );
static char* objValue( char* ptr, json_t* obj, jsonPool_t* pool );
static char* setToNull( char* ch );
static bool isEndOfPrimitive( char ch );
2016-10-12 21:08:38 +00:00
/* Parse a string to get a json. */
json_t const* json_create( char* str, json_t mem[], unsigned int qty ) {
2017-04-03 23:37:49 +00:00
char* ptr = goBlank( str );
2017-03-20 01:36:26 +00:00
if ( !ptr || *ptr != '{' ) return 0;
2016-10-12 22:19:55 +00:00
jsonPool_t pool = { .mem = mem, .qty = qty };
2017-04-03 23:37:49 +00:00
json_t* obj = poolInit( &pool );
2016-10-12 22:19:55 +00:00
obj->name = 0;
obj->sibling = 0;
obj->u.c.child = 0;
2017-04-03 23:37:49 +00:00
ptr = objValue( ptr, obj, &pool );
2016-10-12 21:08:38 +00:00
if ( !ptr ) return 0;
return obj;
}
2016-10-12 22:19:55 +00:00
/** Get a special character with its escape character. Examples:
2016-10-12 21:08:38 +00:00
* 'b' -> '\b', 'n' -> '\n', 't' -> '\t'
* @param ch The escape character.
* @return The character code. */
2017-04-03 23:37:49 +00:00
static char getEscape( char ch ) {
2016-10-12 21:08:38 +00:00
static struct { char ch; char code; } const pair[] = {
2017-05-19 22:21:27 +00:00
{ '\"', '\"' }, { '\\', '\\' },
{ '/', '/' }, { 'b', '\b' },
{ 'f', '\f' }, { 'n', '\n' },
{ 'r', '\r' }, { 't', '\t' },
2016-10-12 21:08:38 +00:00
};
unsigned int i;
for( i = 0; i < sizeof pair / sizeof *pair; ++i )
if ( pair[i].ch == ch )
return pair[i].code;
return '\0';
}
/** Check if a character is a hexadecimal digit. */
2017-04-03 23:37:49 +00:00
static bool isHexaDigit( unsigned char nibble ) {
2016-10-12 21:08:38 +00:00
if ( nibble < '0' ) return false;
if ( nibble <= '9' ) return true;
if ( nibble < 'A' ) return false;
if ( nibble <= 'F' ) return true;
if ( nibble < 'a' ) return false;
if ( nibble <= 'f' ) return true;
return false;
}
2017-04-05 07:43:01 +00:00
/** Parse 4 characters.
2016-10-12 21:08:38 +00:00
* @Param str Pointer to first digit.
* @retval '?' If the four characters are hexadecimal digits.
2016-10-12 22:19:55 +00:00
* @retcal '\0' In other cases. */
static unsigned char getCharFromUnicode( unsigned char const* str ) {
2016-10-12 21:08:38 +00:00
unsigned int i;
for( i = 0; i < 4; ++i )
2017-04-03 23:37:49 +00:00
if ( !isHexaDigit( str[i] ) )
2016-10-12 21:08:38 +00:00
return '\0';
return '?';
}
/** Parse a string and replace the scape characters by their meaning characters.
* This parser stops when finds the character '\"'. Then replaces '\"' by '\0'.
2016-10-12 22:19:55 +00:00
* @param str Pointer to first character.
2016-10-12 21:08:38 +00:00
* @retval Pointer to first non white space after the string. If success.
* @retval Null pointer if any error occur. */
2017-04-03 23:37:49 +00:00
static char* parseString( char* str ) {
unsigned char* head = (unsigned char*)str;
unsigned char* tail = (unsigned char*)str;
2016-10-12 21:08:38 +00:00
for( ; *head >= ' '; ++head, ++tail ) {
if ( *head == '\"' ) {
*tail = '\0';
return (char*)++head;
2016-10-12 21:08:38 +00:00
}
if ( *head == '\\' ) {
if ( *++head == 'u' ) {
2017-04-03 23:37:49 +00:00
char const ch = getCharFromUnicode( ++head );
2016-10-12 21:08:38 +00:00
if ( ch == '\0' ) return 0;
*tail = ch;
head += 3;
}
else {
2017-04-03 23:37:49 +00:00
char const esc = getEscape( *head );
2016-10-12 21:08:38 +00:00
if ( esc == '\0' ) return 0;
*tail = esc;
}
}
else *tail = *head;
}
return 0;
}
2016-10-12 22:19:55 +00:00
/** Parse a string to get the name of a property.
* @param str Pointer to first character.
2016-10-12 21:08:38 +00:00
* @param property The property to assign the name.
* @retval Pointer to first of property value. If success.
* @retval Null pointer if any error occur. */
2017-04-03 23:37:49 +00:00
static char* propertyName( char* ptr, json_t* property ) {
2016-10-12 21:08:38 +00:00
property->name = ++ptr;
2017-04-03 23:37:49 +00:00
ptr = parseString( ptr );
2016-10-12 21:08:38 +00:00
if ( !ptr ) return 0;
2017-04-03 23:37:49 +00:00
ptr = goBlank( ptr );
2016-10-12 21:08:38 +00:00
if ( !ptr ) return 0;
if ( *ptr++ != ':' ) return 0;
2017-04-03 23:37:49 +00:00
return goBlank( ptr );
2016-10-12 21:08:38 +00:00
}
2016-10-12 22:19:55 +00:00
/** Parse a string to get the value of a property when its type is JSON_TEXT.
* @param str Pointer to first character ('\"').
2016-10-12 21:08:38 +00:00
* @param property The property to assign the name.
* @retval Pointer to first non white space after the string. If success.
* @retval Null pointer if any error occur. */
2017-04-03 23:37:49 +00:00
static char* textValue( char* ptr, json_t* property ) {
2016-10-12 21:08:38 +00:00
++property->u.value;
2017-04-03 23:37:49 +00:00
ptr = parseString( ++ptr );
2016-10-12 21:08:38 +00:00
if ( !ptr ) return 0;
2016-10-12 22:19:55 +00:00
property->type = JSON_TEXT;
2016-10-12 21:08:38 +00:00
return ptr;
}
/** Compare two strings until get the null character in the second one.
2016-10-12 22:19:55 +00:00
* @param ptr sub string
* @param str main string
2017-04-05 07:43:01 +00:00
* @retval Pointer to next character.
2016-10-12 21:08:38 +00:00
* @retval Null pointer if any error occur. */
2017-04-03 23:37:49 +00:00
static char* checkStr( char* ptr, char const* str ) {
2016-10-12 21:08:38 +00:00
while( *str )
if ( *ptr++ != *str++ )
return 0;
return ptr;
}
2016-10-12 22:19:55 +00:00
/** Parser a string to get a primitive value.
2017-04-05 07:43:01 +00:00
* If the first character after the value is different of '}' or ']' is set to '\0'.
2016-10-12 22:19:55 +00:00
* @param str Pointer to first character.
2016-10-12 21:08:38 +00:00
* @param property Property handler to set the value and the type, (true, false or null).
* @param value String with the primitive literal.
* @param type The code of the type. ( JSON_BOOLEAN or JSON_NULL )
* @retval Pointer to first non white space after the string. If success.
2016-10-12 22:19:55 +00:00
* @retval Null pointer if any error occur. */
2017-04-03 23:37:49 +00:00
static char* primitiveValue( char* ptr, json_t* property, char const* value, jsonType_t type ) {
ptr = checkStr( ptr, value );
if ( !ptr || !isEndOfPrimitive( *ptr ) ) return 0;
ptr = setToNull( ptr );
2016-10-12 22:19:55 +00:00
property->type = type;
2016-10-12 21:08:38 +00:00
return ptr;
}
2016-10-12 22:19:55 +00:00
/** Parser a string to get a true value.
2017-04-05 07:43:01 +00:00
* If the first character after the value is different of '}' or ']' is set to '\0'.
2016-10-12 22:19:55 +00:00
* @param str Pointer to first character.
2016-10-12 21:08:38 +00:00
* @param property Property handler to set the value and the type, (true, false or null).
* @retval Pointer to first non white space after the string. If success.
2016-10-12 22:19:55 +00:00
* @retval Null pointer if any error occur. */
2017-04-03 23:37:49 +00:00
static char* trueValue( char* ptr, json_t* property ) {
return primitiveValue( ptr, property, "true", JSON_BOOLEAN );
2016-10-12 21:08:38 +00:00
}
2016-10-12 22:19:55 +00:00
/** Parser a string to get a false value.
2017-04-05 07:43:01 +00:00
* If the first character after the value is different of '}' or ']' is set to '\0'.
2016-10-12 22:19:55 +00:00
* @param str Pointer to first character.
2016-10-12 21:08:38 +00:00
* @param property Property handler to set the value and the type, (true, false or null).
* @retval Pointer to first non white space after the string. If success.
2016-10-12 22:19:55 +00:00
* @retval Null pointer if any error occur. */
2017-04-03 23:37:49 +00:00
static char* falseValue( char* ptr, json_t* property ) {
return primitiveValue( ptr, property, "false", JSON_BOOLEAN );
2016-10-12 21:08:38 +00:00
}
2016-10-12 22:19:55 +00:00
/** Parser a string to get a null value.
2017-04-05 07:43:01 +00:00
* If the first character after the value is different of '}' or ']' is set to '\0'.
2016-10-12 22:19:55 +00:00
* @param str Pointer to first character.
2016-10-12 21:08:38 +00:00
* @param property Property handler to set the value and the type, (true, false or null).
* @retval Pointer to first non white space after the string. If success.
2016-10-12 22:19:55 +00:00
* @retval Null pointer if any error occur. */
2017-04-03 23:37:49 +00:00
static char* nullValue( char* ptr, json_t* property ) {
return primitiveValue( ptr, property, "null", JSON_NULL );
2016-10-12 21:08:38 +00:00
}
2017-04-03 23:37:49 +00:00
/** Analyze the exponential part of a real number.
* @param str Pointer to first character.
* @retval Pointer to first non numerical after the string. If success.
* @retval Null pointer if any error occur. */
static char* expValue( char* ptr ) {
2016-10-18 23:17:01 +00:00
if ( *ptr == '-' || *ptr == '+' ) ++ptr;
2017-04-03 23:37:49 +00:00
if ( !isNum( *ptr ) ) return 0;
ptr = goNum( ++ptr );
2016-10-18 23:17:01 +00:00
return ptr;
}
2017-04-03 23:37:49 +00:00
/** Analyze the decimal part of a real number.
* @param str Pointer to first character.
* @retval Pointer to first non numerical after the string. If success.
* @retval Null pointer if any error occur. */
static char* fraqValue( char* ptr ) {
if ( !isNum( *ptr ) ) return 0;
ptr = goNum( ++ptr );
2016-10-18 23:17:01 +00:00
if ( !ptr ) return 0;
return ptr;
}
2017-04-03 23:37:49 +00:00
/** Parser a string to get a numerical value.
2017-04-05 07:43:01 +00:00
* If the first character after the value is different of '}' or ']' is set to '\0'.
2016-10-12 22:19:55 +00:00
* @param str Pointer to first character.
2017-04-03 21:22:15 +00:00
* @param property Property handler to set the value and the type: JSON_REAL or JSON_INTEGER.
2016-10-12 21:08:38 +00:00
* @retval Pointer to first non white space after the string. If success.
2016-10-12 22:19:55 +00:00
* @retval Null pointer if any error occur. */
2017-04-03 23:37:49 +00:00
static char* numValue( char* ptr, json_t* property ) {
2016-10-12 21:08:38 +00:00
if ( *ptr == '-' ) ++ptr;
2017-04-05 07:10:24 +00:00
if ( !isNum( *ptr ) ) return 0;
2016-10-18 23:17:01 +00:00
if ( *ptr != '0' ) {
2017-04-03 23:37:49 +00:00
ptr = goNum( ptr );
2016-10-18 23:17:01 +00:00
if ( !ptr ) return 0;
}
2017-04-03 23:37:49 +00:00
else if ( isNum( *++ptr ) ) return 0;
property->type = JSON_INTEGER;
2016-10-18 23:17:01 +00:00
if ( *ptr == '.' ) {
2017-04-03 23:37:49 +00:00
ptr = fraqValue( ++ptr );
2016-10-18 23:17:01 +00:00
if ( !ptr ) return 0;
2017-04-05 07:10:24 +00:00
property->type = JSON_REAL;
2016-10-18 23:17:01 +00:00
}
2017-04-03 23:37:49 +00:00
if ( *ptr == 'e' || *ptr == 'E' ) {
ptr = expValue( ++ptr );
2016-10-18 23:17:01 +00:00
if ( !ptr ) return 0;
2017-04-05 07:10:24 +00:00
property->type = JSON_REAL;
2017-04-03 23:37:49 +00:00
}
2017-04-05 07:10:24 +00:00
if ( !isEndOfPrimitive( *ptr ) ) return 0;
2017-04-03 23:37:49 +00:00
if ( JSON_INTEGER == property->type ) {
char const* value = property->u.value;
bool const negative = *value == '-';
2017-05-19 22:21:27 +00:00
static char const min[] = "-9223372036854775808";
static char const max[] = "9223372036854775807";
unsigned int const maxdigits = ( negative? sizeof min: sizeof max ) - 1;
2017-04-03 23:37:49 +00:00
unsigned int const len = ptr - value;
if ( len > maxdigits ) return 0;
if ( len == maxdigits ) {
char const tmp = *ptr;
2017-04-05 07:10:24 +00:00
*ptr = '\0';
2017-04-03 23:37:49 +00:00
char const* const threshold = negative ? min: max;
2017-04-05 07:10:24 +00:00
if ( 0 > strcmp( threshold, value ) ) return 0;
2017-04-03 23:37:49 +00:00
*ptr = tmp;
}
2016-10-18 23:17:01 +00:00
}
2017-04-03 23:37:49 +00:00
ptr = setToNull( ptr );
2016-10-12 22:19:55 +00:00
return ptr;
2016-10-12 21:08:38 +00:00
}
2017-05-19 22:21:27 +00:00
2016-10-12 22:19:55 +00:00
/** Add a property to a JSON object or array.
* @param obj The handler of the JSON object or array.
2016-10-12 21:08:38 +00:00
* @param property The handler of the property to be added. */
2017-04-03 23:37:49 +00:00
static void add( json_t* obj, json_t* property ) {
2016-10-12 21:08:38 +00:00
property->sibling = 0;
if ( !obj->u.c.child ){
obj->u.c.child = property;
obj->u.c.last_child = property;
2018-03-29 20:33:51 +00:00
} else {
obj->u.c.last_child->sibling = property;
obj->u.c.last_child = property;
2016-10-12 22:19:55 +00:00
}
2016-10-12 21:08:38 +00:00
}
2016-10-12 22:19:55 +00:00
/** Parser a string to get a json object value.
* @param str Pointer to first character.
2016-10-12 21:08:38 +00:00
* @param pool The handler of a json pool for creating json instances.
* @retval Pointer to first character after the value. If success.
2016-10-12 22:19:55 +00:00
* @retval Null pointer if any error occur. */
2017-04-03 23:37:49 +00:00
static char* objValue( char* ptr, json_t* obj, jsonPool_t* pool ) {
2016-10-12 22:19:55 +00:00
obj->type = JSON_OBJ;
obj->u.c.child = 0;
2016-10-12 22:19:55 +00:00
obj->sibling = 0;
2016-10-12 21:08:38 +00:00
ptr++;
for(;;) {
2017-04-03 23:37:49 +00:00
ptr = goBlank( ptr );
2016-10-12 21:08:38 +00:00
if ( !ptr ) return 0;
2017-04-05 00:25:17 +00:00
if ( *ptr == ',' ) {
++ptr;
continue;
2017-04-05 07:10:24 +00:00
}
2016-10-12 21:08:38 +00:00
char const endchar = ( obj->type == JSON_OBJ )? '}': ']';
if ( *ptr == endchar ) {
*ptr = '\0';
json_t* parentObj = obj->sibling;
if ( !parentObj ) return ++ptr;
obj->sibling = 0;
2016-10-12 22:19:55 +00:00
obj = parentObj;
2016-10-12 21:08:38 +00:00
++ptr;
continue;
}
2017-04-03 23:37:49 +00:00
json_t* property = poolNew( pool );
2016-10-12 21:08:38 +00:00
if ( !property ) return 0;
if( obj->type != JSON_ARRAY ) {
2016-10-12 22:19:55 +00:00
if ( *ptr != '\"' ) return 0;
2017-04-03 23:37:49 +00:00
ptr = propertyName( ptr, property );
2016-10-12 21:08:38 +00:00
if ( !ptr ) return 0;
2016-10-12 22:19:55 +00:00
}
2016-10-12 21:08:38 +00:00
else property->name = 0;
2017-04-03 23:37:49 +00:00
add( obj, property );
2016-10-12 21:08:38 +00:00
property->u.value = ptr;
switch( *ptr ) {
case '{':
2016-10-12 22:19:55 +00:00
property->type = JSON_OBJ;
property->u.c.child = 0;
2016-10-12 21:08:38 +00:00
property->sibling = obj;
obj = property;
++ptr;
break;
case '[':
2016-10-12 22:19:55 +00:00
property->type = JSON_ARRAY;
property->u.c.child = 0;
2016-10-12 21:08:38 +00:00
property->sibling = obj;
obj = property;
++ptr;
2016-10-12 22:19:55 +00:00
break;
2017-04-05 07:10:24 +00:00
case '\"': ptr = textValue( ptr, property ); break;
case 't': ptr = trueValue( ptr, property ); break;
case 'f': ptr = falseValue( ptr, property ); break;
case 'n': ptr = nullValue( ptr, property ); break;
default: ptr = numValue( ptr, property ); break;
2016-10-12 21:08:38 +00:00
}
if ( !ptr ) return 0;
2016-10-12 22:19:55 +00:00
}
2016-10-12 21:08:38 +00:00
}
/** Initialize a json pool.
* @param pool The handler of the pool.
2017-04-05 07:43:01 +00:00
* @return a instance of a json. */
2017-04-03 23:37:49 +00:00
static json_t* poolInit( jsonPool_t* pool ) {
2016-10-12 21:08:38 +00:00
pool->nextFree = 1;
return &pool->mem[0];
}
/** Create an instance of a json from a pool.
* @param pool The handler of the pool.
* @retval The handler of the new instance if success.
* @retval Null pointer if the pool was empty. */
2017-04-03 23:37:49 +00:00
static json_t* poolNew( jsonPool_t* pool ) {
2016-10-12 21:08:38 +00:00
if ( pool->nextFree >= pool->qty ) return 0;
return &pool->mem[pool->nextFree++];
}
/** Checks whether an character belongs to set.
* @param ch Character value to be checked.
* @param set Set of characters. It is just a null-terminated string.
* @return true or false there is membership or not. */
2017-04-03 23:37:49 +00:00
static bool isOneOfThem( char ch, char const* set ) {
2016-10-12 21:08:38 +00:00
while( *set != '\0' )
if ( ch == *set++ )
return true;
return false;
}
2016-10-12 22:19:55 +00:00
/** Increases a pointer while it points to a character that belongs to a set.
2016-10-12 21:08:38 +00:00
* @param str The initial pointer value.
* @param set Set of characters. It is just a null-terminated string.
* @return The final pointer value or null pointer if the null character was found. */
2017-04-03 23:37:49 +00:00
static char* goWhile( char* str, char const* set ) {
2016-10-12 21:08:38 +00:00
for(; *str != '\0'; ++str ) {
2017-04-03 23:37:49 +00:00
if ( !isOneOfThem( *str, set ) )
2016-10-12 21:08:38 +00:00
return str;
}
return 0;
}
2017-04-03 23:37:49 +00:00
/** Set of characters that defines a blank. */
static char const* const blank = " \n\r\t\f";
2017-04-03 21:22:15 +00:00
2016-10-12 21:08:38 +00:00
/** Increases a pointer while it points to a white space character.
* @param str The initial pointer value.
* @return The final pointer value or null pointer if the null character was found. */
2017-04-03 23:37:49 +00:00
static char* goBlank( char* str ) {
return goWhile( str, blank );
2016-10-12 21:08:38 +00:00
}
/** Checks if a character is a decimal digit. */
2017-04-03 23:37:49 +00:00
static bool isNum( unsigned char ch ) {
2016-10-12 21:08:38 +00:00
return ch >= '0' && ch <= '9';
}
/** Increases a pointer while it points to a decimal digit character.
* @param str The initial pointer value.
* @return The final pointer value or null pointer if the null character was found. */
2017-04-03 23:37:49 +00:00
static char* goNum( char* str ) {
2016-10-12 21:08:38 +00:00
for( ; *str != '\0'; ++str ) {
2017-04-03 23:37:49 +00:00
if ( !isNum( *str ) )
2016-10-12 21:08:38 +00:00
return str;
}
2016-10-12 22:19:55 +00:00
return 0;
2016-10-12 21:08:38 +00:00
}
2017-04-03 23:37:49 +00:00
/** Set of characters that defines the end of an array or a JSON object. */
2017-04-03 21:22:15 +00:00
static char const* const endofblock = "}]";
2017-04-05 07:43:01 +00:00
/** Set a char to '\0' and increase its pointer if the char is different to '}' or ']'.
2016-10-12 21:08:38 +00:00
* @param ch Pointer to character.
* @return Final value pointer. */
2017-04-03 23:37:49 +00:00
static char* setToNull( char* ch ) {
if ( !isOneOfThem( *ch, endofblock ) ) *ch++ = '\0';
2016-10-12 21:08:38 +00:00
return ch;
}
2017-04-03 21:22:15 +00:00
2017-04-03 23:37:49 +00:00
/** Indicate if a character is the end of a primitive value. */
static bool isEndOfPrimitive( char ch ) {
2017-04-05 07:10:24 +00:00
return ch == ',' || isOneOfThem( ch, blank ) || isOneOfThem( ch, endofblock );
2017-05-19 22:21:27 +00:00
}