data FEATURE generic JSON format parser
diff --git a/CMakeLists.txt b/CMakeLists.txt
index d93921d..fba0fdb 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -155,6 +155,7 @@
     src/path.c
     src/diff.c
     src/context.c
+    src/json.c
     src/tree_data.c
     src/tree_data_free.c
     src/tree_data_helpers.c
diff --git a/src/common.c b/src/common.c
index b1ccd65..7a8d83a 100644
--- a/src/common.c
+++ b/src/common.c
@@ -265,6 +265,59 @@
     return LY_SUCCESS;
 }
 
+LY_ERR
+ly_pututf8(char *dst, uint32_t value, size_t *bytes_written)
+{
+    if (value < 0x80) {
+        /* one byte character */
+        if (value < 0x20 &&
+                value != 0x09 &&
+                value != 0x0a &&
+                value != 0x0d) {
+            return LY_EINVAL;
+        }
+
+        dst[0] = value;
+        (*bytes_written) = 1;
+    } else if (value < 0x800) {
+        /* two bytes character */
+        dst[0] = 0xc0 | (value >> 6);
+        dst[1] = 0x80 | (value & 0x3f);
+        (*bytes_written) = 2;
+    } else if (value < 0xfffe) {
+        /* three bytes character */
+        if (((value & 0xf800) == 0xd800) ||
+                (value >= 0xfdd0 && value <= 0xfdef)) {
+            /* exclude surrogate blocks %xD800-DFFF */
+            /* exclude noncharacters %xFDD0-FDEF */
+            return LY_EINVAL;
+        }
+
+        dst[0] = 0xe0 | (value >> 12);
+        dst[1] = 0x80 | ((value >> 6) & 0x3f);
+        dst[2] = 0x80 | (value & 0x3f);
+
+        (*bytes_written) = 3;
+    } else if (value < 0x10fffe) {
+        if ((value & 0xffe) == 0xffe) {
+            /* exclude noncharacters %xFFFE-FFFF, %x1FFFE-1FFFF, %x2FFFE-2FFFF, %x3FFFE-3FFFF, %x4FFFE-4FFFF,
+             * %x5FFFE-5FFFF, %x6FFFE-6FFFF, %x7FFFE-7FFFF, %x8FFFE-8FFFF, %x9FFFE-9FFFF, %xAFFFE-AFFFF,
+             * %xBFFFE-BFFFF, %xCFFFE-CFFFF, %xDFFFE-DFFFF, %xEFFFE-EFFFF, %xFFFFE-FFFFF, %x10FFFE-10FFFF */
+            return LY_EINVAL;
+        }
+        /* four bytes character */
+        dst[0] = 0xf0 | (value >> 18);
+        dst[1] = 0x80 | ((value >> 12) & 0x3f);
+        dst[2] = 0x80 | ((value >> 6) & 0x3f);
+        dst[3] = 0x80 | (value & 0x3f);
+
+        (*bytes_written) = 4;
+    } else {
+        return LY_EINVAL;
+    }
+    return LY_SUCCESS;
+}
+
 /**
  * @brief Static table of the UTF8 characters lengths according to their first byte.
  */
