first version of rtjson.h
This commit is contained in:
parent
02b55a6f35
commit
e244af9c38
554
rtjson.h
Normal file
554
rtjson.h
Normal file
@ -0,0 +1,554 @@
|
|||||||
|
#ifndef RT_JSON_H
|
||||||
|
#define RT_JSON_H
|
||||||
|
|
||||||
|
/*
|
||||||
|
* rtjson.h - Simple json parser for C
|
||||||
|
* Copyright (C) 2026 Kevin Trogant
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "rtcore.h"
|
||||||
|
|
||||||
|
#ifndef RTJ_API
|
||||||
|
#define RTJ_API RTC_API
|
||||||
|
#endif
|
||||||
|
|
||||||
|
typedef enum
|
||||||
|
{
|
||||||
|
JSON_TYPE_OBJECT,
|
||||||
|
JSON_TYPE_ARRAY,
|
||||||
|
JSON_TYPE_STRING,
|
||||||
|
JSON_TYPE_INTEGER,
|
||||||
|
JSON_TYPE_FLOAT,
|
||||||
|
} json_type;
|
||||||
|
|
||||||
|
/* Represents a json value */
|
||||||
|
typedef struct json
|
||||||
|
{
|
||||||
|
/* The key, if this value is part of an object.
|
||||||
|
* Empty otherwise. */
|
||||||
|
s8 key;
|
||||||
|
|
||||||
|
json_type type;
|
||||||
|
|
||||||
|
/* The value. */
|
||||||
|
union
|
||||||
|
{
|
||||||
|
s8 s;
|
||||||
|
i64 i;
|
||||||
|
f64 f;
|
||||||
|
|
||||||
|
/* The first subobject.
|
||||||
|
* Element 0 of an array,
|
||||||
|
* one member of an object. */
|
||||||
|
struct json *first_child;
|
||||||
|
} value;
|
||||||
|
|
||||||
|
/* Next in the list of a parents children */
|
||||||
|
struct json *next;
|
||||||
|
} json;
|
||||||
|
|
||||||
|
/* Parses the given json text.
|
||||||
|
* The returned json struct will be allocated on a.
|
||||||
|
* file is only used for error output and can be set to {0} */
|
||||||
|
RTJ_API json *ParseJSON(s8 text, s8 file, arena *a);
|
||||||
|
|
||||||
|
/* Returns a pointer to the objects member with the given key.
|
||||||
|
* Returns NULL, if no such member exists */
|
||||||
|
RTJ_API json *GetJSONMember(json *o, s8 key);
|
||||||
|
|
||||||
|
/* Returns a pointer to the arrays i-th element.
|
||||||
|
* Returns NULL if no such element exists */
|
||||||
|
RTJ_API json *GetJSONArrayElement(json *a, isize i);
|
||||||
|
|
||||||
|
/* Returns the length of the given json array or -1 if the
|
||||||
|
* passed pointer is not an array */
|
||||||
|
RTJ_API isize GetJSONArrayLength(json *a);
|
||||||
|
|
||||||
|
/* Iterates over every member of an object or array */
|
||||||
|
#define JsonForEach(_ChildVarName, _ParentPtr) \
|
||||||
|
for (json *_ChildVarName = (_ParentPtr)->value.first_child; _ChildVarName != 0; _ChildVarName = _ChildVarName->next)
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef RT_JSON_IMPLEMENTATION
|
||||||
|
#undef RT_JSON_IMPLEMENTATION
|
||||||
|
|
||||||
|
#include <stdio.h>
|
||||||
|
|
||||||
|
typedef enum
|
||||||
|
{
|
||||||
|
NOT_A_NUMBER,
|
||||||
|
DOUBLE,
|
||||||
|
INT,
|
||||||
|
} parse_number_result;
|
||||||
|
|
||||||
|
static parse_number_result
|
||||||
|
ParseNumber(s8 text, isize *_at, isize *current_line, s8 file, i64 *_i, f64 *_f)
|
||||||
|
{
|
||||||
|
isize at = *_at;
|
||||||
|
if ((text.data[at] < '0' || text.data[at] > '9') && text.data[at] != '-')
|
||||||
|
return NOT_A_NUMBER;
|
||||||
|
int dot_count = 0, exp_count = 0;
|
||||||
|
isize len = 0;
|
||||||
|
while ((text.data[at + len] >= '0' && text.data[at + len] <= '9') || text.data[at + len] == '.' ||
|
||||||
|
text.data[at + len] == 'e' || text.data[at + len] == 'E' || text.data[at + len] == '+' ||
|
||||||
|
text.data[at + len] == '-')
|
||||||
|
{
|
||||||
|
++len;
|
||||||
|
if (text.data[at + len] == '.')
|
||||||
|
++dot_count;
|
||||||
|
if (text.data[at + len] == 'e' || text.data[at + len] == 'E')
|
||||||
|
++exp_count;
|
||||||
|
}
|
||||||
|
if (dot_count > 1 || exp_count > 1)
|
||||||
|
{
|
||||||
|
if (file.data)
|
||||||
|
printf("%*.s:%zu expected a number\n", (int)file.length, file.data, *current_line);
|
||||||
|
else
|
||||||
|
printf("%zu: expected a number\n", *current_line);
|
||||||
|
return NOT_A_NUMBER;
|
||||||
|
}
|
||||||
|
if (dot_count || exp_count)
|
||||||
|
{
|
||||||
|
s8 n = {.data = &text.data[at], .length = len};
|
||||||
|
s8_parse_f64_result parsed = S8ParseF64(n);
|
||||||
|
*_f = parsed.f;
|
||||||
|
return parsed.ok ? DOUBLE : NOT_A_NUMBER;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
s8 n = {.data = &text.data[at], .length = len};
|
||||||
|
s8_parse_i64_result parsed = S8ParseI64(n, 10);
|
||||||
|
*_i = parsed.i;
|
||||||
|
return parsed.ok ? INT : NOT_A_NUMBER;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static s8
|
||||||
|
ParseString(s8 text, isize *_at, isize *current_line, s8 file, arena *a)
|
||||||
|
{
|
||||||
|
isize at = *_at;
|
||||||
|
if (text.data[at] != '\"')
|
||||||
|
{
|
||||||
|
if (file.data)
|
||||||
|
printf("%.*s:%zu expected string\n", (int)file.length, file.data, *current_line);
|
||||||
|
else
|
||||||
|
printf("%zu: expected string\n", *current_line);
|
||||||
|
return (s8){0};
|
||||||
|
}
|
||||||
|
++at;
|
||||||
|
isize first_char = at;
|
||||||
|
s8 str = {.data = &text.data[first_char], .length = 0};
|
||||||
|
/* If the string contains control codes, we need to copy it into owned
|
||||||
|
* memory to be able to modify it.
|
||||||
|
*/
|
||||||
|
b32 owns_memory = 0;
|
||||||
|
isize str_at = 0;
|
||||||
|
while (text.data[at] != '\"' && at < text.length)
|
||||||
|
{
|
||||||
|
if (text.data[at] == '\\')
|
||||||
|
{
|
||||||
|
/* Control codes.
|
||||||
|
* If this string is still unowned, copy it into owned memory to be able
|
||||||
|
* to modify it. */
|
||||||
|
if (!owns_memory)
|
||||||
|
{
|
||||||
|
isize total_length = at - first_char;
|
||||||
|
isize i = at;
|
||||||
|
while (text.data[i] != '\"' && i < text.length)
|
||||||
|
++total_length;
|
||||||
|
if (i == text.length)
|
||||||
|
{
|
||||||
|
if (file.data)
|
||||||
|
printf("%.*s:%zu unexpected end of file\n", (int)file.length, file.data, *current_line);
|
||||||
|
else
|
||||||
|
printf("%zu: unexpected end of file\n", *current_line);
|
||||||
|
return (s8){0};
|
||||||
|
}
|
||||||
|
u8 *mem = alloc(a, u8, total_length);
|
||||||
|
if (at > first_char)
|
||||||
|
memcpy(mem, str.data, at - first_char);
|
||||||
|
str.data = mem;
|
||||||
|
owns_memory = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
++at;
|
||||||
|
if (at == text.length)
|
||||||
|
{
|
||||||
|
if (file.data)
|
||||||
|
printf("%.*s:%zu unexpected end of file\n", (int)file.length, file.data, *current_line);
|
||||||
|
else
|
||||||
|
printf("%zu: unexpected end of file\n", *current_line);
|
||||||
|
return (s8){0};
|
||||||
|
}
|
||||||
|
switch (text.data[at])
|
||||||
|
{
|
||||||
|
case '\"':
|
||||||
|
case '\\':
|
||||||
|
str.data[str_at] = text.data[at];
|
||||||
|
++str_at;
|
||||||
|
++at;
|
||||||
|
break;
|
||||||
|
case 'b':
|
||||||
|
str.data[str_at] = '\b';
|
||||||
|
++str_at;
|
||||||
|
++at;
|
||||||
|
break;
|
||||||
|
case 'f':
|
||||||
|
str.data[str_at] = '\f';
|
||||||
|
++str_at;
|
||||||
|
++at;
|
||||||
|
break;
|
||||||
|
case 'n':
|
||||||
|
str.data[str_at] = '\n';
|
||||||
|
++str_at;
|
||||||
|
++at;
|
||||||
|
break;
|
||||||
|
case 'r':
|
||||||
|
str.data[str_at] = '\r';
|
||||||
|
++str_at;
|
||||||
|
++at;
|
||||||
|
break;
|
||||||
|
case 't':
|
||||||
|
str.data[str_at] = '\t';
|
||||||
|
++str_at;
|
||||||
|
++at;
|
||||||
|
break;
|
||||||
|
case 'u':
|
||||||
|
/* 4 hex digits unicode codepoint
|
||||||
|
* NOT IMPLEMENTED YET. */
|
||||||
|
default:
|
||||||
|
if (file.data)
|
||||||
|
printf("%.*s:%zu unsupported control code '%c'\n",
|
||||||
|
(int)file.length,
|
||||||
|
file.data,
|
||||||
|
*current_line,
|
||||||
|
text.data[at]);
|
||||||
|
else
|
||||||
|
printf("%zu: unsupported control code '%c'\n", *current_line, text.data[at]);
|
||||||
|
return (s8){0};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
/* Normal character */
|
||||||
|
if (owns_memory)
|
||||||
|
str.data[str_at] = text.data[at];
|
||||||
|
++str_at;
|
||||||
|
++at;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/* Consume terminating quotes */
|
||||||
|
++at;
|
||||||
|
str.length = str_at;
|
||||||
|
|
||||||
|
*_at = at;
|
||||||
|
return str;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Return 1 if it reaches the ned of the file */
|
||||||
|
static b32
|
||||||
|
ConsumeWhitespace(s8 text, isize *_at, isize *current_line)
|
||||||
|
{
|
||||||
|
isize at = *_at;
|
||||||
|
while (at < text.length)
|
||||||
|
{
|
||||||
|
if (text.data[at] == ' ' || text.data[at] == '\t' || text.data[at] == '\r')
|
||||||
|
{
|
||||||
|
++at;
|
||||||
|
}
|
||||||
|
else if (text.data[at] == '\n')
|
||||||
|
{
|
||||||
|
++at;
|
||||||
|
++*current_line;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
*_at = at;
|
||||||
|
return at == text.length;
|
||||||
|
}
|
||||||
|
|
||||||
|
#define ConsumeWhitespaceNoEof(_Text, _PtrAt, _CurLine, _File, _ErrRet) \
|
||||||
|
if (ConsumeWhitespace((_Text), (_PtrAt), (_CurLine))) \
|
||||||
|
{ \
|
||||||
|
if ((_File).data) \
|
||||||
|
printf("%.*s:%zu unexpected end of file\n", (int)(_File).length, (_File).data, *(_CurLine)); \
|
||||||
|
else \
|
||||||
|
printf("%zu: unexpected end of file\n", *(_CurLine)); \
|
||||||
|
return (_ErrRet); \
|
||||||
|
}
|
||||||
|
|
||||||
|
#define ExpectCharacter(_Char, _Text, _At, _CurLine, _File, _ErrRet) \
|
||||||
|
if ((_Text).data[(_At)] != (_Char)) \
|
||||||
|
{ \
|
||||||
|
if ((_File).data) \
|
||||||
|
printf("%.*s:%zu expected '%c', got '%c'\n", \
|
||||||
|
(int)(_File).length, \
|
||||||
|
(_File).data, \
|
||||||
|
*(_CurLine), \
|
||||||
|
(_Char), \
|
||||||
|
(_Text).data[(_At)]); \
|
||||||
|
else \
|
||||||
|
printf("%zu: expected '%c' got '%c'\n", *(_CurLine), (_Char), (_Text).data[(_At)]); \
|
||||||
|
return (_ErrRet); \
|
||||||
|
} \
|
||||||
|
(_At) += 1;
|
||||||
|
|
||||||
|
static json *
|
||||||
|
ParseJSONImpl(s8 text, isize *_at, json *sibling, isize *current_line, s8 file, arena *a)
|
||||||
|
{
|
||||||
|
isize at = *_at;
|
||||||
|
|
||||||
|
json *j = alloc(a, json);
|
||||||
|
j->next = sibling;
|
||||||
|
|
||||||
|
if (text.data[at] == '{')
|
||||||
|
{
|
||||||
|
/* Parse object */
|
||||||
|
++at;
|
||||||
|
|
||||||
|
j->type = JSON_TYPE_OBJECT;
|
||||||
|
|
||||||
|
ConsumeWhitespaceNoEof(text, &at, current_line, file, 0);
|
||||||
|
|
||||||
|
while (text.data[at] != '}' && at < text.length)
|
||||||
|
{
|
||||||
|
/* "string" : value [,] */
|
||||||
|
s8 key = ParseString(text, &at, current_line, file, a);
|
||||||
|
if (!key.data)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
ConsumeWhitespaceNoEof(text, &at, current_line, file, 0);
|
||||||
|
ExpectCharacter(':', text, at, current_line, file, 0);
|
||||||
|
ConsumeWhitespaceNoEof(text, &at, current_line, file, 0);
|
||||||
|
|
||||||
|
/* Value */
|
||||||
|
if (text.data[at] == '{' || text.data[at] == '[')
|
||||||
|
{
|
||||||
|
/* Object or array, recurse into it.
|
||||||
|
* JSON objects are unordered, so it does not matter that we
|
||||||
|
* effectively reverse the order here */
|
||||||
|
j->value.first_child = ParseJSONImpl(text, &at, j->value.first_child, current_line, file, a);
|
||||||
|
if (!j->value.first_child)
|
||||||
|
return 0;
|
||||||
|
j->value.first_child->key = key;
|
||||||
|
}
|
||||||
|
else if (text.data[at] == '\"')
|
||||||
|
{
|
||||||
|
json *child = alloc(a, json);
|
||||||
|
child->key = key;
|
||||||
|
child->type = JSON_TYPE_STRING;
|
||||||
|
child->value.s = ParseString(text, &at, current_line, file, a);
|
||||||
|
child->next = j->value.first_child;
|
||||||
|
j->value.first_child = child;
|
||||||
|
}
|
||||||
|
else if ((text.data[at] >= '0' && text.data[at] <= '9') || text.data[at] == '-')
|
||||||
|
{
|
||||||
|
/* Number. */
|
||||||
|
f64 f;
|
||||||
|
i64 i;
|
||||||
|
parse_number_result res = ParseNumber(text, &at, current_line, file, &i, &f);
|
||||||
|
if (res == NOT_A_NUMBER)
|
||||||
|
return 0;
|
||||||
|
json *child = alloc(a, json);
|
||||||
|
child->key = key;
|
||||||
|
if (res == INT)
|
||||||
|
{
|
||||||
|
child->type = JSON_TYPE_INTEGER;
|
||||||
|
child->value.i = i;
|
||||||
|
}
|
||||||
|
else /* DOUBLE */
|
||||||
|
{
|
||||||
|
child->type = JSON_TYPE_FLOAT;
|
||||||
|
child->value.f = f;
|
||||||
|
}
|
||||||
|
child->next = j->value.first_child;
|
||||||
|
j->value.first_child = child;
|
||||||
|
}
|
||||||
|
|
||||||
|
ConsumeWhitespaceNoEof(text, &at, current_line, file, 0);
|
||||||
|
|
||||||
|
/* Consume ',' We deviate from the spec: We actually support trailing commas,
|
||||||
|
* because a) thats simpler code and b) not allowing trailing commas is stupid. */
|
||||||
|
if (text.data[at] == ',')
|
||||||
|
{
|
||||||
|
++at;
|
||||||
|
ConsumeWhitespaceNoEof(text, &at, current_line, file, 0);
|
||||||
|
}
|
||||||
|
else if (text.data[at] != '}')
|
||||||
|
{
|
||||||
|
if (file.data)
|
||||||
|
printf("%.*s:%zu unexpected character '%c' after value\n",
|
||||||
|
(int)file.length,
|
||||||
|
file.data,
|
||||||
|
*current_line,
|
||||||
|
text.data[at]);
|
||||||
|
else
|
||||||
|
printf("%zu: unexpected character '%c' after value\n", *current_line, text.data[at]);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
++at;
|
||||||
|
}
|
||||||
|
else if (text.data[at] == '[')
|
||||||
|
{
|
||||||
|
/* Parse array */
|
||||||
|
++at;
|
||||||
|
j->type = JSON_TYPE_ARRAY;
|
||||||
|
|
||||||
|
json *last_child = NULL;
|
||||||
|
while (text.data[at] != ']' && at < text.length)
|
||||||
|
{
|
||||||
|
/* value [,] ... */
|
||||||
|
ConsumeWhitespaceNoEof(text, &at, current_line, file, 0);
|
||||||
|
if (text.data[at] == '{' || text.data[at] == '[')
|
||||||
|
{
|
||||||
|
/* Object or array, recurse into it. */
|
||||||
|
json *child = ParseJSONImpl(text, &at, NULL, current_line, file, a);
|
||||||
|
if (!child)
|
||||||
|
return 0;
|
||||||
|
if (!j->value.first_child)
|
||||||
|
j->value.first_child = child;
|
||||||
|
if (last_child)
|
||||||
|
last_child->next = child;
|
||||||
|
last_child = child;
|
||||||
|
}
|
||||||
|
else if (text.data[at] == '\"')
|
||||||
|
{
|
||||||
|
json *child = alloc(a, json);
|
||||||
|
child->value.s = ParseString(text, &at, current_line, file, a);
|
||||||
|
child->type = JSON_TYPE_STRING;
|
||||||
|
if (!j->value.first_child)
|
||||||
|
j->value.first_child = child;
|
||||||
|
if (last_child)
|
||||||
|
last_child->next = child;
|
||||||
|
last_child = child;
|
||||||
|
}
|
||||||
|
else if ((text.data[at] >= '0' && text.data[at] <= '9') || text.data[at] == '-')
|
||||||
|
{
|
||||||
|
/* Number. */
|
||||||
|
f64 f;
|
||||||
|
i64 i;
|
||||||
|
parse_number_result res = ParseNumber(text, &at, current_line, file, &i, &f);
|
||||||
|
if (res == NOT_A_NUMBER)
|
||||||
|
return 0;
|
||||||
|
json *child = alloc(a, json);
|
||||||
|
if (res == INT)
|
||||||
|
{
|
||||||
|
child->value.i = i;
|
||||||
|
child->type = JSON_TYPE_INTEGER;
|
||||||
|
}
|
||||||
|
else /* DOUBLE */
|
||||||
|
{
|
||||||
|
child->value.f = f;
|
||||||
|
child->type = JSON_TYPE_FLOAT;
|
||||||
|
}
|
||||||
|
if (!j->value.first_child)
|
||||||
|
j->value.first_child = child;
|
||||||
|
if (last_child)
|
||||||
|
last_child->next = child;
|
||||||
|
last_child = child;
|
||||||
|
}
|
||||||
|
|
||||||
|
ConsumeWhitespaceNoEof(text, &at, current_line, file, 0);
|
||||||
|
|
||||||
|
/* Consume ',' We deviate from the spec: We actually support trailing commas,
|
||||||
|
* because a) thats simpler code and b) not allowing trailing commas is stupid. */
|
||||||
|
if (text.data[at] == ',')
|
||||||
|
{
|
||||||
|
++at;
|
||||||
|
ConsumeWhitespaceNoEof(text, &at, current_line, file, 0);
|
||||||
|
}
|
||||||
|
else if (text.data[at] != ']')
|
||||||
|
{
|
||||||
|
if (file.data)
|
||||||
|
printf("%.*s:%zu unexpected character '%c' after value\n",
|
||||||
|
(int)file.length,
|
||||||
|
file.data,
|
||||||
|
*current_line,
|
||||||
|
text.data[at]);
|
||||||
|
else
|
||||||
|
printf("%zu: unexpected character '%c' after value\n", *current_line, text.data[at]);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
++at;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (file.data)
|
||||||
|
printf("%.*s:%zu unexpected character %c\n", (int)file.length, file.data, *current_line, text.data[0]);
|
||||||
|
else
|
||||||
|
printf("%zu: unexpected character %c\n", *current_line, text.data[0]);
|
||||||
|
}
|
||||||
|
*_at = at;
|
||||||
|
return j;
|
||||||
|
}
|
||||||
|
|
||||||
|
RTJ_API json *
|
||||||
|
ParseJSON(s8 text, s8 file, arena *a)
|
||||||
|
{
|
||||||
|
isize line = 1, at = 0;
|
||||||
|
return ParseJSONImpl(text, &at, NULL, &line, file, a);
|
||||||
|
}
|
||||||
|
|
||||||
|
RTJ_API json *
|
||||||
|
GetJSONMember(json *o, s8 key)
|
||||||
|
{
|
||||||
|
if (o->type != JSON_TYPE_OBJECT)
|
||||||
|
return NULL;
|
||||||
|
JsonForEach(child, o)
|
||||||
|
{
|
||||||
|
if (S8Equals(child->key, key))
|
||||||
|
return child;
|
||||||
|
}
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
RTJ_API json *
|
||||||
|
GetJSONArrayElement(json *a, isize i)
|
||||||
|
{
|
||||||
|
if (a->type != JSON_TYPE_ARRAY)
|
||||||
|
return NULL;
|
||||||
|
isize at = 0;
|
||||||
|
JsonForEach(child, a)
|
||||||
|
{
|
||||||
|
if (at == i)
|
||||||
|
return child;
|
||||||
|
++at;
|
||||||
|
}
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
RTJ_API isize
|
||||||
|
GetJSONArrayLength(json *a)
|
||||||
|
{
|
||||||
|
if (a->type != JSON_TYPE_ARRAY)
|
||||||
|
return -1;
|
||||||
|
isize at = 0;
|
||||||
|
JsonForEach(child, a) { ++at; }
|
||||||
|
return at;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
Loading…
Reference in New Issue
Block a user