Commit 37105710ee0b332a3020d4b3220c95b8f4267555

Authored by Jay Berkenbilt
1 parent a6df6fda

Implement JSONHandler for recursively processing JSON

include/qpdf/JSON.hh
... ... @@ -30,7 +30,10 @@
30 30 // create temporary JSON objects on the stack, add them to other
31 31 // objects, and let them go out of scope safely. It also means that if
32 32 // the json JSON object is added in more than one place, all copies
33   -// share underlying data.
  33 +// share underlying data. This makes them similar in structure and
  34 +// behavior to QPDFObjectHandle and may feel natural within the QPDF
  35 +// codebase, but it is also a good reason not to use this as a
  36 +// general-purpose JSON package.
34 37  
35 38 #include <qpdf/DLL.h>
36 39 #include <qpdf/PointerHolder.hh>
... ... @@ -38,6 +41,7 @@
38 41 #include <map>
39 42 #include <vector>
40 43 #include <list>
  44 +#include <functional>
41 45  
42 46 class JSON
43 47 {
... ... @@ -77,6 +81,24 @@ class JSON
77 81 QPDF_DLL
78 82 bool isDictionary() const;
79 83  
  84 + // Accessors. Accessor behavior:
  85 + //
  86 + // - If argument is wrong type, including null, return false
  87 + // - If argument is right type, return true and initialize the value
  88 + QPDF_DLL
  89 + bool getString(std::string& utf8) const;
  90 + QPDF_DLL
  91 + bool getNumber(std::string& value) const;
  92 + QPDF_DLL
  93 + bool getBool(bool& value) const;
  94 + QPDF_DLL
  95 + bool isNull() const;
  96 + QPDF_DLL
  97 + bool forEachDictItem(
  98 + std::function<void(std::string const& key, JSON value)> fn) const;
  99 + QPDF_DLL
  100 + bool forEachArrayItem(std::function<void(JSON value)> fn) const;
  101 +
80 102 // Check this JSON object against a "schema". This is not a schema
81 103 // according to any standard. It's just a template of what the
82 104 // JSON is supposed to contain. The checking does the following:
... ... @@ -129,6 +151,7 @@ class JSON
129 151 JSON_string(std::string const& utf8);
130 152 virtual ~JSON_string();
131 153 virtual std::string unparse(size_t depth) const;
  154 + std::string utf8;
132 155 std::string encoded;
133 156 };
134 157 struct JSON_number: public JSON_value
... ...
include/qpdf/JSONHandler.hh 0 โ†’ 100644
  1 +// Copyright (c) 2005-2021 Jay Berkenbilt
  2 +//
  3 +// This file is part of qpdf.
  4 +//
  5 +// Licensed under the Apache License, Version 2.0 (the "License");
  6 +// you may not use this file except in compliance with the License.
  7 +// You may obtain a copy of the License at
  8 +//
  9 +// http://www.apache.org/licenses/LICENSE-2.0
  10 +//
  11 +// Unless required by applicable law or agreed to in writing, software
  12 +// distributed under the License is distributed on an "AS IS" BASIS,
  13 +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14 +// See the License for the specific language governing permissions and
  15 +// limitations under the License.
  16 +//
  17 +// Versions of qpdf prior to version 7 were released under the terms
  18 +// of version 2.0 of the Artistic License. At your option, you may
  19 +// continue to consider qpdf to be licensed under those terms. Please
  20 +// see the manual for additional information.
  21 +
  22 +#ifndef JSONHANDLER_HH
  23 +#define JSONHANDLER_HH
  24 +
  25 +#include <qpdf/DLL.h>
  26 +#include <qpdf/PointerHolder.hh>
  27 +#include <qpdf/JSON.hh>
  28 +#include <string>
  29 +#include <map>
  30 +#include <functional>
  31 +#include <stdexcept>
  32 +#include <memory>
  33 +
  34 +class JSONHandler
  35 +{
  36 + public:
  37 + // Error exception is thrown if there are any errors validating
  38 + // the JSON object.
  39 + class QPDF_DLL_CLASS Error: public std::runtime_error
  40 + {
  41 + public:
  42 + QPDF_DLL
  43 + Error(std::string const&);
  44 + };
  45 +
  46 + QPDF_DLL
  47 + JSONHandler();
  48 +
  49 + QPDF_DLL
  50 + ~JSONHandler() = default;
  51 +
  52 + // Based on the type of handler, expect the object to be of a
  53 + // certain type. JSONHandler::Error is thrown otherwise. Multiple
  54 + // handlers may be registered, which allows the object to be of
  55 + // various types. If an anyHandler is added, no other handler will
  56 + // be called.
  57 +
  58 + typedef std::function<void(
  59 + std::string const& path, JSON value)> json_handler_t;
  60 + typedef std::function<void(
  61 + std::string const& path)> void_handler_t;
  62 + typedef std::function<void(
  63 + std::string const& path, std::string const& value)> string_handler_t;
  64 + typedef std::function<void(
  65 + std::string const& path, bool value)> bool_handler_t;
  66 +
  67 + // If an any handler is added, it will be called for any value
  68 + // including null, and no other handler will be called.
  69 + QPDF_DLL
  70 + void addAnyHandler(json_handler_t fn);
  71 +
  72 + // If any of the remaining handlers are registered, each
  73 + // registered handle will be called.
  74 + QPDF_DLL
  75 + void addNullHandler(void_handler_t fn);
  76 + QPDF_DLL
  77 + void addStringHandler(string_handler_t fn);
  78 + QPDF_DLL
  79 + void addNumberHandler(string_handler_t fn);
  80 + QPDF_DLL
  81 + void addBoolHandler(bool_handler_t fn);
  82 +
  83 + // Returns a reference to a map: keys are expected object keys,
  84 + // and values are handlers for that object.
  85 + QPDF_DLL
  86 + std::map<std::string, std::shared_ptr<JSONHandler>>& addDictHandlers();
  87 +
  88 + // Apply the given handler to any key not explicitly in dict
  89 + // handlers.
  90 + QPDF_DLL
  91 + void addFallbackDictHandler(std::shared_ptr<JSONHandler>);
  92 +
  93 + // Apply the given handler to each element of the array.
  94 + QPDF_DLL
  95 + void addArrayHandler(std::shared_ptr<JSONHandler>);
  96 +
  97 + // Apply handlers recursively to a JSON object.
  98 + QPDF_DLL
  99 + void handle(std::string const& path, JSON j);
  100 +
  101 + private:
  102 + JSONHandler(JSONHandler const&) = delete;
  103 +
  104 + struct Handlers
  105 + {
  106 + Handlers() :
  107 + any_handler(nullptr),
  108 + null_handler(nullptr),
  109 + string_handler(nullptr),
  110 + number_handler(nullptr),
  111 + bool_handler(nullptr)
  112 + {
  113 + }
  114 +
  115 + json_handler_t any_handler;
  116 + void_handler_t null_handler;
  117 + string_handler_t string_handler;
  118 + string_handler_t number_handler;
  119 + bool_handler_t bool_handler;
  120 + std::map<std::string, std::shared_ptr<JSONHandler>> dict_handlers;
  121 + std::shared_ptr<JSONHandler> fallback_dict_handler;
  122 + std::shared_ptr<JSONHandler> array_handler;
  123 + };
  124 +
  125 + class Members
  126 + {
  127 + friend class JSONHandler;
  128 +
  129 + public:
  130 + QPDF_DLL
  131 + ~Members() = default;
  132 +
  133 + private:
  134 + Members();
  135 + Members(Members const&) = delete;
  136 +
  137 + Handlers h;
  138 + };
  139 + PointerHolder<Members> m;
  140 +};
  141 +
  142 +#endif // JSONHANDLER_HH
