]> git.kaiwu.me - nginx.git/commitdiff
Core: added json parser
authorRoman Semenov <r.semenov@f5.com>
Wed, 12 Aug 2026 16:40:34 +0000 (09:40 -0700)
committerRoman S <141866642+roman-f5@users.noreply.github.com>
Mon, 24 Aug 2026 18:08:31 +0000 (11:08 -0700)
An event-driven JSON parser implementing the grammar of RFC 8259.
The full grammar is recognized: objects, arrays, strings with \uXXXX
escapes (including UTF-16 surrogate pairs), numbers, and the
true/false/null literals.  Tokens are reported to a caller-supplied
handler, with nesting bounded by a configurable maximum depth.

Based on previous work by Maxim Dounin.

Co-authored-by: Dmitry Volyntsev <d.volyntsev@f5.com>
auto/sources
src/core/ngx_json_parse.c [new file with mode: 0644]
src/core/ngx_json_parse.h [new file with mode: 0644]

index 022e0087059e286c81da9217290623b7e6148f93..e5f1b44e496284b323c7fe2654cc43160917d5b3 100644 (file)
@@ -43,6 +43,7 @@ CORE_DEPS="src/core/nginx.h \
            src/core/ngx_crypt.h \
            src/core/ngx_data.h \
            src/core/ngx_json.h \
+           src/core/ngx_json_parse.h \
            src/core/ngx_json_unescape.h \
            src/core/ngx_proxy_protocol.h \
            src/core/ngx_syslog.h"
@@ -84,6 +85,7 @@ CORE_SRCS="src/core/nginx.c \
            src/core/ngx_crypt.c \
            src/core/ngx_data.c \
            src/core/ngx_json.c \
+           src/core/ngx_json_parse.c \
            src/core/ngx_json_unescape.c \
            src/core/ngx_proxy_protocol.c \
            src/core/ngx_syslog.c"
