Commit 651179b5da0777f861e427f96fd8560bf1516ae5

Authored by Jay Berkenbilt
1 parent 0776c001

Add simple JSON serializer

ChangeLog
1 1 2018-12-18 Jay Berkenbilt <ejb@ql.org>
2 2  
  3 + * Add a simple JSON serializer. This is not a complete or
  4 + general-purpose JSON library. It allows assembly and serialization
  5 + of JSON structures with some restrictions, which are described in
  6 + the header file.
  7 +
3 8 * Add QPDFNameTreeObjectHelper class. This class provides useful
4 9 methods for dealing with name trees, which are discussed in
5 10 section 7.9.6 of the PDF spec (ISO-32000).
... ...
include/qpdf/JSON.hh 0 โ†’ 100644
  1 +// Copyright (c) 2005-2018 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 JSON_HH
  23 +#define JSON_HH
  24 +
  25 +// This is a simple JSON serializer, primarily designed for
  26 +// serializing QPDF Objects as JSON. JSON objects contain their data
  27 +// as smart pointers. One JSON object is added to another, this
  28 +// pointer is copied. This means you can create temporary JSON objects
  29 +// on the stack, add them to other objects, and let them go out of
  30 +// scope safely. It also means that if the json JSON object is added
  31 +// in more than one place, all copies share underlying data.
  32 +
  33 +#include <qpdf/DLL.h>
  34 +#include <qpdf/PointerHolder.hh>
  35 +#include <string>
  36 +#include <map>
  37 +#include <vector>
  38 +#include <list>
  39 +
  40 +class JSON
  41 +{
  42 + public:
  43 + QPDF_DLL
  44 + std::string serialize() const;
  45 +
  46 + // The JSON spec calls dictionaries "objects", but that creates
  47 + // too much confusion when referring to instances of the JSON
  48 + // class.
  49 + QPDF_DLL
  50 + static JSON makeDictionary();
  51 + // addDictionaryMember returns the newly added item.
  52 + QPDF_DLL
  53 + JSON addDictionaryMember(std::string const& key, JSON const&);
  54 + QPDF_DLL
  55 + static JSON makeArray();
  56 + // addArrayElement returns the newly added item.
  57 + QPDF_DLL
  58 + JSON addArrayElement(JSON const&);
  59 + QPDF_DLL
  60 + static JSON makeString(std::string const& utf8);
  61 + QPDF_DLL
  62 + static JSON makeInt(long long int value);
  63 + QPDF_DLL
  64 + static JSON makeReal(double value);
  65 + QPDF_DLL
  66 + static JSON makeNumber(std::string const& encoded);
  67 + QPDF_DLL
  68 + static JSON makeBool(bool value);
  69 + QPDF_DLL
  70 + static JSON makeNull();
  71 +
  72 + // Check this JSON object against a "schema". This is not a schema
  73 + // according to any standard. It's just a template of what the
  74 + // JSON is supposed to contain. The checking does the following:
  75 + //
  76 + // * The schema is a nested structure containing dictionaries,
  77 + // single-element arrays, and strings only.
  78 + // * Recursively walk the schema
  79 + // * If the current value is a dictionary, this object must have
  80 + // a dictionary in the same place with the same keys
  81 + // * If the current value is an array, this object must have an
  82 + // array in the same place. The schema's array must contain a
  83 + // single element, which is used as a schema to validate each
  84 + // element of this object's corresponding array.
  85 + // * Otherwise, the value is ignored.
  86 + //
  87 + // QPDF's JSON output conforms to certain strict compatability
  88 + // rules as discussed in the manual. The idea is that a JSON
  89 + // structure created manually in qpdf.cc doubles as both JSON help
  90 + // information and a schema for validating the JSON that qpdf
  91 + // generates. Any discrepancies are a bug in qpdf.
  92 + QPDF_DLL
  93 + bool checkSchema(JSON schema, std::list<std::string>& errors);
  94 +
  95 + private:
  96 + static std::string encode_string(std::string const& utf8);
  97 +
  98 + struct JSON_value
  99 + {
  100 + virtual ~JSON_value();
  101 + virtual std::string unparse(size_t depth) const = 0;
  102 + };
  103 + struct JSON_dictionary: public JSON_value
  104 + {
  105 + virtual ~JSON_dictionary();
  106 + virtual std::string unparse(size_t depth) const;
  107 + std::map<std::string, PointerHolder<JSON_value> > members;
  108 + };
  109 + struct JSON_array: public JSON_value
  110 + {
  111 + virtual ~JSON_array();
  112 + virtual std::string unparse(size_t depth) const;
  113 + std::vector<PointerHolder<JSON_value> > elements;
  114 + };
  115 + struct JSON_string: public JSON_value
  116 + {
  117 + JSON_string(std::string const& utf8);
  118 + virtual ~JSON_string();
  119 + virtual std::string unparse(size_t depth) const;
  120 + std::string encoded;
  121 + };
  122 + struct JSON_number: public JSON_value
  123 + {
  124 + JSON_number(long long val);
  125 + JSON_number(double val);
  126 + JSON_number(std::string const& val);
  127 + virtual ~JSON_number();
  128 + virtual std::string unparse(size_t depth) const;
  129 + std::string encoded;
  130 + };
  131 + struct JSON_bool: public JSON_value
  132 + {
  133 + JSON_bool(bool val);
  134 + virtual ~JSON_bool();
  135 + virtual std::string unparse(size_t depth) const;
  136 + bool value;
  137 + };
  138 + struct JSON_null: public JSON_value
  139 + {
  140 + virtual ~JSON_null();
  141 + virtual std::string unparse(size_t depth) const;
  142 + };
  143 +
  144 + JSON(PointerHolder<JSON_value>);
  145 +
  146 + static bool
  147 + checkSchemaInternal(JSON_value* this_v, JSON_value* sch_v,
  148 + std::list<std::string>& errors,
  149 + std::string prefix);
  150 +
  151 + class Members
  152 + {
  153 + friend class JSON;
  154 +
  155 + public:
  156 + QPDF_DLL
  157 + ~Members();
  158 +
  159 + private:
  160 + Members(PointerHolder<JSON_value>);
  161 + Members(Members const&);
  162 +
  163 + PointerHolder<JSON_value> value;
  164 + };
  165 +
  166 + PointerHolder<Members> m;
  167 +};
  168 +
  169 +
  170 +#endif // JSON_HH