... ...
libqpdf/JSON.cc
... ... @@ -90,6 +90,7 @@ std::string JSON::JSON_array::unparse(size_t depth) const
90 90 }
91 91  
92 92 JSON::JSON_string::JSON_string(std::string const& utf8) :
  93 + utf8(utf8),
93 94 encoded(encode_string(utf8))
94 95 {
95 96 }
... ... @@ -312,6 +313,83 @@ JSON::isDictionary() const
312 313 }
313 314  
314 315 bool
  316 +JSON::getString(std::string& utf8) const
  317 +{
  318 + auto v = dynamic_cast<JSON_string const*>(this->m->value.getPointer());
  319 + if (v == nullptr)
  320 + {
  321 + return false;
  322 + }
  323 + utf8 = v->utf8;
  324 + return true;
  325 +}
  326 +
  327 +bool
  328 +JSON::getNumber(std::string& value) const
  329 +{
  330 + auto v = dynamic_cast<JSON_number const*>(this->m->value.getPointer());
  331 + if (v == nullptr)
  332 + {
  333 + return false;
  334 + }
  335 + value = v->encoded;
  336 + return true;
  337 +}
  338 +
  339 +bool
  340 +JSON::getBool(bool& value) const
  341 +{
  342 + auto v = dynamic_cast<JSON_bool const*>(this->m->value.getPointer());
  343 + if (v == nullptr)
  344 + {
  345 + return false;
  346 + }
  347 + value = v->value;
  348 + return true;
  349 +}
  350 +
  351 +bool
  352 +JSON::isNull() const
  353 +{
  354 + if (dynamic_cast<JSON_null const*>(this->m->value.getPointer()))
  355 + {
  356 + return true;
  357 + }
  358 + return false;
  359 +}
  360 +
  361 +bool
  362 +JSON::forEachDictItem(
  363 + std::function<void(std::string const& key, JSON value)> fn) const
  364 +{
  365 + auto v = dynamic_cast<JSON_dictionary const*>(this->m->value.getPointer());
  366 + if (v == nullptr)
  367 + {
  368 + return false;
  369 + }
  370 + for (auto const& k: v->members)
  371 + {
  372 + fn(k.first, JSON(k.second));
  373 + }
  374 + return true;
  375 +}
  376 +
  377 +bool
  378 +JSON::forEachArrayItem(std::function<void(JSON value)> fn) const
  379 +{
  380 + auto v = dynamic_cast<JSON_array const*>(this->m->value.getPointer());
  381 + if (v == nullptr)
  382 + {
  383 + return false;
  384 + }
  385 + for (auto const& i: v->elements)
  386 + {
  387 + fn(JSON(i));
  388 + }
  389 + return true;
  390 +}
  391 +
  392 +bool