diff --git a/src/core/ngx_json_parse.c b/src/core/ngx_json_parse.c
new file mode 100644 (file)
index 0000000..9295380
--- /dev/null
@@ -0,0 +1,831 @@
+
+/*
+ * Copyright (C) Maxim Dounin
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+/*
+ * JSON parser. RFC 8259 for the JSON grammar.
+
+ * Site http://json.org/ may be useful too.
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_json_parse.h>
+
+
+#define NGX_JSON_NOT_SKIPPING  (ngx_uint_t) -1
+#define ngx_json_skipping(d)   ((d) != NGX_JSON_NOT_SKIPPING)
+#define ngx_json_ws(c)  ((c) == ' ' || (c) == '\t' || (c) == CR || (c) == LF)
+
+
+static ngx_inline ngx_int_t ngx_json_end_number(ngx_json_ctx_t *ctx,
+    u_char *start, u_char *end, ngx_json_state_e *state);
+static ngx_inline ngx_int_t ngx_json_emit(ngx_json_ctx_t *ctx,
+    ngx_json_event_e event, u_char *data, size_t len);
+static ngx_inline ngx_json_state_e ngx_json_after_value(ngx_json_ctx_t *ctx);
+static ngx_inline ngx_int_t ngx_json_close(ngx_json_ctx_t *ctx,
+    ngx_json_event_e event, ngx_json_state_e *state, u_char *p);
+static ngx_int_t ngx_json_push_state(ngx_json_ctx_t *ctx,
+    ngx_json_container_e container);
+
+
+void
+ngx_json_ctx_init(ngx_json_ctx_t *ctx, ngx_pool_t *pool)
+{
+    ctx->state = ngx_json_start;
+    ctx->pool = pool;
+    ctx->handler = NULL;
+    ctx->data = NULL;
+    ctx->codepoint = 0;
+    ctx->hex_left = 0;
+
+    ctx->depth = 0;
+    ctx->skip_until_depth = NGX_JSON_NOT_SKIPPING;
+    ctx->max_depth = NGX_JSON_DEFAULT_MAX_DEPTH;
+
+    /* stack is allocated lazily on first push, sized to max_depth */
+
+    ctx->stack = NULL;
+
+    ctx->in_key = 0;
+}
+
+
+ngx_int_t
+ngx_json_parse_ctx(ngx_json_ctx_t *ctx, u_char *data, size_t len)
+{
+    u_char            *p, *last, *start;
+    ngx_int_t          rc, hd;
+    ngx_uint_t         skip_depth;
+    ngx_json_state_e   state;
+
+    if (len == 0) {
+        return NGX_DECLINED;
+    }
+
+    if (data == NULL) {
+        return NGX_ERROR;
+    }
+
+    if (ctx->state == ngx_json_done) {
+        return NGX_DECLINED;
+    }
+
+    last = data + len;
+    state = ctx->state;
+    skip_depth = ctx->skip_until_depth;
+    start = NULL;
+
+    for (p = data; p < last; p++) {
+
+        /* skipping suppresses emission only; the machine still validates */
+
+        if (ngx_json_skipping(skip_depth) && ctx->depth <= skip_depth
+            && (state == ngx_json_object_value_separator
+                || state == ngx_json_array_value_separator
+                || state == ngx_json_done))
+        {
+            skip_depth = NGX_JSON_NOT_SKIPPING;
+            ctx->skip_until_depth = NGX_JSON_NOT_SKIPPING;
+            p--;
+
+            continue;
+        }
+
+        switch (state) {
+
+        case ngx_json_start:
+            if (ngx_json_ws(*p)) {
+                break;
+            }
+
+            if (*p == '{') {
+                rc = ngx_json_push_state(ctx, ngx_json_ctx_object);
+                if (rc != NGX_OK) {
+                    return rc;
+                }
+
+                if (ngx_json_emit(ctx, NGX_JSON_OBJECT_OPEN, p, 1) != NGX_OK) {
+                    return NGX_ERROR;
+                }
+
+                state = ngx_json_object;
+                break;
+            }
+
+            if (*p == '[') {
+                rc = ngx_json_push_state(ctx, ngx_json_ctx_array);
+                if (rc != NGX_OK) {
+                    return rc;
+                }
+
+                if (ngx_json_emit(ctx, NGX_JSON_ARRAY_OPEN, p, 1) != NGX_OK) {
+                    return NGX_ERROR;
+                }
+
+                state = ngx_json_array;
+                break;
+            }
+
+            return NGX_DECLINED;
+
+        case ngx_json_object:
+            if (ngx_json_ws(*p)) {
+                break;
+            }
+
+            if (*p == '}') {
+                rc = ngx_json_close(ctx, NGX_JSON_OBJECT_CLOSE, &state, p);
+                if (rc != NGX_OK) {
+                    return rc;
+                }
+
+                break;
+            }
+
+            /* fall through */
+
+        case ngx_json_object_next:
+            if (ngx_json_ws(*p)) {
+                break;
+            }
+
+            if (*p == '"') {
+                ctx->in_key = 1;
+                start = p + 1;
+                state = ngx_json_string;
+                break;
+            }
+
+            return NGX_DECLINED;
+
+        case ngx_json_object_name_separator:
+            if (ngx_json_ws(*p)) {
+                break;
+            }
+
+            if (*p == ':') {
+                state = ngx_json_value;
+                break;
+            }
+
+            return NGX_DECLINED;
+
+        case ngx_json_object_value_separator:
+            if (ngx_json_ws(*p)) {
+                break;
+            }
+
+            if (*p == ',') {
+                state = ngx_json_object_next;
+                break;
+            }
+
+            if (*p == '}') {
+                rc = ngx_json_close(ctx, NGX_JSON_OBJECT_CLOSE, &state, p);
+                if (rc != NGX_OK) {
+                    return rc;
+                }
+
+                break;
+            }
+
+            return NGX_DECLINED;
+
+        case ngx_json_array_value_separator:
+            if (ngx_json_ws(*p)) {
+                break;
+            }
+
+            if (*p == ',') {
+                state = ngx_json_value;
+                break;
+            }
+
+            if (*p == ']') {
+                rc = ngx_json_close(ctx, NGX_JSON_ARRAY_CLOSE, &state, p);
+                if (rc != NGX_OK) {
+                    return rc;
+                }
+
+                break;
+            }
+
+            return NGX_DECLINED;
+
+        case ngx_json_array:
+            if (ngx_json_ws(*p)) {
+                break;
+            }
+
+            if (*p == ']') {
+                rc = ngx_json_close(ctx, NGX_JSON_ARRAY_CLOSE, &state, p);
+                if (rc != NGX_OK) {
+                    return rc;
+                }
+
+                break;
+            }
+
+            /* fall through */
+
+        case ngx_json_value:
+            if (ngx_json_ws(*p)) {
+                break;
+            }
+
+            start = p;
+
+            if (*p == '"') {
+                ctx->in_key = 0;
+                start = p + 1;
+                state = ngx_json_string;
+                break;
+            }
+
+            if (*p == '{') {
+                rc = ngx_json_emit(ctx, NGX_JSON_OBJECT_OPEN, p, 1);
+                if (rc != NGX_OK) {
+                    return rc;
+                }
+
+                rc = ngx_json_push_state(ctx, ngx_json_ctx_object);
+                if (rc != NGX_OK) {
+                    return rc;
+                }
+
+                state = ngx_json_object;
+                break;
+            }
+
+            if (*p == '[') {
+                rc = ngx_json_emit(ctx, NGX_JSON_ARRAY_OPEN, p, 1);
+                if (rc != NGX_OK) {
+                    return rc;
+                }
+
+                rc = ngx_json_push_state(ctx, ngx_json_ctx_array);
+                if (rc != NGX_OK) {
+                    return rc;
+                }
+
+                state = ngx_json_array;
+                break;
+            }
+
+            if (*p == '-') {
+                state = ngx_json_number_minus;
+                break;
+            }
+
+            if (*p == '0') {
+                state = ngx_json_number_int_zero;
+                break;
+            }
+
+            if (*p >= '1' && *p <= '9') {
+                state = ngx_json_number_int;
+                break;
+            }
+
+            if (*p == 't') {
+                state = ngx_json_true_t;
+                break;
+            }
+
+            if (*p == 'f') {
+                state = ngx_json_false_f;
+                break;
+            }
+
+            if (*p == 'n') {
+                state = ngx_json_null_n;
+                break;
+            }
+
+            return NGX_DECLINED;
+
+        case ngx_json_string:
+            if (*p == '"') {
+
+                if (ctx->in_key) {
+                    rc = ngx_json_emit(ctx, NGX_JSON_KEY, start, p - start);
+                    if (rc != NGX_OK) {
+                        return rc;
+                    }
+
+                    ctx->in_key = 0;
+                    state = ngx_json_object_name_separator;
+
+                } else {
+                    rc = ngx_json_emit(ctx, NGX_JSON_VALUE_STRING, start,
+                                      p - start);
+                    if (rc != NGX_OK) {
+                        return rc;
+                    }
+
+                    state = ngx_json_after_value(ctx);
+                }
+
+                break;
+            }
+
+            if (*p == '\\') {
+                state = ngx_json_escaped;
+                break;
+            }
+
+            if (*p < ' ') {
+                return NGX_DECLINED;
+            }
+
+            break;
+
+        case ngx_json_escaped:
+            if (*p == '"' || *p == '\\' || *p == '/' || *p == 'b'
+                || *p == 'f' || *p == 'n' || *p == 'r' || *p == 't')
+            {
+                state = ngx_json_string;
+                break;
+            }
+
+            if (*p == 'u') {
+                ctx->codepoint = 0;
+                ctx->hex_left = 4;
+                state = ngx_json_escaped_hex;
+                break;
+            }
+
+            return NGX_DECLINED;
+
+        case ngx_json_escaped_hex:
+
+            /* accumulate the four hex digits of the first \uXXXX escape */
+
+            hd = ngx_json_hex_digit(*p);
+            if (hd == NGX_ERROR) {
+                return NGX_DECLINED;
+            }
+
+            ctx->codepoint = (ctx->codepoint << 4) | (uint32_t) hd;
+
+            if (--ctx->hex_left > 0) {
+                break;
+            }
+
+            if (ctx->codepoint >= 0xD800 && ctx->codepoint <= 0xDBFF) {
+                /* high surrogate - must be followed by \uDC00..\uDFFF */
+                state = ngx_json_surrogate_start;
+                break;
+            }
+
+            if (ctx->codepoint >= 0xDC00 && ctx->codepoint <= 0xDFFF) {
+                /* lone low surrogate */
+                return NGX_DECLINED;
+            }
+
+            state = ngx_json_string;
+            break;
+
+        case ngx_json_surrogate_start:
+            if (*p == '\\') {
+                state = ngx_json_surrogate_u;
+                break;
+            }
+
+            /* high surrogate not followed by \uXXXX */
+            return NGX_DECLINED;
+
+        case ngx_json_surrogate_u:
+            if (*p == 'u') {
+                ctx->codepoint = 0;
+                ctx->hex_left = 4;
+                state = ngx_json_surrogate_hex;
+                break;
+            }
+
+            /* high surrogate followed by non-\u escape */
+            return NGX_DECLINED;
+
+        case ngx_json_surrogate_hex:
+
+            /* accumulate the four hex digits of the low \uXXXX escape */
+
+            hd = ngx_json_hex_digit(*p);
+            if (hd == NGX_ERROR) {
+                return NGX_DECLINED;
+            }
+
+            ctx->codepoint = (ctx->codepoint << 4) | (uint32_t) hd;
+
+            if (--ctx->hex_left > 0) {
+                break;
+            }
+
+            if (ctx->codepoint >= 0xDC00 && ctx->codepoint <= 0xDFFF) {
+                /* valid surrogate pair */
+                state = ngx_json_string;
+                break;
+            }
+
+            /* high surrogate not followed by low surrogate */
+            return NGX_DECLINED;
+
+        case ngx_json_number_minus:
+            if (*p >= '1' && *p <= '9') {
+                state = ngx_json_number_int;
+                break;
+            }
+
+            if (*p == '0') {
+                state = ngx_json_number_int_zero;
+                break;
+            }
+
+            return NGX_DECLINED;
+
+        case ngx_json_number_int_zero:
+            if (*p == '.') {
+                state = ngx_json_number_frac;
+                break;
+            }
+
+            if (*p == 'e' || *p == 'E') {
+                /* No sense, but permitted by RFC */
+                state = ngx_json_number_exp;
+                break;
+            }
+
+            p--;
+
+            rc = ngx_json_end_number(ctx, start, p + 1, &state);
+            if (rc != NGX_OK) {
+                return rc;
+            }
+
+            break;
+
+        case ngx_json_number_int:
+            if (*p >= '0' && *p <= '9') {
+                break;
+            }
+
+            if (*p == '.') {
+                state = ngx_json_number_frac;
+                break;
+            }
+
+            if (*p == 'e' || *p == 'E') {
+                /* No sense, but permitted by RFC */
+                state = ngx_json_number_exp;
+                break;
+            }
+
+            p--;
+
+            rc = ngx_json_end_number(ctx, start, p + 1, &state);
+            if (rc!= NGX_OK) {
+                return rc;
+            }
+
+            break;
+
+        case ngx_json_number_frac:
+            if (*p >= '0' && *p <= '9') {
+                state = ngx_json_number_frac_digit;
+                break;
+            }
+
+            return NGX_DECLINED;
+
+        case ngx_json_number_frac_digit:
+            if (*p >= '0' && *p <= '9') {
+                break;
+            }
+
+            if (*p == 'e' || *p == 'E') {
+                state = ngx_json_number_exp;
+                break;
+            }
+
+            p--;
+
+            rc = ngx_json_end_number(ctx, start, p + 1, &state);
+            if (rc != NGX_OK) {
+                return rc;
+            }
+
+            break;
+
+        case ngx_json_number_exp:
+            if (*p == '-' || *p == '+') {
+                state = ngx_json_number_exp_plusminus;
+                break;
+            }
+
+            if (*p >= '0' && *p <= '9') {
+                state = ngx_json_number_exp_digit;
+                break;
+            }
+
+            return NGX_DECLINED;
+
+        case ngx_json_number_exp_plusminus:
+            if (*p >= '0' && *p <= '9') {
+                state = ngx_json_number_exp_digit;
+                break;
+            }
+
+            return NGX_DECLINED;
+
+        case ngx_json_number_exp_digit:
+            if (*p >= '0' && *p <= '9') {
+                break;
+            }
+
+            p--;
+
+            rc = ngx_json_end_number(ctx, start, p + 1, &state);
+            if (rc != NGX_OK) {
+                return rc;
+            }
+
+            break;
+
+        case ngx_json_true_t:
+            if (*p == 'r') {
+                state = ngx_json_true_tr;
+                break;
+            }
+
+            return NGX_DECLINED;
+
+        case ngx_json_true_tr:
+            if (*p == 'u') {
+                state = ngx_json_true_tru;
+                break;
+            }
+
+            return NGX_DECLINED;
+
+        case ngx_json_true_tru:
+            if (*p == 'e') {
+                rc = ngx_json_emit(ctx, NGX_JSON_VALUE_BOOL, start,
+                                  p + 1 - start);
+                if (rc != NGX_OK) {
+                    return rc;
+                }
+
+                state = ngx_json_after_value(ctx);
+                break;
+            }
+
+            return NGX_DECLINED;
+
+        case ngx_json_false_f:
+            if (*p == 'a') {
+                state = ngx_json_false_fa;
+                break;
+            }
+
+            return NGX_DECLINED;
+
+        case ngx_json_false_fa:
+            if (*p == 'l') {
+                state = ngx_json_false_fal;
+                break;
+            }
+
+            return NGX_DECLINED;
+
+        case ngx_json_false_fal:
+            if (*p == 's') {
+                state = ngx_json_false_fals;
+                break;
+            }
+
+            return NGX_DECLINED;
+
+        case ngx_json_false_fals:
+            if (*p == 'e') {
+                rc = ngx_json_emit(ctx, NGX_JSON_VALUE_BOOL, start,
+                                  p + 1 - start);
+                if (rc != NGX_OK) {
+                    return rc;
+                }
+
+                state = ngx_json_after_value(ctx);
+                break;
+            }
+
+            return NGX_DECLINED;
+
+        case ngx_json_null_n:
+            if (*p == 'u') {
+                state = ngx_json_null_nu;
+                break;
+            }
+
+            return NGX_DECLINED;
+
+        case ngx_json_null_nu:
+            if (*p == 'l') {
+                state = ngx_json_null_nul;
+                break;
+            }
+
+            return NGX_DECLINED;
+
+        case ngx_json_null_nul:
+            if (*p == 'l') {
+                rc = ngx_json_emit(ctx, NGX_JSON_VALUE_NULL, start,
+                                  p + 1 - start);
+                if (rc != NGX_OK) {
+                    return rc;
+                }
+
+                state = ngx_json_after_value(ctx);
+                break;
+            }
+
+            return NGX_DECLINED;
+
+        case ngx_json_done:
+            if (ngx_json_ws(*p)) {
+                break;
+            }
+
+            return NGX_DECLINED;
+        }
+
+        skip_depth = ctx->skip_until_depth;
+    }
+
+    ctx->state = state;
+
+    if (state == ngx_json_done) {
+        return NGX_OK;
+    }
+
+    return NGX_DECLINED;
+}
+
+
+ngx_int_t
+ngx_json_parse(ngx_pool_t *pool, ngx_str_t *json, ngx_json_handler_pt handler,
+    void *data)
+{
+    ngx_json_ctx_t  jctx;
+
+    ngx_json_ctx_init(&jctx, pool);
+
+    jctx.data = data;
+    jctx.handler = handler;
+
+    return ngx_json_parse_ctx(&jctx, json->data, json->len);
+}
+
+
+static ngx_inline ngx_int_t
+ngx_json_end_number(ngx_json_ctx_t *ctx, u_char *start, u_char *end,
+    ngx_json_state_e *state)
+{
+    ngx_int_t  rc;
+
+    rc = ngx_json_emit(ctx, NGX_JSON_VALUE_NUMBER, start, end - start);
+    if (rc != NGX_OK) {
+        return rc;
+    }
+
+    *state = ngx_json_after_value(ctx);
+
+    return NGX_OK;
+}
+
+
+static ngx_inline ngx_int_t
+ngx_json_emit(ngx_json_ctx_t *ctx, ngx_json_event_e event, u_char *data,
+    size_t len)
+{
+    ngx_int_t  rc;
+    ngx_str_t  token;
+
+    if (ngx_json_skipping(ctx->skip_until_depth)) {
+        return NGX_OK;
+    }
+
+    if (ctx->handler == NULL) {
+        return NGX_OK;
+    }
+
+    token.data = data;
+    token.len = len;
+    rc = ctx->handler(ctx, event, &token);
+
+    if (rc == NGX_JSON_SKIP) {
+
+        /*
+         * NGX_JSON_SKIP prunes the rest of the current container.  It is
+         * honoured only where it is meaningful:
+         *
+         *   - OBJECT_OPEN / ARRAY_OPEN: skip the whole container;
+         *   - a scalar VALUE_* that is an array element: skip the rest of
+         *     that array.
+         *
+         * A skip requested on a CLOSE, or on an object member value, is
+         * ignored: object members are navigated by KEY (use a KEY skip),
+         * and a CLOSE fires after the container is already complete.
+         */
+
+        switch (event) {
+
+        case NGX_JSON_KEY:
+        case NGX_JSON_OBJECT_OPEN:
+        case NGX_JSON_ARRAY_OPEN:
+            ctx->skip_until_depth = ctx->depth;
+            break;
+
+        case NGX_JSON_VALUE_STRING:
+        case NGX_JSON_VALUE_NUMBER:
+        case NGX_JSON_VALUE_BOOL:
+        case NGX_JSON_VALUE_NULL:
+            if (ctx->depth > 0
+                && ctx->stack[ctx->depth - 1] == ngx_json_ctx_array
+            ) {
+                ctx->skip_until_depth = ctx->depth - 1;
+            }
+            break;
+
+        default:
+            break;
+        }
+
+        return NGX_OK;
+    }
+
+    return rc;
+}
+
+
+static ngx_inline ngx_json_state_e
+ngx_json_after_value(ngx_json_ctx_t *ctx)
+{
+    if (ctx->depth == 0) {
+        return ngx_json_done;
+    }
+
+    if (ctx->stack[ctx->depth - 1] == ngx_json_ctx_object) {
+        return ngx_json_object_value_separator;
+    }
+
+    return ngx_json_array_value_separator;
+}
+
+
+static ngx_inline ngx_int_t
+ngx_json_close(ngx_json_ctx_t *ctx, ngx_json_event_e event,
+    ngx_json_state_e *state, u_char *p)
+{
+    ngx_int_t  rc;
+
+    ctx->depth--;
+
+    rc = ngx_json_emit(ctx, event, p, 1);
+    if (rc != NGX_OK) {
+        return rc;
+    }
+
+    *state = ngx_json_after_value(ctx);
+
+    return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_json_push_state(ngx_json_ctx_t *ctx, ngx_json_container_e container)
+{
+    ngx_json_container_e  *stack;
+
+    if (ctx->depth >= ctx->max_depth) {
+        return NGX_DECLINED;
+    }
+
+    if (ctx->stack == NULL) {
+        stack = ngx_palloc(ctx->pool,
+                           ctx->max_depth * sizeof(ngx_json_container_e));
+        if (stack == NULL) {
+            return NGX_ERROR;
+        }
+
+        ctx->stack = stack;
+    }
+
+    ctx->stack[ctx->depth] = container;
+    ctx->depth++;
+
+    return NGX_OK;
+}
diff --git a/src/core/ngx_json_parse.h b/src/core/ngx_json_parse.h
new file mode 100644 (file)
index 0000000..2d96aa7
--- /dev/null
@@ -0,0 +1,111 @@
+
+/*
+ * Copyright (C) Maxim Dounin
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#ifndef _NGX_JSON_PARSE_H_INCLUDED_
+#define _NGX_JSON_PARSE_H_INCLUDED_
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_json_unescape.h>
+
+
+#define NGX_JSON_SKIP                    -7
+#define NGX_JSON_DEFAULT_MAX_DEPTH       64
+
+
+typedef enum {
+    NGX_JSON_OBJECT_OPEN = 0,
+    NGX_JSON_OBJECT_CLOSE,
+    NGX_JSON_ARRAY_OPEN,
+    NGX_JSON_ARRAY_CLOSE,
+    NGX_JSON_KEY,
+    NGX_JSON_VALUE_STRING,
+    NGX_JSON_VALUE_NUMBER,
+    NGX_JSON_VALUE_BOOL,
+    NGX_JSON_VALUE_NULL
+} ngx_json_event_e;
+
+
+typedef enum {
+    ngx_json_start = 0,
+    ngx_json_done,
+    ngx_json_object,
+    ngx_json_object_next,
+    ngx_json_object_name_separator,
+    ngx_json_value,
+    ngx_json_object_value_separator,
+    ngx_json_array_value_separator,
+    ngx_json_array,
+    ngx_json_string,
+    ngx_json_number_minus,
+    ngx_json_number_int,
+    ngx_json_number_int_zero,
+    ngx_json_number_frac,
+    ngx_json_number_frac_digit,
+    ngx_json_number_exp,
+    ngx_json_number_exp_plusminus,
+    ngx_json_number_exp_digit,
+    ngx_json_escaped,
+    ngx_json_escaped_hex,       /* first \uXXXX: reading the four hex digits */
+    ngx_json_surrogate_start,   /* expect '\' beginning the low surrogate */
+    ngx_json_surrogate_u,       /* expect 'u' of the low surrogate */
+    ngx_json_surrogate_hex,     /* low \uXXXX: reading the four hex digits */
+    ngx_json_true_t,
+    ngx_json_true_tr,
+    ngx_json_true_tru,
+    ngx_json_false_f,
+    ngx_json_false_fa,
+    ngx_json_false_fal,
+    ngx_json_false_fals,
+    ngx_json_null_n,
+    ngx_json_null_nu,
+    ngx_json_null_nul
+} ngx_json_state_e;
+
+
+typedef enum {
+    ngx_json_ctx_none = 0,
+    ngx_json_ctx_object,
+    ngx_json_ctx_array
+} ngx_json_container_e;
+
+
+typedef struct ngx_json_ctx_s  ngx_json_ctx_t;
+
+
+typedef ngx_int_t (*ngx_json_handler_pt)(ngx_json_ctx_t *ctx,
+    ngx_json_event_e event, ngx_str_t *token);
+
+
+struct ngx_json_ctx_s {
+    ngx_json_state_e       state;
+
+    ngx_json_container_e  *stack;
+    ngx_uint_t             depth;
+    ngx_uint_t             max_depth;
+
+    uint32_t               codepoint;
+    ngx_uint_t             hex_left;         /* hex digits still expected */
+
+    ngx_uint_t             skip_until_depth;
+
+    ngx_pool_t            *pool;
+    ngx_json_handler_pt    handler;
+    void                  *data;
+
+    unsigned               in_key; /* unsigned in_key:1 */
+};
+
+
+void ngx_json_ctx_init(ngx_json_ctx_t *ctx, ngx_pool_t *pool);
+ngx_int_t ngx_json_parse_ctx(ngx_json_ctx_t *ctx, u_char *data, size_t len);
+ngx_int_t ngx_json_parse(ngx_pool_t *pool, ngx_str_t *json,
+    ngx_json_handler_pt handler, void *data);
+
+
+#endif /* _NGX_JSON_PARSE_H_INCLUDED_ */