From 8cd41e6a09e7279587dc29774669fbc13685566d Mon Sep 17 00:00:00 2001 From: Adam Dickmeiss Date: Tue, 12 Jan 2010 22:37:31 +0100 Subject: [PATCH] Add JSON encoder and decoder --- include/yaz/Makefile.am | 2 +- include/yaz/json.h | 121 +++++++++++++ src/Makefile.am | 3 +- src/json.c | 451 +++++++++++++++++++++++++++++++++++++++++++++++ test/.gitignore | 1 + test/Makefile.am | 5 +- test/tst_json.c | 136 ++++++++++++++ 7 files changed, 716 insertions(+), 3 deletions(-) create mode 100644 include/yaz/json.h create mode 100644 src/json.c create mode 100644 test/tst_json.c diff --git a/include/yaz/Makefile.am b/include/yaz/Makefile.am index cf41239..91a4672 100644 --- a/include/yaz/Makefile.am +++ b/include/yaz/Makefile.am @@ -19,7 +19,7 @@ pkginclude_HEADERS= backend.h ccl.h ccl_xml.h cql.h rpn2cql.h comstack.h \ z-grs.h z-mterm2.h z-opac.h z-rrf1.h z-rrf2.h z-sum.h z-sutrs.h z-uifr1.h \ z-univ.h z-oclcui.h zes-expi.h zes-exps.h zes-order.h zes-pquery.h \ zes-psched.h zes-admin.h zes-pset.h zes-update.h zes-update0.h \ - zoom.h z-charneg.h charneg.h soap.h srw.h zgdu.h matchstr.h + zoom.h z-charneg.h charneg.h soap.h srw.h zgdu.h matchstr.h json.h EXTRA_DIST = yaz-version.h.in diff --git a/include/yaz/json.h b/include/yaz/json.h new file mode 100644 index 0000000..b56ee4c --- /dev/null +++ b/include/yaz/json.h @@ -0,0 +1,121 @@ +/* This file is part of the YAZ toolkit. + * Copyright (C) 1995-2009 Index Data. + * All rights reserved. + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of Index Data nor the names of its contributors + * may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE REGENTS AND CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** \file json.h + \brief Header for JSON functions +*/ + +#ifndef YAZ_JSON_H +#define YAZ_JSON_H +#include + +YAZ_BEGIN_CDECL + +/** \brief JSON node type for json_node */ +enum json_node_type { + json_node_object, /**< JSON object, u.link[0] is object content */ + json_node_array, /**< JSON array, u.link[0] is array content */ + json_node_list, /**< JSON elements or JSON members, + u.link[0] is value, u.link[1] is next elemen in list */ + json_node_pair, /**< JSON pair, u.link[0] is name, u.link[1] is value */ + json_node_string, /**< JSON string, u.string is content */ + json_node_number, /**< JSON number (floating point), u.number is content */ + json_node_true, /**< JSON true */ + json_node_false, /**< JSON false */ + json_node_null /**< JSON null */ +}; + +/** \brief JSON node */ +struct json_node { + enum json_node_type type; + union { + char *string; + double number; + struct json_node *link[2]; + } u; +}; + +/** \brief JSON parser (opaque) */ +typedef struct json_parser_s *json_parser_t; + +/** \brief create JSON parser + \returns JSON parser handle +*/ +YAZ_EXPORT +json_parser_t json_parser_create(void); + +/** \brief destroys JSON parser + \param p JSON parser handle +*/ +YAZ_EXPORT +void json_parser_destroy(json_parser_t p); + +/** \brief parses JSON string + \param p JSON parser handle + \param json_str JSON string + \returns JSON tree or NULL if parse error occurred. + + The resulting tree should be removed with a call to json_remove_node. +*/ +YAZ_EXPORT +struct json_node *json_parser_parse(json_parser_t p, const char *json_str); + +/** \brief returns parser error + \param p JSON parser handle + \returns parse error msg + + This function should be called if json_parser_parse returns NULL . +*/ +YAZ_EXPORT +const char *json_parser_get_errmsg(json_parser_t p); + +/** \brief destroys JSON tree node and its children + \param n JSON node +*/ +YAZ_EXPORT +void json_remove_node(struct json_node *n); + +/** \brief converts JSON tree to JSON string + \param node JSON tree + \param result resulting JSON string buffer +*/ +YAZ_EXPORT +void json_write_wrbuf(struct json_node *node, WRBUF result); + +YAZ_END_CDECL + +#endif + +/* + * Local variables: + * c-basic-offset: 4 + * c-file-style: "Stroustrup" + * indent-tabs-mode: nil + * End: + * vim: shiftwidth=4 tabstop=8 expandtab + */ + diff --git a/src/Makefile.am b/src/Makefile.am index 032da2e..b3b62a3 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -102,7 +102,8 @@ libyaz_la_SOURCES=version.c options.c log.c \ record_conv.c retrieval.c elementset.c snprintf.c query-charset.c \ copy_types.c match_glob.c poll.c daemon.c \ iconv_encode_marc8.c iconv_encode_iso_8859_1.c iconv_encode_wchar.c \ - iconv_decode_marc8.c iconv_decode_iso5426.c iconv_decode_danmarc.c sc.c + iconv_decode_marc8.c iconv_decode_iso5426.c iconv_decode_danmarc.c sc.c \ + json.c libyaz_la_LDFLAGS=-version-info $(YAZ_VERSION_INFO) diff --git a/src/json.c b/src/json.c new file mode 100644 index 0000000..41b99eb --- /dev/null +++ b/src/json.c @@ -0,0 +1,451 @@ +/* This file is part of the YAZ toolkit. + * Copyright (C) 1995-2009 Index Data + * See the file LICENSE for details. + */ + +/** + * \file json.c + * \brief JSON encoding/decoding + */ + +#include + +#include +#include +#include +#include +#include + +#include + +struct json_parser_s { + const char *buf; + const char *cp; + const char *err_msg; +}; + +json_parser_t json_parser_create(void) +{ + json_parser_t p = (json_parser_t) xmalloc(sizeof(*p)); + + p->buf = 0; + p->cp = 0; + p->err_msg = 0; + return p; +} + +void json_parser_destroy(json_parser_t p) +{ + xfree(p); +} + +static int look_ch(json_parser_t p) +{ + while (*p->cp && strchr(" \t\r\n\f", *p->cp)) + (p->cp)++; + return *p->cp; +} + +static void move_ch(json_parser_t p) +{ + if (*p->cp) + (p->cp)++; +} + +static struct json_node *json_new_node(json_parser_t p, enum json_node_type type) +{ + struct json_node *n = (struct json_node *) xmalloc(sizeof(*n)); + n->type = type; + n->u.link[0] = n->u.link[1] = 0; + return n; +} + +void json_remove_node(struct json_node *n) +{ + if (!n) + return; + switch (n->type) + { + case json_node_object: + case json_node_array: + case json_node_list: + case json_node_pair: + json_remove_node(n->u.link[0]); + json_remove_node(n->u.link[1]); + break; + case json_node_string: + xfree(n->u.string); + break; + case json_node_number: + case json_node_true: + case json_node_false: + case json_node_null: + break; + } + xfree(n); +} + +static struct json_node *json_parse_object(json_parser_t p); +static struct json_node *json_parse_array(json_parser_t p); + +static int json_one_char(const char **p, char *out) +{ + if (**p == '\\' && p[0][1]) + { + (*p)++; + switch(**p) + { + case '"': + *out = '"'; break; + case '\\': + *out = '\\'; break; + case '/': + *out = '/'; break; + case 'b': + *out = '\b'; break; + case 'f': + *out = '\b'; break; + case 'n': + *out = '\n'; break; + case 'r': + *out = '\r'; break; + case 't': + *out = '\t'; break; + case 'u': + if (p[0][1]) + { + unsigned code; + char *outp = out; + int error; + size_t outbytesleft = 6; + sscanf(*p + 1, "%4x", &code); + if (!yaz_write_UTF8_char(code, &outp, &outbytesleft, &error)) + { + *p += 5; + return outp - out; + } + } + default: + *out = '_'; break; + break; + } + (*p)++; + return 1; + } + else + { + *out = **p; + (*p)++; + return 1; + } +} + +static struct json_node *json_parse_string(json_parser_t p) +{ + struct json_node *n; + const char *cp; + char *dst; + int l = 0; + if (look_ch(p) != '\"') + { + p->err_msg = "string expected"; + return 0; + } + move_ch(p); + + cp = p->cp; + while (*cp && *cp != '"') + { + char out[6]; + l += json_one_char(&cp, out); + } + if (!*cp) + { + p->err_msg = "missing \""; + return 0; + } + n = json_new_node(p, json_node_string); + dst = n->u.string = (char *) xmalloc(l + 1); + + cp = p->cp; + while (*cp && *cp != '"') + { + char out[6]; + + l = json_one_char(&cp, out); + memcpy(dst, out, l); + dst += l; + } + *dst = '\0'; + p->cp = cp+1; + return n; +} + +static struct json_node *json_parse_number(json_parser_t p) +{ + struct json_node *n; + char *endptr; + double v; + + look_ch(p); // skip spaces + v = strtod(p->cp, &endptr); + + if (endptr == p->cp) + { + p->err_msg = "bad number"; + return 0; + } + p->cp = endptr; + n = json_new_node(p, json_node_number); + n->u.number = v; + return n; +} + +static struct json_node *json_parse_value(json_parser_t p) +{ + int c = look_ch(p); + if (c == '\"') + return json_parse_string(p); + else if (strchr("0123456789-+", c)) + return json_parse_number(p); + else if (c == '{') + return json_parse_object(p); + else if (c == '[') + return json_parse_array(p); + else + { + char tok[8]; + int i = 0; + while (c >= 'a' && c <= 'z' && i < 7) + { + tok[i++] = c; + p->cp++; + c = *p->cp; + } + tok[i] = 0; + if (!strcmp(tok, "true")) + return json_new_node(p, json_node_true); + else if (!strcmp(tok, "false")) + return json_new_node(p, json_node_false); + else if (!strcmp(tok, "null")) + return json_new_node(p, json_node_null); + else + { + p->err_msg = "bad value"; + return 0; + } + } +} + +static struct json_node *json_parse_elements(json_parser_t p) +{ + struct json_node *n1 = json_parse_value(p); + struct json_node *m0, *m1; + if (!n1) + return 0; + m0 = m1 = json_new_node(p, json_node_list); + m1->u.link[0] = n1; + while (look_ch(p) == ',') + { + struct json_node *n2, *m2; + move_ch(p); + n2 = json_parse_value(p); + if (!n2) + { + json_remove_node(m0); + return 0; + } + m2 = json_new_node(p, json_node_list); + m2->u.link[0] = n2; + + m1->u.link[1] = m2; + m1 = m2; + } + return m0; +} + +static struct json_node *json_parse_array(json_parser_t p) +{ + struct json_node *n; + if (look_ch(p) != '[') + { + p->err_msg = "expecting ["; + return 0; + } + move_ch(p); + n = json_new_node(p, json_node_array); + if (look_ch(p) != ']') + n->u.link[0] = json_parse_elements(p); + + if (look_ch(p) != ']') + { + p->err_msg = "expecting ]"; + json_remove_node(n); + return 0; + } + move_ch(p); + return n; +} + +static struct json_node *json_parse_pair(json_parser_t p) +{ + struct json_node *s = json_parse_string(p); + struct json_node *v, *n; + if (!s) + return 0; + if (look_ch(p) != ':') + { + json_remove_node(s); + return 0; + } + move_ch(p); + v = json_parse_value(p); + if (!v) + { + json_remove_node(s); + return 0; + } + n = json_new_node(p, json_node_pair); + n->u.link[0] = s; + n->u.link[1] = v; + return n; +} + +static struct json_node *json_parse_members(json_parser_t p) +{ + struct json_node *n1 = json_parse_pair(p); + struct json_node *m0, *m1; + if (!n1) + return 0; + m0 = m1 = json_new_node(p, json_node_list); + m1->u.link[0] = n1; + while (look_ch(p) == ',') + { + struct json_node *n2, *m2; + move_ch(p); + n2 = json_parse_pair(p); + if (!n2) + { + json_remove_node(m0); + return 0; + } + m2 = json_new_node(p, json_node_list); + m2->u.link[0] = n2; + + m1->u.link[1] = m2; + m1 = m2; + } + return m0; +} + +static struct json_node *json_parse_object(json_parser_t p) +{ + struct json_node *n; + if (look_ch(p) != '{') + { + p->err_msg = "{ expected"; + return 0; + } + move_ch(p); + + n = json_new_node(p, json_node_object); + if (look_ch(p) != '}') + { + struct json_node *m = json_parse_members(p); + if (!m) + { + json_remove_node(n); + return 0; + } + n->u.link[0] = m; + } + if (look_ch(p) != '}') + { + p->err_msg = "Missing }"; + json_remove_node(n); + return 0; + } + move_ch(p); + return n; +} + +struct json_node *json_parser_parse(json_parser_t p, const char *json_str) +{ + int c; + struct json_node *n; + p->buf = json_str; + p->cp = p->buf; + + n = json_parse_object(p); + c = look_ch(p); + if (c != 0) + { + p->err_msg = "extra characters"; + json_remove_node(n); + return 0; + } + return n; +} + +void json_write_wrbuf(struct json_node *node, WRBUF result) +{ + switch (node->type) + { + case json_node_object: + wrbuf_puts(result, "{"); + if (node->u.link[0]) + json_write_wrbuf(node->u.link[0], result); + wrbuf_puts(result, "}"); + break; + case json_node_array: + wrbuf_puts(result, "["); + if (node->u.link[0]) + json_write_wrbuf(node->u.link[0], result); + wrbuf_puts(result, "]"); + break; + case json_node_list: + json_write_wrbuf(node->u.link[0], result); + if (node->u.link[1]) + { + wrbuf_puts(result, ","); + json_write_wrbuf(node->u.link[1], result); + } + break; + case json_node_pair: + json_write_wrbuf(node->u.link[0], result); + wrbuf_puts(result, ":"); + json_write_wrbuf(node->u.link[1], result); + break; + case json_node_string: + wrbuf_puts(result, "\""); + wrbuf_puts(result, node->u.string); + wrbuf_puts(result, "\""); + break; + case json_node_number: + wrbuf_printf(result, "%lg", node->u.number); + break; + case json_node_true: + wrbuf_puts(result, "true"); + break; + case json_node_false: + wrbuf_puts(result, "false"); + break; + case json_node_null: + wrbuf_puts(result, "null"); + break; + } +} + +const char *json_parser_get_errmsg(json_parser_t p) +{ + return p->err_msg; +} + +/* + * Local variables: + * c-basic-offset: 4 + * c-file-style: "Stroustrup" + * indent-tabs-mode: nil + * End: + * vim: shiftwidth=4 tabstop=8 expandtab + */ diff --git a/test/.gitignore b/test/.gitignore index d310adc..dabacdc 100644 --- a/test/.gitignore +++ b/test/.gitignore @@ -28,6 +28,7 @@ tst_query_charset tst_icu_I18N tst_match_glob tst_rpn2cql +tst_json nfatest1 nfaxmltest1 tst_oid diff --git a/test/Makefile.am b/test/Makefile.am index 5daf359..2fe4ec2 100644 --- a/test/Makefile.am +++ b/test/Makefile.am @@ -5,7 +5,9 @@ check_PROGRAMS = tstxmalloc tsticonv tstnmem tstmatchstr tstwrbuf tstodr \ tstccl tstlog tstcomstack \ tstsoap1 tstsoap2 tstodrstack tstlogthread tstxmlquery tstpquery \ tst_comstack tst_filepath tst_record_conv tst_retrieval tst_tpath \ - tst_timing tst_query_charset tst_oid tst_icu_I18N tst_match_glob tst_rpn2cql + tst_timing tst_query_charset tst_oid tst_icu_I18N tst_match_glob \ + tst_rpn2cql tst_json + check_SCRIPTS = tstmarc.sh tstmarccol.sh tstcql2xcql.sh tstcql2pqf.sh tsticu.sh TESTS = $(check_PROGRAMS) $(check_SCRIPTS) @@ -77,3 +79,4 @@ tst_query_charset_SOURCES = tst_query_charset.c tst_icu_I18N_SOURCES = tst_icu_I18N.c tst_match_glob_SOURCES = tst_match_glob.c tst_rpn2cql_SOURCES = tst_rpn2cql.c +tst_json_SOURCES = tst_json.c diff --git a/test/tst_json.c b/test/tst_json.c new file mode 100644 index 0000000..d20beba --- /dev/null +++ b/test/tst_json.c @@ -0,0 +1,136 @@ +/** + * \file + * \brief JSON test + */ +#include +#include +#include +#include + +static int expect(json_parser_t p, const char *input, + const char *output) +{ + int ret = 0; + struct json_node *n; + + n = json_parser_parse(p, input); + if (n == 0 && output == 0) + ret = 1; + else if (n && output) + { + WRBUF result = wrbuf_alloc(); + + json_write_wrbuf(n, result); + if (strcmp(wrbuf_cstr(result), output) == 0) + ret = 1; + else + { + yaz_log(YLOG_WARN, "expected '%s' but got '%s'", + output, wrbuf_cstr(result)); + } + wrbuf_destroy(result); + } + else if (!n) + { + yaz_log(YLOG_WARN, "expected '%s' but got error '%s'", + output, json_parser_get_errmsg(p)); + } + json_remove_node(n); + return ret; +} + +static void tst1(void) +{ + json_parser_t p = json_parser_create(); + + YAZ_CHECK(p); + if (!p) + return; + + YAZ_CHECK(expect(p, "", 0)); + + YAZ_CHECK(expect(p, "1234", 0)); + + YAZ_CHECK(expect(p, "[ 1234 ]", 0)); + + YAZ_CHECK(expect(p, "{\"k\":tru}", 0)); + + YAZ_CHECK(expect(p, "{\"k\":null", 0)); + + YAZ_CHECK(expect(p, "{\"k\":nullx}", 0)); + + YAZ_CHECK(expect(p, "{\"k\":-", 0)); + + YAZ_CHECK(expect(p, "{\"k\":+", 0)); + + YAZ_CHECK(expect(p, "{\"k\":\"a}", 0)); + + YAZ_CHECK(expect(p, "{\"k\":\"a", 0)); + + YAZ_CHECK(expect(p, "{\"k\":\"", 0)); + + YAZ_CHECK(expect(p, "{", 0)); + + YAZ_CHECK(expect(p, "{}", "{}")); + + YAZ_CHECK(expect(p, "{} extra", 0)); + + YAZ_CHECK(expect(p, "{\"a\":[1,2,3}", 0)); + + YAZ_CHECK(expect(p, "{\"a\":[1,2,", 0)); + + YAZ_CHECK(expect(p, "{\"k\":\"wa\"}", "{\"k\":\"wa\"}")); + + YAZ_CHECK(expect(p, "{\"k\":null}", "{\"k\":null}")); + + YAZ_CHECK(expect(p, "{\"k\":false}", "{\"k\":false}")); + + YAZ_CHECK(expect(p, "{\"k\":true}", "{\"k\":true}")); + + YAZ_CHECK(expect(p, "{\"k\":12}", "{\"k\":12}")); + + YAZ_CHECK(expect(p, "{\"k\":-12}", "{\"k\":-12}")); + + YAZ_CHECK(expect(p, "{\"k\":1.2e6}", "{\"k\":1.2e+06}")); + + YAZ_CHECK(expect(p, "{\"k\":1e3}", "{\"k\":1000}")); + + YAZ_CHECK(expect(p, "{\"k\":\"\"}", "{\"k\":\"\"}")); + + YAZ_CHECK(expect(p, "{\"a\":1,\"b\":2}", "{\"a\":1,\"b\":2}")); + + YAZ_CHECK(expect(p, "{\"a\":1,\"b\":2,\"c\":3}", + "{\"a\":1,\"b\":2,\"c\":3}")); + + YAZ_CHECK(expect(p, "{\"a\":[]}", "{\"a\":[]}")); + + YAZ_CHECK(expect(p, "{\"a\":[1]}", "{\"a\":[1]}")); + + YAZ_CHECK(expect(p, "{\"a\":[1,2]}", "{\"a\":[1,2]}")); + + YAZ_CHECK(expect(p, "{\"a\":[1,2,3]}", "{\"a\":[1,2,3]}")); + + YAZ_CHECK(expect(p, "{\"k\":\"\\t\"}", "{\"k\":\"\x09\"}")); + + YAZ_CHECK(expect(p, "{\"k\":\"\\u0009\"}", "{\"k\":\"\x09\"}")); + + json_parser_destroy(p); +} + +int main (int argc, char **argv) +{ + YAZ_CHECK_INIT(argc, argv); + tst1(); + YAZ_CHECK_TERM; +} + +/* + * Local variables: + * c-basic-offset: 4 + * c-file-style: "Stroustrup" + * indent-tabs-mode: nil + * End: + * vim: shiftwidth=4 tabstop=8 expandtab + */ + + -- 1.7.10.4