... ...
libqpdf/JSON.cc 0 โ†’ 100644
  1 +#include <qpdf/JSON.hh>
  2 +#include <qpdf/QUtil.hh>
  3 +#include <qpdf/QTC.hh>
  4 +#include <stdexcept>
  5 +
  6 +JSON::Members::~Members()
  7 +{
  8 +}
  9 +
  10 +JSON::Members::Members(PointerHolder<JSON_value> value) :
  11 + value(value)
  12 +{
  13 +}
  14 +
  15 +JSON::JSON(PointerHolder<JSON_value> value) :
  16 + m(new Members(value))
  17 +{
  18 +}
  19 +
  20 +JSON::JSON_value::~JSON_value()
  21 +{
  22 +}
  23 +
  24 +JSON::JSON_dictionary::~JSON_dictionary()
  25 +{
  26 +}
  27 +
  28 +std::string JSON::JSON_dictionary::unparse(size_t depth) const
  29 +{
  30 + std::string result = "{";
  31 + bool first = true;
  32 + for (std::map<std::string, PointerHolder<JSON_value> >::const_iterator
  33 + iter = members.begin();
  34 + iter != members.end(); ++iter)
  35 + {
  36 + if (first)
  37 + {
  38 + first = false;
  39 + }
  40 + else
  41 + {
  42 + result.append(1, ',');
  43 + }
  44 + result.append(1, '\n');
  45 + result.append(2 * (1 + depth), ' ');
  46 + result += ("\"" + (*iter).first + "\": " +
  47 + (*iter).second->unparse(1 + depth));
  48 + }
  49 + if (! first)
  50 + {
  51 + result.append(1, '\n');
  52 + result.append(2 * depth, ' ');
  53 + }
  54 + result.append(1, '}');
  55 + return result;
  56 +}
  57 +
  58 +JSON::JSON_array::~JSON_array()
  59 +{
  60 +}
  61 +
  62 +std::string JSON::JSON_array::unparse(size_t depth) const
  63 +{
  64 + std::string result = "[";
  65 + bool first = true;
  66 + for (std::vector<PointerHolder<JSON_value> >::const_iterator iter =
  67 + elements.begin();
  68 + iter != elements.end(); ++iter)
  69 + {
  70 + if (first)
  71 + {
  72 + first = false;
  73 + }
  74 + else
  75 + {
  76 + result.append(1, ',');
  77 + }
  78 + result.append(1, '\n');
  79 + result.append(2 * (1 + depth), ' ');
  80 + result += (*iter)->unparse(1 + depth);
  81 + }
  82 + if (! first)
  83 + {
  84 + result.append(1, '\n');
  85 + result.append(2 * depth, ' ');
  86 + }
  87 + result.append(1, ']');
  88 + return result;
  89 +}
  90 +
  91 +JSON::JSON_string::JSON_string(std::string const& utf8) :
  92 + encoded(encode_string(utf8))
  93 +{
  94 +}
  95 +
  96 +JSON::JSON_string::~JSON_string()
  97 +{
  98 +}
  99 +
  100 +std::string JSON::JSON_string::unparse(size_t) const
  101 +{
  102 + return "\"" + encoded + "\"";
  103 +}
  104 +
  105 +JSON::JSON_number::JSON_number(long long value) :
  106 + encoded(QUtil::int_to_string(value))
  107 +{
  108 +}
  109 +
  110 +JSON::JSON_number::JSON_number(double value) :
  111 + encoded(QUtil::double_to_string(value, 6))
  112 +{
  113 +}
  114 +
  115 +JSON::JSON_number::JSON_number(std::string const& value) :
  116 + encoded(value)
  117 +{
  118 +}
  119 +
  120 +JSON::JSON_number::~JSON_number()
  121 +{
  122 +}
  123 +
  124 +std::string JSON::JSON_number::unparse(size_t) const
  125 +{
  126 + return encoded;
  127 +}
  128 +
  129 +JSON::JSON_bool::JSON_bool(bool val) :
  130 + value(val)
  131 +{
  132 +}
  133 +
  134 +JSON::JSON_bool::~JSON_bool()
  135 +{
  136 +}
  137 +
  138 +std::string JSON::JSON_bool::unparse(size_t) const
  139 +{
  140 + return value ? "true" : "false";
  141 +}
  142 +
  143 +JSON::JSON_null::~JSON_null()
  144 +{
  145 +}
  146 +
  147 +std::string JSON::JSON_null::unparse(size_t) const
  148 +{
  149 + return "null";
  150 +}
  151 +
  152 +std::string
  153 +JSON::serialize() const
  154 +{
  155 + if (0 == this->m->value.getPointer())
  156 + {
  157 + return "null";
  158 + }
  159 + else
  160 + {
  161 + return this->m->value->unparse(0);
  162 + }
  163 +}
  164 +
  165 +std::string
  166 +JSON::encode_string(std::string const& str)
  167 +{
  168 + std::string result;
  169 + size_t len = str.length();
  170 + for (size_t i = 0; i < len; ++i)
  171 + {
  172 + unsigned char ch = static_cast<unsigned char>(str.at(i));
  173 + switch (ch)
  174 + {
  175 + case '\\':
  176 + result += "\\\\";
  177 + break;
  178 + case '\"':
  179 + result += "\\\"";
  180 + break;
  181 + case '\b':
  182 + result += "\\b";
  183 + break;
  184 + case '\n':
  185 + result += "\\n";
  186 + break;
  187 + case '\r':
  188 + result += "\\r";
  189 + break;
  190 + case '\t':
  191 + result += "\\t";
  192 + break;
  193 + default:
  194 + if (ch < 32)
  195 + {
  196 + result += "\\u" + QUtil::int_to_string_base(ch, 16, 4);
  197 + }
  198 + else
  199 + {
  200 + result.append(1, ch);
  201 + }
  202 + }
  203 + }
  204 + return result;
  205 +}
  206 +
  207 +JSON
  208 +JSON::makeDictionary()
  209 +{
  210 + return JSON(new JSON_dictionary());
  211 +}
  212 +
  213 +JSON
  214 +JSON::addDictionaryMember(std::string const& key, JSON const& val)
  215 +{
  216 + JSON_dictionary* obj = dynamic_cast<JSON_dictionary*>(
  217 + this->m->value.getPointer());
  218 + if (0 == obj)
  219 + {
  220 + throw std::runtime_error(
  221 + "JSON::addDictionaryMember called on non-dictionary");
  222 + }
  223 + if (val.m->value.getPointer())
  224 + {
  225 + obj->members[encode_string(key)] = val.m->value;
  226 + }
  227 + else
  228 + {
  229 + obj->members[encode_string(key)] = new JSON_null();
  230 + }
  231 + return obj->members[encode_string(key)];
  232 +}
  233 +
  234 +JSON
  235 +JSON::makeArray()
  236 +{
  237 + return JSON(new JSON_array());
  238 +}
  239 +
  240 +JSON
  241 +JSON::addArrayElement(JSON const& val)
  242 +{
  243 + JSON_array* arr = dynamic_cast<JSON_array*>(
  244 + this->m->value.getPointer());
  245 + if (0 == arr)
  246 + {
  247 + throw std::runtime_error("JSON::addArrayElement called on non-array");
  248 + }
  249 + if (val.m->value.getPointer())
  250 + {
  251 + arr->elements.push_back(val.m->value);
  252 + }
  253 + else
  254 + {
  255 + arr->elements.push_back(new JSON_null());
  256 + }
  257 + return arr->elements.back();
  258 +}
  259 +
  260 +JSON
  261 +JSON::makeString(std::string const& utf8)
  262 +{
  263 + return JSON(new JSON_string(utf8));
  264 +}
  265 +
  266 +JSON
  267 +JSON::makeInt(long long int value)
  268 +{
  269 + return JSON(new JSON_number(value));
  270 +}
  271 +
  272 +JSON
  273 +JSON::makeReal(double value)
  274 +{
  275 + return JSON(new JSON_number(value));
  276 +}
  277 +
  278 +JSON
  279 +JSON::makeNumber(std::string const& encoded)
  280 +{
  281 + return JSON(new JSON_number(encoded));
  282 +}
  283 +
  284 +JSON
  285 +JSON::makeBool(bool value)
  286 +{
  287 + return JSON(new JSON_bool(value));
  288 +}
  289 +
  290 +JSON
  291 +JSON::makeNull()
  292 +{
  293 + return JSON(new JSON_null());
  294 +}
  295 +
  296 +bool
  297 +JSON::checkSchema(JSON schema, std::list<std::string>& errors)
  298 +{
  299 + return checkSchemaInternal(this->m->value.getPointer(),
  300 + schema.m->value.getPointer(),
  301 + errors, "");
  302 +}
  303 +
  304 +
  305 +bool
  306 +JSON::checkSchemaInternal(JSON_value* this_v, JSON_value* sch_v,
  307 + std::list<std::string>& errors,
  308 + std::string prefix)
  309 +{
  310 + JSON_array* this_arr = dynamic_cast<JSON_array*>(this_v);
  311 + JSON_dictionary* this_dict = dynamic_cast<JSON_dictionary*>(this_v);
  312 +
  313 + JSON_array* sch_arr = dynamic_cast<JSON_array*>(sch_v);
  314 + JSON_dictionary* sch_dict = dynamic_cast<JSON_dictionary*>(sch_v);
  315 +
  316 + std::string err_prefix;
  317 + if (prefix.empty())
  318 + {
  319 + err_prefix = "top-level object";
  320 + }
  321 + else
  322 + {
  323 + err_prefix = "json key \"" + prefix + "\"";
  324 + }
  325 +
  326 + if (sch_dict)
  327 + {
  328 + if (! this_dict)
  329 + {
  330 + QTC::TC("libtests", "JSON wanted dictionary");
  331 + errors.push_back(err_prefix + " is supposed to be a dictionary");
  332 + return false;
  333 + }
  334 + for (std::map<std::string, PointerHolder<JSON_value> >::iterator iter =
  335 + sch_dict->members.begin();
  336 + iter != sch_dict->members.end(); ++iter)
  337 + {
  338 + std::string const& key = (*iter).first;
  339 + if (this_dict->members.count(key))
  340 + {
  341 + checkSchemaInternal(
  342 + this_dict->members[key].getPointer(),
  343 + (*iter).second.getPointer(),
  344 + errors, prefix + "." + key);
  345 + }
  346 + else
  347 + {
  348 + QTC::TC("libtests", "JSON key missing in object");
  349 + errors.push_back(
  350 + err_prefix + ": key \"" + key +
  351 + "\" is present in schema but missing in object");
  352 + }
  353 + }
  354 + for (std::map<std::string, PointerHolder<JSON_value> >::iterator iter =
  355 + this_dict->members.begin();
  356 + iter != this_dict->members.end(); ++iter)
  357 + {
  358 + std::string const& key = (*iter).first;
  359 + if (sch_dict->members.count(key) == 0)
  360 + {
  361 + QTC::TC("libtests", "JSON key extra in object");
  362 + errors.push_back(
  363 + err_prefix + ": key \"" + key +
  364 + "\" is not present in schema but appears in object");
  365 + }
  366 + }
  367 + }
  368 + else if (sch_arr)
  369 + {
  370 + if (! this_arr)
  371 + {
  372 + QTC::TC("libtests", "JSON wanted array");
  373 + errors.push_back(err_prefix + " is supposed to be an array");
  374 + return false;
  375 + }
  376 + if (sch_arr->elements.size() != 1)
  377 + {
  378 + QTC::TC("libtests", "JSON schema array error");
  379 + errors.push_back(err_prefix +
  380 + " schema array contains other than one item");
  381 + return false;
  382 + }
  383 + int i = 0;
  384 + for (std::vector<PointerHolder<JSON_value> >::iterator iter =
  385 + this_arr->elements.begin();
  386 + iter != this_arr->elements.end(); ++iter, ++i)
  387 + {
  388 + checkSchemaInternal(
  389 + (*iter).getPointer(),
  390 + sch_arr->elements.at(0).getPointer(),
  391 + errors, prefix + "." + QUtil::int_to_string(i));
  392 + }
  393 + }
  394 +
  395 + return errors.empty();
  396 +}