315 393 JSON::checkSchema(JSON schema, std::list<std::string>& errors)
316 394 {
317 395 return checkSchemaInternal(this->m->value.getPointer(),
... ...
libqpdf/JSONHandler.cc 0 โ†’ 100644
  1 +#include <qpdf/JSONHandler.hh>
  2 +#include <qpdf/QUtil.hh>
  3 +#include <qpdf/QTC.hh>
  4 +
  5 +JSONHandler::Error::Error(std::string const& msg) :
  6 + std::runtime_error(msg)
  7 +{
  8 +}
  9 +
  10 +JSONHandler::JSONHandler() :
  11 + m(new Members())
  12 +{
  13 +}
  14 +
  15 +JSONHandler::Members::Members()
  16 +{
  17 +}
  18 +
  19 +void
  20 +JSONHandler::addAnyHandler(json_handler_t fn)
  21 +{
  22 + this->m->h.any_handler = fn;
  23 +}
  24 +
  25 +void
  26 +JSONHandler::addNullHandler(void_handler_t fn)
  27 +{
  28 + this->m->h.null_handler = fn;
  29 +}
  30 +
  31 +void
  32 +JSONHandler::addStringHandler(string_handler_t fn)
  33 +{
  34 + this->m->h.string_handler = fn;
  35 +}
  36 +
  37 +void
  38 +JSONHandler::addNumberHandler(string_handler_t fn)
  39 +{
  40 + this->m->h.number_handler = fn;
  41 +}
  42 +
  43 +void
  44 +JSONHandler::addBoolHandler(bool_handler_t fn)
  45 +{
  46 + this->m->h.bool_handler = fn;
  47 +}
  48 +
  49 +std::map<std::string, std::shared_ptr<JSONHandler>>&
  50 +JSONHandler::addDictHandlers()
  51 +{
  52 + return this->m->h.dict_handlers;
  53 +}
  54 +
  55 +void
  56 +JSONHandler::addFallbackDictHandler(std::shared_ptr<JSONHandler> fdh)
  57 +{
  58 + this->m->h.fallback_dict_handler = fdh;
  59 +}
  60 +
  61 +void
  62 +JSONHandler::addArrayHandler(std::shared_ptr<JSONHandler> ah)
  63 +{
  64 + this->m->h.array_handler = ah;
  65 +}
  66 +
  67 +void
  68 +JSONHandler::handle(std::string const& path, JSON j)
  69 +{
  70 + if (this->m->h.any_handler)
  71 + {
  72 + this->m->h.any_handler(path, j);
  73 + return;
  74 + }
  75 + bool handled = false;
  76 + bool bvalue = false;
  77 + std::string svalue;
  78 + if (this->m->h.null_handler && j.isNull())
  79 + {
  80 + this->m->h.null_handler(path);
  81 + handled = true;
  82 + }
  83 + if (this->m->h.string_handler && j.getString(svalue))
  84 + {
  85 + this->m->h.string_handler(path, svalue);
  86 + handled = true;
  87 + }
  88 + if (this->m->h.number_handler && j.getNumber(svalue))
  89 + {
  90 + this->m->h.number_handler(path, svalue);
  91 + handled = true;
  92 + }
  93 + if (this->m->h.bool_handler && j.getBool(bvalue))
  94 + {
  95 + this->m->h.bool_handler(path, bvalue);
  96 + handled = true;
  97 + }
  98 + if ((this->m->h.fallback_dict_handler.get() ||
  99 + (! this->m->h.dict_handlers.empty())) && j.isDictionary())
  100 + {
  101 + std::string path_base = path;
  102 + if (path_base != ".")
  103 + {
  104 + path_base += ".";
  105 + }
  106 + j.forEachDictItem([&path, &path_base, this](
  107 + std::string const& k, JSON v) {
  108 + auto i = this->m->h.dict_handlers.find(k);
  109 + if (i == this->m->h.dict_handlers.end())
  110 + {
  111 + if (this->m->h.fallback_dict_handler.get())
  112 + {
  113 + this->m->h.fallback_dict_handler->handle(
  114 + path_base + k, v);
  115 + }
  116 + else
  117 + {
  118 + QTC::TC("libtests", "JSONHandler unexpected key");
  119 + throw Error(
  120 + "JSON handler found unexpected key " + k +
  121 + " in object at " + path);
  122 + }
  123 + }
  124 + else
  125 + {
  126 + i->second->handle(path_base + k, v);
  127 + }
  128 + });
  129 +
  130 + // Set handled = true even if we didn't call any handlers.
  131 + // This dictionary could have been empty, but it's okay since
  132 + // it's a dictionary like it's supposed to be.
  133 + handled = true;
  134 + }
  135 + if (this->m->h.array_handler.get())
  136 + {
  137 + size_t i = 0;
  138 + j.forEachArrayItem([&i, &path, this](JSON v) {
  139 + this->m->h.array_handler->handle(
  140 + path + "[" + QUtil::uint_to_string(i) + "]", v);
  141 + ++i;
  142 + });
  143 + // Set handled = true even if we didn't call any handlers.
  144 + // This could have been an empty array.
  145 + handled = true;
  146 + }
  147 +
  148 + if (! handled)
  149 + {
  150 + // It would be nice to include information about what type the
  151 + // object was and what types were allowed, but we're relying
  152 + // on schema validation to make sure input is properly
  153 + // structured before calling the handlers. It would be
  154 + // different if this code were trying to be part of a
  155 + // general-purpose JSON package.
  156 + QTC::TC("libtests", "JSONHandler unhandled value");
  157 + throw Error("JSON handler: value at " + path +
  158 + " is not of expected type");
  159 + }
  160 +}
... ...
libqpdf/build.mk
... ... @@ -38,6 +38,7 @@ SRCS_libqpdf = \
38 38 libqpdf/InputSource.cc \
39 39 libqpdf/InsecureRandomDataProvider.cc \
40 40 libqpdf/JSON.cc \
  41 + libqpdf/JSONHandler.cc \
41 42 libqpdf/MD5.cc \
42 43 libqpdf/NNTree.cc \
43 44 libqpdf/OffsetInputSource.cc \
... ...
libtests/build.mk
... ... @@ -13,6 +13,7 @@ BINS_libtests = \
13 13 hex \
14 14 input_source \
15 15 json \
  16 + json_handler \
16 17 json_parse \
17 18 lzw \
18 19 main_from_wmain \
... ...
libtests/json.cc
1 1 #include <qpdf/JSON.hh>
2 2 #include <qpdf/QPDFObjectHandle.hh>
3 3 #include <iostream>
4   -#include <assert.h>
  4 +#include <cassert>
5 5  
6 6 static void check(JSON const& j, std::string const& exp)
7 7 {
... ... @@ -20,12 +20,25 @@ static void test_main()
20 20 "\\u0003\\t\\b\\r\\n<4>\"");
21 21 JSON jnull = JSON::makeNull();
22 22 check(jnull, "null");
  23 + assert(jnull.isNull());
  24 + std::string value;
  25 + assert(! jnull.getNumber(value));
23 26 JSON jarr = JSON::makeArray();
24 27 check(jarr, "[]");
25 28 JSON jstr2 = JSON::makeString("a\tb");
  29 + assert(jstr2.getString(value));
  30 + assert(value == "a\tb");
  31 + assert(! jstr2.getNumber(value));
26 32 JSON jint = JSON::makeInt(16059);
27 33 JSON jdouble = JSON::makeReal(3.14159);
28 34 JSON jexp = JSON::makeNumber("2.1e5");
  35 + JSON jbool1 = JSON::makeBool(true);
  36 + JSON jbool2 = JSON::makeBool(false);
  37 + bool bvalue = false;
  38 + assert(jbool1.getBool(bvalue));
  39 + assert(bvalue);
  40 + assert(jbool2.getBool(bvalue));
  41 + assert(! bvalue);
29 42 jarr.addArrayElement(jstr2);
30 43 jarr.addArrayElement(jnull);
31 44 jarr.addArrayElement(jint);
... ... @@ -39,6 +52,18 @@ static void test_main()
39 52 " 3.14159,\n"
40 53 " 2.1e5\n"
41 54 "]");
  55 + std::vector<std::string> avalue;
  56 + assert(jarr.forEachArrayItem([&avalue](JSON j) {
  57 + avalue.push_back(j.unparse());
  58 + }));
  59 + std::vector<std::string> xavalue = {
  60 + "\"a\\tb\"",
  61 + "null",
  62 + "16059",
  63 + "3.14159",
  64 + "2.1e5",
  65 + };
  66 + assert(avalue == xavalue);
42 67 JSON jmap = JSON::makeDictionary();
43 68 check(jmap, "{}");
44 69 jmap.addDictionaryMember("b", jstr2);
... ... @@ -73,6 +98,18 @@ static void test_main()
73 98 check(QPDFObjectHandle::newReal(".34").getJSON(), "0.34");
74 99 check(QPDFObjectHandle::newReal("-0.56").getJSON(), "-0.56");
75 100 check(QPDFObjectHandle::newReal("-.78").getJSON(), "-0.78");
  101 + JSON jmap2 = JSON::parse(R"({"a": 1, "b": "two", "c": [true]})");
  102 + std::map<std::string, std::string> dvalue;
  103 + assert(jmap2.forEachDictItem([&dvalue]
  104 + (std::string const& k, JSON j) {
  105 + dvalue[k] = j.unparse();
  106 + }));
  107 + std::map<std::string, std::string> xdvalue = {
  108 + {"a", "1"},
  109 + {"b", "\"two\""},
  110 + {"c", "[\n true\n]"},
  111 + };
  112 + assert(dvalue == xdvalue);
76 113 }
77 114  
78 115 static void check_schema(JSON& obj, JSON& schema, bool exp,
... ...
libtests/json_handler.cc 0 โ†’ 100644
  1 +#include <qpdf/JSONHandler.hh>
  2 +#include <qpdf/QUtil.hh>
  3 +#include <iostream>
  4 +#include <cassert>
  5 +
  6 +static void print_null(std::string const& path)
  7 +{
  8 + std::cout << path << ": null" << std::endl;
  9 +}
  10 +
  11 +static void print_string(std::string const& path, std::string const& value)
  12 +{
  13 + std::cout << path << ": string: " << value << std::endl;
  14 +}
  15 +
  16 +static void print_number(std::string const& path, std::string const& value)
  17 +{
  18 + std::cout << path << ": number: " << value << std::endl;
  19 +}
  20 +
  21 +static void print_bool(std::string const& path, bool value)
  22 +{
  23 + std::cout << path << ": bool: " << (value ? "true" : "false") << std::endl;
  24 +}
  25 +
  26 +static void print_json(std::string const& path, JSON value)
  27 +{
  28 + std::cout << path << ": json: " << value.unparse() << std::endl;
  29 +}
  30 +
  31 +static void test_scalar()
  32 +{
  33 + std::cout << "-- scalar --" << std::endl;
  34 + JSONHandler h;
  35 + h.addStringHandler(print_string);
  36 + JSON j = JSON::parse("\"potato\"");
  37 + h.handle(".", j);
  38 +}
  39 +
  40 +static std::shared_ptr<JSONHandler> make_all_handler()
  41 +{
  42 + auto h = std::make_shared<JSONHandler>();
  43 + auto& m = h->addDictHandlers();
  44 + auto h1 = std::make_shared<JSONHandler>();
  45 + h1->addStringHandler(print_string);
  46 + m["one"] = h1;
  47 + auto h2 = std::make_shared<JSONHandler>();
  48 + h2->addNumberHandler(print_number);
  49 + m["two"] = h2;
  50 + auto h3 = std::make_shared<JSONHandler>();
  51 + h3->addBoolHandler(print_bool);
  52 + m["three"] = h3;
  53 + auto h4 = std::make_shared<JSONHandler>();
  54 + h4->addAnyHandler(print_json);
  55 + m["four"] = h4;
  56 + m["phour"] = h4; // share h4
  57 + auto h5 = std::make_shared<JSONHandler>();
  58 + // Allow to be either string or bool
  59 + h5->addBoolHandler(print_bool);
  60 + h5->addStringHandler(print_string);
  61 + h5->addNullHandler(print_null);
  62 + auto h5s = std::make_shared<JSONHandler>();
  63 + m["five"] = h5s;
  64 + h5s->addArrayHandler(h5);
  65 + auto h6 = std::make_shared<JSONHandler>();
  66 + auto& m6 = h6->addDictHandlers();
  67 + auto h6a = std::make_shared<JSONHandler>();
  68 + m6["a"] = h6a;
  69 + auto& m6a = h6a->addDictHandlers();
  70 + auto h6ab = std::make_shared<JSONHandler>();
  71 + m6a["b"] = h6ab;
  72 + auto h6ax = std::make_shared<JSONHandler>();
  73 + h6ax->addAnyHandler(print_json);
  74 + h6a->addFallbackDictHandler(h6ax);
  75 + m6["b"] = h6ab; // share
  76 + h6ab->addStringHandler(print_string);
  77 + m["six"] = h6;
  78 + return h;
  79 +}
  80 +
  81 +static void test_all()
  82 +{
  83 + std::cout << "-- all --" << std::endl;
  84 + auto h = make_all_handler();
  85 + JSON j = JSON::parse(R"({
  86 + "one": "potato",
  87 + "two": 3.14,
  88 + "three": true,
  89 + "four": ["a", 1],
  90 + "five": ["x", false, "y", null, true],
  91 + "phour": null,
  92 + "six": {"a": {"b": "quack", "Q": "baaa"}, "b": "moo"}
  93 +})");
  94 + h->handle(".", j);
  95 +}
  96 +
  97 +static void test_errors()
  98 +{
  99 + std::cout << "-- errors --" << std::endl;
  100 + auto h = make_all_handler();
  101 + auto t = [h](std::string const& msg, std::function<void()> fn) {
  102 + try
  103 + {
  104 + fn();
  105 + assert(false);
  106 + }
  107 + catch (JSONHandler::Error& e)
  108 + {
  109 + std::cout << msg << ": " << e.what() << std::endl;
  110 + }
  111 + };
  112 +
  113 + t("bad type at top", [&h](){
  114 + h->handle(".", JSON::makeString("oops"));
  115 + });
  116 + t("unexpected key", [&h](){
  117 + JSON j = JSON::parse(R"({"x": "y"})");
  118 + h->handle(".", j);
  119 + });
  120 +}
  121 +
  122 +int main(int argc, char* argv[])
  123 +{
  124 + test_scalar();
  125 + test_all();
  126 + test_errors();
  127 + return 0;
  128 +}
