blob: 56bf734b12b994e9b59b277bd6f7ffb95fcc1299 [file] [log] [blame]
/**
* @file json.c
* @author Radek Krejci <rkrejci@cesnet.cz>
* @brief Generic JSON format parser for libyang
*
* Copyright (c) 2020 CESNET, z.s.p.o.
*
* This source code is licensed under BSD 3-Clause License (the "License").
* You may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://opensource.org/licenses/BSD-3-Clause
*/
#include <assert.h>
#include <ctype.h>
#include <errno.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include "common.h"
#include "in_internal.h"
#include "json.h"
#define JSON_PUSH_STATUS_RET(CTX, STATUS) \
LY_CHECK_RET(ly_set_add(&CTX->status, (void*)STATUS, 1, NULL))
#define JSON_POP_STATUS_RET(CTX) \
assert(CTX->status.count); CTX->status.count--;
const char *
lyjson_token2str(enum LYJSON_PARSER_STATUS status)
{
switch (status) {
case LYJSON_ERROR:
return "error";
case LYJSON_ROOT:
return "document root";
case LYJSON_FALSE:
return "false";
case LYJSON_TRUE:
return "true";
case LYJSON_NULL:
return "null";
case LYJSON_OBJECT:
return "object";
case LYJSON_OBJECT_CLOSED:
return "object closed";
case LYJSON_OBJECT_EMPTY:
return "empty object";
case LYJSON_ARRAY:
return "array";
case LYJSON_ARRAY_CLOSED:
return "array closed";
case LYJSON_ARRAY_EMPTY:
return "empty array";
case LYJSON_NUMBER:
return "number";
case LYJSON_STRING:
return "string";
case LYJSON_END:
return "end of input";
}
return "";
}
static LY_ERR
skip_ws(struct lyjson_ctx *jsonctx)
{
/* skip leading whitespaces */
while (*jsonctx->in->current != '\0' && is_jsonws(*jsonctx->in->current)) {
if (*jsonctx->in->current == 0x0a) { /* new line */
jsonctx->line++;
}
ly_in_skip(jsonctx->in, 1);
}
if (*jsonctx->in->current == '\0') {
JSON_PUSH_STATUS_RET(jsonctx, LYJSON_END);
}
return LY_SUCCESS;
}
/*
* @brief Set value corresponding to the current context's status
*/
static void
lyjson_ctx_set_value(struct lyjson_ctx *jsonctx, const char *value, size_t value_len, ly_bool dynamic)
{
assert(jsonctx);
if (dynamic) {
free((char *)jsonctx->value);
}
jsonctx->value = value;
jsonctx->value_len = value_len;
jsonctx->dynamic = dynamic;
}
static LY_ERR
lyjson_check_next(struct lyjson_ctx *jsonctx)
{
if (jsonctx->status.count == 1) {
/* top level value (JSON-text), ws expected */
if ((*jsonctx->in->current == '\0') || is_jsonws(*jsonctx->in->current)) {
return LY_SUCCESS;
}
} else if (lyjson_ctx_status(jsonctx, 1) == LYJSON_OBJECT) {
LY_CHECK_RET(skip_ws(jsonctx));
if ((*jsonctx->in->current == ',') || (*jsonctx->in->current == '}')) {
return LY_SUCCESS;
}
} else if (lyjson_ctx_status(jsonctx, 1) == LYJSON_ARRAY) {
LY_CHECK_RET(skip_ws(jsonctx));
if ((*jsonctx->in->current == ',') || (*jsonctx->in->current == ']')) {
return LY_SUCCESS;
}
} else {
LOGVAL(jsonctx->ctx, LY_VLOG_LINE, &jsonctx->line, LYVE_SYNTAX,
"Unexpected character \"%c\" after JSON %s.", *jsonctx->in->current, lyjson_token2str(lyjson_ctx_status(jsonctx, 0)));
}
return LY_EVALID;
}
/**
* Input is expected to start after the opening quotation-mark.
* When succeeds, input is moved after the closing quotation-mark.
*/
static LY_ERR
lyjson_string_(struct lyjson_ctx *jsonctx)
{
#define BUFSIZE 24
#define BUFSIZE_STEP 128
const char *in = jsonctx->in->current, *start;
char *buf = NULL;
size_t offset; /* read offset in input buffer */
size_t len; /* length of the output string (write offset in output buffer) */
size_t size = 0; /* size of the output buffer */
size_t u;
uint64_t start_line;
assert(jsonctx);
/* init */
start = in;
start_line = jsonctx->line;
offset = len = 0;
/* parse */
while (in[offset]) {
if (in[offset] == '\\') {
/* escape sequence */
size_t slash = offset;
uint32_t value;
uint8_t i = 1;
if (!buf) {
/* prepare output buffer */
buf = malloc(BUFSIZE);
LY_CHECK_ERR_RET(!buf, LOGMEM(jsonctx->ctx), LY_EMEM);
size = BUFSIZE;
}
/* allocate enough for the offset and next character,
* we will need 4 bytes at most since we support only the predefined
* (one-char) entities and character references */
if (len + offset + 4 >= size) {
buf = ly_realloc(buf, size + BUFSIZE_STEP);
LY_CHECK_ERR_RET(!buf, LOGMEM(jsonctx->ctx), LY_EMEM);
size += BUFSIZE_STEP;
}
if (offset) {
/* store what we have so far */
memcpy(&buf[len], in, offset);
len += offset;
in += offset;
offset = 0;
}
switch (in[++offset]) {
case '"':
/* quotation mark */
value = 0x22;
break;
case '\\':
/* reverse solidus */
value = 0x5c;
break;
case '/':
/* solidus */
value = 0x2f;
break;
case 'b':
/* backspace */
value = 0x08;
break;
case 'f':
/* form feed */
value = 0x0c;
break;
case 'n':
/* line feed */
value = 0x0a;
break;
case 'r':
/* carriage return */
value = 0x0d;
break;
case 't':
/* tab */
value = 0x09;
break;
case 'u':
/* Basic Multilingual Plane character \uXXXX */
offset++;
for (value = i = 0; i < 4; i++) {
if (isdigit(in[offset + i])) {
u = (in[offset + i] - '0');
} else if (in[offset + i] > 'F') {
u = 10 + (in[offset + i] - 'a');
} else {
u = 10 + (in[offset + i] - 'A');
}
value = (16 * value) + u;
}
break;
default:
/* invalid escape sequence */
LOGVAL(jsonctx->ctx, LY_VLOG_LINE, &jsonctx->line, LYVE_SYNTAX,
"Invalid character escape sequence \\%c.", in[offset]);
goto error;
}
offset += i; /* add read escaped characters */
LY_CHECK_ERR_GOTO(ly_pututf8(&buf[len], value, &u),
LOGVAL(jsonctx->ctx, LY_VLOG_LINE, &jsonctx->line, LYVE_SYNTAX,
"Invalid character reference \"%.*s\" (0x%08x).", offset - slash, &in[slash], value),
error);
len += u; /* update number of bytes in buffer */
in += offset; /* move the input by the processed bytes stored in the buffer ... */
offset = 0; /* ... and reset the offset index for future moving data into buffer */
} else if (in[offset] == '"') {
/* end of string */
if (buf) {
/* realloc exact size string */
buf = ly_realloc(buf, len + offset + 1);
LY_CHECK_ERR_RET(!buf, LOGMEM(jsonctx->ctx), LY_EMEM);
size = len + offset + 1;
memcpy(&buf[len], in, offset);
/* set terminating NULL byte */
buf[len + offset] = '\0';
}
len += offset;
++offset;
in += offset;
goto success;
} else {
/* get it as UTF-8 character for check */
const char *c = &in[offset];
uint32_t code = 0;
size_t code_len = 0;
LY_CHECK_ERR_GOTO(ly_getutf8(&c, &code, &code_len),
LOGVAL(jsonctx->ctx, LY_VLOG_LINE, &jsonctx->line, LY_VCODE_INCHAR, in[offset]), error);
LY_CHECK_ERR_GOTO(!is_jsonstrchar(code),
LOGVAL(jsonctx->ctx, LY_VLOG_LINE, &jsonctx->line, LYVE_SYNTAX,
"Invalid character in JSON string \"%.*s\" (0x%08x).", &in[offset] - start + code_len, start, code),
error);
/* character is ok, continue */
offset += code_len;
}
}
/* EOF reached before endchar */
LOGVAL(jsonctx->ctx, LY_VLOG_LINE, &jsonctx->line, LY_VCODE_EOF);
LOGVAL(jsonctx->ctx, LY_VLOG_LINE, &start_line, LYVE_SYNTAX, "Missing quotation-mark at the end of a JSON string.");
error:
free(buf);
return LY_EVALID;
success:
ly_in_skip(jsonctx->in, in - jsonctx->in->current);
if (buf) {
lyjson_ctx_set_value(jsonctx, buf, len, 1);
} else {
lyjson_ctx_set_value(jsonctx, start, len, 0);
}
return LY_SUCCESS;
#undef BUFSIZE
#undef BUFSIZE_STEP
}
/*
*
* Wrapper around lyjson_string_() adding LYJSON_STRING status into context to allow using lyjson_string_() for parsing object's name.
*/
static LY_ERR
lyjson_string(struct lyjson_ctx *jsonctx)
{
LY_CHECK_RET(lyjson_string_(jsonctx));
JSON_PUSH_STATUS_RET(jsonctx, LYJSON_STRING);
LY_CHECK_RET(lyjson_check_next(jsonctx));
return LY_SUCCESS;
}
static LY_ERR
lyjson_number(struct lyjson_ctx *jsonctx)
{
size_t offset = 0, exponent = 0;
const char *in = jsonctx->in->current;
uint8_t minus = 0;
if (in[offset] == '-') {
++offset;
minus = 1;
}
if (in[offset] == '0') {
++offset;
} else if (isdigit(in[offset])) {
++offset;
while (isdigit(in[offset])) {
++offset;
}
} else {
invalid_character:
if (in[offset]) {
LOGVAL(jsonctx->ctx, LY_VLOG_LINE, &jsonctx->line, LYVE_SYNTAX, "Invalid character in JSON Number value (\"%c\").", in[offset]);
} else {
LOGVAL(jsonctx->ctx, LY_VLOG_LINE, &jsonctx->line, LY_VCODE_EOF);
}
return LY_EVALID;
}
if (in[offset] == '.') {
++offset;
if (!isdigit(in[offset])) {
goto invalid_character;
}
while (isdigit(in[offset])) {
++offset;
}
}
if ((in[offset] == 'e') || (in[offset] == 'E')) {
exponent = offset++;
if ((in[offset] == '+') || (in[offset] == '-')) {
++offset;
}
if (!isdigit(in[offset])) {
goto invalid_character;
}
while (isdigit(in[offset])) {
++offset;
}
}
if (exponent) {
/* convert JSON number with exponent into the representation used by YANG */
long int e_val;
char *ptr, *dec_point, *num;
const char *e_ptr = &in[exponent + 1];
size_t num_len, i;
int64_t dp_position; /* final position of the deciaml point */
errno = 0;
e_val = strtol(e_ptr, &ptr, 10);
if (errno) {
LOGVAL(jsonctx->ctx, LY_VLOG_LINE, &jsonctx->line, LYVE_SEMANTICS,
"Exponent out-of-bounds in a JSON Number value (%.*s).", offset - minus - (e_ptr - in), e_ptr);
return LY_EVALID;
}
dec_point = ly_strnchr(in, '.', exponent);
if (!dec_point) {
/* value is integer, we are just ... */
if (e_val >= 0) {
/* adding zeros at the end */
num_len = exponent + e_val;
dp_position = num_len; /* decimal point is behind the actual value */
} else if ((size_t)labs(e_val) < exponent) {
/* adding decimal point between the integer's digits */
num_len = exponent + 1;
dp_position = exponent + e_val;
} else {
/* adding decimal point before the integer with adding leading zero(s) */
num_len = labs(e_val) + 2;
dp_position = exponent + e_val;
}
dp_position -= minus;
} else {
/* value is decimal, we are moving the decimal point */
dp_position = dec_point - in + e_val - minus;
if (dp_position > (ssize_t)exponent) {
/* moving decimal point after the decimal value make the integer result */
num_len = dp_position;
} else if (dp_position < 0) {
/* moving decimal point before the decimal value requires additional zero(s)
* (decimal point is already count in exponent value) */
num_len = exponent + labs(dp_position) + 1;
} else {
/* moving decimal point just inside the decimal value does not make any change in length */
num_len = exponent;
}
}
/* allocate buffer for the result (add terminating NULL-byte */
num = malloc(num_len + 1);
LY_CHECK_ERR_RET(!num, LOGMEM(jsonctx->ctx), LY_EMEM);
/* compose the resulting vlaue */
i = 0;
if (minus) {
num[i++] = '-';
}
/* add leading zeros */
if (dp_position <= 0) {
num[i++] = '0';
num[i++] = '.';
for ( ; dp_position; dp_position++) {
num[i++] = '0';
}
}
/* copy the value */
ly_bool dp_placed;
size_t j;
for (dp_placed = dp_position ? 0 : 1, j = minus; j < exponent; j++) {
if (in[j] == '.') {
continue;
}
if (!dp_placed) {
if (!dp_position) {
num[i++] = '.';
dp_placed = 1;
} else {
dp_position--;
if (in[j] == '0') {
num_len--;
continue;
}
}
}
num[i++] = in[j];
}
/* trailing zeros */
while (dp_position--) {
num[i++] = '0';
}
/* terminating NULL byte */
num[i] = '\0';
/* store the modified number */
lyjson_ctx_set_value(jsonctx, num, num_len, 1);
} else {
/* store the number */
lyjson_ctx_set_value(jsonctx, jsonctx->in->current, offset, 0);
}
ly_in_skip(jsonctx->in, offset);
JSON_PUSH_STATUS_RET(jsonctx, LYJSON_NUMBER);
LY_CHECK_RET(lyjson_check_next(jsonctx));
return LY_SUCCESS;
}
static LY_ERR
lyjson_object_name(struct lyjson_ctx *jsonctx)
{
if (*jsonctx->in->current != '"') {
LOGVAL(jsonctx->ctx, LY_VLOG_LINE, &jsonctx->line, LY_VCODE_INSTREXP, LY_VCODE_INSTREXP_len(jsonctx->in->current),
jsonctx->in->current, "a JSON object's member");
return LY_EVALID;
}
ly_in_skip(jsonctx->in, 1);
LY_CHECK_RET(lyjson_string_(jsonctx));
LY_CHECK_RET(skip_ws(jsonctx));
if (*jsonctx->in->current != ':') {
LOGVAL(jsonctx->ctx, LY_VLOG_LINE, &jsonctx->line, LY_VCODE_INSTREXP,
LY_VCODE_INSTREXP_len(jsonctx->in->current), jsonctx->in->current, "a JSON object's name-separator ':'");
return LY_EVALID;
}
ly_in_skip(jsonctx->in, 1);
LY_CHECK_RET(skip_ws(jsonctx));
return LY_SUCCESS;
}
static LY_ERR
lyjson_object(struct lyjson_ctx *jsonctx)
{
LY_CHECK_RET(skip_ws(jsonctx));
if (*jsonctx->in->current == '}') {
/* empty object */
ly_in_skip(jsonctx->in, 1);
lyjson_ctx_set_value(jsonctx, NULL, 0, 0);
JSON_PUSH_STATUS_RET(jsonctx, LYJSON_OBJECT_EMPTY);
return LY_SUCCESS;
}
LY_CHECK_RET(lyjson_object_name(jsonctx));
/* output data are set by lyjson_string_() */
JSON_PUSH_STATUS_RET(jsonctx, LYJSON_OBJECT);
return LY_SUCCESS;
}
/*
* @brief Process JSON array envelope
*
*
*
* @param[in] jsonctx JSON parser context
* @return LY_SUCCESS or LY_EMEM
*/
static LY_ERR
lyjson_array(struct lyjson_ctx *jsonctx)
{
LY_CHECK_RET(skip_ws(jsonctx));
if (*jsonctx->in->current == ']') {
/* empty array */
ly_in_skip(jsonctx->in, 1);
JSON_PUSH_STATUS_RET(jsonctx, LYJSON_ARRAY_EMPTY);
} else {
JSON_PUSH_STATUS_RET(jsonctx, LYJSON_ARRAY);
}
/* erase previous values, array has no value on its own */
lyjson_ctx_set_value(jsonctx, NULL, 0, 0);
return LY_SUCCESS;
}
static LY_ERR
lyjson_value(struct lyjson_ctx *jsonctx)
{
if (jsonctx->status.count && (lyjson_ctx_status(jsonctx, 0) == LYJSON_END)) {
return LY_SUCCESS;
}
if ((*jsonctx->in->current == 'f') && !strncmp(jsonctx->in->current, "false", 5)) {
/* false */
lyjson_ctx_set_value(jsonctx, jsonctx->in->current, 5, 0);
ly_in_skip(jsonctx->in, 5);
JSON_PUSH_STATUS_RET(jsonctx, LYJSON_FALSE);
LY_CHECK_RET(lyjson_check_next(jsonctx));
} else if ((*jsonctx->in->current == 't') && !strncmp(jsonctx->in->current, "true", 4)) {
/* true */
lyjson_ctx_set_value(jsonctx, jsonctx->in->current, 4, 0);
ly_in_skip(jsonctx->in, 4);
JSON_PUSH_STATUS_RET(jsonctx, LYJSON_TRUE);
LY_CHECK_RET(lyjson_check_next(jsonctx));
} else if ((*jsonctx->in->current == 'n') && !strncmp(jsonctx->in->current, "null", 4)) {
/* none */
lyjson_ctx_set_value(jsonctx, jsonctx->in->current, 0, 0);
ly_in_skip(jsonctx->in, 4);
JSON_PUSH_STATUS_RET(jsonctx, LYJSON_NULL);
LY_CHECK_RET(lyjson_check_next(jsonctx));
} else if (*jsonctx->in->current == '"') {
/* string */
ly_in_skip(jsonctx->in, 1);
LY_CHECK_RET(lyjson_string(jsonctx));
} else if (*jsonctx->in->current == '[') {
/* array */
ly_in_skip(jsonctx->in, 1);
LY_CHECK_RET(lyjson_array(jsonctx));
} else if (*jsonctx->in->current == '{') {
/* object */
ly_in_skip(jsonctx->in, 1);
LY_CHECK_RET(lyjson_object(jsonctx));
} else if ((*jsonctx->in->current == '-') || ((*jsonctx->in->current >= '0') && (*jsonctx->in->current <= '9'))) {
/* number */
LY_CHECK_RET(lyjson_number(jsonctx));
} else {
/* unexpected value */
LOGVAL(jsonctx->ctx, LY_VLOG_LINE, &jsonctx->line, LY_VCODE_INSTREXP, LY_VCODE_INSTREXP_len(jsonctx->in->current),
jsonctx->in->current, "a JSON value");
return LY_EVALID;
}
return LY_SUCCESS;
}
LY_ERR
lyjson_ctx_new(const struct ly_ctx *ctx, struct ly_in *in, struct lyjson_ctx **jsonctx_p)
{
LY_ERR ret = LY_SUCCESS;
struct lyjson_ctx *jsonctx;
assert(ctx);
assert(in);
assert(jsonctx_p);
/* new context */
jsonctx = calloc(1, sizeof *jsonctx);
LY_CHECK_ERR_RET(!jsonctx, LOGMEM(ctx), LY_EMEM);
jsonctx->ctx = ctx;
jsonctx->line = 1;
jsonctx->in = in;
/* parse JSON value, if any */
LY_CHECK_GOTO(ret = skip_ws(jsonctx), cleanup);
if (lyjson_ctx_status(jsonctx, 0) == LYJSON_END) {
/* empty data input */
goto cleanup;
}
ret = lyjson_value(jsonctx);
if ((jsonctx->status.count > 1) && (lyjson_ctx_status(jsonctx, 0) == LYJSON_END)) {
LOGVAL(jsonctx->ctx, LY_VLOG_LINE, &jsonctx->line, LY_VCODE_EOF);
ret = LY_EVALID;
}
cleanup:
if (ret) {
lyjson_ctx_free(jsonctx);
} else {
*jsonctx_p = jsonctx;
}
return ret;
}
void
lyjson_ctx_backup(struct lyjson_ctx *jsonctx)
{
if (jsonctx->backup.dynamic) {
free((char *)jsonctx->backup.value);
}
jsonctx->backup.status = lyjson_ctx_status(jsonctx, 0);
jsonctx->backup.status_count = jsonctx->status.count;
jsonctx->backup.value = jsonctx->value;
jsonctx->backup.value_len = jsonctx->value_len;
jsonctx->backup.input = jsonctx->in->current;
jsonctx->backup.dynamic = jsonctx->dynamic;
jsonctx->dynamic = 0;
}
void
lyjson_ctx_restore(struct lyjson_ctx *jsonctx)
{
if (jsonctx->dynamic) {
free((char *)jsonctx->value);
}
jsonctx->status.count = jsonctx->backup.status_count;
jsonctx->status.objs[jsonctx->backup.status_count - 1] = (void *)jsonctx->backup.status;
jsonctx->value = jsonctx->backup.value;
jsonctx->value_len = jsonctx->backup.value_len;
jsonctx->in->current = jsonctx->backup.input;
jsonctx->dynamic = jsonctx->backup.dynamic;
jsonctx->backup.dynamic = 0;
}
LY_ERR
lyjson_ctx_next(struct lyjson_ctx *jsonctx, enum LYJSON_PARSER_STATUS *status)
{
LY_ERR ret = LY_SUCCESS;
ly_bool toplevel = 0;
enum LYJSON_PARSER_STATUS prev;
assert(jsonctx);
prev = lyjson_ctx_status(jsonctx, 0);
if ((prev == LYJSON_OBJECT) || (prev == LYJSON_ARRAY)) {
/* get value for the object's member OR the first value in the array */
ret = lyjson_value(jsonctx);
goto result;
} else {
/* the previous token is closed and should be completely processed */
JSON_POP_STATUS_RET(jsonctx);
prev = lyjson_ctx_status(jsonctx, 0);
}
if (!jsonctx->status.count) {
/* we are done with the top level value */
toplevel = 1;
}
LY_CHECK_RET(skip_ws(jsonctx));
if (toplevel && !jsonctx->status.count) {
/* EOF expected, but there are some data after the top level token */
LOGVAL(jsonctx->ctx, LY_VLOG_LINE, &jsonctx->line, LYVE_SYNTAX,
"Expecting end-of-input, but some data follows the top level JSON value.");
return LY_EVALID;
}
if (toplevel) {
/* we are done */
return LY_SUCCESS;
}
/* continue with the next token */
assert(prev == LYJSON_OBJECT || prev == LYJSON_ARRAY);
if (*jsonctx->in->current == ',') {
/* sibling item in the ... */
ly_in_skip(jsonctx->in, 1);
LY_CHECK_RET(skip_ws(jsonctx));
if (prev == LYJSON_OBJECT) {
/* ... object - get another object's member */
ret = lyjson_object_name(jsonctx);
} else { /* LYJSON_ARRAY */
/* ... array - get another complete value */
ret = lyjson_value(jsonctx);
}
} else if (((prev == LYJSON_OBJECT) && (*jsonctx->in->current == '}')) || ((prev == LYJSON_ARRAY) && (*jsonctx->in->current == ']'))) {
ly_in_skip(jsonctx->in, 1);
JSON_POP_STATUS_RET(jsonctx);
JSON_PUSH_STATUS_RET(jsonctx, prev + 1);
} else {
/* unexpected value */
LOGVAL(jsonctx->ctx, LY_VLOG_LINE, &jsonctx->line, LY_VCODE_INSTREXP, LY_VCODE_INSTREXP_len(jsonctx->in->current),
jsonctx->in->current, prev == LYJSON_ARRAY ? "another JSON value in array" : "another JSON object's member");
return LY_EVALID;
}
result:
if ((ret == LY_SUCCESS) && (jsonctx->status.count > 1) && (lyjson_ctx_status(jsonctx, 0) == LYJSON_END)) {
LOGVAL(jsonctx->ctx, LY_VLOG_LINE, &jsonctx->line, LY_VCODE_EOF);
ret = LY_EVALID;
}
if ((ret == LY_SUCCESS) && status) {
*status = lyjson_ctx_status(jsonctx, 0);
}
return ret;
}
enum LYJSON_PARSER_STATUS
lyjson_ctx_status(struct lyjson_ctx *jsonctx, uint32_t index)
{
assert(jsonctx);
if (jsonctx->status.count < index) {
return LYJSON_ERROR;
} else if (jsonctx->status.count == index) {
return LYJSON_ROOT;
} else {
return (enum LYJSON_PARSER_STATUS)(uintptr_t)jsonctx->status.objs[jsonctx->status.count - (index + 1)];
}
}
void
lyjson_ctx_free(struct lyjson_ctx *jsonctx)
{
if (!jsonctx) {
return;
}
if (jsonctx->dynamic) {
free((char *)jsonctx->value);
}
if (jsonctx->backup.dynamic) {
free((char *)jsonctx->backup.value);
}
ly_set_erase(&jsonctx->status, NULL);
free(jsonctx);
}