... ...
libqpdf/build.mk
... ... @@ -14,6 +14,7 @@ SRCS_libqpdf = \
14 14 libqpdf/FileInputSource.cc \
15 15 libqpdf/InputSource.cc \
16 16 libqpdf/InsecureRandomDataProvider.cc \
  17 + libqpdf/JSON.cc \
17 18 libqpdf/MD5.cc \
18 19 libqpdf/OffsetInputSource.cc \
19 20 libqpdf/Pipeline.cc \
... ...
libtests/build.mk
... ... @@ -10,6 +10,7 @@ BINS_libtests = \
10 10 flate \
11 11 hex \
12 12 input_source \
  13 + json \
13 14 lzw \
14 15 md5 \
15 16 pointer_holder \
... ...
libtests/json.cc 0 โ†’ 100644
  1 +#include <qpdf/JSON.hh>
  2 +#include <qpdf/QPDFObjectHandle.hh>
  3 +#include <iostream>
  4 +#include <assert.h>
  5 +
  6 +static void check(JSON& j, std::string const& exp)
  7 +{
  8 + if (exp != j.serialize())
  9 + {
  10 + std::cout << "Got " << j.serialize() << "; wanted " << exp << "\n";
  11 + }
  12 +}
  13 +
  14 +static void test_main()
  15 +{
  16 + JSON jstr = JSON::makeString(
  17 + "<1>\xcf\x80<2>\xf0\x9f\xa5\x94\\\"<3>\x03\t\b\r\n<4>");
  18 + check(jstr,
  19 + "\"<1>\xcf\x80<2>\xf0\x9f\xa5\x94\\\\\\\"<3>"
  20 + "\\u0003\\t\\b\\r\\n<4>\"");
  21 + JSON jnull = JSON::makeNull();
  22 + check(jnull, "null");
  23 + JSON jarr = JSON::makeArray();
  24 + check(jarr, "[]");
  25 + JSON jstr2 = JSON::makeString("a\tb");
  26 + JSON jint = JSON::makeInt(16059);
  27 + JSON jdouble = JSON::makeReal(3.14159);
  28 + JSON jexp = JSON::makeNumber("2.1e5");
  29 + jarr.addArrayElement(jstr2);
  30 + jarr.addArrayElement(jnull);
  31 + jarr.addArrayElement(jint);
  32 + jarr.addArrayElement(jdouble);
  33 + jarr.addArrayElement(jexp);
  34 + check(jarr,
  35 + "[\n"
  36 + " \"a\\tb\",\n"
  37 + " null,\n"
  38 + " 16059,\n"
  39 + " 3.141590,\n"
  40 + " 2.1e5\n"
  41 + "]");
  42 + JSON jmap = JSON::makeDictionary();
  43 + check(jmap, "{}");
  44 + jmap.addDictionaryMember("b", jstr2);
  45 + jmap.addDictionaryMember("a", jarr);
  46 + jmap.addDictionaryMember("c\r\nd", jnull);
  47 + jmap.addDictionaryMember("yes", JSON::makeBool(false));
  48 + jmap.addDictionaryMember("no", JSON::makeBool(true));
  49 + jmap.addDictionaryMember("empty_dict", JSON::makeDictionary());
  50 + jmap.addDictionaryMember("empty_list", JSON::makeArray());
  51 + jmap.addDictionaryMember("single", JSON::makeArray()).
  52 + addArrayElement(JSON::makeInt(12));
  53 + check(jmap,
  54 + "{\n"
  55 + " \"a\": [\n"
  56 + " \"a\\tb\",\n"
  57 + " null,\n"
  58 + " 16059,\n"
  59 + " 3.141590,\n"
  60 + " 2.1e5\n"
  61 + " ],\n"
  62 + " \"b\": \"a\\tb\",\n"
  63 + " \"c\\r\\nd\": null,\n"
  64 + " \"empty_dict\": {},\n"
  65 + " \"empty_list\": [],\n"
  66 + " \"no\": true,\n"
  67 + " \"single\": [\n"
  68 + " 12\n"
  69 + " ],\n"
  70 + " \"yes\": false\n"
  71 + "}");
  72 +}
  73 +
  74 +static void check_schema(JSON& obj, JSON& schema, bool exp,
  75 + std::string const& description)
  76 +{
  77 + std::list<std::string> errors;
  78 + std::cout << "--- " << description << std::endl;
  79 + assert(exp == obj.checkSchema(schema, errors));
  80 + for (std::list<std::string>::iterator iter = errors.begin();
  81 + iter != errors.end(); ++iter)
  82 + {
  83 + std::cout << *iter << std::endl;
  84 + }
  85 + std::cout << "---" << std::endl;
  86 +}
  87 +
  88 +static void test_schema()
  89 +{
  90 + // Since we don't have a JSON parser, use the PDF parser as a
  91 + // shortcut for creating a complex JSON structure.
  92 + JSON schema = QPDFObjectHandle::parse(
  93 + "<<"
  94 + " /one <<"
  95 + " /a <<"
  96 + " /q (queue)"
  97 + " /r <<"
  98 + " /x (ecks)"
  99 + " /y (why)"
  100 + " >>"
  101 + " /s [ (esses) ]"
  102 + " >>"
  103 + " >>"
  104 + " /two ["
  105 + " <<"
  106 + " /goose (gander)"
  107 + " /glarp (enspliel)"
  108 + " >>"
  109 + " ]"
  110 + ">>").getJSON();
  111 + JSON a = QPDFObjectHandle::parse("[(not a) (dictionary)]").getJSON();
  112 + check_schema(a, schema, false, "top-level type mismatch");
  113 + JSON b = QPDFObjectHandle::parse(
  114 + "<<"
  115 + " /one <<"
  116 + " /a <<"
  117 + " /t (oops)"
  118 + " /r ["
  119 + " /x (ecks)"
  120 + " /y (why)"
  121 + " ]"
  122 + " /s << /z (esses) >>"
  123 + " >>"
  124 + " >>"
  125 + " /two ["
  126 + " <<"
  127 + " /goose (0 gander)"
  128 + " /glarp (0 enspliel)"
  129 + " >>"
  130 + " <<"
  131 + " /goose (1 gander)"
  132 + " /flarp (1 enspliel)"
  133 + " >>"
  134 + " 2"
  135 + " [ (three) ]"
  136 + " <<"
  137 + " /goose (4 gander)"
  138 + " /glarp (4 enspliel)"
  139 + " >>"
  140 + " ]"
  141 + ">>").getJSON();
  142 + check_schema(b, schema, false, "top-level type mismatch");
  143 + check_schema(a, a, false, "top-level schema array error");
  144 + check_schema(b, b, false, "lower-level schema array error");
  145 + check_schema(schema, schema, true, "pass");
  146 +}
  147 +
  148 +int main()
  149 +{
  150 + test_main();
  151 + test_schema();
  152 +
  153 + std::cout << "end of json tests\n";
  154 + return 0;
  155 +}