... ...
libtests/libtests.testcov
... ... @@ -87,3 +87,5 @@ JSON parse leading zero 0
87 87 JSON parse number no digits 0
88 88 JSON parse premature end of u 0
89 89 JSON parse bad hex after u 0
  90 +JSONHandler unhandled value 0
  91 +JSONHandler unexpected key 0
... ...
libtests/qtest/json_handler.test 0 โ†’ 100644
  1 +#!/usr/bin/env perl
  2 +require 5.008;
  3 +use warnings;
  4 +use strict;
  5 +
  6 +chdir("json_handler") or die "chdir testdir failed: $!\n";
  7 +
  8 +require TestDriver;
  9 +
  10 +my $td = new TestDriver('json_handler');
  11 +
  12 +$td->runtest("JSON handler",
  13 + {$td->COMMAND => "json_handler"},
  14 + {$td->FILE => "json_handler.out", $td->EXIT_STATUS => 0},
  15 + $td->NORMALIZE_NEWLINES);
  16 +
  17 +$td->report(1);
... ...
libtests/qtest/json_handler/json_handler.out 0 โ†’ 100644
  1 +-- scalar --
  2 +.: string: potato
  3 +-- all --
  4 +.five[0]: string: x
  5 +.five[1]: bool: false
  6 +.five[2]: string: y
  7 +.five[3]: null
  8 +.five[4]: bool: true
  9 +.four: json: [
  10 + "a",
  11 + 1
  12 +]
  13 +.one: string: potato
  14 +.phour: json: null
  15 +.six.a.Q: json: "baaa"
  16 +.six.a.b: string: quack
  17 +.six.b: string: moo
  18 +.three: bool: true
  19 +.two: number: 3.14
  20 +-- errors --
  21 +bad type at top: JSON handler: value at . is not of expected type
  22 +unexpected key: JSON handler found unexpected key x in object at .
... ...