diff --git a/src/common.h b/src/common.h
index 4ff5220..20c95e1 100644
--- a/src/common.h
+++ b/src/common.h
@@ -342,6 +342,25 @@
 LY_ERR ly_getutf8(const char **input, uint32_t *utf8_char, size_t *bytes_read);
 
 /**
+ * Store UTF-8 character specified as 4byte integer into the dst buffer.
+ *
+ * UTF-8 mapping:
+ * 00000000 -- 0000007F:    0xxxxxxx
+ * 00000080 -- 000007FF:    110xxxxx 10xxxxxx
+ * 00000800 -- 0000FFFF:    1110xxxx 10xxxxxx 10xxxxxx
+ * 00010000 -- 001FFFFF:    11110xxx 10xxxxxx 10xxxxxx 10xxxxxx
+ *
+ * Includes checking for valid characters (following RFC 7950, sec 9.4)
+ *
+ * @param[in, out] dst Destination buffer to store the UTF-8 character, must provide enough space (up to 4 bytes) for storing the UTF-8 character.
+ * @param[in] value 32b value of the UTF-8 character to store.
+ * @param[out] bytes_written Number of bytes written into @p dst (size of the written UTF-8 character).
+ * @return LY_SUCCESS on success
+ * @return LY_EINVAL in case of invalid UTF-8 @p value to store.
+ */
+LY_ERR ly_pututf8(char *dst, uint32_t value, size_t *bytes_written);
+
+/**
  * @brief Get number of characters in the @p str, taking multibyte characters into account.
  * @param[in] str String to examine.
  * @param[in] bytes Number of valid bytes that are supposed to be taken into account in @p str.
diff --git a/src/json.c b/src/json.c
new file mode 100644
index 0000000..3a449e6
--- /dev/null
+++ b/src/json.c
@@ -0,0 +1,788 @@
+/**
+ * @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 <stdio.h>
+#include <stdlib.h>
+#include <sys/types.h>
+
+#include "common.h"
+#include "json.h"
+#include "parser_internal.h"
+
+#define JSON_PUSH_STATUS_RET(CTX, STATUS) \
+    LY_CHECK_ERR_RET(ly_set_add(&CTX->status, (void*)STATUS, LY_SET_OPT_USEASLIST) == -1, LOGMEM(CTX->ctx), LY_EMEM)
+
+#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, int 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;
+    int 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;
+        long int 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 */
+        for (unsigned int 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));
+    LY_CHECK_ERR_RET(
+            *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 ':'"),
+            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;
+    int 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)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);
+}
+
diff --git a/src/json.h b/src/json.h
new file mode 100644
index 0000000..8373284
--- /dev/null
+++ b/src/json.h
@@ -0,0 +1,127 @@
+/**
+ * @file json.h
+ * @author Radek Krejci <rkrejci@cesnet.cz>
+ * @brief Generic JSON format parser routines.
+ *
+ * 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
+ */
+
+#ifndef LY_JSON_H_
+#define LY_JSON_H_
+
+#include <stddef.h>
+#include <stdint.h>
+
+#include "log.h"
+#include "set.h"
+
+struct ly_ctx;
+struct ly_out;
+struct ly_prefix;
+
+/* Macro to test if character is whitespace */
+#define is_jsonws(c) (c == 0x20 || c == 0x9 || c == 0xa || c == 0xd)
+
+/* Macro to test if character is valid string character */
+#define is_jsonstrchar(c) (c == 0x20 || c == 0x21 || (c >= 0x23 && c <= 0x5b) || (c >= 0x5d && c <= 0x10ffff))
+
+/**
+ * @brief Status of the parser providing information what is expected next (which function is supposed to be called).
+ */
+enum LYJSON_PARSER_STATUS {
+    LYJSON_ERROR,          /* JSON parser error - value is used as an error return code */
+    LYJSON_ROOT,           /* JSON document root, used internally */
+    LYJSON_OBJECT,         /* JSON object */
+    LYJSON_OBJECT_CLOSED,  /* JSON object closed */
+    LYJSON_OBJECT_EMPTY,   /* empty JSON object { }*/
+    LYJSON_ARRAY,          /* JSON array */
+    LYJSON_ARRAY_CLOSED,   /* JSON array closed */
+    LYJSON_ARRAY_EMPTY,    /* empty JSON array */
+    LYJSON_NUMBER,         /* JSON number value */
+    LYJSON_STRING,         /* JSON string value */
+    LYJSON_FALSE,          /* JSON false value */
+    LYJSON_TRUE,           /* JSON true value */
+    LYJSON_NULL,           /* JSON null value */
+    LYJSON_END             /* end of input data */
+};
+
+struct lyjson_ctx {
+
+    struct ly_set status;   /* stack of LYJSON_PARSER_STATUS values corresponding to the JSON items being processed */
+
+    const char *value;      /* LYJSON_STRING, LYJSON_NUMBER, LYJSON_OBJECT */
+    size_t value_len;       /* LYJSON_STRING, LYJSON_NUMBER, LYJSON_OBJECT */
+    int dynamic;            /* LYJSON_STRING, LYJSON_NUMBER, LYJSON_OBJECT */
+
+    struct {
+        enum LYJSON_PARSER_STATUS status;
+        uint32_t status_count;
+        const char *value;
+        size_t value_len;
+        int dynamic;
+        const char *input;
+    } backup;
+};
+
+/**
+ * @brief Create a new JSON parser context and start parsing.
+ *
+ * @param[in] ctx libyang context.
+ * @param[in] in JSON string data to parse.
+ * @param[out] jsonctx New JSON context with status ::LYJSON_VALUE.
+ * @return LY_ERR value.
+ */
+LY_ERR lyjson_ctx_new(const struct ly_ctx *ctx,  struct ly_in *in, struct lyjson_ctx **jsonctx);
+
+/**
+ * @brief Get status of the parser as the last/previous parsed token
+ *
+ * @param[in] jsonctx JSON context to check.
+ * @param[in] index Index of the token, starting by 0 for the last token
+ * @return LYJSON_ERROR in case of invalid index, other LYJSON_PARSER_STATUS corresponding to the token.
+ */
+enum LYJSON_PARSER_STATUS lyjson_ctx_status(struct lyjson_ctx *jsonctx, uint32_t index);
+
+/**
+ * @brief Get string representation of the JSON context status (token).
+ *
+ * @param[in] status Context status (aka JSON token)
+ * @return String representation of the @p status.
+ */
+const char* lyjson_token2str(enum LYJSON_PARSER_STATUS status);
+
+/**
+ * @brief Move to the next JSON artefact and update parser status.
+ *
+ * @param[in] jsonctx XML context to move.
+ * @param[out] status Optional parameter to provide new parser status
+ * @return LY_ERR value.
+ */
+LY_ERR lyjson_ctx_next(struct lyjson_ctx *jsonctx, enum LYJSON_PARSER_STATUS *status);
+
+/**
+ * @brief Backup the JSON parser context's state To restore the backup, use lyjson_ctx_restore().
+ * @param[in] jsonctx JSON parser context to backup.
+ */
+void lyjson_ctx_backup(struct lyjson_ctx *jsonctx);
+
+/**
+ * @brief REstore the JSON parser context's state from the backup created by lyjson_ctx_backup().
+ * @param[in] jsonctx JSON parser context to restore.
+ */
+void lyjson_ctx_restore(struct lyjson_ctx *jsonctx);
+
+/**
+ * @brief Remove the allocated working memory of the context.
+ *
+ * @param[in] jsonctx JSON context to clear.
+ */
+void lyjson_ctx_free(struct lyjson_ctx *jsonctx);
+
+#endif /* LY_JSON_H_ */
diff --git a/src/xml.c b/src/xml.c
index 67819ee..d1ba184 100644
--- a/src/xml.c
+++ b/src/xml.c
@@ -325,71 +325,6 @@
 }
 
 /**
- * Store UTF-8 character specified as 4byte integer into the dst buffer.
- * Returns number of written bytes (4 max), expects that dst has enough space.
- *
- * UTF-8 mapping:
- * 00000000 -- 0000007F:    0xxxxxxx
- * 00000080 -- 000007FF:    110xxxxx 10xxxxxx
- * 00000800 -- 0000FFFF:    1110xxxx 10xxxxxx 10xxxxxx
- * 00010000 -- 001FFFFF:    11110xxx 10xxxxxx 10xxxxxx 10xxxxxx
- *
- * Includes checking for valid characters (following RFC 7950, sec 9.4)
- */
-static LY_ERR
-lyxml_pututf8(char *dst, uint32_t value, size_t *bytes_written)
-{
-    if (value < 0x80) {
-        /* one byte character */
-        if (value < 0x20 &&
-                value != 0x09 &&
-                value != 0x0a &&
-                value != 0x0d) {
-            return LY_EINVAL;
-        }
-
-        dst[0] = value;
-        (*bytes_written) = 1;
-    } else if (value < 0x800) {
-        /* two bytes character */
-        dst[0] = 0xc0 | (value >> 6);
-        dst[1] = 0x80 | (value & 0x3f);
-        (*bytes_written) = 2;
-    } else if (value < 0xfffe) {
-        /* three bytes character */
-        if (((value & 0xf800) == 0xd800) ||
-                (value >= 0xfdd0 && value <= 0xfdef)) {
-            /* exclude surrogate blocks %xD800-DFFF */
-            /* exclude noncharacters %xFDD0-FDEF */
-            return LY_EINVAL;
-        }
-
-        dst[0] = 0xe0 | (value >> 12);
-        dst[1] = 0x80 | ((value >> 6) & 0x3f);
-        dst[2] = 0x80 | (value & 0x3f);
-
-        (*bytes_written) = 3;
-    } else if (value < 0x10fffe) {
-        if ((value & 0xffe) == 0xffe) {
-            /* exclude noncharacters %xFFFE-FFFF, %x1FFFE-1FFFF, %x2FFFE-2FFFF, %x3FFFE-3FFFF, %x4FFFE-4FFFF,
-             * %x5FFFE-5FFFF, %x6FFFE-6FFFF, %x7FFFE-7FFFF, %x8FFFE-8FFFF, %x9FFFE-9FFFF, %xAFFFE-AFFFF,
-             * %xBFFFE-BFFFF, %xCFFFE-CFFFF, %xDFFFE-DFFFF, %xEFFFE-EFFFF, %xFFFFE-FFFFF, %x10FFFE-10FFFF */
-            return LY_EINVAL;
-        }
-        /* four bytes character */
-        dst[0] = 0xf0 | (value >> 18);
-        dst[1] = 0x80 | ((value >> 12) & 0x3f);
-        dst[2] = 0x80 | ((value >> 6) & 0x3f);
-        dst[3] = 0x80 | (value & 0x3f);
-
-        (*bytes_written) = 4;
-    } else {
-	    return LY_EINVAL;
-    }
-    return LY_SUCCESS;
-}
-
-/**
  * @brief Parse XML text content (value).
  *
  * @param[in] xmlctx XML context to use.
@@ -507,7 +442,7 @@
                                          LY_VCODE_INSTREXP_len(&in[offset]), &in[offset], ";"),
                                   error);
                 ++offset;
-                LY_CHECK_ERR_GOTO(lyxml_pututf8(&buf[len], n, &u),
+                LY_CHECK_ERR_GOTO(ly_pututf8(&buf[len], n, &u),
                                   LOGVAL(ctx, LY_VLOG_LINE, &xmlctx->line, LYVE_SYNTAX,
                                          "Invalid character reference \"%.*s\" (0x%08x).", 12, p, n),
                                   error);
diff --git a/tests/utests/CMakeLists.txt b/tests/utests/CMakeLists.txt
index 624cfdc..5180113 100644
--- a/tests/utests/CMakeLists.txt
+++ b/tests/utests/CMakeLists.txt
@@ -4,6 +4,7 @@
 ly_add_utest(NAME inout SOURCES test_inout.c)
 ly_add_utest(NAME context SOURCES test_context.c)
 ly_add_utest(NAME xml SOURCES test_xml.c)
+ly_add_utest(NAME json SOURCES test_json.c)
 ly_add_utest(NAME xpath SOURCES test_xpath.c)
 ly_add_utest(NAME yanglib SOURCES test_yanglib.c)
 ly_add_utest(NAME schema SOURCES schema/test_schema.c schema/test_schema_common.c schema/test_schema_stmts.c)
diff --git a/tests/utests/test_json.c b/tests/utests/test_json.c
new file mode 100644
index 0000000..0482fd8
--- /dev/null
+++ b/tests/utests/test_json.c
@@ -0,0 +1,579 @@
+/*
+ * @file json.c
+ * @author: Radek Krejci <rkrejci@cesnet.cz>
+ * @brief unit tests for a generic JSON parser
+ *
+ * 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
+ */
+
+#define _DEFAULT_SOURCE
+#define _GNU_SOURCE
+
+#include <stdarg.h>
+#include <stddef.h>
+#include <setjmp.h>
+#include <cmocka.h>
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+
+#include "json.h"
+#include "context.h"
+#include "parser_internal.h"
+
+void *testfunc = NULL;
+
+static int
+setup(void **state)
+{
+    if (ly_ctx_new(NULL, 0, (struct ly_ctx **)state)) {
+        return 1;
+    }
+
+    return 0;
+}
+
+static int
+teardown(void **state)
+{
+    ly_ctx_destroy(*state, NULL);
+    return 0;
+}
+
+#define BUFSIZE 1024
+char logbuf[BUFSIZE] = {0};
+
+/* set to 0 to printing error messages to stderr instead of checking them in code */
+#define ENABLE_LOGGER_CHECKING 1
+
+static void
+logger(LY_LOG_LEVEL level, const char *msg, const char *path)
+{
+    (void) level; /* unused */
+
+    if (path) {
+        snprintf(logbuf, BUFSIZE - 1, "%s %s", msg, path);
+    } else {
+        strncpy(logbuf, msg, BUFSIZE - 1);
+    }
+}
+
+static int
+logger_setup(void **state)
+{
+    (void) state; /* unused */
+#if ENABLE_LOGGER_CHECKING
+    ly_set_log_clb(logger, 1);
+#endif
+    return 0;
+}
+
+static int
+logger_teardown(void **state)
+{
+    (void) state; /* unused */
+#if ENABLE_LOGGER_CHECKING
+    if (testfunc) {
+        fprintf(stderr, "%s\n", logbuf);
+    }
+#endif
+    return 0;
+}
+
+void
+logbuf_clean(void)
+{
+    logbuf[0] = '\0';
+}
+
+#if ENABLE_LOGGER_CHECKING
+#   define logbuf_assert(str) assert_string_equal(logbuf, str)
+#else
+#   define logbuf_assert(str)
+#endif
+
+
+
+static void
+test_general(void **state)
+{
+    struct lyjson_ctx *jsonctx;
+    struct ly_in *in;
+    const char *str;
+
+    testfunc = test_general;
+
+    /* empty */
+    str = "";
+    assert_int_equal(LY_SUCCESS, ly_in_new_memory(str, &in));
+    assert_int_equal(LY_SUCCESS, lyjson_ctx_new(*state, in, &jsonctx));
+    assert_int_equal(LYJSON_END, lyjson_ctx_status(jsonctx, 0));
+    lyjson_ctx_free(jsonctx);
+
+    str = "  \n\t \n";
+    assert_non_null(ly_in_memory(in, str));
+    assert_int_equal(LY_SUCCESS, lyjson_ctx_new(*state, in, &jsonctx));
+    assert_int_equal(LYJSON_END, lyjson_ctx_status(jsonctx, 0));
+    lyjson_ctx_free(jsonctx);
+
+    /* constant values */
+    str = "true";
+    assert_non_null(ly_in_memory(in, str));
+    assert_int_equal(LY_SUCCESS, lyjson_ctx_new(*state, in, &jsonctx));
+    assert_int_equal(LYJSON_TRUE, lyjson_ctx_status(jsonctx, 0));
+    assert_int_equal(LY_SUCCESS, lyjson_ctx_next(jsonctx, NULL));
+    assert_int_equal(LYJSON_END, lyjson_ctx_status(jsonctx, 0));
+    lyjson_ctx_free(jsonctx);
+
+    str = "false";
+    assert_non_null(ly_in_memory(in, str));
+    assert_int_equal(LY_SUCCESS, lyjson_ctx_new(*state, in, &jsonctx));
+    assert_int_equal(LYJSON_FALSE, lyjson_ctx_status(jsonctx, 0));
+    assert_int_equal(LY_SUCCESS, lyjson_ctx_next(jsonctx, NULL));
+    assert_int_equal(LYJSON_END, lyjson_ctx_status(jsonctx, 0));
+    lyjson_ctx_free(jsonctx);
+
+    str = "null";
+    assert_non_null(ly_in_memory(in, str));
+    assert_int_equal(LY_SUCCESS, lyjson_ctx_new(*state, in, &jsonctx));
+    assert_int_equal(LYJSON_NULL, lyjson_ctx_status(jsonctx, 0));
+    assert_int_equal(LY_SUCCESS, lyjson_ctx_next(jsonctx, NULL));
+    assert_int_equal(LYJSON_END, lyjson_ctx_status(jsonctx, 0));
+    lyjson_ctx_free(jsonctx);
+
+    ly_in_free(in, 0);
+    testfunc = NULL;
+}
+
+static void
+test_number(void **state)
+{
+    struct lyjson_ctx *jsonctx;
+    struct ly_in *in;
+    const char *str;
+
+    testfunc = test_number;
+
+    /* simple value */
+    str = "11";
+    assert_int_equal(LY_SUCCESS, ly_in_new_memory(str, &in));
+    assert_int_equal(LY_SUCCESS, lyjson_ctx_new(*state, in, &jsonctx));
+    assert_int_equal(LYJSON_NUMBER, lyjson_ctx_status(jsonctx, 0));
+    assert_string_equal("11", jsonctx->value);
+    assert_int_equal(2, jsonctx->value_len);
+    assert_int_equal(0, jsonctx->dynamic);
+    lyjson_ctx_free(jsonctx);
+
+    /* fraction number */
+    str = "37.7668";
+    assert_non_null(ly_in_memory(in, str));
+    assert_int_equal(LY_SUCCESS, lyjson_ctx_new(*state, in, &jsonctx));
+    assert_int_equal(LYJSON_NUMBER, lyjson_ctx_status(jsonctx, 0));
+    assert_string_equal("37.7668", jsonctx->value);
+    assert_int_equal(7, jsonctx->value_len);
+    assert_int_equal(0, jsonctx->dynamic);
+    lyjson_ctx_free(jsonctx);
+
+    /* negative number */
+    str = "-122.3959";
+    assert_non_null(ly_in_memory(in, str));
+    assert_int_equal(LY_SUCCESS, lyjson_ctx_new(*state, in, &jsonctx));
+    assert_int_equal(LYJSON_NUMBER, lyjson_ctx_status(jsonctx, 0));
+    assert_string_equal("-122.3959", jsonctx->value);
+    assert_int_equal(9, jsonctx->value_len);
+    assert_int_equal(0, jsonctx->dynamic);
+    lyjson_ctx_free(jsonctx);
+
+    /* exp number */
+    str = "1E10";
+    assert_non_null(ly_in_memory(in, str));
+    assert_int_equal(LY_SUCCESS, lyjson_ctx_new(*state, in, &jsonctx));
+    assert_int_equal(LYJSON_NUMBER, lyjson_ctx_status(jsonctx, 0));
+    assert_string_equal("10000000000", jsonctx->value);
+    assert_int_equal(11, jsonctx->value_len);
+    assert_int_equal(1, jsonctx->dynamic);
+    lyjson_ctx_free(jsonctx);
+
+    str = "15E-1";
+    assert_non_null(ly_in_memory(in, str));
+    assert_int_equal(LY_SUCCESS, lyjson_ctx_new(*state, in, &jsonctx));
+    assert_int_equal(LYJSON_NUMBER, lyjson_ctx_status(jsonctx, 0));
+    assert_string_equal("1.5", jsonctx->value);
+    assert_int_equal(3, jsonctx->value_len);
+    assert_int_equal(1, jsonctx->dynamic);
+    lyjson_ctx_free(jsonctx);
+
+    str = "15E-3";
+    assert_non_null(ly_in_memory(in, str));
+    assert_int_equal(LY_SUCCESS, lyjson_ctx_new(*state, in, &jsonctx));
+    assert_int_equal(LYJSON_NUMBER, lyjson_ctx_status(jsonctx, 0));
+    assert_string_equal("0.015", jsonctx->value);
+    assert_int_equal(5, jsonctx->value_len);
+    assert_int_equal(1, jsonctx->dynamic);
+    lyjson_ctx_free(jsonctx);
+
+    /* exp fraction number */
+    str = "1.1e3";
+    assert_non_null(ly_in_memory(in, str));
+    assert_int_equal(LY_SUCCESS, lyjson_ctx_new(*state, in, &jsonctx));
+    assert_int_equal(LYJSON_NUMBER, lyjson_ctx_status(jsonctx, 0));
+    assert_string_equal("1100", jsonctx->value);
+    assert_int_equal(4, jsonctx->value_len);
+    assert_int_equal(1, jsonctx->dynamic);
+    lyjson_ctx_free(jsonctx);
+
+    /* negative exp fraction number */
+    str = "1.1e-3";
+    assert_non_null(ly_in_memory(in, str));
+    assert_int_equal(LY_SUCCESS, lyjson_ctx_new(*state, in, &jsonctx));
+    assert_int_equal(LYJSON_NUMBER, lyjson_ctx_status(jsonctx, 0));
+    assert_string_equal("0.0011", jsonctx->value);
+    assert_int_equal(6, jsonctx->value_len);
+    assert_int_equal(1, jsonctx->dynamic);
+    lyjson_ctx_free(jsonctx);
+
+    /* exp negative fraction number */
+    str = "-0.11e3";
+    assert_non_null(ly_in_memory(in, str));
+    assert_int_equal(LY_SUCCESS, lyjson_ctx_new(*state, in, &jsonctx));
+    assert_int_equal(LYJSON_NUMBER, lyjson_ctx_status(jsonctx, 0));
+    assert_string_equal("-110", jsonctx->value);
+    assert_int_equal(4, jsonctx->value_len);
+    assert_int_equal(1, jsonctx->dynamic);
+    lyjson_ctx_free(jsonctx);
+
+    /* negative exp negative fraction number */
+    str = "-3.14e-3";
+    assert_non_null(ly_in_memory(in, str));
+    assert_int_equal(LY_SUCCESS, lyjson_ctx_new(*state, in, &jsonctx));
+    assert_int_equal(LYJSON_NUMBER, lyjson_ctx_status(jsonctx, 0));
+    assert_string_equal("-0.00314", jsonctx->value);
+    assert_int_equal(8, jsonctx->value_len);
+    assert_int_equal(1, jsonctx->dynamic);
+    lyjson_ctx_free(jsonctx);
+
+    /* various invalid inputs */
+    str = "-x";
+    assert_non_null(ly_in_memory(in, str));
+    assert_int_equal(LY_EVALID, lyjson_ctx_new(*state, in, &jsonctx));
+    logbuf_assert("Invalid character in JSON Number value (\"x\"). Line number 1.");
+
+    str = "  -";
+    assert_non_null(ly_in_memory(in, str));
+    assert_int_equal(LY_EVALID, lyjson_ctx_new(*state, in, &jsonctx));
+    logbuf_assert("Unexpected end-of-input. Line number 1.");
+
+    str = "--1";
+    assert_non_null(ly_in_memory(in, str));
+    assert_int_equal(LY_EVALID, lyjson_ctx_new(*state, in, &jsonctx));
+    logbuf_assert("Invalid character in JSON Number value (\"-\"). Line number 1.");
+
+    str = "+1";
+    assert_non_null(ly_in_memory(in, str));
+    assert_int_equal(LY_EVALID, lyjson_ctx_new(*state, in, &jsonctx));
+    logbuf_assert("Invalid character sequence \"+1\", expected a JSON value. Line number 1.");
+
+    str = "  1.x ";
+    assert_non_null(ly_in_memory(in, str));
+    assert_int_equal(LY_EVALID, lyjson_ctx_new(*state, in, &jsonctx));
+    logbuf_assert("Invalid character in JSON Number value (\"x\"). Line number 1.");
+
+    str = "1.";
+    assert_non_null(ly_in_memory(in, str));
+    assert_int_equal(LY_EVALID, lyjson_ctx_new(*state, in, &jsonctx));
+    logbuf_assert("Unexpected end-of-input. Line number 1.");
+
+    str = "  1eo ";
+    assert_non_null(ly_in_memory(in, str));
+    assert_int_equal(LY_EVALID, lyjson_ctx_new(*state, in, &jsonctx));
+    logbuf_assert("Invalid character in JSON Number value (\"o\"). Line number 1.");
+
+    str = "1e";
+    assert_non_null(ly_in_memory(in, str));
+    assert_int_equal(LY_EVALID, lyjson_ctx_new(*state, in, &jsonctx));
+    logbuf_assert("Unexpected end-of-input. Line number 1.");
+
+    ly_in_free(in, 0);
+    testfunc = NULL;
+}
+
+static void
+test_string(void **state)
+{
+    struct lyjson_ctx *jsonctx;
+    struct ly_in *in;
+    const char *str;
+
+    testfunc = test_string;
+
+    /* simple string */
+    str = "\"hello\"";
+    assert_int_equal(LY_SUCCESS, ly_in_new_memory(str, &in));
+    assert_int_equal(LY_SUCCESS, lyjson_ctx_new(*state, in, &jsonctx));
+    assert_int_equal(LYJSON_STRING, lyjson_ctx_status(jsonctx, 0));
+    assert_ptr_equal(&str[1], jsonctx->value);
+    assert_int_equal(5, jsonctx->value_len);
+    assert_int_equal(0, jsonctx->dynamic);
+    lyjson_ctx_free(jsonctx);
+
+    /* 4-byte utf8 character */
+    str = "\"\\t𠜎\"";
+    assert_non_null(ly_in_memory(in, str));
+    assert_int_equal(LY_SUCCESS, lyjson_ctx_new(*state, in, &jsonctx));
+    assert_int_equal(LYJSON_STRING, lyjson_ctx_status(jsonctx, 0));
+    assert_string_equal("\t𠜎", jsonctx->value);
+    assert_int_equal(5, jsonctx->value_len);
+    assert_int_equal(1, jsonctx->dynamic);
+    lyjson_ctx_free(jsonctx);
+
+    /* valid escape sequences - note that here it mixes valid JSON string characters (RFC 7159, sec. 7) and
+     * valid characters in YANG string type (RFC 7950, sec. 9.4). Since the latter is a subset of JSON string,
+     * the YANG string type's restrictions apply to the JSON escape sequences */
+    str = "\"\\\" \\\\ \\r \\/ \\n \\t \\u20ac\"";
+    assert_non_null(ly_in_memory(in, str));
+    assert_int_equal(LY_SUCCESS, lyjson_ctx_new(*state, in, &jsonctx));
+    assert_int_equal(LYJSON_STRING, lyjson_ctx_status(jsonctx, 0));
+    assert_string_equal("\" \\ \r / \n \t €", jsonctx->value);
+    assert_int_equal(15, jsonctx->value_len);
+    assert_int_equal(1, jsonctx->dynamic);
+    lyjson_ctx_free(jsonctx);
+
+    /* backspace and form feed are valid JSON escape sequences, but the control characters they represents are not allowed values for YANG string type */
+    str = "\"\\b\"";
+    assert_non_null(ly_in_memory(in, str));
+    assert_int_equal(LY_EVALID, lyjson_ctx_new(*state, in, &jsonctx));
+    logbuf_assert("Invalid character reference \"\\b\" (0x00000008). Line number 1.");
+
+    str = "\"\\f\"";
+    assert_non_null(ly_in_memory(in, str));
+    assert_int_equal(LY_EVALID, lyjson_ctx_new(*state, in, &jsonctx));
+    logbuf_assert("Invalid character reference \"\\f\" (0x0000000c). Line number 1.");
+
+    /* unterminated string */
+    str = "\"unterminated string";
+    assert_non_null(ly_in_memory(in, str));
+    assert_int_equal(LY_EVALID, lyjson_ctx_new(*state, in, &jsonctx));
+    logbuf_assert("Missing quotation-mark at the end of a JSON string. Line number 1.");
+
+    /* invalid escape sequence */
+    str = "\"char \\x \"";
+    assert_non_null(ly_in_memory(in, str));
+    assert_int_equal(LY_EVALID, lyjson_ctx_new(*state, in, &jsonctx));
+    logbuf_assert("Invalid character escape sequence \\x. Line number 1.");
+
+    /* new line is allowed only as escaped character in JSON */
+    str = "\"\n\"";
+    assert_non_null(ly_in_memory(in, str));
+    assert_int_equal(LY_EVALID, lyjson_ctx_new(*state, in, &jsonctx));
+    logbuf_assert("Invalid character in JSON string \"\n\" (0x0000000a). Line number 1.");
+
+    ly_in_free(in, 0);
+    testfunc = NULL;
+}
+
+static void
+test_object(void **state)
+{
+    struct lyjson_ctx *jsonctx;
+    struct ly_in *in;
+    const char *str;
+
+    testfunc = test_object;
+
+    /* empty */
+    str = "  { }  ";
+    assert_int_equal(LY_SUCCESS, ly_in_new_memory(str, &in));
+    assert_int_equal(LY_SUCCESS, lyjson_ctx_new(*state, in, &jsonctx));
+    assert_int_equal(LYJSON_OBJECT_EMPTY, lyjson_ctx_status(jsonctx, 0));
+    assert_int_equal(LY_SUCCESS, lyjson_ctx_next(jsonctx, NULL));
+    assert_int_equal(LYJSON_END, lyjson_ctx_status(jsonctx, 0));
+    lyjson_ctx_free(jsonctx);
+
+    /* simple value */
+    str = "{\"name\" : \"Radek\"}";
+    assert_non_null(ly_in_memory(in, str));
+    assert_int_equal(LY_SUCCESS, lyjson_ctx_new(*state, in, &jsonctx));
+    assert_int_equal(LYJSON_OBJECT, lyjson_ctx_status(jsonctx, 0));
+    assert_ptr_equal(&str[2], jsonctx->value);
+    assert_int_equal(4, jsonctx->value_len);
+    assert_int_equal(0, jsonctx->dynamic);
+    assert_string_equal("\"Radek\"}", jsonctx->in->current);
+    assert_int_equal(LY_SUCCESS, lyjson_ctx_next(jsonctx, NULL));
+    assert_int_equal(LYJSON_STRING, lyjson_ctx_status(jsonctx, 0));
+    assert_string_equal("Radek\"}", jsonctx->value);
+    assert_int_equal(5, jsonctx->value_len);
+    assert_int_equal(0, jsonctx->dynamic);
+    assert_string_equal("}", jsonctx->in->current);
+    assert_int_equal(LY_SUCCESS, lyjson_ctx_next(jsonctx, NULL));
+    assert_int_equal(LYJSON_OBJECT_CLOSED, lyjson_ctx_status(jsonctx, 0));
+    assert_int_equal(LY_SUCCESS, lyjson_ctx_next(jsonctx, NULL));
+    assert_int_equal(LYJSON_END, lyjson_ctx_status(jsonctx, 0));
+    lyjson_ctx_free(jsonctx);
+
+    /* two values */
+    str = "{\"smart\" : true,\"handsom\":false}";
+    assert_non_null(ly_in_memory(in, str));
+    assert_int_equal(LY_SUCCESS, lyjson_ctx_new(*state, in, &jsonctx));
+    assert_int_equal(LYJSON_OBJECT, lyjson_ctx_status(jsonctx, 0));
+    assert_string_equal("smart\" : true,\"handsom\":false}", jsonctx->value);
+    assert_int_equal(5, jsonctx->value_len);
+    assert_int_equal(0, jsonctx->dynamic);
+    assert_string_equal("true,\"handsom\":false}", jsonctx->in->current);
+    assert_int_equal(LY_SUCCESS, lyjson_ctx_next(jsonctx, NULL));
+    assert_int_equal(LYJSON_TRUE, lyjson_ctx_status(jsonctx, 0));
+    assert_string_equal(",\"handsom\":false}", jsonctx->in->current);
+    assert_int_equal(LY_SUCCESS, lyjson_ctx_next(jsonctx, NULL));
+    assert_int_equal(LYJSON_OBJECT, lyjson_ctx_status(jsonctx, 0));
+    assert_string_equal("handsom\":false}", jsonctx->value);
+    assert_int_equal(7, jsonctx->value_len);
+    assert_int_equal(0, jsonctx->dynamic);
+    assert_string_equal("false}", jsonctx->in->current);
+    assert_int_equal(LY_SUCCESS, lyjson_ctx_next(jsonctx, NULL));
+    assert_int_equal(LYJSON_FALSE, lyjson_ctx_status(jsonctx, 0));
+    assert_string_equal("}", jsonctx->in->current);
+    assert_int_equal(LY_SUCCESS, lyjson_ctx_next(jsonctx, NULL));
+    assert_int_equal(LYJSON_OBJECT_CLOSED, lyjson_ctx_status(jsonctx, 0));
+    assert_int_equal(LY_SUCCESS, lyjson_ctx_next(jsonctx, NULL));
+    assert_int_equal(LYJSON_END, lyjson_ctx_status(jsonctx, 0));
+    lyjson_ctx_free(jsonctx);
+
+    /* inherited objects */
+    str = "{\"person\" : {\"name\":\"Radek\"}}";
+    assert_non_null(ly_in_memory(in, str));
+    assert_int_equal(LY_SUCCESS, lyjson_ctx_new(*state, in, &jsonctx));
+    assert_int_equal(LYJSON_OBJECT, lyjson_ctx_status(jsonctx, 0));
+    assert_string_equal("person\" : {\"name\":\"Radek\"}}", jsonctx->value);
+    assert_int_equal(6, jsonctx->value_len);
+    assert_int_equal(0, jsonctx->dynamic);
+    assert_string_equal("{\"name\":\"Radek\"}}", jsonctx->in->current);
+    assert_int_equal(LY_SUCCESS, lyjson_ctx_next(jsonctx, NULL));
+    assert_int_equal(LYJSON_OBJECT, lyjson_ctx_status(jsonctx, 0));
+    assert_string_equal("name\":\"Radek\"}}", jsonctx->value);
+    assert_int_equal(4, jsonctx->value_len);
+    assert_int_equal(0, jsonctx->dynamic);
+    assert_string_equal("\"Radek\"}}", jsonctx->in->current);
+    assert_int_equal(LY_SUCCESS, lyjson_ctx_next(jsonctx, NULL));
+    assert_int_equal(LYJSON_STRING, lyjson_ctx_status(jsonctx, 0));
+    assert_string_equal("Radek\"}}", jsonctx->value);
+    assert_int_equal(5, jsonctx->value_len);
+    assert_int_equal(0, jsonctx->dynamic);
+    assert_string_equal("}}", jsonctx->in->current);
+    assert_int_equal(LY_SUCCESS, lyjson_ctx_next(jsonctx, NULL));
+    assert_int_equal(LYJSON_OBJECT_CLOSED, lyjson_ctx_status(jsonctx, 0));
+    assert_string_equal("}", jsonctx->in->current);
+    assert_int_equal(LY_SUCCESS, lyjson_ctx_next(jsonctx, NULL));
+    assert_int_equal(LYJSON_OBJECT_CLOSED, lyjson_ctx_status(jsonctx, 0));
+    assert_int_equal(LY_SUCCESS, lyjson_ctx_next(jsonctx, NULL));
+    assert_int_equal(LYJSON_END, lyjson_ctx_status(jsonctx, 0));
+    lyjson_ctx_free(jsonctx);
+
+    /* new line is allowed only as escaped character in JSON */
+    str = "{ unquoted : \"data\"}";
+    assert_non_null(ly_in_memory(in, str));
+    assert_int_equal(LY_EVALID, lyjson_ctx_new(*state, in, &jsonctx));
+    logbuf_assert("Invalid character sequence \"unquoted : \"data\"}\", expected a JSON object's member. Line number 1.");
+
+    ly_in_free(in, 0);
+    testfunc = NULL;
+}
+
+static void
+test_array(void **state)
+{
+    struct lyjson_ctx *jsonctx;
+    struct ly_in *in;
+    const char *str;
+
+    testfunc = test_array;
+
+    /* empty */
+    str = "  [  ]  ";
+    assert_int_equal(LY_SUCCESS, ly_in_new_memory(str, &in));
+    assert_int_equal(LY_SUCCESS, lyjson_ctx_new(*state, in, &jsonctx));
+    assert_int_equal(LYJSON_ARRAY_EMPTY, lyjson_ctx_status(jsonctx, 0));
+    assert_int_equal(LY_SUCCESS, lyjson_ctx_next(jsonctx, NULL));
+    assert_int_equal(LYJSON_END, lyjson_ctx_status(jsonctx, 0));
+    lyjson_ctx_free(jsonctx);
+
+    /* simple value */
+    str = "[ null]";
+    assert_non_null(ly_in_memory(in, str));
+    assert_int_equal(LY_SUCCESS, lyjson_ctx_new(*state, in, &jsonctx));
+    assert_int_equal(LYJSON_ARRAY, lyjson_ctx_status(jsonctx, 0));
+    assert_null(jsonctx->value);
+    assert_int_equal(0, jsonctx->value_len);
+    assert_int_equal(0, jsonctx->dynamic);
+    assert_string_equal("null]", jsonctx->in->current);
+    assert_int_equal(LY_SUCCESS, lyjson_ctx_next(jsonctx, NULL));
+    assert_int_equal(LYJSON_NULL, lyjson_ctx_status(jsonctx, 0));
+    assert_string_equal("]", jsonctx->in->current);
+    assert_int_equal(LY_SUCCESS, lyjson_ctx_next(jsonctx, NULL));
+    assert_int_equal(LYJSON_ARRAY_CLOSED, lyjson_ctx_status(jsonctx, 0));
+    assert_int_equal(LY_SUCCESS, lyjson_ctx_next(jsonctx, NULL));
+    assert_int_equal(LYJSON_END, lyjson_ctx_status(jsonctx, 0));
+    lyjson_ctx_free(jsonctx);
+
+    /* two values */
+    str = "[{\"a\":null},\"x\"]";
+    assert_non_null(ly_in_memory(in, str));
+    assert_int_equal(LY_SUCCESS, lyjson_ctx_new(*state, in, &jsonctx));
+    assert_int_equal(LYJSON_ARRAY, lyjson_ctx_status(jsonctx, 0));
+    assert_null(jsonctx->value);
+    assert_int_equal(0, jsonctx->value_len);
+    assert_int_equal(0, jsonctx->dynamic);
+    assert_string_equal("{\"a\":null},\"x\"]", jsonctx->in->current);
+    assert_int_equal(LY_SUCCESS, lyjson_ctx_next(jsonctx, NULL));
+    assert_int_equal(LYJSON_OBJECT, lyjson_ctx_status(jsonctx, 0));
+    assert_string_equal("a\":null},\"x\"]", jsonctx->value);
+    assert_int_equal(1, jsonctx->value_len);
+    assert_int_equal(0, jsonctx->dynamic);
+    assert_string_equal("null},\"x\"]", jsonctx->in->current);
+    assert_int_equal(LY_SUCCESS, lyjson_ctx_next(jsonctx, NULL));
+    assert_int_equal(LYJSON_NULL, lyjson_ctx_status(jsonctx, 0));
+    assert_string_equal("},\"x\"]", jsonctx->in->current);
+    assert_int_equal(LY_SUCCESS, lyjson_ctx_next(jsonctx, NULL));
+    assert_int_equal(LYJSON_OBJECT_CLOSED, lyjson_ctx_status(jsonctx, 0));
+    assert_string_equal(",\"x\"]", jsonctx->in->current);
+    assert_int_equal(LY_SUCCESS, lyjson_ctx_next(jsonctx, NULL));
+    assert_int_equal(LYJSON_STRING, lyjson_ctx_status(jsonctx, 0));
+    assert_string_equal("x\"]", jsonctx->value);
+    assert_int_equal(1, jsonctx->value_len);
+    assert_int_equal(0, jsonctx->dynamic);
+    assert_string_equal("]", jsonctx->in->current);
+    assert_int_equal(LY_SUCCESS, lyjson_ctx_next(jsonctx, NULL));
+    assert_int_equal(LYJSON_ARRAY_CLOSED, lyjson_ctx_status(jsonctx, 0));
+    assert_int_equal(LY_SUCCESS, lyjson_ctx_next(jsonctx, NULL));
+    assert_int_equal(LYJSON_END, lyjson_ctx_status(jsonctx, 0));
+    lyjson_ctx_free(jsonctx);
+
+    /* new line is allowed only as escaped character in JSON */
+    str = "[ , null]";
+    assert_non_null(ly_in_memory(in, str));
+    assert_int_equal(LY_SUCCESS, lyjson_ctx_new(*state, in, &jsonctx));
+    assert_int_equal(LY_EVALID, lyjson_ctx_next(jsonctx, NULL));
+    logbuf_assert("Invalid character sequence \", null]\", expected a JSON value. Line number 1.");
+    lyjson_ctx_free(jsonctx);
+
+    ly_in_free(in, 0);
+    testfunc = NULL;
+}
+
+int main(void)
+{
+    const struct CMUnitTest tests[] = {
+        cmocka_unit_test_setup_teardown(test_general, logger_setup, logger_teardown),
+        cmocka_unit_test_setup_teardown(test_number, logger_setup, logger_teardown),
+        cmocka_unit_test_setup_teardown(test_string, logger_setup, logger_teardown),
+        cmocka_unit_test_setup_teardown(test_object, logger_setup, logger_teardown),
+        cmocka_unit_test_setup_teardown(test_array, logger_setup, logger_teardown),
+    };
+
+    return cmocka_run_group_tests(tests, setup, teardown);
+}