From 690fa6e2065438de172dd64f8982ea26e5ba8f6a Mon Sep 17 00:00:00 2001 From: Paul Beckingham Date: Fri, 20 May 2011 00:18:36 -0400 Subject: [PATCH] JSON - Replaced old Tree-based parser with a faster, leaner parser. Currently lacking good error handling and a large test suite. - Integrated new parser into Task object, for encode/decode. - Replicated same basic unit tests. Needs more. - Fixed bug in handleShell that failed to call the new Context::initialize2. - Removed debugging code from CmdInstall. - Implemented format() that does not require width and precision args. --- src/JSON.cpp | 576 +++++++++++++++++++++--------------- src/JSON.h | 108 +++++-- src/Task.cpp | 4 +- src/command.cpp | 3 +- src/commands/CmdInstall.cpp | 2 - src/text.cpp | 8 + src/text.h | 1 + test/json.t.cpp | 112 ++++--- 8 files changed, 517 insertions(+), 297 deletions(-) diff --git a/src/JSON.cpp b/src/JSON.cpp index 925d17fc2..417b1600e 100644 --- a/src/JSON.cpp +++ b/src/JSON.cpp @@ -1,7 +1,7 @@ //////////////////////////////////////////////////////////////////////////////// // taskwarrior - a command line task list manager. // -// Copyright 2010 - 2011, Paul Beckingham, Federico Hernandez. +// Copyright 2006 - 2011, Paul Beckingham, Federico Hernandez. // All rights reserved. // // This program is free software; you can redistribute it and/or modify it under @@ -25,35 +25,361 @@ // //////////////////////////////////////////////////////////////////////////////// -#include -#include +#include // TODO Remove. +#include #include #include //////////////////////////////////////////////////////////////////////////////// -JSON::JSON () -: root ("root") +json::value* json::value::parse (Nibbler& nibbler) { + json::value* v; + if ((v = json::object::parse (nibbler)) || + (v = json::array::parse (nibbler)) || + (v = json::string::parse (nibbler)) || + (v = json::number::parse (nibbler)) || + (v = json::literal::parse (nibbler))) + return v; + + return NULL; } //////////////////////////////////////////////////////////////////////////////// -JSON::JSON (const std::string& input) -: root ("root") +json::jtype json::value::type () { + return json::j_value; +} + +//////////////////////////////////////////////////////////////////////////////// +std::string json::value::dump () +{ + return ""; +} + +//////////////////////////////////////////////////////////////////////////////// +json::string::string (const std::string& other) +{ + *this = other; +} + +//////////////////////////////////////////////////////////////////////////////// +json::string* json::string::parse (Nibbler& nibbler) +{ + std::string value; + if (nibbler.getQuoted ('"', value, false)) + { + json::string* s = new json::string (); + *(std::string*)s = value; + return s; + } + + return NULL; +} + +//////////////////////////////////////////////////////////////////////////////// +json::jtype json::string::type () +{ + return json::j_string; +} + +//////////////////////////////////////////////////////////////////////////////// +std::string json::string::dump () +{ + return std::string ("\"") + (std::string) *this + "\""; +} + +//////////////////////////////////////////////////////////////////////////////// +json::number* json::number::parse (Nibbler& nibbler) +{ + int i; + double d; + if (nibbler.getNumber (d)) + { + json::number* s = new json::number (); + s->_dvalue = d; + return s; + } + + return NULL; +} + +//////////////////////////////////////////////////////////////////////////////// +json::jtype json::number::type () +{ + return json::j_number; +} + +//////////////////////////////////////////////////////////////////////////////// +std::string json::number::dump () +{ + return format (_dvalue); +} + +//////////////////////////////////////////////////////////////////////////////// +json::number::operator double () const +{ + return _dvalue; +} + +//////////////////////////////////////////////////////////////////////////////// +json::literal* json::literal::parse (Nibbler& nibbler) +{ + if (nibbler.getLiteral ("null")) + { + json::literal* s = new json::literal (); + s->_lvalue = nullvalue; + return s; + } + else if (nibbler.getLiteral ("false")) + { + json::literal* s = new json::literal (); + s->_lvalue = falsevalue; + return s; + } + else if (nibbler.getLiteral ("true")) + { + json::literal* s = new json::literal (); + s->_lvalue = truevalue; + return s; + } + + return NULL; +} + +//////////////////////////////////////////////////////////////////////////////// +json::jtype json::literal::type () +{ + return json::j_literal; +} + +//////////////////////////////////////////////////////////////////////////////// +std::string json::literal::dump () +{ + if (_lvalue == nullvalue) return "null"; + else if (_lvalue == falsevalue) return "false"; + else return "true"; +} + +//////////////////////////////////////////////////////////////////////////////// +json::array::~array () +{ + std::vector ::iterator i; + for (i = ((std::vector *)this)->begin (); + i != ((std::vector *)this)->end (); + ++i) + delete *i; +} + +//////////////////////////////////////////////////////////////////////////////// +json::array* json::array::parse (Nibbler& nibbler) +{ + Nibbler n (nibbler); + n.skipWS (); + if (n.skip ('[')) + { + n.skipWS (); + + json::array* arr = new json::array (); + + json::value* value; + if (value = json::value::parse (n)) + { + arr->push_back (value); + value = NULL; // Not a leak. Looks like a leak. + n.skipWS (); + while (n.skip (',')) + { + n.skipWS (); + + if (value = json::value::parse (n)) + { + arr->push_back (value); + n.skipWS (); + } + else + { + delete arr; + return NULL; + } + } + } + if (n.skip (']')) + { + nibbler = n; + return arr; + } + + delete arr; + } + + return NULL; +} + +//////////////////////////////////////////////////////////////////////////////// +json::jtype json::array::type () +{ + return json::j_array; +} + +//////////////////////////////////////////////////////////////////////////////// +std::string json::array::dump () +{ + std::string output; + output += "["; + + std::vector ::iterator i; + for (i = ((std::vector *)this)->begin (); + i != ((std::vector *)this)->end (); + ++i) + { + if (i != ((std::vector *)this)->begin ()) + output += ","; + + output += (*i)->dump (); + } + + output += "]"; + return output; +} + +//////////////////////////////////////////////////////////////////////////////// +json::object::~object () +{ + std::map ::iterator i; + for (i = ((std::map *)this)->begin (); + i != ((std::map *)this)->end (); + ++i) + delete i->second; +} + +//////////////////////////////////////////////////////////////////////////////// +json::object* json::object::parse (Nibbler& nibbler) +{ + Nibbler n (nibbler); + n.skipWS (); + if (n.skip ('{')) + { + n.skipWS (); + + json::object* obj = new json::object (); + + std::string name; + json::value* value; + if (json::object::parse_pair (n, name, value)) + { + obj->insert (std::pair (name, value)); + value = NULL; // Not a leak. Looks like a leak. + + n.skipWS (); + while (n.skip (',')) + { + n.skipWS (); + + if (json::object::parse_pair (n, name, value)) + { + obj->insert (std::pair (name, value)); + n.skipWS (); + } + else + { + delete obj; + return NULL; + } + } + } + + if (n.skip ('}')) + { + nibbler = n; + return obj; + } + + delete obj; + } + + return NULL; +} + +//////////////////////////////////////////////////////////////////////////////// +bool json::object::parse_pair ( + Nibbler& nibbler, + std::string& name, + json::value*& val) +{ + Nibbler n (nibbler); + + if (n.getQuoted ('"', name, false)) + { + n.skipWS (); + if (n.skip (':')) + { + n.skipWS (); + if (val = json::value::parse (n)) + { + nibbler = n; + return true; + } + } + } + + return NULL; +} + +//////////////////////////////////////////////////////////////////////////////// +json::jtype json::object::type () +{ + return json::j_object; +} + +//////////////////////////////////////////////////////////////////////////////// +std::string json::object::dump () +{ + std::string output; + output += "{"; + + std::map ::iterator i; + for (i = ((std::map *)this)->begin (); + i != ((std::map *)this)->end (); + ++i) + { + if (i != ((std::map *)this)->begin ()) + output += ","; + + output += "\"" + i->first + "\":"; + output += i->second->dump (); + } + + output += "}"; + return output; +} + +//////////////////////////////////////////////////////////////////////////////// +json::value* json::parse (const std::string& input) +{ + json::value* root = NULL; + Nibbler n (input); - if (!parseObject (&root, n)) - throw std::string ("Syntax error in request."); + n.skipWS (); + + if (n.next () == '{') root = json::object::parse (n); + else if (n.next () == '[') root = json::array::parse (n); + else + throw std::string ("Error: expected '{' or '[' at position ") + + format ((int)n.cursor ()); + + // Check for end condition. + n.skipWS (); + if (!n.depleted ()) + { + delete root; + throw std::string ("Error: extra characters found: ") + n.dump (); + } + + return root; } //////////////////////////////////////////////////////////////////////////////// -JSON::~JSON () -{ -} - -//////////////////////////////////////////////////////////////////////////////// -// \n -> "\\n" -// \t -> "\\t" -std::string JSON::encode (const std::string& input) +std::string json::encode (const std::string& input) { std::string output; @@ -80,7 +406,7 @@ std::string JSON::encode (const std::string& input) } //////////////////////////////////////////////////////////////////////////////// -std::string JSON::decode (const std::string& input) +std::string json::decode (const std::string& input) { std::string output; @@ -122,217 +448,3 @@ std::string JSON::decode (const std::string& input) } //////////////////////////////////////////////////////////////////////////////// -Tree* JSON::tree () -{ - return &root; -} - -//////////////////////////////////////////////////////////////////////////////// -// object -// {} -// { pair , ... } -bool JSON::parseObject (Tree* t, Nibbler& nibbler) -{ - Nibbler n (nibbler); - n.skipWS (); - - if (n.skip ('{')) - { - n.skipWS (); - - Tree* node = new Tree ("node"); - if (parsePair (node, n)) - { - t->addBranch (node); - - n.skipWS (); - while (n.skip (',')) - { - n.skipWS (); - - node = new Tree ("node"); - if (!parsePair (node, n)) - { - delete node; - return false; - } - - t->addBranch (node); - n.skipWS (); - } - } - else - delete node; - - if (n.skip ('}')) - { - n.skipWS (); - nibbler = n; - t->attribute ("type", "collection"); - return true; - } - } - - return false; -} - -//////////////////////////////////////////////////////////////////////////////// -// pair -// string : value -bool JSON::parsePair (Tree* t, Nibbler& nibbler) -{ - Nibbler n (nibbler); - - std::string value; - if (n.getQuoted ('"', value)) - { - n.skipWS (); - if (n.skip (':')) - { - n.skipWS (); - if (parseValue (t, n)) - { - nibbler = n; - t->name (value); - return true; - } - } - } - - return false; -} - -//////////////////////////////////////////////////////////////////////////////// -// array -// [] -// [ value , ... ] -bool JSON::parseArray (Tree* t, Nibbler& nibbler) -{ - Nibbler n (nibbler); - n.skipWS (); - - if (n.skip ('[')) - { - n.skipWS (); - - Tree* node = new Tree ("node"); - if (parseValue (node, n)) - { - t->addBranch (node); - - n.skipWS (); - while (n.skip (',')) - { - n.skipWS (); - - node = new Tree ("node"); - if (!parseValue (node, n)) - { - delete node; - return false; - } - - t->addBranch (node); - n.skipWS (); - } - } - else - delete node; - - if (n.skip (']')) - { - n.skipWS (); - nibbler = n; - t->attribute ("type", "list"); - return true; - } - } - - return false; -} - -//////////////////////////////////////////////////////////////////////////////// -// value -// string -// number -// object -// array -// true -// false -// null -bool JSON::parseValue (Tree* t, Nibbler& nibbler) -{ - if (parseString (t, nibbler) || - parseNumber (t, nibbler) || - parseObject (t, nibbler) || - parseArray (t, nibbler) || - nibbler.getLiteral ("true") || - nibbler.getLiteral ("false") || - nibbler.getLiteral ("null")) - { - return true; - } - - return false; -} - -//////////////////////////////////////////////////////////////////////////////// -// string -// "" -// " chars " -// -// chars -// char -// char chars -// -// char -// any-Unicode-character-except-"-or-\-or-control-character -// \" -// \\ [extra text to de-confuse gcc] -// \/ -// \b -// \f -// \n -// \r -// \t -// \u four-hex-digits -bool JSON::parseString (Tree* t, Nibbler& nibbler) -{ - std::string value; - if (nibbler.getQuoted ('"', value, false)) - { - t->attribute ("type", "string"); - t->attribute ("value", value); - return true; - } - - return false; -} - -//////////////////////////////////////////////////////////////////////////////// -// number -// int frac exp -// int frac -// int exp -// int -bool JSON::parseNumber (Tree* t, Nibbler& nibbler) -{ - int i; - double d; - if (nibbler.getNumber (d)) - { - t->attribute ("type", "number"); - t->attribute ("value", d); - return true; - } - else if (nibbler.getInt (i)) - { - t->attribute ("type", "number"); - t->attribute ("value", i); - return true; - } - - return false; -} - -//////////////////////////////////////////////////////////////////////////////// diff --git a/src/JSON.h b/src/JSON.h index de37e6305..e23366efb 100644 --- a/src/JSON.h +++ b/src/JSON.h @@ -1,7 +1,7 @@ //////////////////////////////////////////////////////////////////////////////// // taskwarrior - a command line task list manager. // -// Copyright 2010 - 2011, Paul Beckingham, Federico Hernandez. +// Copyright 2006 - 2011, Paul Beckingham, Federico Hernandez. // All rights reserved. // // This program is free software; you can redistribute it and/or modify it under @@ -24,38 +24,102 @@ // USA // //////////////////////////////////////////////////////////////////////////////// + #ifndef INCLUDED_JSON #define INCLUDED_JSON +#include +#include #include -#include #include -class JSON +namespace json { -public: - JSON (); // Default constructor - JSON (const std::string&); // Constructor - JSON (const JSON&); // Copy constructor - JSON& operator= (const JSON&); // Assignment operator - ~JSON (); // Destructor + enum jtype + { + j_value, + j_object, + j_array, + j_string, + j_number, + j_literal + }; - static std::string encode (const std::string&); - static std::string decode (const std::string&); + class value + { + public: + value () {} + virtual ~value () {} + static value* parse (Nibbler&); + virtual jtype type (); + virtual std::string dump (); + }; - Tree* tree (); + class string : public value, public std::string + { + public: + string () {} + string (const std::string&); + ~string () {} + static string* parse (Nibbler&); + jtype type (); + std::string dump (); + }; -private: - bool parseObject (Tree*, Nibbler&); - bool parsePair (Tree*, Nibbler&); - bool parseArray (Tree*, Nibbler&); - bool parseValue (Tree*, Nibbler&); - bool parseString (Tree*, Nibbler&); - bool parseNumber (Tree*, Nibbler&); + class number : public value, public std::string + { + public: + number () : _dvalue (0.0) {} + ~number () {} + static number* parse (Nibbler&); + jtype type (); + std::string dump (); + operator double () const; -private: - Tree root; -}; + double _dvalue; + }; + + class literal : public value + { + public: + literal () : _lvalue (none) {} + ~literal () {} + static literal* parse (Nibbler&); + jtype type (); + std::string dump (); + + enum literal_value {none, nullvalue, falsevalue, truevalue}; + literal_value _lvalue; + }; + + class array : public value, public std::vector + { + public: + array () {} + ~array (); + static array* parse (Nibbler&); + jtype type (); + std::string dump (); + }; + + class object : public value, public std::map + { + public: + object () {} + ~object (); + static object* parse (Nibbler&); + static bool parse_pair (Nibbler&, std::string&, value*&); + jtype type (); + std::string dump (); + }; + + // Parser entry point. + value* parse (const std::string&); + + // Encode/decode for JSON entities. + std::string encode (const std::string&); + std::string decode (const std::string&); +} #endif //////////////////////////////////////////////////////////////////////////////// diff --git a/src/Task.cpp b/src/Task.cpp index fb263d676..652cf323f 100644 --- a/src/Task.cpp +++ b/src/Task.cpp @@ -505,7 +505,7 @@ std::string Task::composeJSON (bool include_id /*= false*/) const out << "\"" << i->second.name () << "\":\"" - << JSON::encode (i->second.value ()) + << json::encode (i->second.value ()) << "\""; ++attributes_written; @@ -530,7 +530,7 @@ std::string Task::composeJSON (bool include_id /*= false*/) const out << "{\"entry\":\"" << d.toISO () << "\",\"description\":\"" - << JSON::encode (i->second.value ()) + << json::encode (i->second.value ()) << "\"}"; ++annotations_written; diff --git a/src/command.cpp b/src/command.cpp index 2135bfc29..3a866738f 100644 --- a/src/command.cpp +++ b/src/command.cpp @@ -2250,6 +2250,7 @@ int handleIds (std::string& outs) } //////////////////////////////////////////////////////////////////////////////// +// TODO Obsolete. void handleShell () { // Display some kind of welcome message. @@ -2291,11 +2292,11 @@ void handleShell () try { context.clear (); - std::vector args; split (args, decoratedCommand, ' '); foreach (arg, args) context.args.push_back (*arg); + context.initialize2 (0, NULL); context.initialize (); context.run (); } diff --git a/src/commands/CmdInstall.cpp b/src/commands/CmdInstall.cpp index 6be1db420..03b5f308d 100644 --- a/src/commands/CmdInstall.cpp +++ b/src/commands/CmdInstall.cpp @@ -43,7 +43,6 @@ CmdInstall::CmdInstall () //////////////////////////////////////////////////////////////////////////////// bool CmdInstall::implements (const std::string& command_line) { - std::cout << "# CmdInstall::implements '" << command_line << "'\n"; return false; } @@ -55,7 +54,6 @@ bool CmdInstall::implements (const std::string& command_line) // extension.= int CmdInstall::execute (const std::string& commandLine, std::string& output) { - std::cout << "# CmdInstall::execute\n"; return 1; } diff --git a/src/text.cpp b/src/text.cpp index 8ffc42ed1..3ec018641 100644 --- a/src/text.cpp +++ b/src/text.cpp @@ -760,6 +760,14 @@ std::string format (double value, int width, int precision) return s.str (); } +//////////////////////////////////////////////////////////////////////////////// +std::string format (double value) +{ + std::stringstream s; + s << value; + return s.str (); +} + //////////////////////////////////////////////////////////////////////////////// std::string leftJustify (const int input, const int width) { diff --git a/src/text.h b/src/text.h index dbb04a43e..d226f9403 100644 --- a/src/text.h +++ b/src/text.h @@ -68,6 +68,7 @@ std::string format (int); std::string formatHex (int); std::string format (float, int, int); std::string format (double, int, int); +std::string format (double); std::string leftJustify (const int, const int); std::string leftJustify (const std::string&, const int); std::string rightJustify (const int, const int); diff --git a/test/json.t.cpp b/test/json.t.cpp index 268b53907..bc8618779 100644 --- a/test/json.t.cpp +++ b/test/json.t.cpp @@ -35,30 +35,53 @@ Context context; //////////////////////////////////////////////////////////////////////////////// int main (int argc, char** argv) { - UnitTest t (14); + UnitTest t (19); try { - // Basic parsing tests. + // j1 std::string input = "{}"; - std::cout << "-- j1 -------------------\n" - << "input: " << input << "\n"; - JSON j1 (input); - j1.tree ()->dump (); + std::cout << "# -- j1 -------------------\n" + << "# input: " << input << "\n"; + json::value* root = json::parse (input); + t.ok (root, "j1 parse ok"); + if (root) + { + t.diag ("output: " + root->dump ()); + delete root; + } + else + t.fail ("j1 parse error"); + // j2 input = "{\"name\":123}"; - std::cout << "-- j2 -------------------\n" - << "input: " << input << "\n"; - JSON j2 (input); - j2.tree ()->dump (); + std::cout << "# -- j2 -------------------\n" + << "# input: " << input << "\n"; + root = json::parse (input); + t.ok (root, "j2 parse ok"); + if (root) + { + t.diag ("output: " + root->dump ()); + delete root; + } + else + t.fail ("j2 parse error"); + // j3 input = "{\"name\":123, \"array\":[1,2,3.4], \"map\":{\"m1\":\"v1\", \"m2\":\"v2\"}}"; - std::cout << "-- j3 -------------------\n" - << "input: " << input << "\n"; - JSON j3 (input); - j3.tree ()->dump (); + std::cout << "# -- j3 -------------------\n" + << "# input: " << input << "\n"; + root = json::parse (input); + t.ok (root, "j3 parse ok"); + if (root) + { + t.diag ("output: " + root->dump ()); + delete root; + } + else + t.fail ("j3 parse error"); - // Sample ticket as a parsing test. + // j4 input = "{\n" "\"ticket\": { \"type\":\"add\", \"client\":\"taskwarrior 2.x\"},\n" "\"auth\": { \"user\":\"paul\", \"org\":\"gbf\", \"key\":\".........\",\n" @@ -68,33 +91,39 @@ int main (int argc, char** argv) " \"project\":\"home\",\n" " \"due\":\"20101101T000000Z\" }\n" "}"; - std::cout << "-- j4 -------------------\n" - << "input: " << input << "\n"; - JSON j4 (input); - j4.tree ()->dump (); - std::cout << "-------------------------\n"; + std::cout << "# -- j4 -------------------\n" + << "# input: " << input << "\n"; + root = json::parse (input); + t.ok (root, "j4 parse ok"); + if (root) + { + t.diag ("output: " + root->dump ()); + delete root; + } + else + t.fail ("j4 parse error"); // Regular unit tests. - t.is (JSON::encode ("1\b2"), "1\\b2", "JSON::encode \\b -> \\\\b"); - t.is (JSON::decode ("1\\b2"), "1\b2", "JSON::decode \\\\b -> \\b"); + t.is (json::encode ("1\b2"), "1\\b2", "json::encode \\b -> \\\\b"); + t.is (json::decode ("1\\b2"), "1\b2", "json::decode \\\\b -> \\b"); - t.is (JSON::encode ("1\n2"), "1\\n2", "JSON::encode \\n -> \\\\n"); - t.is (JSON::decode ("1\\n2"), "1\n2", "JSON::decode \\\\n -> \\n"); + t.is (json::encode ("1\n2"), "1\\n2", "json::encode \\n -> \\\\n"); + t.is (json::decode ("1\\n2"), "1\n2", "json::decode \\\\n -> \\n"); - t.is (JSON::encode ("1\r2"), "1\\r2", "JSON::encode \\r -> \\\\r"); - t.is (JSON::decode ("1\\r2"), "1\r2", "JSON::decode \\\\r -> \\r"); + t.is (json::encode ("1\r2"), "1\\r2", "json::encode \\r -> \\\\r"); + t.is (json::decode ("1\\r2"), "1\r2", "json::decode \\\\r -> \\r"); - t.is (JSON::encode ("1\t2"), "1\\t2", "JSON::encode \\t -> \\\\t"); - t.is (JSON::decode ("1\\t2"), "1\t2", "JSON::decode \\\\t -> \\t"); + t.is (json::encode ("1\t2"), "1\\t2", "json::encode \\t -> \\\\t"); + t.is (json::decode ("1\\t2"), "1\t2", "json::decode \\\\t -> \\t"); - t.is (JSON::encode ("1\\2"), "1\\\\2", "JSON::encode \\ -> \\\\"); - t.is (JSON::decode ("1\\\\2"), "1\\2", "JSON::decode \\\\ -> \\"); + t.is (json::encode ("1\\2"), "1\\\\2", "json::encode \\ -> \\\\"); + t.is (json::decode ("1\\\\2"), "1\\2", "json::decode \\\\ -> \\"); - t.is (JSON::encode ("1\x2"), "1\x2", "JSON::encode \\x -> \\x (NOP)"); - t.is (JSON::decode ("1\x2"), "1\x2", "JSON::decode \\x -> \\x (NOP)"); + t.is (json::encode ("1\x2"), "1\x2", "json::encode \\x -> \\x (NOP)"); + t.is (json::decode ("1\x2"), "1\x2", "json::decode \\x -> \\x (NOP)"); - t.is (JSON::encode ("1€2"), "1€2", "JSON::encode € -> €"); - t.is (JSON::decode ("1\\u20ac2"), "1€2", "JSON::decode \\u20ac -> €"); + t.is (json::encode ("1€2"), "1€2", "json::encode € -> €"); + t.is (json::decode ("1\\u20ac2"), "1€2", "json::decode \\u20ac -> €"); /* { @@ -131,10 +160,17 @@ int main (int argc, char** argv) } */ input = "{\"ticket\":{\"type\":\"synch\",\"client\":\"taskd-test-suite 1.0\"},\"synch\":{\"user\":{\"data\":[{\"uuid\":\"11111111-1111-1111-1111-111111111111\",\"status\":\"pending\",\"description\":\"This is a test\",\"entry\":\"20110111T124000Z\"}],\"synch\":\"key\"}},\"auth\":{\"org\":\"gbf\",\"user\":\"Paul Beckingham\",\"key\":\"K\",\"locale\":\"en-US\"}}"; - std::cout << "-- j4 -------------------\n" - << "input: " << input << "\n"; - JSON j5 (input); - j5.tree ()->dump (); + std::cout << "# -- j5 -------------------\n" + << "# input: " << input << "\n"; + root = json::parse (input); + t.ok (root, "j5 parse ok"); + if (root) + { + t.diag ("output: " + root->dump ()); + delete root; + } + else + t.fail ("j5 parse error"); } catch (std::string& e) {t.diag (e);}