... ...
libtests/libtests.testcov
... ... @@ -34,3 +34,8 @@ Pl_PNGFilter decodeUp 0
34 34 Pl_PNGFilter decodeAverage 0
35 35 Pl_PNGFilter decodePaeth 0
36 36 Pl_TIFFPredictor processRow 1
  37 +JSON wanted dictionary 0
  38 +JSON key missing in object 0
  39 +JSON wanted array 0
  40 +JSON schema array error 0
  41 +JSON key extra in object 0
... ...
libtests/qtest/json.test 0 โ†’ 100644
  1 +#!/usr/bin/env perl
  2 +require 5.008;
  3 +use warnings;
  4 +use strict;
  5 +
  6 +chdir("json") or die "chdir testdir failed: $!\n";
  7 +
  8 +require TestDriver;
  9 +
  10 +my $td = new TestDriver('json');
  11 +
  12 +$td->runtest("json",
  13 + {$td->COMMAND => "json"},
  14 + {$td->FILE => "json.out", $td->EXIT_STATUS => 0},
  15 + $td->NORMALIZE_NEWLINES);
  16 +
  17 +$td->report(1);
... ...
libtests/qtest/json/json.out 0 โ†’ 100644
  1 +--- top-level type mismatch
  2 +top-level object is supposed to be a dictionary
  3 +---
  4 +--- top-level type mismatch
  5 +json key "./one./a": key "/q" is present in schema but missing in object
  6 +json key "./one./a./r" is supposed to be a dictionary
  7 +json key "./one./a./s" is supposed to be an array
  8 +json key "./one./a": key "/t" is not present in schema but appears in object
  9 +json key "./two.1": key "/glarp" is present in schema but missing in object
  10 +json key "./two.1": key "/flarp" is not present in schema but appears in object
  11 +json key "./two.2" is supposed to be a dictionary
  12 +json key "./two.3" is supposed to be a dictionary
  13 +---
  14 +--- top-level schema array error
  15 +top-level object schema array contains other than one item
  16 +---
  17 +--- lower-level schema array error
  18 +json key "./one./a./r" schema array contains other than one item
  19 +json key "./two" schema array contains other than one item
  20 +---
  21 +--- pass
  22 +---
  23 +end of json tests
... ...