]> git.kaiwu.me - nginx.git/commitdiff
Added the json parser module
authorRoman Semenov <r.semenov@f5.com>
Wed, 12 Aug 2026 16:52:08 +0000 (09:52 -0700)
committerRoman S <141866642+roman-f5@users.noreply.github.com>
Mon, 24 Aug 2026 18:08:31 +0000 (11:08 -0700)
Added the http_json_module with a json_set directive that
extracts values from JSON stored in nginx variables and exposes them
as new nginx variables.  Parsed results are cached per request, so
multiple json_set directives using the same source expression trigger
only one parse.  The directive is available in the http main context.

Example usage:

    http {
        json_set $user_name $arg_data user.name;
        json_set $user_age  $arg_data user.age;
        json_set $tag0      $arg_data tags[0];

        server {
            listen 8080;

            location / {
                return 200 "name=$user_name age=$user_age "
                           "tag0=$tag0\n";
            }
        }

        # quoted keys for names containing '.', '[', ']', or that
        # are empty; the whole argument is single-quoted so that the
        # inner '"' reach the path parser intact
        json_set $host      $arg_data '["a.b.com"]';
        json_set $nested    $arg_data 'obj["a.b"].c[2]';
        json_set $quoted    $arg_data 'obj["a\\"b"]';
    }

Co-authored-by: Dmitry Volyntsev <d.volyntsev@f5.com>
auto/modules
auto/options
src/http/modules/ngx_http_json_module.c [new file with mode: 0644]

index 572eebe6ed939b24e9191ba05d8e0e638f9ed2a3..f0286024780867d0f7e4bce6784e75f71b493f46 100644 (file)
@@ -638,6 +638,17 @@ if [ $HTTP = YES ]; then
         . auto/module
     fi
 
+    if [ $HTTP_JSON = YES ]; then
+        ngx_module_name=ngx_http_json_module
+        ngx_module_incs=
+        ngx_module_deps=
+        ngx_module_srcs=src/http/modules/ngx_http_json_module.c
+        ngx_module_libs=
+        ngx_module_link=$HTTP_JSON
+
+        . auto/module
+    fi
+
     if [ $HTTP_GEO = YES ]; then
         have=NGX_HTTP_X_FORWARDED_FOR . auto/have
 
index 6699c69141c09344308755cb377c423ebbc06290..1aeb73074b0aa300e6bbb6d7bd9151ba18909f38 100644 (file)
@@ -75,6 +75,7 @@ HTTP_AUTH_REQUEST=NO
 HTTP_MIRROR=YES
 HTTP_USERID=YES
 HTTP_SLICE=NO
+HTTP_JSON=NO
 HTTP_AUTOINDEX=YES
 HTTP_RANDOM_INDEX=NO
 HTTP_STATUS=NO
@@ -263,6 +264,7 @@ $0: warning: the \"--with-ipv6\" option is deprecated"
         --with-http_secure_link_module)  HTTP_SECURE_LINK=YES       ;;
         --with-http_degradation_module)  HTTP_DEGRADATION=YES       ;;
         --with-http_slice_module)        HTTP_SLICE=YES             ;;
+        --with-http_json_module)         HTTP_JSON=YES              ;;
 
         --without-http_charset_module)   HTTP_CHARSET=NO            ;;
         --without-http_gzip_module)      HTTP_GZIP=NO               ;;
@@ -495,6 +497,7 @@ cat << END
   --with-http_secure_link_module     enable ngx_http_secure_link_module
   --with-http_degradation_module     enable ngx_http_degradation_module
   --with-http_slice_module           enable ngx_http_slice_module
+  --with-http_json_module            enable ngx_http_json_module
   --with-http_stub_status_module     enable ngx_http_stub_status_module
 
   --without-http_charset_module      disable ngx_http_charset_module
