541 lines
19 KiB
C
541 lines
19 KiB
C
/*
|
|
Copyright (c) 2009-2017 Dave Gamble and cJSON contributors
|
|
|
|
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:
|
|
*/
|
|
|
|
#include <string.h>
|
|
#include <stdio.h>
|
|
#include <math.h>
|
|
#include <stdlib.h>
|
|
#include <float.h>
|
|
#include <limits.h>
|
|
#include <ctype.h>
|
|
#include "cJSON.h"
|
|
|
|
/* define our own boolean type */
|
|
#ifdef true
|
|
#undef true
|
|
#endif
|
|
#define true ((cJSON_bool)1)
|
|
|
|
#ifdef false
|
|
#undef false
|
|
#endif
|
|
#define false ((cJSON_bool)0)
|
|
|
|
typedef struct {
|
|
const unsigned char *content;
|
|
size_t length;
|
|
size_t offset;
|
|
size_t depth;
|
|
} parse_buffer;
|
|
|
|
/* case insensitive strcmp */
|
|
CJSON_PUBLIC(int) cJSON_Strcasecmp(const char *a, const char *b)
|
|
{
|
|
if (!a && !b) return 0;
|
|
if (!a || !b) return 1;
|
|
for (; tolower(*a) == tolower(*b); a++, b++)
|
|
{
|
|
if (*a == '\0') return 0;
|
|
}
|
|
return tolower(*a) - tolower(*b);
|
|
}
|
|
|
|
static cJSON_bool parse_number(cJSON *item, parse_buffer *input)
|
|
{
|
|
double number = 0;
|
|
unsigned char *after_end = NULL;
|
|
unsigned char number_c_string[64];
|
|
size_t i = 0;
|
|
|
|
if (input->length == 0) return false;
|
|
if (input->length > (sizeof(number_c_string) - 1)) return false;
|
|
|
|
for (i = 0; (i < input->length) && (input->content[input->offset + i] != '\0'); i++)
|
|
{
|
|
number_c_string[i] = input->content[input->offset + i];
|
|
}
|
|
number_c_string[i] = '\0';
|
|
|
|
number = strtod((char *)number_c_string, (char **)&after_end);
|
|
if (number_c_string == after_end) return false;
|
|
|
|
item->valuedouble = number;
|
|
item->valueint = (int)number;
|
|
item->type = cJSON_Number;
|
|
|
|
input->offset += (size_t)(after_end - number_c_string);
|
|
return true;
|
|
}
|
|
|
|
static cJSON_bool parse_string(cJSON *item, parse_buffer *input)
|
|
{
|
|
const unsigned char *input_pointer = input->content + input->offset;
|
|
size_t i = 0;
|
|
|
|
if (input->length < 2) return false;
|
|
if (input_pointer[0] != '\"') return false;
|
|
|
|
input_pointer++;
|
|
while ((i < input->length) && (input_pointer[0] != '\0'))
|
|
{
|
|
if (input_pointer[0] == '\"')
|
|
{
|
|
char *output = (char *)malloc(i + 1);
|
|
if (!output) return false;
|
|
memcpy(output, input_pointer - i, i);
|
|
output[i] = '\0';
|
|
|
|
item->valuestring = output;
|
|
item->type = cJSON_String;
|
|
input->offset += i + 2;
|
|
return true;
|
|
}
|
|
i++;
|
|
input_pointer++;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
static parse_buffer *skip_whitespace(parse_buffer *buffer)
|
|
{
|
|
if (buffer == NULL || buffer->content == NULL) return NULL;
|
|
while ((buffer->offset < buffer->length) && isspace(buffer->content[buffer->offset]))
|
|
{
|
|
buffer->offset++;
|
|
}
|
|
return buffer;
|
|
}
|
|
|
|
static cJSON_bool parse_value(cJSON *item, parse_buffer *input);
|
|
|
|
static cJSON_bool parse_array(cJSON *item, parse_buffer *input)
|
|
{
|
|
cJSON *child = NULL;
|
|
if (input->content[input->offset] != '[') return false;
|
|
input->offset++;
|
|
skip_whitespace(input);
|
|
if (input->content[input->offset] == ']')
|
|
{
|
|
item->type = cJSON_Array;
|
|
input->offset++;
|
|
return true;
|
|
}
|
|
input->offset--;
|
|
child = cJSON_CreateNull();
|
|
if (child == NULL) return false;
|
|
input->offset++;
|
|
if (!parse_value(child, skip_whitespace(input))) { cJSON_Delete(child); return false; }
|
|
item->child = child;
|
|
skip_whitespace(input);
|
|
while (input->content[input->offset] == ',')
|
|
{
|
|
cJSON *new_item = cJSON_CreateNull();
|
|
if (new_item == NULL) { cJSON_Delete(child); return false; }
|
|
input->offset++;
|
|
if (!parse_value(new_item, skip_whitespace(input))) { cJSON_Delete(new_item); cJSON_Delete(child); return false; }
|
|
child->next = new_item;
|
|
new_item->prev = child;
|
|
child = new_item;
|
|
skip_whitespace(input);
|
|
}
|
|
if (input->content[input->offset] != ']') { cJSON_Delete(child); return false; }
|
|
input->offset++;
|
|
item->type = cJSON_Array;
|
|
return true;
|
|
}
|
|
|
|
static cJSON_bool parse_object(cJSON *item, parse_buffer *input)
|
|
{
|
|
cJSON *child = NULL;
|
|
if (input->content[input->offset] != '{') return false;
|
|
input->offset++;
|
|
skip_whitespace(input);
|
|
if (input->content[input->offset] == '}')
|
|
{
|
|
item->type = cJSON_Object;
|
|
input->offset++;
|
|
return true;
|
|
}
|
|
child = cJSON_CreateNull();
|
|
if (child == NULL) return false;
|
|
|
|
/* Parse key */
|
|
if (!parse_string(child, input)) { cJSON_Delete(child); return false; }
|
|
char *key = child->valuestring;
|
|
child->valuestring = NULL; /* clear before parse_value overwrites it */
|
|
|
|
/* Parse value */
|
|
skip_whitespace(input);
|
|
if (input->content[input->offset] != ':') { cJSON_Delete(child); return false; }
|
|
input->offset++;
|
|
if (!parse_value(child, skip_whitespace(input))) { cJSON_Delete(child); return false; }
|
|
|
|
item->child = child;
|
|
child->string = key;
|
|
skip_whitespace(input);
|
|
while (input->content[input->offset] == ',')
|
|
{
|
|
cJSON *new_item = cJSON_CreateNull();
|
|
if (new_item == NULL) { cJSON_Delete(child); return false; }
|
|
input->offset++;
|
|
|
|
/* Parse key */
|
|
if (!parse_string(new_item, input)) { cJSON_Delete(new_item); cJSON_Delete(child); return false; }
|
|
char *new_key = new_item->valuestring;
|
|
new_item->valuestring = NULL;
|
|
|
|
/* Parse value */
|
|
skip_whitespace(input);
|
|
if (input->content[input->offset] != ':') { cJSON_Delete(new_item); cJSON_Delete(child); return false; }
|
|
input->offset++;
|
|
if (!parse_value(new_item, skip_whitespace(input))) { cJSON_Delete(new_item); cJSON_Delete(child); return false; }
|
|
|
|
new_item->string = new_key;
|
|
child->next = new_item;
|
|
new_item->prev = child;
|
|
child = new_item;
|
|
skip_whitespace(input);
|
|
}
|
|
if (input->content[input->offset] != '}') { cJSON_Delete(child); return false; }
|
|
input->offset++;
|
|
item->type = cJSON_Object;
|
|
return true;
|
|
}
|
|
|
|
static cJSON_bool parse_value(cJSON *item, parse_buffer *input)
|
|
{
|
|
if (input == NULL || item == NULL) return false;
|
|
skip_whitespace(input);
|
|
if (input->length == 0) return false;
|
|
|
|
switch (input->content[input->offset])
|
|
{
|
|
case '-': case '0': case '1': case '2': case '3': case '4':
|
|
case '5': case '6': case '7': case '8': case '9':
|
|
return parse_number(item, input);
|
|
case 't': case 'f':
|
|
if (strncmp((const char *)input->content + input->offset, "true", 4) == 0)
|
|
{
|
|
item->type = cJSON_True;
|
|
input->offset += 4;
|
|
return true;
|
|
}
|
|
if (strncmp((const char *)input->content + input->offset, "false", 5) == 0)
|
|
{
|
|
item->type = cJSON_False;
|
|
input->offset += 5;
|
|
return true;
|
|
}
|
|
return false;
|
|
case 'n':
|
|
if (strncmp((const char *)input->content + input->offset, "null", 4) == 0)
|
|
{
|
|
item->type = cJSON_NULL;
|
|
input->offset += 4;
|
|
return true;
|
|
}
|
|
return false;
|
|
case '\"':
|
|
return parse_string(item, input);
|
|
case '[':
|
|
return parse_array(item, input);
|
|
case '{':
|
|
return parse_object(item, input);
|
|
default:
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/* Custom hooks */
|
|
static cJSON_Hooks hooks = { malloc, free };
|
|
|
|
CJSON_PUBLIC(void) cJSON_InitHooks(cJSON_Hooks *h)
|
|
{
|
|
if (h != NULL)
|
|
{
|
|
hooks.malloc_fn = h->malloc_fn;
|
|
hooks.free_fn = h->free_fn;
|
|
}
|
|
}
|
|
|
|
CJSON_PUBLIC(cJSON *) cJSON_Parse(const char *value)
|
|
{
|
|
parse_buffer buffer = {0};
|
|
cJSON *item = NULL;
|
|
|
|
if (value == NULL) return NULL;
|
|
|
|
buffer.length = strlen(value);
|
|
buffer.content = (const unsigned char *)value;
|
|
buffer.offset = 0;
|
|
|
|
item = (cJSON *)hooks.malloc_fn(sizeof(cJSON));
|
|
if (item == NULL) return NULL;
|
|
memset(item, 0, sizeof(cJSON));
|
|
|
|
if (!parse_value(item, skip_whitespace(&buffer)))
|
|
{
|
|
cJSON_Delete(item);
|
|
return NULL;
|
|
}
|
|
return item;
|
|
}
|
|
|
|
CJSON_PUBLIC(void) cJSON_Delete(cJSON *item)
|
|
{
|
|
cJSON *next = NULL;
|
|
while (item != NULL)
|
|
{
|
|
next = item->next;
|
|
if (item->child != NULL) cJSON_Delete(item->child);
|
|
if (item->valuestring != NULL) hooks.free_fn(item->valuestring);
|
|
if (item->string != NULL) hooks.free_fn(item->string);
|
|
hooks.free_fn(item);
|
|
item = next;
|
|
}
|
|
}
|
|
|
|
/* --- Print functions (unformatted) --- */
|
|
|
|
static size_t print_value_ptr(const cJSON *item, char *buffer);
|
|
|
|
static size_t print_string_to(const char *str, char *buf)
|
|
{
|
|
size_t len = strlen(str) + 2;
|
|
if (buf)
|
|
{
|
|
buf[0] = '\"';
|
|
memcpy(buf + 1, str, len - 2);
|
|
buf[len - 1] = '\"';
|
|
}
|
|
return len;
|
|
}
|
|
|
|
static size_t print_number_to(const cJSON *item, char *buf)
|
|
{
|
|
char tmp[32];
|
|
size_t len;
|
|
if (fabs(floor(item->valuedouble) - item->valuedouble) <= DBL_EPSILON && fabs(item->valuedouble) < 1.0e15)
|
|
len = (size_t)sprintf(tmp, "%d", item->valueint);
|
|
else
|
|
len = (size_t)sprintf(tmp, "%g", item->valuedouble);
|
|
if (buf) memcpy(buf, tmp, len);
|
|
return len;
|
|
}
|
|
|
|
static size_t print_array_to(const cJSON *item, char *buf)
|
|
{
|
|
size_t pos = 0;
|
|
if (buf) buf[pos++] = '['; else pos++;
|
|
cJSON *child = item->child;
|
|
while (child != NULL)
|
|
{
|
|
pos += print_value_ptr(child, buf ? buf + pos : NULL);
|
|
if (child->next != NULL) { if (buf) buf[pos++] = ','; else pos++; }
|
|
child = child->next;
|
|
}
|
|
if (buf) buf[pos++] = ']'; else pos++;
|
|
return pos;
|
|
}
|
|
|
|
static size_t print_object_to(const cJSON *item, char *buf)
|
|
{
|
|
size_t pos = 0;
|
|
if (buf) buf[pos++] = '{'; else pos++;
|
|
cJSON *child = item->child;
|
|
while (child != NULL)
|
|
{
|
|
pos += print_string_to(child->string, buf ? buf + pos : NULL);
|
|
if (buf) buf[pos++] = ':'; else pos++;
|
|
pos += print_value_ptr(child, buf ? buf + pos : NULL);
|
|
if (child->next != NULL) { if (buf) buf[pos++] = ','; else pos++; }
|
|
child = child->next;
|
|
}
|
|
if (buf) buf[pos++] = '}'; else pos++;
|
|
return pos;
|
|
}
|
|
|
|
static size_t print_value_ptr(const cJSON *item, char *buffer)
|
|
{
|
|
switch (item->type & 0xFF)
|
|
{
|
|
case cJSON_NULL: if (buffer) { memcpy(buffer, "null", 4); } return 4;
|
|
case cJSON_False: if (buffer) { memcpy(buffer, "false", 5); } return 5;
|
|
case cJSON_True: if (buffer) { memcpy(buffer, "true", 4); } return 4;
|
|
case cJSON_Number: return print_number_to(item, buffer);
|
|
case cJSON_String: return print_string_to(item->valuestring, buffer);
|
|
case cJSON_Array: return print_array_to(item, buffer);
|
|
case cJSON_Object: return print_object_to(item, buffer);
|
|
default: return 0;
|
|
}
|
|
}
|
|
|
|
CJSON_PUBLIC(char *) cJSON_Print(const cJSON *item)
|
|
{
|
|
return cJSON_PrintUnformatted(item);
|
|
}
|
|
|
|
CJSON_PUBLIC(char *) cJSON_PrintUnformatted(const cJSON *item)
|
|
{
|
|
size_t length = 0;
|
|
char *output = NULL;
|
|
|
|
length = print_value_ptr(item, NULL);
|
|
output = (char *)hooks.malloc_fn(length + 1);
|
|
if (output == NULL) return NULL;
|
|
print_value_ptr(item, output);
|
|
output[length] = '\0';
|
|
return output;
|
|
}
|
|
|
|
/* --- Item access --- */
|
|
CJSON_PUBLIC(int) cJSON_GetArraySize(const cJSON *array)
|
|
{
|
|
cJSON *child = NULL;
|
|
size_t size = 0;
|
|
if (array == NULL || !cJSON_IsArray(array)) return 0;
|
|
child = array->child;
|
|
while (child != NULL) { size++; child = child->next; }
|
|
return (int)size;
|
|
}
|
|
|
|
CJSON_PUBLIC(cJSON *) cJSON_GetArrayItem(const cJSON *array, int index)
|
|
{
|
|
cJSON *child = NULL;
|
|
int i = 0;
|
|
if (array == NULL || !cJSON_IsArray(array)) return NULL;
|
|
child = array->child;
|
|
while (child != NULL && i < index) { i++; child = child->next; }
|
|
return child;
|
|
}
|
|
|
|
CJSON_PUBLIC(cJSON *) cJSON_GetObjectItemCaseSensitive(const cJSON *object, const char *string)
|
|
{
|
|
cJSON *current = NULL;
|
|
if (object == NULL || string == NULL) return NULL;
|
|
current = object->child;
|
|
while (current != NULL)
|
|
{
|
|
if (current->string != NULL && strcmp(current->string, string) == 0) return current;
|
|
current = current->next;
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
CJSON_PUBLIC(cJSON *) cJSON_GetObjectItem(const cJSON *object, const char *string)
|
|
{
|
|
cJSON *current = NULL;
|
|
if (object == NULL || string == NULL) return NULL;
|
|
current = object->child;
|
|
while (current != NULL)
|
|
{
|
|
if (current->string != NULL && cJSON_Strcasecmp(current->string, string) == 0) return current;
|
|
current = current->next;
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
CJSON_PUBLIC(cJSON_bool) cJSON_HasObjectItem(const cJSON *object, const char *string)
|
|
{
|
|
return cJSON_GetObjectItem(object, string) != NULL;
|
|
}
|
|
|
|
static const char *global_error_ptr = NULL;
|
|
CJSON_PUBLIC(const char *) cJSON_GetErrorPtr(void) { return global_error_ptr; }
|
|
|
|
/* --- Create items --- */
|
|
static cJSON *cJSON_New_Item(void)
|
|
{
|
|
cJSON *item = (cJSON *)hooks.malloc_fn(sizeof(cJSON));
|
|
if (item != NULL) memset(item, 0, sizeof(cJSON));
|
|
return item;
|
|
}
|
|
|
|
CJSON_PUBLIC(cJSON *) cJSON_CreateNull(void) { cJSON *item = cJSON_New_Item(); if(item) item->type = cJSON_NULL; return item; }
|
|
CJSON_PUBLIC(cJSON *) cJSON_CreateTrue(void) { cJSON *item = cJSON_New_Item(); if(item) item->type = cJSON_True; return item; }
|
|
CJSON_PUBLIC(cJSON *) cJSON_CreateFalse(void) { cJSON *item = cJSON_New_Item(); if(item) item->type = cJSON_False; return item; }
|
|
CJSON_PUBLIC(cJSON *) cJSON_CreateBool(cJSON_bool b) { cJSON *item = cJSON_New_Item(); if(item) item->type = b ? cJSON_True : cJSON_False; return item; }
|
|
CJSON_PUBLIC(cJSON *) cJSON_CreateNumber(double num) { cJSON *item = cJSON_New_Item(); if(item) { item->type = cJSON_Number; item->valuedouble = num; item->valueint = (int)num; } return item; }
|
|
CJSON_PUBLIC(cJSON *) cJSON_CreateString(const char *str)
|
|
{
|
|
cJSON *item = cJSON_New_Item();
|
|
if (item && str)
|
|
{
|
|
item->type = cJSON_String;
|
|
item->valuestring = (char *)hooks.malloc_fn(strlen(str) + 1);
|
|
if (item->valuestring) strcpy(item->valuestring, str);
|
|
}
|
|
return item;
|
|
}
|
|
CJSON_PUBLIC(cJSON *) cJSON_CreateRaw(const char *raw) { return cJSON_CreateString(raw); }
|
|
CJSON_PUBLIC(cJSON *) cJSON_CreateArray(void) { cJSON *item = cJSON_New_Item(); if(item) item->type = cJSON_Array; return item; }
|
|
CJSON_PUBLIC(cJSON *) cJSON_CreateObject(void) { cJSON *item = cJSON_New_Item(); if(item) item->type = cJSON_Object; return item; }
|
|
|
|
/* --- Add items --- */
|
|
CJSON_PUBLIC(cJSON_bool) cJSON_AddItemToArray(cJSON *array, cJSON *item)
|
|
{
|
|
cJSON *child = NULL;
|
|
if (array == NULL || item == NULL) return false;
|
|
child = array->child;
|
|
if (child == NULL) { array->child = item; }
|
|
else
|
|
{
|
|
while (child->next) child = child->next;
|
|
child->next = item;
|
|
item->prev = child;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
CJSON_PUBLIC(cJSON_bool) cJSON_AddItemToObject(cJSON *object, const char *name, cJSON *item)
|
|
{
|
|
if (object == NULL || name == NULL || item == NULL) return false;
|
|
if (item->string != NULL) hooks.free_fn(item->string);
|
|
item->string = (char *)hooks.malloc_fn(strlen(name) + 1);
|
|
if (item->string) strcpy(item->string, name);
|
|
return cJSON_AddItemToArray(object, item);
|
|
}
|
|
|
|
/* --- Type checks --- */
|
|
CJSON_PUBLIC(cJSON_bool) cJSON_IsInvalid(const cJSON *item) { return (item != NULL) && (item->type == cJSON_Invalid); }
|
|
CJSON_PUBLIC(cJSON_bool) cJSON_IsFalse(const cJSON *item) { return (item != NULL) && (item->type == cJSON_False); }
|
|
CJSON_PUBLIC(cJSON_bool) cJSON_IsTrue(const cJSON *item) { return (item != NULL) && (item->type == cJSON_True); }
|
|
CJSON_PUBLIC(cJSON_bool) cJSON_IsBool(const cJSON *item) { return (item != NULL) && (item->type & cJSON_Bool); }
|
|
CJSON_PUBLIC(cJSON_bool) cJSON_IsNumber(const cJSON *item) { return (item != NULL) && (item->type == cJSON_Number); }
|
|
CJSON_PUBLIC(cJSON_bool) cJSON_IsString(const cJSON *item) { return (item != NULL) && (item->type == cJSON_String); }
|
|
CJSON_PUBLIC(cJSON_bool) cJSON_IsArray(const cJSON *item) { return (item != NULL) && (item->type == cJSON_Array); }
|
|
CJSON_PUBLIC(cJSON_bool) cJSON_IsObject(const cJSON *item) { return (item != NULL) && (item->type == cJSON_Object); }
|
|
CJSON_PUBLIC(cJSON_bool) cJSON_IsRaw(const cJSON *item) { return (item != NULL) && (item->type == cJSON_Raw); }
|
|
|
|
/* --- Add helpers --- */
|
|
CJSON_PUBLIC(cJSON*) cJSON_AddNullToObject(cJSON *object, const char *name) { cJSON *item = cJSON_CreateNull(); if(item) cJSON_AddItemToObject(object, name, item); return item; }
|
|
CJSON_PUBLIC(cJSON*) cJSON_AddTrueToObject(cJSON *object, const char *name) { cJSON *item = cJSON_CreateTrue(); if(item) cJSON_AddItemToObject(object, name, item); return item; }
|
|
CJSON_PUBLIC(cJSON*) cJSON_AddFalseToObject(cJSON *object, const char *name) { cJSON *item = cJSON_CreateFalse(); if(item) cJSON_AddItemToObject(object, name, item); return item; }
|
|
CJSON_PUBLIC(cJSON*) cJSON_AddBoolToObject(cJSON *object, const char *name, cJSON_bool b) { cJSON *item = cJSON_CreateBool(b); if(item) cJSON_AddItemToObject(object, name, item); return item; }
|
|
CJSON_PUBLIC(cJSON*) cJSON_AddNumberToObject(cJSON *object, const char *name, double n) { cJSON *item = cJSON_CreateNumber(n); if(item) cJSON_AddItemToObject(object, name, item); return item; }
|
|
CJSON_PUBLIC(cJSON*) cJSON_AddStringToObject(cJSON *object, const char *name, const char *s) { cJSON *item = cJSON_CreateString(s); if(item) cJSON_AddItemToObject(object, name, item); return item; }
|
|
CJSON_PUBLIC(cJSON*) cJSON_AddRawToObject(cJSON *object, const char *name, const char *raw) { cJSON *item = cJSON_CreateRaw(raw); if(item) cJSON_AddItemToObject(object, name, item); return item; }
|
|
|
|
CJSON_PUBLIC(void) cJSON_SetValuestring(cJSON *object, const char *valuestring)
|
|
{
|
|
if (object == NULL || !cJSON_IsString(object) || valuestring == NULL) return;
|
|
if (object->valuestring != NULL) hooks.free_fn(object->valuestring);
|
|
object->valuestring = (char *)hooks.malloc_fn(strlen(valuestring) + 1);
|
|
if (object->valuestring) strcpy(object->valuestring, valuestring);
|
|
}
|
|
|
|
CJSON_PUBLIC(cJSON *) cJSON_CreateIntArray(const int *numbers, int count) { (void)numbers; (void)count; return NULL; }
|
|
CJSON_PUBLIC(cJSON *) cJSON_CreateFloatArray(const float *numbers, int count) { (void)numbers; (void)count; return NULL; }
|
|
CJSON_PUBLIC(cJSON *) cJSON_CreateDoubleArray(const double *numbers, int count) { (void)numbers; (void)count; return NULL; }
|
|
CJSON_PUBLIC(cJSON *) cJSON_CreateStringArray(const char **strings, int count) { (void)strings; (void)count; return NULL; }
|
|
|
|
CJSON_PUBLIC(void) cJSON_free(void *ptr) { if(ptr) hooks.free_fn(ptr); }
|