aboutsummaryrefslogtreecommitdiff
path: root/test/json_test
diff options
context:
space:
mode:
authorToni Uhlig <matzeton@googlemail.com>2023-07-16 02:03:33 +0200
committerToni Uhlig <matzeton@googlemail.com>2023-07-16 02:03:33 +0200
commit5a40295c4cf0af5ea8da9ced04a4ce7d3621a080 (patch)
treecb21506e7b04d10b45d6066a0ee1655563d5d52b /test/json_test
Squashed 'flatcc/' content from commit 473da2a
git-subtree-dir: flatcc git-subtree-split: 473da2afa5ca435363f8c5e6569167aee6bc31c5
Diffstat (limited to 'test/json_test')
-rw-r--r--test/json_test/CMakeLists.txt64
-rw-r--r--test/json_test/flatcc_golden.c45
-rwxr-xr-xtest/json_test/json_test.sh74
-rw-r--r--test/json_test/test_basic_parse.c291
-rw-r--r--test/json_test/test_json.c882
-rw-r--r--test/json_test/test_json_parser.c164
-rw-r--r--test/json_test/test_json_printer.c129
7 files changed, 1649 insertions, 0 deletions
diff --git a/test/json_test/CMakeLists.txt b/test/json_test/CMakeLists.txt
new file mode 100644
index 0000000..332905d
--- /dev/null
+++ b/test/json_test/CMakeLists.txt
@@ -0,0 +1,64 @@
+include(CTest)
+
+set(INC_DIR "${PROJECT_SOURCE_DIR}/include")
+set(GEN_DIR "${CMAKE_CURRENT_BINARY_DIR}/generated")
+set(FBS_DIR "${PROJECT_SOURCE_DIR}/test/monster_test")
+
+set(DATA_DST "${CMAKE_CURRENT_BINARY_DIR}")
+set(DATA_SRC "${PROJECT_SOURCE_DIR}/test/flatc_compat")
+
+include_directories("${GEN_DIR}" "${INC_DIR}")
+
+add_custom_target(gen_monster_test_json ALL)
+add_custom_command (
+ TARGET gen_monster_test_json
+ COMMAND ${CMAKE_COMMAND} -E make_directory "${GEN_DIR}"
+ COMMAND ${CMAKE_COMMAND} -E copy "${DATA_SRC}/monsterdata_test.golden" "${DATA_DST}"
+ COMMAND ${CMAKE_COMMAND} -E copy "${DATA_SRC}/monsterdata_test.mon" "${DATA_DST}"
+ COMMAND flatcc_cli -av --json -o "${GEN_DIR}" "${FBS_DIR}/monster_test.fbs"
+ DEPENDS flatcc_cli "${FBS_DIR}/monster_test.fbs" "${FBS_DIR}/include_test1.fbs" "${FBS_DIR}/include_test2.fbs"
+)
+
+add_executable(test_basic_parse test_basic_parse.c)
+add_executable(test_json_parser test_json_parser.c)
+add_executable(test_json_printer test_json_printer.c)
+add_executable(test_json test_json.c)
+
+add_dependencies(test_basic_parse gen_monster_test_json)
+add_dependencies(test_json_parser gen_monster_test_json)
+add_dependencies(test_json_printer gen_monster_test_json)
+add_dependencies(test_json gen_monster_test_json)
+
+target_link_libraries(test_basic_parse flatccrt)
+target_link_libraries(test_json_parser flatccrt)
+target_link_libraries(test_json_printer flatccrt)
+target_link_libraries(test_json flatccrt)
+
+add_test(test_basic_parse test_basic_parse${CMAKE_EXECUTABLE_SUFFIX})
+add_test(test_json_parser test_json_parser${CMAKE_EXECUTABLE_SUFFIX})
+add_test(test_json_printer test_json_printer${CMAKE_EXECUTABLE_SUFFIX})
+add_test(test_json test_json${CMAKE_EXECUTABLE_SUFFIX})
+
+# Compile without default library in order to test various runtime flags
+set(RTPATH "${PROJECT_SOURCE_DIR}/src/runtime")
+set(RTSRC
+ "${RTPATH}/builder.c"
+ "${RTPATH}/emitter.c"
+ "${RTPATH}/refmap.c"
+ "${RTPATH}/verifier.c"
+ "${RTPATH}/json_parser.c"
+ "${RTPATH}/json_printer.c"
+)
+
+macro(jstest trg flags)
+ add_executable(${trg} test_json.c ${RTSRC})
+ add_dependencies(${trg} gen_monster_test_json)
+ add_test(${trg} ${trg}${CMAKE_EXECUTABLE_SUFFIX})
+ set_target_properties(${trg} PROPERTIES COMPILE_FLAGS ${flags})
+endmacro()
+
+jstest(json_test_uql "-DFLATCC_JSON_PARSE_ALLOW_UNQUOTED_LIST=1")
+jstest(json_test_uql_off "-DFLATCC_JSON_PARSE_ALLOW_UNQUOTED_LIST=0")
+jstest(json_test_uq "-DFLATCC_JSON_PARSE_ALLOW_UNQUOTED=1")
+jstest(json_test_uq_off "-DFLATCC_JSON_PARSE_ALLOW_UNQUOTED=0")
+jstest(json_test "-DFLATCC_JSON_PARSE_WIDE_SPACE=1")
diff --git a/test/json_test/flatcc_golden.c b/test/json_test/flatcc_golden.c
new file mode 100644
index 0000000..a22e3d3
--- /dev/null
+++ b/test/json_test/flatcc_golden.c
@@ -0,0 +1,45 @@
+/*
+ * Flatcc generated monster test binary based on parsing Google flatc's
+ * golden json file.
+ */
+static const unsigned char flatcc_golden_le[] = {
+ 0x0c, 0x00, 0x00, 0x00, 0x4d, 0x4f, 0x4e, 0x53, 0x00, 0x00, 0x00, 0x00, 0x20, 0xff, 0xff, 0xff,
+ 0x00, 0x00, 0x80, 0x3f, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x40, 0x40, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x40, 0x02, 0x00, 0x05, 0x00, 0x06, 0x00, 0x00, 0x00,
+ 0x50, 0x00, 0x00, 0x00, 0x9c, 0x00, 0x00, 0x00, 0x8c, 0x00, 0x00, 0x00, 0x74, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x60, 0x00, 0x00, 0x00, 0x38, 0x00, 0x00, 0x00, 0x41, 0xc9, 0x79, 0xdd,
+ 0x41, 0xc9, 0x79, 0xdd, 0x00, 0x00, 0x00, 0x00, 0x81, 0x91, 0x7b, 0xf2, 0xcd, 0x80, 0x0f, 0x6e,
+ 0x81, 0x91, 0x7b, 0xf2, 0xcd, 0x80, 0x0f, 0x6e, 0x71, 0xa4, 0x81, 0x8e, 0x71, 0xa4, 0x81, 0x8e,
+ 0xf1, 0xdd, 0x67, 0xc7, 0xdc, 0x48, 0xf9, 0x43, 0xf1, 0xdd, 0x67, 0xc7, 0xdc, 0x48, 0xf9, 0x43,
+ 0x02, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00,
+ 0x74, 0x65, 0x73, 0x74, 0x32, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x74, 0x65, 0x73, 0x74,
+ 0x31, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x14, 0x00, 0x1e, 0x00, 0x28, 0x00,
+ 0xd0, 0xff, 0xff, 0xff, 0x04, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x46, 0x72, 0x65, 0x64,
+ 0x00, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x00, 0x01, 0x02, 0x03, 0x04, 0x00, 0x00, 0x00,
+ 0x09, 0x00, 0x00, 0x00, 0x4d, 0x79, 0x4d, 0x6f, 0x6e, 0x73, 0x74, 0x65, 0x72, 0x00, 0x00, 0x00,
+ 0x0c, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x34, 0x00, 0x74, 0x00,
+ 0x04, 0x00, 0x00, 0x00, 0x24, 0x00, 0x28, 0x00, 0x00, 0x00, 0x2c, 0x00, 0x00, 0x00, 0x34, 0x00,
+ 0x30, 0x00, 0x38, 0x00, 0x3c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x40, 0x00, 0x44, 0x00, 0x4c, 0x00, 0x54, 0x00, 0x5c, 0x00, 0x60, 0x00, 0x64, 0x00, 0x6c, 0x00,
+};
+
+static const unsigned char flatcc_golden_be[] = {
+ 0x00, 0x00, 0x00, 0x0c, 0x53, 0x4e, 0x4f, 0x4d, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0x20,
+ 0x3f, 0x80, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x40, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x40, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x05, 0x06, 0x00, 0x00, 0x00,
+ 0x00, 0x50, 0x00, 0x00, 0x00, 0x00, 0x00, 0x9c, 0x00, 0x00, 0x00, 0x8c, 0x00, 0x00, 0x00, 0x74,
+ 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x60, 0x00, 0x00, 0x00, 0x38, 0xdd, 0x79, 0xc9, 0x41,
+ 0xdd, 0x79, 0xc9, 0x41, 0x00, 0x00, 0x00, 0x00, 0x6e, 0x0f, 0x80, 0xcd, 0xf2, 0x7b, 0x91, 0x81,
+ 0x6e, 0x0f, 0x80, 0xcd, 0xf2, 0x7b, 0x91, 0x81, 0x8e, 0x81, 0xa4, 0x71, 0x8e, 0x81, 0xa4, 0x71,
+ 0x43, 0xf9, 0x48, 0xdc, 0xc7, 0x67, 0xdd, 0xf1, 0x43, 0xf9, 0x48, 0xdc, 0xc7, 0x67, 0xdd, 0xf1,
+ 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x05,
+ 0x74, 0x65, 0x73, 0x74, 0x32, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x74, 0x65, 0x73, 0x74,
+ 0x31, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x0a, 0x14, 0x00, 0x00, 0x1e, 0x28, 0x00,
+ 0xff, 0xff, 0xff, 0xd0, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x04, 0x46, 0x72, 0x65, 0x64,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x00, 0x01, 0x02, 0x03, 0x04, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x09, 0x4d, 0x79, 0x4d, 0x6f, 0x6e, 0x73, 0x74, 0x65, 0x72, 0x00, 0x00, 0x00,
+ 0x00, 0x0c, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x34, 0x00, 0x74,
+ 0x00, 0x04, 0x00, 0x00, 0x00, 0x24, 0x00, 0x28, 0x00, 0x00, 0x00, 0x2c, 0x00, 0x00, 0x00, 0x34,
+ 0x00, 0x30, 0x00, 0x38, 0x00, 0x3c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x40, 0x00, 0x44, 0x00, 0x4c, 0x00, 0x54, 0x00, 0x5c, 0x00, 0x60, 0x00, 0x64, 0x00, 0x6c,
+};
diff --git a/test/json_test/json_test.sh b/test/json_test/json_test.sh
new file mode 100755
index 0000000..e08bab5
--- /dev/null
+++ b/test/json_test/json_test.sh
@@ -0,0 +1,74 @@
+#!/usr/bin/env bash
+
+set -e
+cd `dirname $0`/../..
+ROOT=`pwd`
+TMP=${ROOT}/build/tmp/test/json_test
+
+CC=${CC:-cc}
+${ROOT}/scripts/build.sh
+mkdir -p ${TMP}
+rm -rf ${TMP}/*
+
+# could also use --json to generate both at once
+bin/flatcc -av --json -o ${TMP} test/monster_test/monster_test.fbs
+
+cp test/json_test/*.c ${TMP}
+cp test/flatc_compat/monsterdata_test.golden ${TMP}
+cp test/flatc_compat/monsterdata_test.mon ${TMP}
+
+cd ${TMP}
+
+$CC -g -I ${ROOT}/include test_basic_parse.c \
+ ${ROOT}/lib/libflatccrt_d.a -o test_basic_parse_d
+
+$CC -g -I ${ROOT}/include test_json_parser.c \
+ ${ROOT}/lib/libflatccrt_d.a -o test_json_parser_d
+
+$CC -g -I ${ROOT}/include test_json_printer.c \
+ ${ROOT}/lib/libflatccrt_d.a -o test_json_printer_d
+
+$CC -g -I ${ROOT}/include test_json.c\
+ ${ROOT}/lib/libflatccrt_d.a -o test_json_d
+
+
+echo "running json basic parse test debug"
+./test_basic_parse_d
+
+echo "running json parser test debug"
+./test_json_parser_d
+
+echo "running json printer test debug"
+./test_json_printer_d
+
+echo "running json test debug"
+./test_json_d
+
+$CC -O2 -DNDEBUG -I ${ROOT}/include test_basic_parse.c \
+ ${ROOT}/lib/libflatccrt.a -o test_basic_parse
+
+#$CC -O3 -DNDEBUG -I ${ROOT}/include test_json_parser.c \
+#$CC -Os -save-temps -DNDEBUG -I ${ROOT}/include test_json_parser.c \
+
+$CC -O2 -DNDEBUG -I ${ROOT}/include test_json_parser.c \
+ ${ROOT}/lib/libflatccrt.a -o test_json_parser
+
+$CC -O2 -DNDEBUG -I ${ROOT}/include test_json_printer.c\
+ ${ROOT}/lib/libflatccrt.a -o test_json_printer
+
+$CC -O2 -DNDEBUG -I ${ROOT}/include test_json.c\
+ ${ROOT}/lib/libflatccrt.a -o test_json
+
+echo "running json basic parse test optimized"
+./test_basic_parse
+
+echo "running json parser test optimized"
+./test_json_parser
+
+echo "running json printer test optimimized"
+./test_json_printer
+
+echo "running json test optimized"
+./test_json
+
+echo "json tests passed"
diff --git a/test/json_test/test_basic_parse.c b/test/json_test/test_basic_parse.c
new file mode 100644
index 0000000..7b8f4ba
--- /dev/null
+++ b/test/json_test/test_basic_parse.c
@@ -0,0 +1,291 @@
+#include <stdio.h>
+#include "flatcc/flatcc_builder.h"
+#include "flatcc/flatcc_json_parser.h"
+
+/*
+ * Helper macros for generating compile time tries.
+ *
+ * - this is for prototyping - codegenerator does this without macroes.
+ */
+#define __FLATCC_CHARW(x, p) (((uint64_t)(x)) << ((p) * 8))
+#define __FLATCC_KW1(s) (__FLATCC_CHARW(s[0], 7))
+#define __FLATCC_KW2(s) (__FLATCC_KW1(s) | __FLATCC_CHARW(s[1], 6))
+#define __FLATCC_KW3(s) (__FLATCC_KW2(s) | __FLATCC_CHARW(s[2], 5))
+#define __FLATCC_KW4(s) (__FLATCC_KW3(s) | __FLATCC_CHARW(s[3], 4))
+#define __FLATCC_KW5(s) (__FLATCC_KW4(s) | __FLATCC_CHARW(s[4], 3))
+#define __FLATCC_KW6(s) (__FLATCC_KW5(s) | __FLATCC_CHARW(s[5], 2))
+#define __FLATCC_KW7(s) (__FLATCC_KW6(s) | __FLATCC_CHARW(s[6], 1))
+#define __FLATCC_KW8(s) (__FLATCC_KW7(s) | __FLATCC_CHARW(s[7], 0))
+#define __FLATCC_KW(s, n) __FLATCC_KW ## n(s)
+
+#define __FLATCC_MASKKW(n) ((~(uint64_t)0) << ((8 - (n)) * 8))
+#define __FLATCC_MATCHKW(w, s, n) ((__FLATCC_MASKKW(n) & (w)) == __FLATCC_KW(s, n))
+#define __FLATCC_LTKW(w, s, n) ((__FLATCC_MASKKW(n) & (w)) < __FLATCC_KW(s, n))
+
+
+const char g_data[] = " \
+ \
+{ \r\n \
+ \"first\": 1, \
+ \"second\": 2.0, \
+ \"seconds left\": 42, \
+ \"seconds lead\": 1, \n \
+ \"zulu\": \"really\" \n \
+} \
+";
+
+/*
+ * This is proof of concept test before code-generation to evaluate
+ * efficient parsing and buffer construction principles while scanning
+ * text such as a JSON. We do no use a schema per se, but implicitly
+ * define one in the way that we construct the parser.
+ */
+
+#define match(x) if (end > buf && buf[0] == x) { ++buf; } \
+ else { fprintf(stderr, "failed to match '%c'\n", x); \
+ buf = flatcc_json_parser_set_error(ctx, buf, end, \
+ flatcc_json_parser_error_invalid_character); goto fail; }
+
+/* Space is optional, but we do expect more input. */
+#define space() { \
+ buf = flatcc_json_parser_space(ctx, buf, end); \
+ if (buf == end) { fprintf(stderr, "parse failed\n"); goto fail; }} \
+
+#ifdef FLATCC_JSON_ALLOW_UNKNOWN_FIELD
+#define ignore_field() { \
+ buf = flatcc_json_parser_symbol_end(ctx, buf, end); \
+ space(); match(':'); space(); \
+ buf = flatcc_json_parser_generic_json(ctx, buf, end); \
+ if (buf == end) { \
+ goto fail; \
+ }}
+#else
+#define ignore_field() { \
+ buf = flatcc_json_parser_set_error(ctx, buf, end, flatcc_json_parser_error_unknown_symbol);\
+ goto fail; }
+#endif
+
+
+/*
+ * We build a flatbuffer dynamically without a schema, but we still need
+ * to assigned vtable entries.
+ */
+enum {
+ id_first = 0,
+ id_second = 1,
+ id_seconds_left = 2,
+ id_seconds_lead = 3,
+ id_zulu = 10
+};
+
+enum {
+ ctx_done = 0, ctx_t1_start, ctx_t1_again
+};
+
+const char *test(flatcc_builder_t *B, const char *buf, const char *end, int *ret)
+{
+ flatcc_json_parser_t parse_ctx, *ctx;
+ flatcc_builder_ref_t root = 0, ref, *p_ref;
+ uint64_t w;
+ const char *k;
+ char *s;
+ flatcc_json_parser_escape_buffer_t code;
+
+ void *p;
+
+ ctx = &parse_ctx;
+ memset(ctx, 0, sizeof(*ctx));
+ ctx->line = 1;
+ ctx->line_start = buf;
+
+ flatcc_builder_start_buffer(B, "TEST", 0, 0);
+
+ space(); match('{'); space();
+ flatcc_builder_start_table(B, id_zulu + 1);
+
+t1_again:
+
+ buf = flatcc_json_parser_symbol_start(ctx, buf, end);
+ w = flatcc_json_parser_symbol_part(buf, end);
+ k = end - buf > 8 ? buf + 8 : end;
+ /*
+ * We implement a trie here. Because we compare big endian
+ * any trailing garbage in a word is least significant
+ * and masked out in MATCH tests.
+ *
+ * When a keyword is a prefix of another, the shorter keyword
+ * must be tested first because any trailing "garbage" will
+ * be larger (or equal if at buffer end or invalid nulls are
+ * contained) than the short keyword, but if testing the long
+ * keyword, the shorter keyword may be either larger or smaller
+ * depending on what content follows.
+ *
+ * Errors result in `buf` being set to `end` so we need not test
+ * for errors all the time. We use space as a convenient bailout
+ * point.
+ */
+ if (__FLATCC_LTKW(w, "second", 6)) {
+ if (!__FLATCC_MATCHKW(w, "first", 5)) {
+ ignore_field();
+ } else {
+ buf = flatcc_json_parser_symbol_end(ctx, buf + 5, end);
+ space(); match(':'); space();
+ p = flatcc_builder_table_add(B, id_first, 1, 1);
+ if (!p) { goto fail; }
+ k = buf;
+ buf = flatcc_json_parser_uint8(ctx, buf, end, p);
+ /* Here we could optionally parse for symbolic constants. */
+ if (k == buf) { goto fail; };
+ /* Successfully parsed field. */
+ }
+ } else {
+ if (__FLATCC_LTKW(w, "zulu", 4)) {
+ if (__FLATCC_LTKW(w, "seconds ", 8)) {
+ if (!__FLATCC_MATCHKW(w, "second", 6)) {
+ ignore_field();
+ } else {
+ buf = flatcc_json_parser_symbol_end(ctx, buf + 6, end);
+ space(); match(':'); space();
+ p = flatcc_builder_table_add(B, id_second, 8, 8);
+ if (!p) { goto fail; }
+ k = buf;
+ buf = flatcc_json_parser_double(ctx, buf, end, p);
+ /* Here we could optionally parse for symbolic constants. */
+ if (k == buf) { goto fail; };
+ /* Successfully parsed field. */
+ }
+ } else {
+ if (!__FLATCC_MATCHKW(w, "seconds ", 8)) {
+ ignore_field();
+ } else {
+ /* We have multiple keys matching the first word, so we load another. */
+ buf = k;
+ w = flatcc_json_parser_symbol_part(buf, end);
+ k = end - buf > 8 ? buf + 8 : end;
+ if (__FLATCC_LTKW(w, "left", 4)) {
+ if (!__FLATCC_MATCHKW(w, "lead", 4)) {
+ ignore_field();
+ } else {
+ buf = flatcc_json_parser_symbol_end(ctx, buf + 4, end);
+ space(); match(':'); space();
+ p = flatcc_builder_table_add(B, id_seconds_lead, 8, 8);
+ if (!p) { goto fail; }
+ k = buf;
+ buf = flatcc_json_parser_int64(ctx, buf, end, p);
+ /* Here we could optionally parse for symbolic constants. */
+ if (k == buf) { goto fail; };
+ /* Successfully parsed field. */
+ }
+ } else {
+ if (!__FLATCC_MATCHKW(w, "left", 4)) {
+ ignore_field();
+ } else {
+ buf = flatcc_json_parser_symbol_end(ctx, buf + 4, end);
+ space(); match(':'); space();
+ p = flatcc_builder_table_add(B, id_seconds_left, 4, 4);
+ if (!p) { goto fail; }
+ k = buf;
+ buf = flatcc_json_parser_uint32(ctx, buf, end, p);
+ /* Here we could optionally parse for symbolic constants. */
+ if (k == buf) { goto fail; };
+ /* Successfully parsed field. */
+ }
+ }
+ }
+ }
+ } else {
+ if (!__FLATCC_MATCHKW(w, "zulu", 4)) {
+ ignore_field();
+ } else {
+ buf = flatcc_json_parser_symbol_end(ctx, buf + 4, end);
+ space(); match(':'); space();
+ /*
+ * Parse field as string. If we are lucky, we can
+ * create the string in one go, which is faster.
+ * We can't if the string contains escape codes.
+ */
+ buf = flatcc_json_parser_string_start(ctx, buf, end);
+ k = buf;
+ buf = flatcc_json_parser_string_part(ctx, buf, end);
+ if (buf == end) {
+ goto fail;
+ }
+ if (buf[0] == '\"') {
+ ref = flatcc_builder_create_string(B, k, (size_t)(buf - k));
+ } else {
+ /* Start string with enough space for what we have. */
+ flatcc_builder_start_string(B);
+ s = flatcc_builder_extend_string(B, (size_t)(buf - k));
+ if (!s) { goto fail; }
+ memcpy(s, k, (size_t)(buf - k));
+ do {
+ buf = flatcc_json_parser_string_escape(ctx, buf, end, code);
+ flatcc_builder_append_string(B, code + 1, (size_t)code[0]);
+ k = buf;
+ buf = flatcc_json_parser_string_part(ctx, buf, end);
+ if (buf == end) {
+ goto fail;
+ }
+ flatcc_builder_append_string(B, k, (size_t)(buf - k));
+ } while (buf[0] != '\"');
+ ref = flatcc_builder_end_string(B);
+ }
+ if (!ref) {
+ goto fail;
+ }
+ /* Duplicate fields may fail or assert. */
+ p_ref = flatcc_builder_table_add_offset(B, id_zulu);
+ if (!p_ref) {
+ goto fail;
+ }
+ *p_ref = ref;
+ buf = flatcc_json_parser_string_end(ctx, buf, end);
+ /* Successfully parsed field. */
+ }
+ }
+ }
+ space();
+ if (*buf == ',') {
+ ++buf;
+ space();
+ if (*buf != '}') {
+ goto t1_again;
+ }
+#if !FLATCC_JSON_PARSE_ALLOW_TRAILING_COMMA
+ return flatcc_json_parser_set_error(ctx, buf, end, flatcc_json_parser_error_trailing_comma);
+#endif
+ }
+ match('}');
+ root = flatcc_builder_end_table(B);
+
+ flatcc_builder_end_buffer(B, root);
+#if !FLATCC_JSON_PARSE_IGNORE_TRAILING_DATA
+ buf = flatcc_json_parser_space(ctx, buf, end);
+ if (buf != end) {
+ fprintf(stderr, "extra characters in input\n");
+ goto fail;
+ }
+#endif
+fail:
+ if (ctx->error) {
+ fprintf(stderr, "%d:%d: %s\n", (int)ctx->line, (int)(ctx->error_loc - ctx->line_start + 1), flatcc_json_parser_error_string(ctx->error));
+ flatcc_builder_reset(B);
+ } else {
+ fprintf(stderr, "parse accepted\n");
+ }
+ *ret = ctx->error;
+ return buf;
+}
+
+int main(void)
+{
+ int ret = -1;
+ flatcc_builder_t builder;
+
+ flatcc_builder_init(&builder);
+
+ test(&builder, g_data, g_data + sizeof(g_data) - 1, &ret);
+
+ flatcc_builder_clear(&builder);
+ return ret;
+}
diff --git a/test/json_test/test_json.c b/test/json_test/test_json.c
new file mode 100644
index 0000000..aeee13a
--- /dev/null
+++ b/test/json_test/test_json.c
@@ -0,0 +1,882 @@
+#include <stdio.h>
+#include "monster_test_json_parser.h"
+#include "monster_test_json_printer.h"
+#include "monster_test_verifier.h"
+
+#include "flatcc/support/hexdump.h"
+
+#define UQL FLATCC_JSON_PARSE_ALLOW_UNQUOTED_LIST
+#define UQ FLATCC_JSON_PARSE_ALLOW_UNQUOTED
+
+#undef ns
+#define ns(x) FLATBUFFERS_WRAP_NAMESPACE(MyGame_Example, x)
+
+#undef nsf
+#define nsf(x) FLATBUFFERS_WRAP_NAMESPACE(Fantasy, x)
+
+struct test_scope {
+ const char *identifier;
+ flatcc_json_parser_table_f *parser;
+ flatcc_json_printer_table_f *printer;
+ flatcc_table_verifier_f *verifier;
+};
+
+static const struct test_scope Monster = {
+ /* The is the schema global file identifier. */
+ ns(Monster_file_identifier),
+ ns(Monster_parse_json_table),
+ ns(Monster_print_json_table),
+ ns(Monster_verify_table)
+};
+
+static const struct test_scope Alt = {
+ /* This is the type hash identifier. */
+ ns(Alt_type_identifier),
+ ns(Alt_parse_json_table),
+ ns(Alt_print_json_table),
+ ns(Alt_verify_table)
+};
+
+static const struct test_scope Movie = {
+ /* This is the type hash identifier. */
+ nsf(Movie_type_identifier),
+ nsf(Movie_parse_json_table),
+ nsf(Movie_print_json_table),
+ nsf(Movie_verify_table)
+};
+
+int test_json(const struct test_scope *scope, char *json,
+ char *expect, int expect_err,
+ flatcc_json_parser_flags_t parse_flags, flatcc_json_printer_flags_t print_flags, int line)
+{
+ int ret = -1;
+ int err;
+ void *flatbuffer = 0;
+ char *buf = 0;
+ size_t flatbuffer_size, buf_size;
+ flatcc_builder_t builder, *B;
+ flatcc_json_parser_t parser_ctx;
+ flatcc_json_printer_t printer_ctx;
+ int i;
+
+ B = &builder;
+ flatcc_builder_init(B);
+ flatcc_json_printer_init_dynamic_buffer(&printer_ctx, 0);
+ flatcc_json_printer_set_flags(&printer_ctx, print_flags);
+ err = flatcc_json_parser_table_as_root(B, &parser_ctx, json, strlen(json), parse_flags,
+ scope->identifier, scope->parser);
+ if (err != expect_err) {
+ if (expect_err) {
+ if (err) {
+ fprintf(stderr, "%d: json test: parse failed with: %s\n",
+ line, flatcc_json_parser_error_string(err));
+ fprintf(stderr, "but expected to fail with: %s\n",
+ flatcc_json_parser_error_string(expect_err));
+ fprintf(stderr, "%s\n", json);
+ } else {
+ fprintf(stderr, "%d: json test: parse successful, but expected to fail with: %s\n",
+ line, flatcc_json_parser_error_string(expect_err));
+ fprintf(stderr, "%s\n", json);
+ }
+ } else {
+ fprintf(stderr, "%d: json test: parse failed: %s\n", line, flatcc_json_parser_error_string(err));
+ fprintf(stderr, "%s\n", json);
+ }
+ for (i = 0; i < parser_ctx.pos - 1; ++i) {
+ fprintf(stderr, " ");
+ }
+ fprintf(stderr, "^\n");
+ goto failed;
+ }
+ if (expect_err) {
+ ret = 0;
+ goto done;
+ }
+ flatbuffer = flatcc_builder_finalize_aligned_buffer(B, &flatbuffer_size);
+ if ((ret = flatcc_verify_table_as_root(flatbuffer, flatbuffer_size, scope->identifier, scope->verifier))) {
+ fprintf(stderr, "%s:%d: buffer verification failed: %s\n",
+ __FILE__, line, flatcc_verify_error_string(ret));
+ goto failed;
+ }
+
+ flatcc_json_printer_table_as_root(&printer_ctx, flatbuffer, flatbuffer_size, scope->identifier, scope->printer);
+ buf = flatcc_json_printer_get_buffer(&printer_ctx, &buf_size);
+ if (!buf || strcmp(expect, buf)) {
+ fprintf(stderr, "%d: json test: printed buffer not as expected, got:\n", line);
+ fprintf(stderr, "%s\n", buf);
+ fprintf(stderr, "expected:\n");
+ fprintf(stderr, "%s\n", expect);
+ goto failed;
+ }
+ ret = 0;
+
+done:
+ flatcc_builder_aligned_free(flatbuffer);
+ flatcc_builder_clear(B);
+ flatcc_json_printer_clear(&printer_ctx);
+ return ret;
+
+failed:
+ if (flatbuffer) {
+ hexdump("parsed buffer", flatbuffer, flatbuffer_size, stderr);
+ }
+ ret = -1;
+ goto done;
+}
+
+#define BEGIN_TEST(name) int ret = 0; const struct test_scope *scope = &name
+#define END_TEST() return ret;
+
+#define TEST(x, y) \
+ ret |= test_json(scope, (x), (y), 0, 0, 0, __LINE__);
+
+#define TEST_ERROR(x, err) \
+ ret |= test_json(scope, (x), 0, err, 0, 0, __LINE__);
+
+#define TEST_FLAGS(fparse, fprint, x, y) \
+ ret |= test_json(scope, (x), (y), 0, (fparse), (fprint), __LINE__);
+
+#define TEST_ERROR_FLAGS(fparse, fprint, x, err) \
+ ret |= test_json(scope, (x), 0, err, (fparse), (fprint), __LINE__);
+
+int edge_case_tests(void)
+{
+ BEGIN_TEST(Monster);
+/*
+ * Each symbolic value is type coerced and added. One might expect
+ * or'ing flags together, but it doesn't work with signed values
+ * and floating point target values. We would either need a much
+ * more complicated parser or restrict the places where symbols are
+ * allowed.
+ */
+#if 0
+ TEST( "{ name: \"Monster\", color: \"Green Blue Red Blue\"}",
+ "{\"name\":\"Monster\",\"color\":\"Red Green Blue\"}");
+#else
+#if UQ
+ TEST( "{ name: \"Monster\", color: \"Green Blue Red Blue\"}",
+ "{\"name\":\"Monster\",\"color\":19}");
+#else
+ TEST( "{ \"name\": \"Monster\", \"color\": \"Green Blue Red Blue\"}",
+ "{\"name\":\"Monster\",\"color\":19}");
+#endif
+#endif
+
+/*
+ * If a value is stored, even if default, it is also printed.
+ * This option can also be flagged compile time for better performance.
+ */
+ TEST_FLAGS(flatcc_json_parser_f_force_add, 0,
+ "{ \"name\": \"Monster\", \"color\": 8}",
+ "{\"name\":\"Monster\",\"color\":\"Blue\"}");
+
+ TEST_FLAGS(0, flatcc_json_printer_f_noenum,
+ "{ \"name\": \"Monster\", \"color\": \"Green\"}",
+ "{\"name\":\"Monster\",\"color\":2}");
+
+ TEST_FLAGS(flatcc_json_parser_f_force_add, flatcc_json_printer_f_skip_default,
+ "{ \"name\": \"Monster\", \"color\": 8}",
+ "{\"name\":\"Monster\"}");
+
+ TEST_FLAGS(0, flatcc_json_printer_f_force_default,
+ "{ \"name\": \"Monster\", \"testf\":3.0}",
+"{\"mana\":150,\"hp\":100,\"name\":\"Monster\",\"color\":\"Blue\",\"testbool\":true,\"testhashs32_fnv1\":0,\"testhashu32_fnv1\":0,\"testhashs64_fnv1\":0,\"testhashu64_fnv1\":0,\"testhashs32_fnv1a\":0,\"testhashu32_fnv1a\":0,\"testhashs64_fnv1a\":0,\"testhashu64_fnv1a\":0,\"testf\":3,\"testf2\":3,\"testf3\":0}");
+
+
+ /*
+ * Cannot test the default of testf field because float is printed as double with
+ * configuration dependent precision.
+ */
+#if 0
+ TEST_FLAGS(0, flatcc_json_printer_f_force_default,
+ "{ \"name\": \"Monster\", \"testf3\":3.14159012}",
+"{\"mana\":150,\"hp\":100,\"name\":\"Monster\",\"color\":\"Blue\",\"testbool\":true,\"testhashs32_fnv1\":0,\"testhashu32_fnv1\":0,\"testhashs64_fnv1\":0,\"testhashu64_fnv1\":0,\"testhashs32_fnv1a\":0,\"testhashu32_fnv1a\":0,\"testhashs64_fnv1a\":0,\"testhashu64_fnv1a\":0,\"testf\":3.14159,\"testf2\":3,\"testf3\":0}");
+#endif
+
+ TEST_FLAGS(flatcc_json_parser_f_force_add, 0,
+ "{ \"name\": \"Monster\", \"color\": \"Blue\"}",
+ "{\"name\":\"Monster\",\"color\":\"Blue\"}");
+
+ TEST_FLAGS(flatcc_json_parser_f_skip_unknown, 0,
+ "{ \"name\": \"Monster\", \"xcolor\": \"Green\", \"hp\": 42}",
+ "{\"hp\":42,\"name\":\"Monster\"}");
+
+ TEST_FLAGS(flatcc_json_parser_f_skip_unknown, flatcc_json_printer_f_unquote,
+ "{ \"name\": \"Monster\", \"xcolor\": \"Green\", \"hp\": 42}",
+ "{hp:42,name:\"Monster\"}");
+
+ /* Also test generic parser used with unions with late type field. */
+ TEST_FLAGS(flatcc_json_parser_f_skip_unknown, 0,
+ "{ \"name\": \"Monster\", \"xcolor\": \"Green\", "
+ "\"foobar\": { \"a\": [1, 2.0, ], \"a1\": {}, \"b\": null, \"c\":[], }, \"hp\": 42 }",
+ "{\"hp\":42,\"name\":\"Monster\"}");
+#if UQ
+/*
+ * If a value is stored, even if default, it is also printed.
+ * This option can also be flagged compile time for better performance.
+ */
+ TEST_FLAGS(flatcc_json_parser_f_force_add, 0,
+ "{ name: \"Monster\", color: 8}",
+ "{\"name\":\"Monster\",\"color\":\"Blue\"}");
+
+ TEST_FLAGS(0, flatcc_json_printer_f_noenum,
+ "{ name: \"Monster\", color: Green}",
+ "{\"name\":\"Monster\",\"color\":2}");
+
+ TEST_FLAGS(flatcc_json_parser_f_force_add, flatcc_json_printer_f_skip_default,
+ "{ name: \"Monster\", color: 8}",
+ "{\"name\":\"Monster\"}");
+
+ TEST_FLAGS(0, flatcc_json_printer_f_force_default,
+ "{ name: \"Monster\"}",
+"{\"mana\":150,\"hp\":100,\"name\":\"Monster\",\"color\":\"Blue\",\"testbool\":true,\"testhashs32_fnv1\":0,\"testhashu32_fnv1\":0,\"testhashs64_fnv1\":0,\"testhashu64_fnv1\":0,\"testhashs32_fnv1a\":0,\"testhashu32_fnv1a\":0,\"testhashs64_fnv1a\":0,\"testhashu64_fnv1a\":0,\"testf\":314159,\"testf2\":3,\"testf3\":0}");
+
+ TEST_FLAGS(flatcc_json_parser_f_force_add, 0,
+ "{ name: \"Monster\", color: Blue}",
+ "{\"name\":\"Monster\",\"color\":\"Blue\"}");
+
+ TEST_FLAGS(flatcc_json_parser_f_skip_unknown, 0,
+ "{ name: \"Monster\", xcolor: Green, hp: 42}",
+ "{\"hp\":42,\"name\":\"Monster\"}");
+
+ TEST_FLAGS(flatcc_json_parser_f_skip_unknown, flatcc_json_printer_f_unquote,
+ "{ name: \"Monster\", xcolor: Green, hp: 42}",
+ "{hp:42,name:\"Monster\"}");
+
+ /* Also test generic parser used with unions with late type field. */
+ TEST_FLAGS(flatcc_json_parser_f_skip_unknown, 0,
+ "{ name: \"Monster\", xcolor: Green, "
+ "foobar: { a: [1, 2.0, ], a1: {}, b: null, c:[], }, hp: 42 }",
+ "{\"hp\":42,\"name\":\"Monster\"}");
+#endif
+
+/* Without skip unknown, we should expect failure. */
+#if 0
+ TEST( "{ name: \"Monster\", xcolor: Green}",
+ "{\"name\":\"Monster\"}");
+#endif
+
+/* We do not support null. */
+#if 0
+ TEST(
+ "{ name: \"Monster\", test_type: null }",
+ "{\"name\":\"Monster\"}");
+#endif
+
+/*
+ * We do not allow empty flag strings because they might mean
+ * either default value, or 0.
+ */
+#if 0
+ /* Questionable if this really is an error. */
+ TEST( "{ name: \"Monster\", color: \"\"}",
+ "{\"name\":\"Monster\",\"color\":0}"); // TODO: should this be color:"" ?
+
+ TEST( "{ name: \"Monster\", color: \" \"}",
+ "{\"name\":\"Monster\",\"color\":0}");
+
+#endif
+
+ END_TEST();
+}
+
+int error_case_tests(void)
+{
+ BEGIN_TEST(Monster);
+
+ TEST_ERROR( "{ \"nickname\": \"Monster\" }",
+ flatcc_json_parser_error_unknown_symbol );
+ TEST_ERROR( "{ \"name\": \"Monster\", \"test_type\": \"Monster\", \"test\": { \"nickname\": \"Joker\", \"color\": \"Red\" } } }",
+ flatcc_json_parser_error_unknown_symbol );
+ TEST_ERROR( "{ \"name\": \"Monster\", \"test_type\": \"Monster\", \"test\": { \"name\": \"Joker\", \"colour\": \"Red\" } } }",
+ flatcc_json_parser_error_unknown_symbol );
+ TEST_ERROR( "{ \"name\": \"Monster\", \"testarrayoftables\": [ { \"nickname\": \"Joker\", \"color\": \"Red\" } ] }",
+ flatcc_json_parser_error_unknown_symbol );
+ TEST_ERROR( "{ \"name\": \"Monster\", \"testarrayoftables\": [ { \"name\": \"Joker\", \"colour\": \"Red\" } ] }",
+ flatcc_json_parser_error_unknown_symbol );
+ TEST_ERROR( "{ \"name\": \"Monster\", \"testarrayoftables\": ["
+ "{ \"name\": \"Joker\", \"color\": \"Red\", \"test_type\": \"Monster\", \"test\": { \"nickname\": \"Harley\", \"color\": \"Blue\" } } ] }",
+ flatcc_json_parser_error_unknown_symbol );
+ TEST_ERROR( "{ \"name\": \"Monster\", \"testarrayoftables\": ["
+ "{ \"name\": \"Joker\", \"color\": \"Red\", \"test_type\": \"Monster\", \"test\": { \"name\": \"Harley\", \"colour\": \"Blue\" } } ] }",
+ flatcc_json_parser_error_unknown_symbol );
+ TEST_ERROR( "{ \"name\": \"Monster\", \"testarrayoftables\": ["
+ "{ \"name\": \"Joker\", \"test_type\": \"Monster\", \"test\": { \"nickname\": \"Harley\" } },"
+ "{ \"name\": \"Bonnie\", \"test_type\": \"Monster\", \"test\": { \"name\": \"Clyde\" } } ] }",
+ flatcc_json_parser_error_unknown_symbol );
+ TEST_ERROR( "{ \"name\": \"Monster\", \"testarrayoftables\": ["
+ "{ \"name\": \"Joker\", \"test_type\": \"Monster\", \"test\": { \"name\": \"Harley\" } },"
+ "{ \"name\": \"Bonnie\", \"test_type\": \"Monster\", \"test\": { \"nickname\": \"Clyde\" } } ] }",
+ flatcc_json_parser_error_unknown_symbol );
+
+#if !UQ
+ TEST_ERROR( "{ nickname: \"Monster\" }",
+ flatcc_json_parser_error_unexpected_character );
+
+ TEST_ERROR( "{ \"name\": \"Monster\", \"color\": Green }",
+ flatcc_json_parser_error_unexpected_character );
+
+ TEST_ERROR( "{ \"name\": \"Monster\", \"color\": Green Red Blue }",
+ flatcc_json_parser_error_unexpected_character );
+#endif
+
+#if UQ
+ TEST_ERROR( "{ nickname: \"Monster\" }",
+ flatcc_json_parser_error_unknown_symbol );
+ TEST_ERROR( "{ name: \"Monster\", test_type: Monster, test: { nickname: \"Joker\", color: \"Red\" } } }",
+ flatcc_json_parser_error_unknown_symbol );
+ TEST_ERROR( "{ name: \"Monster\", test_type: Monster, test: { name: \"Joker\", colour: \"Red\" } } }",
+ flatcc_json_parser_error_unknown_symbol );
+ TEST_ERROR( "{ name: \"Monster\", testarrayoftables: [ { nickname: \"Joker\", color: \"Red\" } ] }",
+ flatcc_json_parser_error_unknown_symbol );
+ TEST_ERROR( "{ name: \"Monster\", testarrayoftables: [ { name: \"Joker\", colour: \"Red\" } ] }",
+ flatcc_json_parser_error_unknown_symbol );
+ TEST_ERROR( "{ name: \"Monster\", testarrayoftables: ["
+ "{ name: \"Joker\", color: \"Red\", test_type: Monster, test: { nickname: \"Harley\", color: \"Blue\" } } ] }",
+ flatcc_json_parser_error_unknown_symbol );
+ TEST_ERROR( "{ name: \"Monster\", testarrayoftables: ["
+ "{ name: \"Joker\", color: \"Red\", test_type: Monster, test: { name: \"Harley\", colour: \"Blue\" } } ] }",
+ flatcc_json_parser_error_unknown_symbol );
+ TEST_ERROR( "{ name: \"Monster\", testarrayoftables: ["
+ "{ name: \"Joker\", test_type: Monster, test: { nickname: \"Harley\" } },"
+ "{ name: \"Bonnie\", test_type: Monster, test: { name: \"Clyde\" } } ] }",
+ flatcc_json_parser_error_unknown_symbol );
+ TEST_ERROR( "{ name: \"Monster\", testarrayoftables: ["
+ "{ name: \"Joker\", test_type: Monster, test: { name: \"Harley\" } },"
+ "{ name: \"Bonnie\", test_type: Monster, test: { nickname: \"Clyde\" } } ] }",
+ flatcc_json_parser_error_unknown_symbol );
+
+#endif
+
+ END_TEST();
+}
+
+#define RANDOM_BASE64 "zLOuiUjH49tz4Ap2JnmpTX5NqoiMzlD8hSw45QCS2yaSp7UYoA" \
+ "oE8KpY/5pKYmk+54NI40hyeyZ1zRUE4vKQT0hEdVl0iXq2fqPamkVD1AZlVvQJ1m00PaoXOSgG+64Zv+Uygw=="
+
+#define RANDOM_BASE64_NOPAD "zLOuiUjH49tz4Ap2JnmpTX5NqoiMzlD8hSw45QCS2yaSp7UYoA" \
+ "oE8KpY/5pKYmk+54NI40hyeyZ1zRUE4vKQT0hEdVl0iXq2fqPamkVD1AZlVvQJ1m00PaoXOSgG+64Zv+Uygw"
+
+#define RANDOM_BASE64URL "zLOuiUjH49tz4Ap2JnmpTX5NqoiMzlD8hSw45QCS2yaSp7UYoA" \
+ "oE8KpY_5pKYmk-54NI40hyeyZ1zRUE4vKQT0hEdVl0iXq2fqPamkVD1AZlVvQJ1m00PaoXOSgG-64Zv-Uygw=="
+
+#define RANDOM_BASE64URL_NOPAD "zLOuiUjH49tz4Ap2JnmpTX5NqoiMzlD8hSw45QCS2yaSp7UYoA" \
+ "oE8KpY_5pKYmk-54NI40hyeyZ1zRUE4vKQT0hEdVl0iXq2fqPamkVD1AZlVvQJ1m00PaoXOSgG-64Zv-Uygw"
+
+int base64_tests(void)
+{
+ BEGIN_TEST(Monster);
+
+ /* Reference */
+ TEST( "{ \"name\": \"Monster\" }",
+ "{\"name\":\"Monster\"}");
+
+ TEST( "{ \"name\": \"Monster\", \"testbase64\":{} }",
+ "{\"name\":\"Monster\",\"testbase64\":{}}");
+
+
+ TEST( "{ \"name\": \"Monster\", \"testbase64\":{ \"data\":\"" RANDOM_BASE64 "\"} }",
+ "{\"name\":\"Monster\",\"testbase64\":{\"data\":\"" RANDOM_BASE64 "\"}}");
+
+ TEST( "{ \"name\": \"Monster\", \"testbase64\":{ \"urldata\":\"" RANDOM_BASE64URL "\"} }",
+ "{\"name\":\"Monster\",\"testbase64\":{\"urldata\":\"" RANDOM_BASE64URL "\"}}");
+
+ TEST( "{ \"name\": \"Monster\", \"testbase64\":{ \"data\":\"" RANDOM_BASE64_NOPAD "\"} }",
+ "{\"name\":\"Monster\",\"testbase64\":{\"data\":\"" RANDOM_BASE64 "\"}}");
+
+ TEST( "{ \"name\": \"Monster\", \"testbase64\":{ \"urldata\":\"" RANDOM_BASE64URL_NOPAD "\"} }",
+ "{\"name\":\"Monster\",\"testbase64\":{\"urldata\":\"" RANDOM_BASE64URL "\"}}");
+
+ TEST_ERROR( "{ \"name\": \"Monster\", \"testbase64\":{ \"data\":\"" RANDOM_BASE64URL "\"} }",
+ flatcc_json_parser_error_base64);
+
+ TEST_ERROR( "{ \"name\": \"Monster\", \"testbase64\":{ \"urldata\":\"" RANDOM_BASE64 "\"} }",
+ flatcc_json_parser_error_base64url);
+
+/* Test case from Googles flatc implementation. */
+#if UQ
+ TEST( "{name: \"Monster\","
+ "testbase64: {"
+ "data: \"23A/47d450+sdfx9+wRYIS09ckas/asdFBQ=\","
+ "urldata: \"23A_47d450-sdfx9-wRYIS09ckas_asdFBQ=\","
+ "nested: \"FAAAAE1PTlMMAAwAAAAEAAYACAAMAAAAAAAAAAQAAAANAAAATmVzdGVkTW9uc3RlcgAAAA==\""
+ "}}",
+ "{\"name\":\"Monster\","
+ "\"testbase64\":{"
+ "\"data\":\"23A/47d450+sdfx9+wRYIS09ckas/asdFBQ=\","
+ "\"urldata\":\"23A_47d450-sdfx9-wRYIS09ckas_asdFBQ=\","
+ "\"nested\":\"FAAAAE1PTlMMAAwAAAAEAAYACAAMAAAAAAAAAAQAAAANAAAATmVzdGVkTW9uc3RlcgAAAA==\""
+ "}}");
+
+ TEST( "{name: \"Monster\","
+ "testbase64: {"
+ "data: \"23A/47d450+sdfx9+wRYIS09ckas/asdFBQ\","
+ "urldata: \"23A_47d450-sdfx9-wRYIS09ckas_asdFBQ\","
+ "nested: \"FAAAAE1PTlMMAAwAAAAEAAYACAAMAAAAAAAAAAQAAAANAAAATmVzdGVkTW9uc3RlcgAAAA\""
+ "}}",
+ "{\"name\":\"Monster\","
+ "\"testbase64\":{"
+ "\"data\":\"23A/47d450+sdfx9+wRYIS09ckas/asdFBQ=\","
+ "\"urldata\":\"23A_47d450-sdfx9-wRYIS09ckas_asdFBQ=\","
+ "\"nested\":\"FAAAAE1PTlMMAAwAAAAEAAYACAAMAAAAAAAAAAQAAAANAAAATmVzdGVkTW9uc3RlcgAAAA==\""
+ "}}");
+#endif
+
+ END_TEST();
+}
+
+int mixed_type_union_tests(void)
+{
+ BEGIN_TEST(Movie);
+
+ /* Reference */
+
+ TEST( "{ \"main_character_type\": \"Rapunzel\", \"main_character\": { \"hair_length\": 19 } }",
+ "{\"main_character_type\":\"Rapunzel\",\"main_character\":{\"hair_length\":19}}");
+
+ TEST( "{ \"main_character_type\": \"Rapunzel\", \"main_character\": { \"hair_length\": 19 },"
+ " \"side_kick_type\": \"Other\", \"side_kick\": \"a donkey\"}",
+ "{\"main_character_type\":\"Rapunzel\",\"main_character\":{\"hair_length\":19},"
+ "\"side_kick_type\":\"Other\",\"side_kick\":\"a donkey\"}");
+
+ TEST( "{ \"main_character_type\": \"Rapunzel\", \"main_character\": { \"hair_length\": 19 },"
+ " \"side_kick_type\": \"Fantasy.Character.Other\", \"side_kick\": \"a donkey\"}}",
+ "{\"main_character_type\":\"Rapunzel\",\"main_character\":{\"hair_length\":19},"
+ "\"side_kick_type\":\"Other\",\"side_kick\":\"a donkey\"}");
+
+ TEST( "{ \"main_character_type\": \"Rapunzel\", \"main_character\": { \"hair_length\": 19 },"
+ " \"side_kick_type\": \"Fantasy.Character.Other\", \"side_kick\": \"a donkey\","
+ " \"antagonist_type\": \"MuLan\", \"antagonist\": {\"sword_attack_damage\": 42}}",
+ "{\"main_character_type\":\"Rapunzel\",\"main_character\":{\"hair_length\":19},"
+ "\"antagonist_type\":\"MuLan\",\"antagonist\":{\"sword_attack_damage\":42},"
+ "\"side_kick_type\":\"Other\",\"side_kick\":\"a donkey\"}");
+
+ TEST( "{ \"main_character_type\": \"Rapunzel\", \"main_character\": { \"hair_length\": 19 },"
+ " \"side_kick_type\": \"Fantasy.Character.Other\", \"side_kick\": \"a donkey\","
+ " \"antagonist_type\": \"MuLan\", \"antagonist\": {\"sword_attack_damage\": 42},"
+ " \"characters_type\": [], \"characters\": []}",
+ "{\"main_character_type\":\"Rapunzel\",\"main_character\":{\"hair_length\":19},"
+ "\"antagonist_type\":\"MuLan\",\"antagonist\":{\"sword_attack_damage\":42},"
+ "\"side_kick_type\":\"Other\",\"side_kick\":\"a donkey\","
+ "\"characters_type\":[],\"characters\":[]}")
+
+ TEST( "{ \"main_character_type\": \"Rapunzel\", \"main_character\": { \"hair_length\": 19 },"
+ " \"side_kick_type\": \"Fantasy.Character.Other\", \"side_kick\": \"a donkey\","
+ " \"antagonist_type\": \"MuLan\", \"antagonist\": {\"sword_attack_damage\": 42},"
+ " \"characters_type\": [\"Fantasy.Character.Rapunzel\", \"Other\", 0, \"MuLan\"],"
+ " \"characters\": [{\"hair_length\":19}, \"unattributed extras\", null, {\"sword_attack_damage\":2}]}",
+ "{\"main_character_type\":\"Rapunzel\",\"main_character\":{\"hair_length\":19},"
+ "\"antagonist_type\":\"MuLan\",\"antagonist\":{\"sword_attack_damage\":42},"
+ "\"side_kick_type\":\"Other\",\"side_kick\":\"a donkey\","
+ "\"characters_type\":[\"Rapunzel\",\"Other\",\"NONE\",\"MuLan\"],"
+ "\"characters\":[{\"hair_length\":19},\"unattributed extras\",null,{\"sword_attack_damage\":2}]}")
+
+ TEST( "{ \"main_character_type\": \"Rapunzel\", \"main_character\": { \"hair_length\": 19 },"
+ " \"side_kick_type\": \"Character.Other\", \"side_kick\": \"a donkey\"}",
+ "{\"main_character_type\":\"Rapunzel\",\"main_character\":{\"hair_length\":19},"
+ "\"side_kick_type\":\"Other\",\"side_kick\":\"a donkey\"}");
+
+ END_TEST();
+}
+
+int union_vector_tests(void)
+{
+ BEGIN_TEST(Alt);
+ /* Union vector */
+
+ TEST( "{ \"manyany_type\": [ \"Monster\" ], \"manyany\": [{\"name\": \"Joe\"}] }",
+ "{\"manyany_type\":[\"Monster\"],\"manyany\":[{\"name\":\"Joe\"}]}");
+
+ TEST( "{\"manyany_type\": [ \"NONE\" ], \"manyany\": [ null ] }",
+ "{\"manyany_type\":[\"NONE\"],\"manyany\":[null]}");
+
+ TEST( "{\"manyany_type\": [ \"Monster\", \"NONE\" ], \"manyany\": [{\"name\": \"Joe\"}, null] }",
+ "{\"manyany_type\":[\"Monster\",\"NONE\"],\"manyany\":[{\"name\":\"Joe\"},null]}");
+
+ TEST( "{\"manyany_type\": [ \"Monster\" ], \"manyany\": [ { \"name\":\"Joe\", \"test_type\": \"Monster\", \"test\": { \"name\": \"any Monster\" } } ] }",
+ "{\"manyany_type\":[\"Monster\"],\"manyany\":[{\"name\":\"Joe\",\"test_type\":\"Monster\",\"test\":{\"name\":\"any Monster\"}}]}");
+
+ TEST( "{\"manyany\": [{\"name\": \"Joe\"}], \"manyany_type\": [ \"Monster\" ] }",
+ "{\"manyany_type\":[\"Monster\"],\"manyany\":[{\"name\":\"Joe\"}]}");
+
+ TEST( "{\"manyany\": [{\"manyany\":[null, null], \"manyany_type\": [\"NONE\", \"NONE\"]}], \"manyany_type\": [ \"Alt\" ] }",
+ "{\"manyany_type\":[\"Alt\"],\"manyany\":[{\"manyany_type\":[\"NONE\",\"NONE\"],\"manyany\":[null,null]}]}");
+
+ END_TEST();
+}
+
+int fixed_array_tests(void)
+{
+ BEGIN_TEST(Alt);
+ /* Fixed array */
+
+#if UQ
+ TEST( "{ \"fixed_array\": { \"foo\": [ 1.0, 2.0, 0, 0, 0, 0, 0,"
+ " 0, 0, 0, 0, 0, 0, 0, 0, 16.0], col:[\"Blue Red\", Green, Red],"
+ "tests:[ {b:4}, {a:1, b:2}],"
+ " \"bar\": [ 100, 0, 0, 0, 0, 0, 0, 0, 0, 1000],"
+ " \"text\":\"hello\"}}",
+ "{\"fixed_array\":{\"foo\":[1,2,0,0,0,0,0,"
+ "0,0,0,0,0,0,0,0,16],"
+ "\"bar\":[100,0,0,0,0,0,0,0,0,1000],"
+ "\"col\":[\"Red Blue\",\"Green\",\"Red\"],"
+ "\"tests\":[{\"a\":0,\"b\":4},{\"a\":1,\"b\":2}],"
+ "\"text\":\"hello\"}}");
+#else
+ TEST( "{ \"fixed_array\": { \"foo\": [ 1.0, 2.0, 0, 0, 0, 0, 0,"
+ " 0, 0, 0, 0, 0, 0, 0, 0, 16.0], \"col\":[\"Blue Red\", \"Green\", \"Red\"],"
+ "\"tests\":[ {\"b\":4}, {\"a\":1, \"b\":2}],"
+ " \"bar\": [ 100, 0, 0, 0, 0, 0, 0, 0, 0, 1000],"
+ " \"text\":\"hello\"}}",
+ "{\"fixed_array\":{\"foo\":[1,2,0,0,0,0,0,"
+ "0,0,0,0,0,0,0,0,16],"
+ "\"bar\":[100,0,0,0,0,0,0,0,0,1000],"
+ "\"col\":[\"Red Blue\",\"Green\",\"Red\"],"
+ "\"tests\":[{\"a\":0,\"b\":4},{\"a\":1,\"b\":2}],"
+ "\"text\":\"hello\"}}");
+#endif
+
+ TEST_FLAGS(flatcc_json_parser_f_skip_array_overflow, 0,
+ "{ \"fixed_array\": { \"foo\": [ 1.0, 2.0, 0, 0, 0, 0, 0,"
+ " 0, 0, 0, 0, 0, 0, 0, 0, 16.0, 99],"
+ " \"bar\": [ 100, 0, 0, 0, 0, 0, 0, 0, 0, 1000, 99],"
+ " \"text\":\"hello, world\"}}",
+ "{\"fixed_array\":{\"foo\":[1,2,0,0,0,0,0,"
+ "0,0,0,0,0,0,0,0,16],"
+ "\"bar\":[100,0,0,0,0,0,0,0,0,1000],"
+ "\"col\":[0,0,0],"
+ "\"tests\":[{\"a\":0,\"b\":0},{\"a\":0,\"b\":0}],"
+ "\"text\":\"hello\"}}");
+
+ TEST( "{ \"fixed_array\": { \"foo\": [ 1.0, 2.0 ],"
+ " \"bar\": [ 100 ], \"text\": \"K\\x00A\\x00\" }}",
+ "{\"fixed_array\":{\"foo\":[1,2,0,0,0,0,0,"
+ "0,0,0,0,0,0,0,0,0],"
+ "\"bar\":[100,0,0,0,0,0,0,0,0,0],"
+ "\"col\":[0,0,0],"
+ "\"tests\":[{\"a\":0,\"b\":0},{\"a\":0,\"b\":0}],"
+ "\"text\":\"K\\u0000A\"}}");
+
+ TEST_ERROR_FLAGS(flatcc_json_parser_f_reject_array_underflow, 0,
+ "{ \"fixed_array\": { \"foo\": [ 1.0, 2.0 ] }}",
+ flatcc_json_parser_error_array_underflow);
+
+ TEST_ERROR_FLAGS(flatcc_json_parser_f_reject_array_underflow, 0,
+ "{ \"fixed_array\": { \"text\": \"K\\x00A\\x00\" }}",
+ flatcc_json_parser_error_array_underflow);
+
+ TEST_ERROR(
+ "{ \"fixed_array\": { \"foo\": [ 1.0, 2.0, 0, 0, 0, 0, 0,"
+ " 0, 0, 0, 0, 0, 0, 0, 0, 16.0, 99],"
+ " \"bar\": [ 100, 0, 0, 0, 0, 0, 0, 0, 0, 1000, 99] }}",
+ flatcc_json_parser_error_array_overflow);
+
+ END_TEST();
+}
+
+/*
+ * Here we cover some border cases around unions and flag
+ * enumerations, and nested buffers.
+ *
+ * More complex objects with struct members etc. are reasonably
+ * covered in the printer and parser tests using the golden data
+ * set.
+ */
+int main(void)
+{
+ BEGIN_TEST(Monster);
+
+ ret |= edge_case_tests();
+ ret |= error_case_tests();
+ ret |= union_vector_tests();
+ ret |= fixed_array_tests();
+ ret |= base64_tests();
+ ret |= mixed_type_union_tests();
+
+ /* Allow trailing comma. */
+ TEST( "{ \"name\": \"Monster\", }",
+ "{\"name\":\"Monster\"}");
+
+ TEST( "{\"color\": \"Red\", \"name\": \"Monster\", }",
+ "{\"name\":\"Monster\",\"color\":\"Red\"}");
+
+ TEST( "{ \"name\": \"Monster\", \"color\": \"Green\" }",
+ "{\"name\":\"Monster\",\"color\":\"Green\"}");
+
+ TEST( "{ \"name\": \"Monster\", \"color\": \"Green Red Blue\" }",
+ "{\"name\":\"Monster\",\"color\":\"Red Green Blue\"}");
+
+ TEST( "{ \"name\": \"Monster\", \"color\": \" Green Red Blue \" }",
+ "{\"name\":\"Monster\",\"color\":\"Red Green Blue\"}");
+
+ TEST( "{ \"name\": \"Monster\", \"color\": \"Red\" }",
+ "{\"name\":\"Monster\",\"color\":\"Red\"}");
+
+ TEST( "{ \"name\": \"Monster\", \"color\" :\"Green\" }",
+ "{\"name\":\"Monster\",\"color\":\"Green\"}");
+
+ /* Default value. */
+ TEST( "{ \"name\": \"Monster\", \"color\": \"Blue\" }",
+ "{\"name\":\"Monster\"}");
+
+ /* Default value. */
+ TEST( "{ \"name\": \"Monster\", \"color\": 8}",
+ "{\"name\":\"Monster\"}");
+#if UQ
+ /* Allow trailing comma. */
+ TEST( "{ name: \"Monster\", }",
+ "{\"name\":\"Monster\"}");
+
+ TEST( "{color: \"Red\", name: \"Monster\", }",
+ "{\"name\":\"Monster\",\"color\":\"Red\"}");
+
+ TEST( "{ name: \"Monster\", color: \"Green\" }",
+ "{\"name\":\"Monster\",\"color\":\"Green\"}");
+
+ TEST( "{ name: \"Monster\", color: \"Green Red Blue\" }",
+ "{\"name\":\"Monster\",\"color\":\"Red Green Blue\"}");
+
+ TEST( "{ name: \"Monster\", color: \" Green Red Blue \" }",
+ "{\"name\":\"Monster\",\"color\":\"Red Green Blue\"}");
+
+ TEST( "{ name: \"Monster\", color: Red }",
+ "{\"name\":\"Monster\",\"color\":\"Red\"}");
+
+ TEST( "{ name: \"Monster\", color: Green }",
+ "{\"name\":\"Monster\",\"color\":\"Green\"}");
+
+ /* Default value. */
+ TEST( "{ name: \"Monster\", color: Blue }",
+ "{\"name\":\"Monster\"}");
+
+ /* Default value. */
+ TEST( "{ name: \"Monster\", color: 8}",
+ "{\"name\":\"Monster\"}");
+#endif
+#if UQL
+ TEST( "{ name: \"Monster\", color: Green Red }",
+ "{\"name\":\"Monster\",\"color\":\"Red Green\"}");
+#endif
+
+#if UQL
+ /* No leading space in unquoted flag. */
+ TEST( "{ name: \"Monster\", color:Green Red }",
+ "{\"name\":\"Monster\",\"color\":\"Red Green\"}");
+
+ TEST( "{ name: \"Monster\", color: Green Red}",
+ "{\"name\":\"Monster\",\"color\":\"Red Green\"}");
+
+ TEST( "{ name: \"Monster\", color:Green Blue Red }",
+ "{\"name\":\"Monster\",\"color\":\"Red Green Blue\"}");
+#endif
+
+ TEST( "{ \"name\": \"Monster\", \"color\": 1}",
+ "{\"name\":\"Monster\",\"color\":\"Red\"}");
+
+ TEST( "{ \"name\": \"Monster\", \"color\": 2}",
+ "{\"name\":\"Monster\",\"color\":\"Green\"}");
+
+ TEST( "{ \"name\": \"Monster\", \"color\": 9}",
+ "{\"name\":\"Monster\",\"color\":\"Red Blue\"}");
+
+ TEST( "{ \"name\": \"Monster\", \"color\": 11}",
+ "{\"name\":\"Monster\",\"color\":\"Red Green Blue\"}");
+
+ TEST( "{ \"name\": \"Monster\", \"color\": 12}",
+ "{\"name\":\"Monster\",\"color\":12}");
+
+ TEST( "{ \"name\": \"Monster\", \"color\": 15}",
+ "{\"name\":\"Monster\",\"color\":15}");
+
+ TEST( "{ \"name\": \"Monster\", \"color\": 0}",
+ "{\"name\":\"Monster\",\"color\":0}");
+
+ TEST( "{ \"name\": \"Monster\", \"color\": \"Color.Red\"}",
+ "{\"name\":\"Monster\",\"color\":\"Red\"}");
+
+ TEST( "{ \"name\": \"Monster\", \"color\": \"MyGame.Example.Color.Red\"}",
+ "{\"name\":\"Monster\",\"color\":\"Red\"}");
+
+ TEST( "{ \"name\": \"Monster\", \"hp\": \"Color.Green\"}",
+ "{\"hp\":2,\"name\":\"Monster\"}");
+
+ TEST( "{ \"name\": \"Monster\", \"hp\": \"Color.Green\"}",
+ "{\"hp\":2,\"name\":\"Monster\"}");
+
+ TEST( "{ \"name\": \"Monster\", \"test_type\": \"Monster\", \"test\": { \"name\": \"any Monster\" } }",
+ "{\"name\":\"Monster\",\"test_type\":\"Monster\",\"test\":{\"name\":\"any Monster\"}}");
+
+ /* This is tricky because the test field must be reparsed after discovering the test type. */
+ TEST( "{ \"name\": \"Monster\", \"test\": { \"name\": \"second Monster\" }, \"test_type\": \"Monster\" }",
+ "{\"name\":\"Monster\",\"test_type\":\"Monster\",\"test\":{\"name\":\"second Monster\"}}");
+
+ /* Also test that parsing can continue after reparse. */
+ TEST( "{ \"name\": \"Monster\", \"test\": { \"name\": \"second Monster\" }, \"hp\":17, \"test_type\":\n \"Monster\", \"color\":\"Green\" }",
+ "{\"hp\":17,\"name\":\"Monster\",\"color\":\"Green\",\"test_type\":\"Monster\",\"test\":{\"name\":\"second Monster\"}}");
+
+ /* Test that NONE is recognized, and that we do not get a missing table error.*/
+ TEST( "{ \"name\": \"Monster\", \"test_type\": \"NONE\" }",
+ "{\"name\":\"Monster\"}");
+
+ TEST( "{ \"name\": \"Monster\", \"test_type\": 0 }",
+ "{\"name\":\"Monster\"}");
+
+#if UQ
+ TEST( "{ name: \"Monster\", color: 1}",
+ "{\"name\":\"Monster\",\"color\":\"Red\"}");
+
+ TEST( "{ name: \"Monster\", color: 2}",
+ "{\"name\":\"Monster\",\"color\":\"Green\"}");
+
+ TEST( "{ name: \"Monster\", color: 9}",
+ "{\"name\":\"Monster\",\"color\":\"Red Blue\"}");
+
+ TEST( "{ name: \"Monster\", color: 11}",
+ "{\"name\":\"Monster\",\"color\":\"Red Green Blue\"}");
+
+ TEST( "{ name: \"Monster\", color: 12}",
+ "{\"name\":\"Monster\",\"color\":12}");
+
+ TEST( "{ name: \"Monster\", color: 15}",
+ "{\"name\":\"Monster\",\"color\":15}");
+
+ TEST( "{ name: \"Monster\", color: 0}",
+ "{\"name\":\"Monster\",\"color\":0}");
+
+ TEST( "{ name: \"Monster\", color: Color.Red}",
+ "{\"name\":\"Monster\",\"color\":\"Red\"}");
+
+ TEST( "{ name: \"Monster\", color: MyGame.Example.Color.Red}",
+ "{\"name\":\"Monster\",\"color\":\"Red\"}");
+
+ TEST( "{ name: \"Monster\", hp: Color.Green}",
+ "{\"hp\":2,\"name\":\"Monster\"}");
+
+ TEST( "{ name: \"Monster\", hp: Color.Green}",
+ "{\"hp\":2,\"name\":\"Monster\"}");
+
+ TEST( "{ name: \"Monster\", test_type: Monster, test: { name: \"any Monster\" } }",
+ "{\"name\":\"Monster\",\"test_type\":\"Monster\",\"test\":{\"name\":\"any Monster\"}}");
+
+ /* This is tricky because the test field must be reparsed after discovering the test type. */
+ TEST( "{ name: \"Monster\", test: { name: \"second Monster\" }, test_type: Monster }",
+ "{\"name\":\"Monster\",\"test_type\":\"Monster\",\"test\":{\"name\":\"second Monster\"}}");
+
+ /* Also test that parsing can continue after reparse. */
+ TEST( "{ name: \"Monster\", test: { name: \"second Monster\" }, hp:17, test_type:\n Monster, color:Green }",
+ "{\"hp\":17,\"name\":\"Monster\",\"color\":\"Green\",\"test_type\":\"Monster\",\"test\":{\"name\":\"second Monster\"}}");
+
+ /* Test that NONE is recognized, and that we do not get a missing table error.*/
+ TEST( "{ name: \"Monster\", test_type: NONE }",
+ "{\"name\":\"Monster\"}");
+
+ TEST( "{ name: \"Monster\", test_type: 0 }",
+ "{\"name\":\"Monster\"}");
+
+#endif
+
+#if UQL
+ /*
+ * Test that generic parsing handles multiple flags correctly during
+ * first pass before backtracking.
+ */
+ TEST( "{ name: \"Monster\", test: { name: \"second Monster\", color: Red Green }, test_type: Monster }",
+ "{\"name\":\"Monster\",\"test_type\":\"Monster\",\"test\":{\"name\":\"second Monster\",\"color\":\"Red Green\"}}");
+#endif
+
+ /* Ditto quoted flags. */
+ TEST( "{ \"name\": \"Monster\", \"test\": { \"name\": \"second Monster\", \"color\": \" Red Green \" }, \"test_type\": \"Monster\" }",
+ "{\"name\":\"Monster\",\"test_type\":\"Monster\",\"test\":{\"name\":\"second Monster\",\"color\":\"Red Green\"}}");
+
+ /*
+ * Note the '\/' becomes just '/', and that '/' also works in input.
+ *
+ * The json printer does not have a concept of \x it always uses
+ * unicode.
+ *
+ * We use json extension \x to inject a control 03 which extends
+ * to a printed unicode escape, while the \u00F8 is a valid
+ * character after encoding, and is thus not escaped after printing
+ * but rather becoems a two-byte utf-8 encoding of 'ΓΈ' which
+ * we use C encoding to form utf8 C3B8 == \u00F8.
+ */
+ TEST( "{ \"name\": \"Mon\xfF\xFf\\x03s\\xC3\\xF9\\u00F8ter\\b\\f\\n\\r\\t\\\"\\\\\\/'/\", }",
+ "{\"name\":\"Mon\xff\xff\\u0003s\xc3\xf9\xc3\xb8ter\\b\\f\\n\\r\\t\\\"\\\\/'/\"}");
+
+ TEST( "{ \"name\": \"\\u168B\\u1691\"}",
+ "{\"name\":\"\xe1\x9a\x8b\xe1\x9a\x91\"}");
+
+ /* Nested flatbuffer, either is a known object, or as a vector. */
+ TEST( "{ \"name\": \"Monster\", \"testnestedflatbuffer\":{ \"name\": \"sub Monster\" } }",
+ "{\"name\":\"Monster\",\"testnestedflatbuffer\":{\"name\":\"sub Monster\"}}");
+
+#if FLATBUFFERS_PROTOCOL_IS_LE
+ TEST( "{ \"name\": \"Monster\", \"testnestedflatbuffer\":"
+ "[" /* start of nested flatbuffer, implicit size: 40 */
+ "4,0,0,0," /* header: object offset = 4, no identifier */
+ "248,255,255,255," /* vtable offset */
+ "16,0,0,0," /* offset to name */
+ "12,0,8,0,0,0,0,0,0,0,4,0," /* vtable */
+ "11,0,0,0,115,117,98,32,77,111,110,115,116,101,114,0" /* name = "sub Monster" */
+ "]" /* end of nested flatbuffer */
+ "}",
+ "{\"name\":\"Monster\",\"testnestedflatbuffer\":{\"name\":\"sub Monster\"}}");
+#else
+ TEST( "{ \"name\": \"Monster\", \"testnestedflatbuffer\":"
+ "[" /* start of nested flatbuffer, implicit size: 40 */
+ "0,0,0,4," /* header: object offset = 4, no identifier */
+ "255,255,255,248," /* vtable offset */
+ "0,0,0,16," /* offset to name */
+ "0,12,0,8,0,0,0,0,0,0,0,4," /* vtable */
+ "0,0,0,11,115,117,98,32,77,111,110,115,116,101,114,0" /* name = "sub Monster" */
+ "]" /* end of nested flatbuffer */
+ "}",
+ "{\"name\":\"Monster\",\"testnestedflatbuffer\":{\"name\":\"sub Monster\"}}");
+#endif
+
+ /* Test empty table */
+ TEST( "{ \"name\": \"Monster\", \"testempty\": {} }",
+ "{\"name\":\"Monster\",\"testempty\":{}}");
+
+ /* Test empty array */
+ TEST( "{ \"name\": \"Monster\", \"testarrayoftables\": [] }",
+ "{\"name\":\"Monster\",\"testarrayoftables\":[]}");
+
+ /* Test JSON prefix parsing */
+ TEST( "{ \"name\": \"Monster\", \"test_type\":\"Alt\", \"test\":{\"prefix\":{"
+ "\"testjsonprefixparsing\": { \"aaaa\": \"test\", \"aaaa12345\": 17 } }}}",
+ "{\"name\":\"Monster\",\"test_type\":\"Alt\",\"test\":{\"prefix\":{"
+ "\"testjsonprefixparsing\":{\"aaaa\":\"test\",\"aaaa12345\":17}}}}");
+
+ /* TODO: this parses with the last to }} missing, although it does not add the broken objects. */
+ TEST( "{ \"name\": \"Monster\", \"test_type\":\"Alt\", \"test\":{\"prefix\":{"
+ "\"testjsonprefixparsing\": { \"bbbb\": \"test\", \"bbbb1234\": 19 } }",
+ "{\"name\":\"Monster\"}");
+
+ TEST( "{ \"name\": \"Monster\", \"test_type\":\"Alt\", \"test\":{\"prefix\":{"
+ "\"testjsonprefixparsing\": { \"bbbb\": \"test\", \"bbbb1234\": 19 } }}}",
+ "{\"name\":\"Monster\",\"test_type\":\"Alt\",\"test\":{\"prefix\":{"
+ "\"testjsonprefixparsing\":{\"bbbb\":\"test\",\"bbbb1234\":19}}}}");
+
+ TEST( "{ \"name\": \"Monster\", \"test_type\":\"Alt\", \"test\":{\"prefix\":{"
+ "\"testjsonprefixparsing\": { \"cccc\": \"test\", \"cccc1234\": 19, \"cccc12345\": 17 } }}}",
+ "{\"name\":\"Monster\",\"test_type\":\"Alt\",\"test\":{\"prefix\":{"
+ "\"testjsonprefixparsing\":{\"cccc\":\"test\",\"cccc1234\":19,\"cccc12345\":17}}}}");
+
+ TEST( "{ \"name\": \"Monster\", \"test_type\":\"Alt\", \"test\":{\"prefix\":{"
+ "\"testjsonprefixparsing\": { \"dddd1234\": 19, \"dddd12345\": 17 } }}}",
+ "{\"name\":\"Monster\",\"test_type\":\"Alt\",\"test\":{\"prefix\":{"
+ "\"testjsonprefixparsing\":{\"dddd1234\":19,\"dddd12345\":17}}}}");
+
+ TEST( "{ \"name\": \"Monster\", \"test_type\":\"Alt\", \"test\":{\"prefix\":{"
+ "\"testjsonprefixparsing2\": { \"aaaa_bbbb_steps\": 19, \"aaaa_bbbb_start_\": 17 } }}}",
+ "{\"name\":\"Monster\",\"test_type\":\"Alt\",\"test\":{\"prefix\":{"
+ "\"testjsonprefixparsing2\":{\"aaaa_bbbb_steps\":19,\"aaaa_bbbb_start_\":17}}}}");
+
+ TEST( "{ \"name\": \"Monster\", \"test_type\":\"Alt\", \"test\":{\"prefix\":{"
+ "\"testjsonprefixparsing3\": { \"aaaa_bbbb_steps\": 19, \"aaaa_bbbb_start_steps\": 17 } }}}",
+ "{\"name\":\"Monster\",\"test_type\":\"Alt\",\"test\":{\"prefix\":{"
+ "\"testjsonprefixparsing3\":{\"aaaa_bbbb_steps\":19,\"aaaa_bbbb_start_steps\":17}}}}");
+
+ return ret ? -1: 0;
+}
diff --git a/test/json_test/test_json_parser.c b/test/json_test/test_json_parser.c
new file mode 100644
index 0000000..a985cfa
--- /dev/null
+++ b/test/json_test/test_json_parser.c
@@ -0,0 +1,164 @@
+#include <stdio.h>
+
+#ifndef FLATCC_BENCHMARK
+#define FLATCC_BENCHMARK 0
+#endif
+
+/* Only needed for verification. */
+#include "monster_test_reader.h"
+#include "monster_test_json_parser.h"
+#include "flatcc/support/hexdump.h"
+#include "flatcc/support/cdump.h"
+#include "flatcc/support/readfile.h"
+
+#if FLATCC_BENCHMARK
+#include "flatcc/support/elapsed.h"
+#endif
+
+const char *filename = "monsterdata_test.golden";
+
+#define BENCH_TITLE "monsterdata_test.golden"
+
+#ifdef NDEBUG
+#define COMPILE_TYPE "(optimized)"
+#else
+#define COMPILE_TYPE "(debug)"
+#endif
+
+#define FILE_SIZE_MAX (1024 * 10)
+
+#undef ns
+#define ns(x) FLATBUFFERS_WRAP_NAMESPACE(MyGame_Example, x)
+
+#define test_assert(x) do { if (!(x)) { assert(0); return -1; }} while(0)
+
+/* A helper to simplify creating buffers vectors from C-arrays. */
+#define c_vec_len(V) (sizeof(V)/sizeof((V)[0]))
+
+int verify_parse(void *buffer)
+{
+ ns(Test_struct_t) test;
+ ns(Vec3_struct_t) pos;
+ ns(Monster_table_t) monster = ns(Monster_as_root_with_identifier)(buffer, ns(Monster_file_identifier));
+
+ pos = ns(Monster_pos(monster));
+ test_assert(pos);
+ test_assert(ns(Vec3_x(pos) == 1));
+ test_assert(ns(Vec3_y(pos) == 2));
+ test_assert(ns(Vec3_z(pos) == 3));
+ test_assert(ns(Vec3_test1(pos) == 3.0));
+ test_assert(ns(Vec3_test2(pos) == ns(Color_Green)));
+ test = ns(Vec3_test3(pos));
+ test_assert(test);
+ test_assert(ns(Test_a(test)) == 5);
+ test_assert(ns(Test_b(test)) == 6);
+
+ // TODO: hp and further fields
+
+ return 0;
+
+}
+// TODO:
+// when running benchmark with the wrong size argument (output size
+// instead of input size), the warmup loop iterates indefinitely in the
+// first iteration. This suggests there is an end check missing somwhere
+// and this needs to be debugged. The input size as of this writing is 701
+// bytes, and the output size is 288 bytes.
+int test_parse(void)
+{
+#if FLATCC_BENCHMARK
+ double t1, t2;
+ int i;
+ int rep = 1000000;
+ int warmup_rep = 1000000;
+#endif
+
+ const char *buf;
+ void *flatbuffer = 0;
+ size_t in_size, out_size;
+ flatcc_json_parser_t ctx;
+ flatcc_builder_t builder;
+ flatcc_builder_t *B = &builder;
+ int ret = -1;
+ flatcc_json_parser_flags_t flags = 0;
+
+ flatcc_builder_init(B);
+
+ buf = readfile(filename, FILE_SIZE_MAX, &in_size);
+ if (!buf) {
+ fprintf(stderr, "%s: could not read input json file\n", filename);
+ return -1;
+ }
+
+ if (monster_test_parse_json(B, &ctx, buf, in_size, flags)) {
+ goto failed;
+ }
+ fprintf(stderr, "%s: successfully parsed %d lines\n", filename, ctx.line);
+ flatbuffer = flatcc_builder_finalize_aligned_buffer(B, &out_size);
+ hexdump("parsed monsterdata_test.golden", flatbuffer, out_size, stdout);
+ fprintf(stderr, "input size: %lu, output size: %lu\n",
+ (unsigned long)in_size, (unsigned long)out_size);
+ verify_parse(flatbuffer);
+
+ cdump("golden", flatbuffer, out_size, stdout);
+
+ flatcc_builder_reset(B);
+#if FLATCC_BENCHMARK
+ fprintf(stderr, "Now warming up\n");
+ for (i = 0; i < warmup_rep; ++i) {
+ if (monster_test_parse_json(B, &ctx, buf, in_size, flags)) {
+ goto failed;
+ }
+ flatcc_builder_reset(B);
+ }
+
+ fprintf(stderr, "Now benchmarking\n");
+ t1 = elapsed_realtime();
+ for (i = 0; i < rep; ++i) {
+ if (monster_test_parse_json(B, &ctx, buf, in_size, flags)) {
+ goto failed;
+ }
+ flatcc_builder_reset(B);
+ }
+ t2 = elapsed_realtime();
+
+ printf("----\n");
+ show_benchmark(BENCH_TITLE " C generated JSON parse " COMPILE_TYPE, t1, t2, in_size, rep, "1M");
+#endif
+ ret = 0;
+
+done:
+ if (flatbuffer) {
+ flatcc_builder_aligned_free(flatbuffer);
+ }
+ if (buf) {
+ free((void *)buf);
+ }
+ flatcc_builder_clear(B);
+ return ret;
+
+failed:
+ fprintf(stderr, "%s:%d:%d: %s\n",
+ filename, (int)ctx.line, (int)(ctx.error_loc - ctx.line_start + 1),
+ flatcc_json_parser_error_string(ctx.error));
+ goto done;
+}
+
+/* We take arguments so test can run without copying sources. */
+#define usage \
+"wrong number of arguments:\n" \
+"usage: <program> [<input-filename>]\n"
+
+int main(int argc, const char *argv[])
+{
+ fprintf(stderr, "JSON parse test\n");
+
+ if (argc != 1 && argc != 2) {
+ fprintf(stderr, usage);
+ exit(1);
+ }
+ if (argc == 2) {
+ filename = argv[1];
+ }
+ return test_parse();
+}
diff --git a/test/json_test/test_json_printer.c b/test/json_test/test_json_printer.c
new file mode 100644
index 0000000..efbd572
--- /dev/null
+++ b/test/json_test/test_json_printer.c
@@ -0,0 +1,129 @@
+#include <stdio.h>
+
+/* Only needed for verification. */
+#include "monster_test_json_printer.h"
+#include "flatcc/support/readfile.h"
+#include "flatcc_golden.c"
+
+#ifdef NDEBUG
+#define COMPILE_TYPE "(optimized)"
+#else
+#define COMPILE_TYPE "(debug)"
+#endif
+
+#define FILE_SIZE_MAX (1024 * 10)
+
+#undef ns
+#define ns(x) FLATBUFFERS_WRAP_NAMESPACE(MyGame_Example, x)
+
+/* A helper to simplify creating buffers vectors from C-arrays. */
+#define c_vec_len(V) (sizeof(V)/sizeof((V)[0]))
+
+const char *filename = 0; /* "monsterdata_test.mon"; */
+const char *golden_filename = "monsterdata_test.golden";
+const char *target_filename = "monsterdata_test.json.txt";
+
+int test_print(void)
+{
+ int ret = 0;
+ const char *buf = 0;
+ const char *golden = 0;
+ const char *target = 0;
+ size_t size = 0, golden_size = 0, target_size = 0;
+ flatcc_json_printer_t ctx_obj, *ctx;
+ FILE *fp = 0;
+
+ ctx = &ctx_obj;
+
+ fp = fopen(target_filename, "wb");
+ if (!fp) {
+ fprintf(stderr, "%s: could not open output file\n", target_filename);
+ /* ctx not ready for clenaup, so exit directly. */
+ return -1;
+ }
+ flatcc_json_printer_init(ctx, fp);
+ /* Uses same formatting as golden reference file. */
+ flatcc_json_printer_set_nonstrict(ctx);
+
+ if (filename && strcmp(filename, "-")) {
+ buf = readfile(filename, FILE_SIZE_MAX, &size);
+ } else {
+#if FLATBUFFERS_PROTOCOL_IS_LE
+ buf = (const char *)flatcc_golden_le;
+ size = sizeof(flatcc_golden_le);
+#else
+ buf = (const char *)flatcc_golden_be;
+ size = sizeof(flatcc_golden_be);
+#endif
+ }
+
+ if (!buf) {
+ fprintf(stderr, "%s: could not read input flatbuffer file\n", filename);
+ goto fail;
+ }
+ golden = readfile(golden_filename, FILE_SIZE_MAX, &golden_size);
+ if (!golden) {
+ fprintf(stderr, "%s: could not read verification json file\n", golden_filename);
+ goto fail;
+ }
+ ns(Monster_print_json_as_root(ctx, buf, size, "MONS"));
+ flatcc_json_printer_flush(ctx);
+ if (flatcc_json_printer_get_error(ctx)) {
+ printf("could not print monster data\n");
+ }
+ fclose(fp);
+ fp = 0;
+ target = readfile(target_filename, FILE_SIZE_MAX, &target_size);
+ if (!target) {
+ fprintf(stderr, "%s: could not read back output file\n", target_filename);
+ goto fail;
+ }
+ if (target_size != golden_size || memcmp(target, golden, target_size)) {
+ fprintf(stderr, "generated output file did not match verification file\n");
+ goto fail;
+ }
+ fprintf(stderr, "json print test succeeded\n");
+
+done:
+ flatcc_json_printer_clear(ctx);
+ if (!filename) {
+ buf = 0;
+ }
+ if (buf) {
+ free((void *)buf);
+ }
+ if (golden) {
+ free((void *)golden);
+ }
+ if (target) {
+ free((void *)target);
+ }
+ if (fp) {
+ fclose(fp);
+ }
+ return ret;
+fail:
+ ret = -1;
+ goto done;
+}
+
+/* We take arguments so output file can be generated in build directory without copying sources. */
+#define usage \
+"wrong number of arguments:\n" \
+"usage: <program> [(<input-filename>|'-') <reference-filename> <output-filename>]\n" \
+" noargs, or '-' use default binary buffer matching endianness of flatbuffer format\n"
+
+int main(int argc, const char *argv[])
+{
+ fprintf(stderr, "running json print test\n");
+ if (argc != 1 && argc != 4) {
+ fprintf(stderr, usage);
+ exit(1);
+ }
+ if (argc == 4) {
+ filename = argv[1];
+ golden_filename = argv[2];
+ target_filename = argv[3];
+ }
+ return test_print();
+}