diff --git a/src/http/modules/ngx_http_json_module.c b/src/http/modules/ngx_http_json_module.c
new file mode 100644 (file)
index 0000000..78099a4
--- /dev/null
@@ -0,0 +1,978 @@
+
+/*
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_http.h>
+#include <ngx_json_parse.h>
+
+
+#define NGX_HTTP_JSON_DEFAULT_MAX_DEPTH       32
+
+typedef struct {
+    ngx_uint_t                     is_index;
+    ngx_str_t                      key;
+    ngx_uint_t                     index;
+} ngx_http_json_seg_t;
+
+
+typedef struct ngx_http_json_node_s  ngx_http_json_node_t;
+
+
+struct ngx_http_json_node_s {
+    ngx_http_json_seg_t            seg;
+    ngx_array_t                   *children;  /* ngx_http_json_node_t */
+    ngx_uint_t                    *dests;
+    ngx_uint_t                     ndests;
+};
+
+
+typedef struct {
+    ngx_int_t                      index;
+    ngx_http_json_node_t          *root;
+} ngx_http_json_source_t;
+
+
+typedef struct {
+    ngx_uint_t                     source_index;
+} ngx_http_json_variable_t;
+
+
+typedef struct {
+    ngx_uint_t                     index;
+    u_char                        *start;
+    ngx_http_json_node_t          *node;
+    ngx_uint_t                     is_array;
+} ngx_http_json_frame_t;
+
+
+typedef struct {
+    ngx_int_t                      max_depth;
+    ngx_array_t                   *sources;
+    ngx_array_t                   *variables;
+} ngx_http_json_main_conf_t;
+
+
+typedef struct {
+    ngx_http_request_t            *r;
+    ngx_http_variable_value_t     *values;
+    ngx_array_t                    stack;
+    ngx_str_t                      current_key;
+    ngx_http_json_node_t          *current_node;
+    ngx_http_json_node_t          *root;
+} ngx_http_json_state_t;
+
+
+static char *ngx_http_json_set(ngx_conf_t *cf, ngx_command_t *cmd, void *conf);
+static ngx_int_t ngx_http_json_variable(ngx_http_request_t *r,
+    ngx_http_variable_value_t *v, uintptr_t data);
+static void ngx_http_json_reset(ngx_http_variable_value_t *values,
+    ngx_http_json_main_conf_t *jmcf, ngx_uint_t source_index);
+static char *ngx_http_json_insert_path(ngx_conf_t *cf,
+    ngx_http_json_node_t *root, ngx_uint_t dest, ngx_str_t *path);
+static ngx_http_json_node_t *ngx_http_json_child(ngx_conf_t *cf,
+    ngx_http_json_node_t *parent, ngx_http_json_seg_t *seg);
+static ngx_int_t ngx_http_json_handler(ngx_json_ctx_t *ctx,
+    ngx_json_event_e event, ngx_str_t *token);
+static void ngx_http_json_inc_index(ngx_http_json_state_t *state);
+static ngx_http_json_node_t *ngx_http_json_lookup_key(
+    ngx_http_json_node_t *parent, ngx_str_t *key);
+static ngx_http_json_node_t *ngx_http_json_lookup_index(
+    ngx_http_json_node_t *parent, ngx_uint_t index);
+static ngx_int_t ngx_http_json_store(ngx_http_json_state_t *state,
+    ngx_http_json_node_t *node, ngx_str_t *value, ngx_uint_t unescape);
+static ngx_int_t ngx_http_json_push(ngx_http_json_state_t *state,
+    ngx_json_event_e event, u_char *start, ngx_http_json_node_t *node);
+static void *ngx_http_json_create_main_conf(ngx_conf_t *cf);
+static char *ngx_http_json_init_main_conf(ngx_conf_t *cf, void *conf);
+
+
+static ngx_conf_num_bounds_t  ngx_http_json_max_depth_bounds = {
+    ngx_conf_check_num_bounds, 1, 256
+};
+
+
+static ngx_command_t  ngx_http_json_commands[] = {
+
+    { ngx_string("json_set"),
+      NGX_HTTP_MAIN_CONF|NGX_CONF_TAKE3,
+      ngx_http_json_set,
+      NGX_HTTP_MAIN_CONF_OFFSET,
+      0,
+      NULL },
+
+    { ngx_string("json_max_depth"),
+      NGX_HTTP_MAIN_CONF|NGX_CONF_TAKE1,
+      ngx_conf_set_num_slot,
+      NGX_HTTP_MAIN_CONF_OFFSET,
+      offsetof(ngx_http_json_main_conf_t, max_depth),
+      &ngx_http_json_max_depth_bounds },
+
+      ngx_null_command
+};
+
+
+static ngx_http_module_t  ngx_http_json_module_ctx = {
+    NULL,                            /* preconfiguration */
+    NULL,                            /* postconfiguration */
+
+    ngx_http_json_create_main_conf,  /* create main configuration */
+    ngx_http_json_init_main_conf,    /* init main configuration */
+
+    NULL,                            /* create server configuration */
+    NULL,                            /* merge server configuration */
+
+    NULL,                            /* create location configuration */
+    NULL                             /* merge location configuration */
+};
+
+
+ngx_module_t  ngx_http_json_module = {
+    NGX_MODULE_V1,
+    &ngx_http_json_module_ctx,       /* module context */
+    ngx_http_json_commands,          /* module directives */
+    NGX_HTTP_MODULE,                 /* module type */
+    NULL,                            /* init master */
+    NULL,                            /* init module */
+    NULL,                            /* init process */
+    NULL,                            /* init thread */
+    NULL,                            /* exit thread */
+    NULL,                            /* exit process */
+    NULL,                            /* exit master */
+    NGX_MODULE_V1_PADDING
+};
+
+
+static char *
+ngx_http_json_set(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
+{
+    ngx_http_json_main_conf_t  *jmcf = conf;
+
+    ngx_str_t                 *value, name, source;
+    ngx_int_t                  sindex;
+    ngx_uint_t                 i, oi, si;
+    ngx_http_variable_t       *var;
+    ngx_http_json_source_t    *src, *srcs;
+    ngx_http_json_variable_t  *jv;
+
+    value = cf->args->elts;
+
+    name = value[1];
+
+    if (name.data[0] != '$') {
+        ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                           "invalid variable name \"%V\"", &name);
+        return NGX_CONF_ERROR;
+    }
+
+    name.len--;
+    name.data++;
+
+    var = ngx_http_add_variable(cf, &name, NGX_HTTP_VAR_CHANGEABLE);
+    if (var == NULL) {
+        return NGX_CONF_ERROR;
+    }
+
+    if (var->get_handler == ngx_http_json_variable) {
+        ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                           "json_set variable \"%V\" is already defined",
+                           &name);
+        return NGX_CONF_ERROR;
+    }
+
+    source = value[2];
+
+    if (source.data[0] != '$') {
+        ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                           "invalid variable name \"%V\"", &source);
+        return NGX_CONF_ERROR;
+    }
+
+    source.len--;
+    source.data++;
+
+    sindex = ngx_http_get_variable_index(cf, &source);
+    if (sindex == NGX_ERROR) {
+        return NGX_CONF_ERROR;
+    }
+
+    if (jmcf->sources == NULL) {
+        jmcf->sources = ngx_array_create(cf->pool, 4,
+                                         sizeof(ngx_http_json_source_t));
+        if (jmcf->sources == NULL) {
+            return NGX_CONF_ERROR;
+        }
+    }
+
+    srcs = jmcf->sources->elts;
+    si = jmcf->sources->nelts;
+
+    for (i = 0; i < jmcf->sources->nelts; i++) {
+        if (srcs[i].index == sindex) {
+            si = i;
+            break;
+        }
+    }
+
+    if (si == jmcf->sources->nelts) {
+        src = ngx_array_push(jmcf->sources);
+        if (src == NULL) {
+            return NGX_CONF_ERROR;
+        }
+
+        src->index = sindex;
+
+        src->root = ngx_pcalloc(cf->pool, sizeof(ngx_http_json_node_t));
+        if (src->root == NULL) {
+            return NGX_CONF_ERROR;
+        }
+
+    } else {
+        src = &srcs[si];
+    }
+
+    if (jmcf->variables == NULL) {
+        jmcf->variables = ngx_array_create(cf->pool, 4,
+                                           sizeof(ngx_http_json_variable_t));
+        if (jmcf->variables == NULL) {
+            return NGX_CONF_ERROR;
+        }
+    }
+
+    oi = jmcf->variables->nelts;
+
+    jv = ngx_array_push(jmcf->variables);
+    if (jv == NULL) {
+        return NGX_CONF_ERROR;
+    }
+
+    jv->source_index = si;
+
+    if (ngx_http_json_insert_path(cf, src->root, oi, &value[3])
+        != NGX_CONF_OK)
+    {
+        return NGX_CONF_ERROR;
+    }
+
+    var->get_handler = ngx_http_json_variable;
+    var->data = (uintptr_t) oi;
+
+    return NGX_CONF_OK;
+}
+
+
+static ngx_int_t
+ngx_http_json_variable(ngx_http_request_t *r, ngx_http_variable_value_t *v,
+    uintptr_t data)
+{
+    ngx_int_t                   rc;
+    ngx_uint_t                  oi, si;
+    ngx_json_ctx_t              jctx;
+    ngx_http_json_state_t       state;
+    ngx_http_json_source_t     *src, *srcs;
+    ngx_http_json_variable_t   *jv;
+    ngx_http_variable_value_t  *cached, *values, *vv;
+    ngx_http_json_main_conf_t  *jmcf;
+
+    oi = (ngx_uint_t) data;
+
+    jmcf = ngx_http_get_module_main_conf(r, ngx_http_json_module);
+
+    if (jmcf->variables == NULL || oi >= jmcf->variables->nelts) {
+        v->not_found = 1;
+        return NGX_OK;
+    }
+
+    values = ngx_http_get_module_ctx(r, ngx_http_json_module);
+
+    if (values == NULL) {
+        values = ngx_pcalloc(r->pool, jmcf->variables->nelts
+                             * sizeof(ngx_http_variable_value_t));
+        if (values == NULL) {
+            return NGX_ERROR;
+        }
+
+        ngx_http_set_ctx(r, values, ngx_http_json_module);
+    }
+
+    cached = &values[oi];
+
+    if (cached->valid || cached->not_found) {
+        *v = *cached;
+        return NGX_OK;
+    }
+
+    jv = jmcf->variables->elts;
+    si = jv[oi].source_index;
+
+    srcs = jmcf->sources->elts;
+    src = &srcs[si];
+
+    vv = ngx_http_get_flushed_variable(r, (ngx_uint_t) src->index);
+
+    if (vv == NULL) {
+        return NGX_ERROR;
+    }
+
+    ngx_http_json_reset(values, jmcf, si);
+
+    if (vv->not_found || vv->len == 0) {
+        *v = *cached;
+        return NGX_OK;
+    }
+
+    ngx_memzero(&state, sizeof(ngx_http_json_state_t));
+
+    state.r = r;
+    state.values = values;
+    state.root = src->root;
+
+    if (ngx_array_init(&state.stack, r->pool, 8, sizeof(ngx_http_json_frame_t))
+        != NGX_OK)
+    {
+        return NGX_ERROR;
+    }
+
+    ngx_json_ctx_init(&jctx, r->pool);
+
+    jctx.handler = ngx_http_json_handler;
+    jctx.data = &state;
+    jctx.max_depth = jmcf->max_depth;
+
+    rc = ngx_json_parse_ctx(&jctx, vv->data, vv->len);
+
+    if (rc == NGX_OK) {
+        *v = *cached;
+        return NGX_OK;
+    }
+
+    ngx_http_json_reset(values, jmcf, si);
+
+    if (rc == NGX_DECLINED) {
+        ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                       "json_set: invalid JSON source");
+    }
+
+    if (rc == NGX_ERROR) {
+        return NGX_ERROR;
+    }
+
+    *v = *cached;
+
+    return NGX_OK;
+}
+
+
+static void
+ngx_http_json_reset(ngx_http_variable_value_t *values,
+    ngx_http_json_main_conf_t *jmcf, ngx_uint_t source_index)
+{
+    ngx_uint_t                 i;
+    ngx_http_json_variable_t  *jv;
+
+    jv = jmcf->variables->elts;
+
+    for (i = 0; i < jmcf->variables->nelts; i++) {
+        if (jv[i].source_index != source_index) {
+            continue;
+        }
+
+        values[i].valid = 0;
+        values[i].not_found = 1;
+    }
+}
+
+
+static char *
+ngx_http_json_insert_path(ngx_conf_t *cf, ngx_http_json_node_t *root,
+    ngx_uint_t dest, ngx_str_t *path)
+{
+    u_char                *p, *last, *start, *dst;
+    ngx_int_t              index;
+    ngx_uint_t            *term;
+    ngx_http_json_seg_t    seg;
+    ngx_http_json_node_t  *node;
+    enum {
+        sw_start = 0,
+        sw_key,
+        sw_dot,
+        sw_bracket,
+        sw_index,
+        sw_quoted,
+        sw_quoted_escape,
+        sw_quoted_close,
+        sw_after_bracket
+    } state;
+
+    if (path->len == 0) {
+        ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "empty json_set path");
+        return NGX_CONF_ERROR;
+    }
+
+    node = root;
+    start = NULL;
+
+    last = path->data + path->len;
+    state = sw_start;
+
+    for (p = path->data; p < last; p++) {
+
+        switch (state) {
+
+        case sw_start:
+
+            if (*p == '[') {
+                state = sw_bracket;
+                break;
+            }
+
+            if (*p == '.' || *p == '$') {
+                goto invalid;
+            }
+
+            start = p;
+            state = sw_key;
+            break;
+
+        case sw_key:
+
+            if (*p == '.' || *p == '[') {
+
+                seg.is_index = 0;
+                seg.key.data = start;
+                seg.key.len = p - start;
+                seg.index = 0;
+
+                node = ngx_http_json_child(cf, node, &seg);
+                if (node == NULL) {
+                    return NGX_CONF_ERROR;
+                }
+
+                state = (*p == '.') ? sw_dot : sw_bracket;
+            }
+
+            break;
+
+        case sw_dot:
+
+            if (*p == '.' || *p == '[' || *p == '$') {
+                goto invalid;
+            }
+
+            start = p;
+            state = sw_key;
+            break;
+
+        case sw_bracket:
+
+            if (*p == '"') {
+                start = p + 1;
+                state = sw_quoted;
+                break;
+            }
+
+            if (*p >= '0' && *p <= '9') {
+                start = p;
+                state = sw_index;
+                break;
+            }
+
+            goto invalid;
+
+        case sw_index:
+
+            if (*p >= '0' && *p <= '9') {
+                break;
+            }
+
+            if (*p != ']') {
+                goto invalid;
+            }
+
+            index = ngx_atoi(start, p - start);
+            if (index == NGX_ERROR) {
+                goto invalid;
+            }
+
+            seg.is_index = 1;
+            seg.index = index;
+            ngx_str_null(&seg.key);
+
+            node = ngx_http_json_child(cf, node, &seg);
+            if (node == NULL) {
+                return NGX_CONF_ERROR;
+            }
+
+            state = sw_after_bracket;
+            break;
+
+        case sw_quoted:
+
+            if (*p == '\\') {
+                state = sw_quoted_escape;
+                break;
+            }
+
+            if (*p == '"') {
+
+                dst = ngx_pnalloc(cf->pool, (size_t) (p - start));
+                if (dst == NULL) {
+                    return NGX_CONF_ERROR;
+                }
+
+                ngx_memcpy(dst, start, p - start);
+
+                seg.is_index = 0;
+                seg.key.data = dst;
+                seg.key.len = p - start;
+                seg.index = 0;
+
+                if (ngx_json_unescape_string(&seg.key) != NGX_OK) {
+                    goto invalid;
+                }
+
+                node = ngx_http_json_child(cf, node, &seg);
+                if (node == NULL) {
+                    return NGX_CONF_ERROR;
+                }
+
+                state = sw_quoted_close;
+            }
+
+            break;
+
+        case sw_quoted_escape:
+
+            state = sw_quoted;
+            break;
+
+        case sw_quoted_close:
+
+            if (*p != ']') {
+                goto invalid;
+            }
+
+            state = sw_after_bracket;
+            break;
+
+        case sw_after_bracket:
+
+            if (*p == '.') {
+                state = sw_dot;
+                break;
+            }
+
+            if (*p == '[') {
+                state = sw_bracket;
+                break;
+            }
+
+            goto invalid;
+        }
+    }
+
+    switch (state) {
+
+    case sw_key:
+
+        seg.is_index = 0;
+        seg.key.data = start;
+        seg.key.len = last - start;
+        seg.index = 0;
+
+        node = ngx_http_json_child(cf, node, &seg);
+        if (node == NULL) {
+            return NGX_CONF_ERROR;
+        }
+
+        break;
+
+    case sw_after_bracket:
+        break;
+
+    default:
+        goto invalid;
+    }
+
+    term = ngx_palloc(cf->pool, (node->ndests + 1) * sizeof(ngx_uint_t));
+    if (term == NULL) {
+        return NGX_CONF_ERROR;
+    }
+
+    if (node->ndests) {
+        ngx_memcpy(term, node->dests, node->ndests * sizeof(ngx_uint_t));
+    }
+
+    term[node->ndests] = dest;
+    node->dests = term;
+    node->ndests++;
+
+    return NGX_CONF_OK;
+
+invalid:
+
+    ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                       "invalid json_set path \"%V\"", path);
+    return NGX_CONF_ERROR;
+}
+
+
+static ngx_http_json_node_t *
+ngx_http_json_child(ngx_conf_t *cf, ngx_http_json_node_t *parent,
+    ngx_http_json_seg_t *seg)
+{
+    ngx_uint_t             i;
+    ngx_http_json_seg_t   *cseg;
+    ngx_http_json_node_t  *child, *elts;
+
+    if (parent->children != NULL) {
+        elts = parent->children->elts;
+
+        for (i = 0; i < parent->children->nelts; i++) {
+            child = &elts[i];
+            cseg = &child->seg;
+
+            if (seg->is_index) {
+                if (cseg->is_index && cseg->index == seg->index) {
+                    return child;
+                }
+
+            } else {
+                if (!cseg->is_index
+                    && cseg->key.len == seg->key.len
+                    && ngx_memcmp(cseg->key.data, seg->key.data, seg->key.len)
+                       == 0)
+                {
+                    return child;
+                }
+            }
+        }
+
+    } else {
+        parent->children = ngx_array_create(cf->pool, 1,
+                                            sizeof(ngx_http_json_node_t));
+        if (parent->children == NULL) {
+            return NULL;
+        }
+    }
+
+    child = ngx_array_push(parent->children);
+    if (child == NULL) {
+        return NULL;
+    }
+
+    ngx_memzero(child, sizeof(ngx_http_json_node_t));
+
+    child->seg = *seg;
+
+    return child;
+}
+
+
+static ngx_int_t
+ngx_http_json_handler(ngx_json_ctx_t *ctx, ngx_json_event_e event,
+    ngx_str_t *token)
+{
+    ngx_str_t               slice, value;
+    ngx_uint_t              is_member;
+    ngx_http_json_node_t   *node;
+    ngx_http_json_state_t  *state;
+    ngx_http_json_frame_t  *top;
+
+    state = ctx->data;
+
+    top = NULL;
+    if (state->stack.nelts) {
+        top = (ngx_http_json_frame_t *) state->stack.elts
+              + state->stack.nelts - 1;
+    }
+
+    switch (event) {
+
+    case NGX_JSON_OBJECT_OPEN:
+    case NGX_JSON_ARRAY_OPEN:
+
+        if (state->stack.nelts == 0) {
+
+            ngx_str_null(&state->current_key);
+            state->current_node = NULL;
+
+            return ngx_http_json_push(state, event, token->data, state->root);
+        }
+
+        is_member = (state->current_key.data != NULL);
+        ngx_str_null(&state->current_key);
+
+        if (is_member) {
+
+            node = state->current_node;
+            state->current_node = NULL;
+
+        } else {
+
+            node = ngx_http_json_lookup_index(top->node, top->index);
+
+            ngx_http_json_inc_index(state);
+
+            if (node == NULL) {
+                return NGX_JSON_SKIP;
+            }
+        }
+
+        return ngx_http_json_push(state, event, token->data, node);
+
+    case NGX_JSON_OBJECT_CLOSE:
+    case NGX_JSON_ARRAY_CLOSE:
+
+        if (top != NULL) {
+
+            if (top->node->ndests > 0) {
+                slice.data = top->start;
+                slice.len = token->data - top->start + 1;
+
+                if (ngx_http_json_store(state, top->node, &slice, 0)
+                    != NGX_OK)
+                {
+                    return NGX_ERROR;
+                }
+            }
+
+            state->stack.nelts--;
+        }
+
+        break;
+
+    case NGX_JSON_KEY:
+
+        if (ngx_strlchr(token->data, token->data + token->len, '\\') == NULL) {
+            state->current_key = *token;
+
+        } else {
+            state->current_key.data = ngx_pstrdup(ctx->pool, token);
+            if (state->current_key.data == NULL) {
+                return NGX_ERROR;
+            }
+
+            state->current_key.len = token->len;
+
+            if (ngx_json_unescape_string(&state->current_key) != NGX_OK) {
+                return NGX_ERROR;
+            }
+        }
+
+        node = ngx_http_json_lookup_key(top->node, &state->current_key);
+        if (node == NULL) {
+            ngx_str_null(&state->current_key);
+            state->current_node = NULL;
+            return NGX_JSON_SKIP;
+        }
+
+        state->current_node = node;
+
+        break;
+
+    case NGX_JSON_VALUE_STRING:
+    case NGX_JSON_VALUE_NUMBER:
+    case NGX_JSON_VALUE_BOOL:
+    case NGX_JSON_VALUE_NULL:
+
+        if (top == NULL) {
+            ngx_str_null(&state->current_key);
+            state->current_node = NULL;
+            break;
+        }
+
+        is_member = (state->current_key.data != NULL);
+        ngx_str_null(&state->current_key);
+
+        if (is_member) {
+
+            node = state->current_node;
+            state->current_node = NULL;
+
+        } else {
+
+            node = ngx_http_json_lookup_index(top->node, top->index);
+
+            ngx_http_json_inc_index(state);
+        }
+
+        if (node != NULL && node->ndests > 0) {
+
+            value = *token;
+
+            if (ngx_http_json_store(state, node, &value,
+                                          event == NGX_JSON_VALUE_STRING)
+                != NGX_OK)
+            {
+                return NGX_ERROR;
+            }
+        }
+
+        break;
+    }
+
+    return NGX_OK;
+}
+
+
+static void
+ngx_http_json_inc_index(ngx_http_json_state_t *state)
+{
+    ngx_http_json_frame_t  *top;
+
+    if (state->stack.nelts == 0) {
+        return;
+    }
+
+    top = state->stack.elts;
+    top += state->stack.nelts - 1;
+
+    if (top->is_array) {
+        top->index++;
+    }
+}
+
+
+static ngx_http_json_node_t *
+ngx_http_json_lookup_key(ngx_http_json_node_t *parent, ngx_str_t *key)
+{
+    ngx_uint_t             i;
+    ngx_http_json_seg_t   *seg;
+    ngx_http_json_node_t  *child, *elts;
+
+    if (parent->children == NULL) {
+        return NULL;
+    }
+
+    elts = parent->children->elts;
+
+    for (i = 0; i < parent->children->nelts; i++) {
+        child = &elts[i];
+        seg = &child->seg;
+
+        if (!seg->is_index
+            && seg->key.len == key->len
+            && ngx_memcmp(seg->key.data, key->data, key->len) == 0)
+        {
+            return child;
+        }
+    }
+
+    return NULL;
+}
+
+
+static ngx_http_json_node_t *
+ngx_http_json_lookup_index(ngx_http_json_node_t *parent, ngx_uint_t index)
+{
+    ngx_uint_t             i;
+    ngx_http_json_seg_t   *seg;
+    ngx_http_json_node_t  *child, *elts;
+
+    if (parent->children == NULL) {
+        return NULL;
+    }
+
+    elts = parent->children->elts;
+
+    for (i = 0; i < parent->children->nelts; i++) {
+        child = &elts[i];
+        seg = &child->seg;
+
+        if (seg->is_index && seg->index == index) {
+            return child;
+        }
+    }
+
+    return NULL;
+}
+
+
+static ngx_int_t
+ngx_http_json_store(ngx_http_json_state_t *state, ngx_http_json_node_t *node,
+    ngx_str_t *value, ngx_uint_t unescape)
+{
+    ngx_str_t                   val;
+    ngx_uint_t                  i;
+    ngx_http_variable_value_t  *slot;
+
+    if (value->len == 0) {
+        val.len = 0;
+        val.data = (u_char *) "";
+
+    } else {
+        val.data = ngx_pstrdup(state->r->pool, value);
+        if (val.data == NULL) {
+            return NGX_ERROR;
+        }
+
+        val.len = value->len;
+
+        if (unescape && ngx_json_unescape_string(&val) != NGX_OK) {
+            return NGX_ERROR;
+        }
+    }
+
+    for (i = 0; i < node->ndests; i++) {
+        slot = &state->values[node->dests[i]];
+        slot->valid = 1;
+        slot->not_found = 0;
+        slot->no_cacheable = 0;
+        slot->escape = 0;
+        slot->len = val.len;
+        slot->data = val.data;
+    }
+
+    return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_json_push(ngx_http_json_state_t *state, ngx_json_event_e event,
+    u_char *start, ngx_http_json_node_t *node)
+{
+    ngx_http_json_frame_t  *frame;
+
+    frame = ngx_array_push(&state->stack);
+    if (frame == NULL) {
+        return NGX_ERROR;
+    }
+
+    frame->index = 0;
+    frame->start = start;
+    frame->is_array = (event == NGX_JSON_ARRAY_OPEN);
+    frame->node = node;
+
+    return NGX_OK;
+}
+
+
+static void *
+ngx_http_json_create_main_conf(ngx_conf_t *cf)
+{
+    ngx_http_json_main_conf_t  *jmcf;
+
+    jmcf = ngx_pcalloc(cf->pool, sizeof(ngx_http_json_main_conf_t));
+    if (jmcf == NULL) {
+        return NULL;
+    }
+
+    jmcf->max_depth = NGX_CONF_UNSET;
+
+    return jmcf;
+}
+
+
+static char *
+ngx_http_json_init_main_conf(ngx_conf_t *cf, void *conf)
+{
+    ngx_http_json_main_conf_t  *jmcf = conf;
+
+    ngx_conf_init_value(jmcf->max_depth, NGX_HTTP_JSON_DEFAULT_MAX_DEPTH);
+
+    return NGX_CONF_OK;
+}