Unverified Commit badade33 authored by Kenton Varda's avatar Kenton Varda Committed by GitHub

Merge pull request #677 from RReverser/json-decode

Rewrite JSON decoding
parents 3d97775b 2d14ff20
...@@ -17,6 +17,7 @@ Harris Hancock <vortrab@gmail.com>: MSVC support ...@@ -17,6 +17,7 @@ Harris Hancock <vortrab@gmail.com>: MSVC support
Branislav Katreniak <branislav.katreniak@digitalstrom.com>: JSON decode Branislav Katreniak <branislav.katreniak@digitalstrom.com>: JSON decode
Matthew Maurer <matthew.r.maurer@gmail.com>: Canonicalization Support Matthew Maurer <matthew.r.maurer@gmail.com>: Canonicalization Support
David Renshaw <david@sandstorm.io>: bugfixes and miscellaneous maintenance David Renshaw <david@sandstorm.io>: bugfixes and miscellaneous maintenance
Ingvar Stepanyan <me@rreverser.com> <ingvar@cloudflare.com>: Custom handlers for JSON decode
This file does not list people who maintain their own Cap'n Proto This file does not list people who maintain their own Cap'n Proto
implementations as separate projects. Those people are awesome too! :) implementations as separate projects. Those people are awesome too! :)
...@@ -185,13 +185,20 @@ KJ_TEST("encode union") { ...@@ -185,13 +185,20 @@ KJ_TEST("encode union") {
KJ_TEST("decode all types") { KJ_TEST("decode all types") {
JsonCodec json; JsonCodec json;
#define CASE(s, f) \ json.setHasMode(HasMode::NON_DEFAULT);
#define CASE_MAYBE_ROUNDTRIP(s, f, roundtrip) \
{ \ { \
MallocMessageBuilder message; \ MallocMessageBuilder message; \
auto root = message.initRoot<TestAllTypes>(); \ auto root = message.initRoot<TestAllTypes>(); \
json.decode(s, root); \ kj::StringPtr input = s; \
KJ_EXPECT((f)) \ json.decode(input, root); \
} KJ_EXPECT((f), input, root); \
auto reencoded = json.encode(root); \
KJ_EXPECT(roundtrip == (input == reencoded), roundtrip, input, reencoded); \
}
#define CASE_NO_ROUNDTRIP(s, f) CASE_MAYBE_ROUNDTRIP(s, f, false)
#define CASE(s, f) CASE_MAYBE_ROUNDTRIP(s, f, true)
#define CASE_THROW(s, errorMessage) \ #define CASE_THROW(s, errorMessage) \
{ \ { \
MallocMessageBuilder message; \ MallocMessageBuilder message; \
...@@ -206,113 +213,126 @@ KJ_TEST("decode all types") { ...@@ -206,113 +213,126 @@ KJ_TEST("decode all types") {
} }
CASE(R"({})", root.getBoolField() == false); CASE(R"({})", root.getBoolField() == false);
CASE(R"({"unknownField":7})", root.getBoolField() == false); CASE_NO_ROUNDTRIP(R"({"unknownField":7})", root.getBoolField() == false);
CASE(R"({"boolField":true})", root.getBoolField() == true); CASE(R"({"boolField":true})", root.getBoolField() == true);
CASE(R"({"int8Field":-128})", root.getInt8Field() == -128); CASE(R"({"int8Field":-128})", root.getInt8Field() == -128);
CASE(R"({"int8Field":"127"})", root.getInt8Field() == 127); CASE_NO_ROUNDTRIP(R"({"int8Field":"127"})", root.getInt8Field() == 127);
CASE_THROW_RECOVERABLE(R"({"int8Field":"-129"})", "Value out-of-range"); CASE_THROW_RECOVERABLE(R"({"int8Field":"-129"})", "Value out-of-range");
CASE_THROW_RECOVERABLE(R"({"int8Field":128})", "Value out-of-range"); CASE_THROW_RECOVERABLE(R"({"int8Field":128})", "Value out-of-range");
CASE(R"({"int16Field":-32768})", root.getInt16Field() == -32768); CASE(R"({"int16Field":-32768})", root.getInt16Field() == -32768);
CASE(R"({"int16Field":"32767"})", root.getInt16Field() == 32767); CASE_NO_ROUNDTRIP(R"({"int16Field":"32767"})", root.getInt16Field() == 32767);
CASE_THROW_RECOVERABLE(R"({"int16Field":"-32769"})", "Value out-of-range"); CASE_THROW_RECOVERABLE(R"({"int16Field":"-32769"})", "Value out-of-range");
CASE_THROW_RECOVERABLE(R"({"int16Field":32768})", "Value out-of-range"); CASE_THROW_RECOVERABLE(R"({"int16Field":32768})", "Value out-of-range");
CASE(R"({"int32Field":-2147483648})", root.getInt32Field() == -2147483648); CASE(R"({"int32Field":-2147483648})", root.getInt32Field() == -2147483648);
CASE(R"({"int32Field":"2147483647"})", root.getInt32Field() == 2147483647); CASE_NO_ROUNDTRIP(R"({"int32Field":"2147483647"})", root.getInt32Field() == 2147483647);
CASE(R"({"int64Field":-9007199254740992})", root.getInt64Field() == -9007199254740992LL); CASE_NO_ROUNDTRIP(R"({"int64Field":-9007199254740992})", root.getInt64Field() == -9007199254740992LL);
CASE(R"({"int64Field":9007199254740991})", root.getInt64Field() == 9007199254740991LL); CASE_NO_ROUNDTRIP(R"({"int64Field":9007199254740991})", root.getInt64Field() == 9007199254740991LL);
CASE(R"({"int64Field":"-9223372036854775808"})", root.getInt64Field() == -9223372036854775808ULL); CASE(R"({"int64Field":"-9223372036854775808"})", root.getInt64Field() == -9223372036854775808ULL);
CASE(R"({"int64Field":"9223372036854775807"})", root.getInt64Field() == 9223372036854775807LL); CASE(R"({"int64Field":"9223372036854775807"})", root.getInt64Field() == 9223372036854775807LL);
CASE_THROW_RECOVERABLE(R"({"int64Field":"-9223372036854775809"})", "Value out-of-range"); CASE_THROW_RECOVERABLE(R"({"int64Field":"-9223372036854775809"})", "Value out-of-range");
CASE_THROW_RECOVERABLE(R"({"int64Field":"9223372036854775808"})", "Value out-of-range"); CASE_THROW_RECOVERABLE(R"({"int64Field":"9223372036854775808"})", "Value out-of-range");
CASE(R"({"uInt8Field":255})", root.getUInt8Field() == 255); CASE(R"({"uInt8Field":255})", root.getUInt8Field() == 255);
CASE(R"({"uInt8Field":"0"})", root.getUInt8Field() == 0); CASE_NO_ROUNDTRIP(R"({"uInt8Field":"0"})", root.getUInt8Field() == 0);
CASE_THROW_RECOVERABLE(R"({"uInt8Field":"256"})", "Value out-of-range"); CASE_THROW_RECOVERABLE(R"({"uInt8Field":"256"})", "Value out-of-range");
CASE_THROW_RECOVERABLE(R"({"uInt8Field":-1})", "Value out-of-range"); CASE_THROW_RECOVERABLE(R"({"uInt8Field":-1})", "Value out-of-range");
CASE(R"({"uInt16Field":65535})", root.getUInt16Field() == 65535); CASE(R"({"uInt16Field":65535})", root.getUInt16Field() == 65535);
CASE(R"({"uInt16Field":"0"})", root.getUInt16Field() == 0); CASE_NO_ROUNDTRIP(R"({"uInt16Field":"0"})", root.getUInt16Field() == 0);
CASE_THROW_RECOVERABLE(R"({"uInt16Field":"655356"})", "Value out-of-range"); CASE_THROW_RECOVERABLE(R"({"uInt16Field":"655356"})", "Value out-of-range");
CASE_THROW_RECOVERABLE(R"({"uInt16Field":-1})", "Value out-of-range"); CASE_THROW_RECOVERABLE(R"({"uInt16Field":-1})", "Value out-of-range");
CASE(R"({"uInt32Field":4294967295})", root.getUInt32Field() == 4294967295); CASE(R"({"uInt32Field":4294967295})", root.getUInt32Field() == 4294967295);
CASE(R"({"uInt32Field":"0"})", root.getUInt32Field() == 0); CASE_NO_ROUNDTRIP(R"({"uInt32Field":"0"})", root.getUInt32Field() == 0);
CASE_THROW_RECOVERABLE(R"({"uInt32Field":"42949672956"})", "Value out-of-range"); CASE_THROW_RECOVERABLE(R"({"uInt32Field":"42949672956"})", "Value out-of-range");
CASE_THROW_RECOVERABLE(R"({"uInt32Field":-1})", "Value out-of-range"); CASE_THROW_RECOVERABLE(R"({"uInt32Field":-1})", "Value out-of-range");
CASE(R"({"uInt64Field":9007199254740991})", root.getUInt64Field() == 9007199254740991ULL); CASE_NO_ROUNDTRIP(R"({"uInt64Field":9007199254740991})", root.getUInt64Field() == 9007199254740991ULL);
CASE(R"({"uInt64Field":"18446744073709551615"})", root.getUInt64Field() == 18446744073709551615ULL); CASE(R"({"uInt64Field":"18446744073709551615"})", root.getUInt64Field() == 18446744073709551615ULL);
CASE(R"({"uInt64Field":"0"})", root.getUInt64Field() == 0); CASE_NO_ROUNDTRIP(R"({"uInt64Field":"0"})", root.getUInt64Field() == 0);
CASE_THROW_RECOVERABLE(R"({"uInt64Field":"18446744073709551616"})", "Value out-of-range"); CASE_THROW_RECOVERABLE(R"({"uInt64Field":"18446744073709551616"})", "Value out-of-range");
CASE(R"({"float32Field":0})", root.getFloat32Field() == 0); CASE_NO_ROUNDTRIP(R"({"float32Field":0})", root.getFloat32Field() == 0);
CASE(R"({"float32Field":4.5})", root.getFloat32Field() == 4.5); CASE(R"({"float32Field":4.5})", root.getFloat32Field() == 4.5);
CASE(R"({"float32Field":null})", kj::isNaN(root.getFloat32Field())); CASE_NO_ROUNDTRIP(R"({"float32Field":null})", kj::isNaN(root.getFloat32Field()));
CASE(R"({"float32Field":"nan"})", kj::isNaN(root.getFloat32Field())); CASE(R"({"float32Field":"NaN"})", kj::isNaN(root.getFloat32Field()));
CASE(R"({"float32Field":"nan"})", kj::isNaN(root.getFloat32Field())); CASE_NO_ROUNDTRIP(R"({"float32Field":"nan"})", kj::isNaN(root.getFloat32Field()));
CASE(R"({"float32Field":"Infinity"})", root.getFloat32Field() == kj::inf()); CASE(R"({"float32Field":"Infinity"})", root.getFloat32Field() == kj::inf());
CASE(R"({"float32Field":"-Infinity"})", root.getFloat32Field() == -kj::inf()); CASE(R"({"float32Field":"-Infinity"})", root.getFloat32Field() == -kj::inf());
CASE(R"({"float64Field":0})", root.getFloat64Field() == 0); CASE_NO_ROUNDTRIP(R"({"float32Field":"infinity"})", root.getFloat32Field() == kj::inf());
CASE_NO_ROUNDTRIP(R"({"float32Field":"-infinity"})", root.getFloat32Field() == -kj::inf());
CASE_NO_ROUNDTRIP(R"({"float32Field":"INF"})", root.getFloat32Field() == kj::inf());
CASE_NO_ROUNDTRIP(R"({"float32Field":"-INF"})", root.getFloat32Field() == -kj::inf());
CASE_NO_ROUNDTRIP(R"({"float32Field":1e39})", root.getFloat32Field() == kj::inf());
CASE_NO_ROUNDTRIP(R"({"float32Field":-1e39})", root.getFloat32Field() == -kj::inf());
CASE_NO_ROUNDTRIP(R"({"float64Field":0})", root.getFloat64Field() == 0);
CASE(R"({"float64Field":4.5})", root.getFloat64Field() == 4.5); CASE(R"({"float64Field":4.5})", root.getFloat64Field() == 4.5);
CASE(R"({"float64Field":null})", kj::isNaN(root.getFloat64Field())); CASE_NO_ROUNDTRIP(R"({"float64Field":null})", kj::isNaN(root.getFloat64Field()));
CASE(R"({"float64Field":"nan"})", kj::isNaN(root.getFloat64Field())); CASE(R"({"float64Field":"NaN"})", kj::isNaN(root.getFloat64Field()));
CASE(R"({"float64Field":"nan"})", kj::isNaN(root.getFloat64Field())); CASE_NO_ROUNDTRIP(R"({"float64Field":"nan"})", kj::isNaN(root.getFloat64Field()));
CASE(R"({"float64Field":"Infinity"})", root.getFloat64Field() == kj::inf()); CASE(R"({"float64Field":"Infinity"})", root.getFloat64Field() == kj::inf());
CASE(R"({"float64Field":"-Infinity"})", root.getFloat64Field() == -kj::inf()); CASE_NO_ROUNDTRIP(R"({"float64Field":"infinity"})", root.getFloat64Field() == kj::inf());
CASE_NO_ROUNDTRIP(R"({"float64Field":"-infinity"})", root.getFloat64Field() == -kj::inf());
CASE_NO_ROUNDTRIP(R"({"float64Field":"INF"})", root.getFloat64Field() == kj::inf());
CASE_NO_ROUNDTRIP(R"({"float64Field":"-INF"})", root.getFloat64Field() == -kj::inf());
CASE_NO_ROUNDTRIP(R"({"float64Field":1e309})", root.getFloat64Field() == kj::inf());
CASE_NO_ROUNDTRIP(R"({"float64Field":-1e309})", root.getFloat64Field() == -kj::inf());
CASE(R"({"textField":"hello"})", kj::str("hello") == root.getTextField()); CASE(R"({"textField":"hello"})", kj::str("hello") == root.getTextField());
CASE(R"({"dataField":[7,0,122]})", CASE(R"({"dataField":[7,0,122]})",
kj::heapArray<byte>({7,0,122}).asPtr() == root.getDataField()); kj::heapArray<byte>({7,0,122}).asPtr() == root.getDataField());
CASE(R"({"structField":null})", root.hasStructField() == false);
CASE(R"({"structField":{}})", root.hasStructField() == true); CASE(R"({"structField":{}})", root.hasStructField() == true);
CASE(R"({"structField":{}})", root.getStructField().getBoolField() == false); CASE(R"({"structField":{}})", root.getStructField().getBoolField() == false);
CASE(R"({"structField":{"boolField":false}})", root.getStructField().getBoolField() == false); CASE_NO_ROUNDTRIP(R"({"structField":{"boolField":false}})", root.getStructField().getBoolField() == false);
CASE(R"({"structField":{"boolField":true}})", root.getStructField().getBoolField() == true); CASE(R"({"structField":{"boolField":true}})", root.getStructField().getBoolField() == true);
CASE(R"({"enumField":"bar"})", root.getEnumField() == TestEnum::BAR); CASE(R"({"enumField":"bar"})", root.getEnumField() == TestEnum::BAR);
CASE_THROW_RECOVERABLE(R"({"structField":null})", "Expected object value");
CASE_THROW_RECOVERABLE(R"({"structList":null})", "Expected list value");
CASE_THROW_RECOVERABLE(R"({"boolList":null})", "Expected list value");
CASE_THROW_RECOVERABLE(R"({"structList":[null]})", "Expected object value");
CASE_THROW_RECOVERABLE(R"({"int64Field":"177a"})", "String does not contain valid"); CASE_THROW_RECOVERABLE(R"({"int64Field":"177a"})", "String does not contain valid");
CASE_THROW_RECOVERABLE(R"({"uInt64Field":"177a"})", "String does not contain valid"); CASE_THROW_RECOVERABLE(R"({"uInt64Field":"177a"})", "String does not contain valid");
CASE_THROW_RECOVERABLE(R"({"float64Field":"177a"})", "String does not contain valid"); CASE_THROW_RECOVERABLE(R"({"float64Field":"177a"})", "String does not contain valid");
CASE(R"({})", root.hasBoolList() == false); CASE(R"({})", root.hasBoolList() == false);
CASE(R"({"boolList":null})", root.hasBoolList() == false);
CASE(R"({"boolList":[]})", root.hasBoolList() == true); CASE(R"({"boolList":[]})", root.hasBoolList() == true);
CASE(R"({"boolList":[]})", root.getBoolList().size() == 0); CASE(R"({"boolList":[]})", root.getBoolList().size() == 0);
CASE(R"({"boolList":[false]})", root.getBoolList().size() == 1); CASE(R"({"boolList":[false]})", root.getBoolList().size() == 1);
CASE(R"({"boolList":[false]})", root.getBoolList()[0] == false); CASE(R"({"boolList":[false]})", root.getBoolList()[0] == false);
CASE(R"({"boolList":[true]})", root.getBoolList()[0] == true); CASE(R"({"boolList":[true]})", root.getBoolList()[0] == true);
CASE(R"({"int8List":[7]})", root.getInt8List()[0] == 7); CASE(R"({"int8List":[7]})", root.getInt8List()[0] == 7);
CASE(R"({"int8List":["7"]})", root.getInt8List()[0] == 7); CASE_NO_ROUNDTRIP(R"({"int8List":["7"]})", root.getInt8List()[0] == 7);
CASE(R"({"int16List":[7]})", root.getInt16List()[0] == 7); CASE(R"({"int16List":[7]})", root.getInt16List()[0] == 7);
CASE(R"({"int16List":["7"]})", root.getInt16List()[0] == 7); CASE_NO_ROUNDTRIP(R"({"int16List":["7"]})", root.getInt16List()[0] == 7);
CASE(R"({"int32List":[7]})", root.getInt32List()[0] == 7); CASE(R"({"int32List":[7]})", root.getInt32List()[0] == 7);
CASE(R"({"int32List":["7"]})", root.getInt32List()[0] == 7); CASE_NO_ROUNDTRIP(R"({"int32List":["7"]})", root.getInt32List()[0] == 7);
CASE(R"({"int64List":[7]})", root.getInt64List()[0] == 7); CASE_NO_ROUNDTRIP(R"({"int64List":[7]})", root.getInt64List()[0] == 7);
CASE(R"({"int64List":["7"]})", root.getInt64List()[0] == 7); CASE(R"({"int64List":["7"]})", root.getInt64List()[0] == 7);
CASE(R"({"uInt8List":[7]})", root.getUInt8List()[0] == 7); CASE(R"({"uInt8List":[7]})", root.getUInt8List()[0] == 7);
CASE(R"({"uInt8List":["7"]})", root.getUInt8List()[0] == 7); CASE_NO_ROUNDTRIP(R"({"uInt8List":["7"]})", root.getUInt8List()[0] == 7);
CASE(R"({"uInt16List":[7]})", root.getUInt16List()[0] == 7); CASE(R"({"uInt16List":[7]})", root.getUInt16List()[0] == 7);
CASE(R"({"uInt16List":["7"]})", root.getUInt16List()[0] == 7); CASE_NO_ROUNDTRIP(R"({"uInt16List":["7"]})", root.getUInt16List()[0] == 7);
CASE(R"({"uInt32List":[7]})", root.getUInt32List()[0] == 7); CASE(R"({"uInt32List":[7]})", root.getUInt32List()[0] == 7);
CASE(R"({"uInt32List":["7"]})", root.getUInt32List()[0] == 7); CASE_NO_ROUNDTRIP(R"({"uInt32List":["7"]})", root.getUInt32List()[0] == 7);
CASE(R"({"uInt64List":[7]})", root.getUInt64List()[0] == 7); CASE_NO_ROUNDTRIP(R"({"uInt64List":[7]})", root.getUInt64List()[0] == 7);
CASE(R"({"uInt64List":["7"]})", root.getUInt64List()[0] == 7); CASE(R"({"uInt64List":["7"]})", root.getUInt64List()[0] == 7);
CASE(R"({"float32List":[4.5]})", root.getFloat32List()[0] == 4.5); CASE(R"({"float32List":[4.5]})", root.getFloat32List()[0] == 4.5);
CASE(R"({"float32List":["4.5"]})", root.getFloat32List()[0] == 4.5); CASE_NO_ROUNDTRIP(R"({"float32List":["4.5"]})", root.getFloat32List()[0] == 4.5);
CASE(R"({"float32List":[null]})", kj::isNaN(root.getFloat32List()[0])); CASE_NO_ROUNDTRIP(R"({"float32List":[null]})", kj::isNaN(root.getFloat32List()[0]));
CASE(R"({"float32List":["nan"]})", kj::isNaN(root.getFloat32List()[0])); CASE(R"({"float32List":["NaN"]})", kj::isNaN(root.getFloat32List()[0]));
CASE(R"({"float32List":["infinity"]})", root.getFloat32List()[0] == kj::inf()); CASE(R"({"float32List":["Infinity"]})", root.getFloat32List()[0] == kj::inf());
CASE(R"({"float32List":["-infinity"]})", root.getFloat32List()[0] == -kj::inf()); CASE(R"({"float32List":["-Infinity"]})", root.getFloat32List()[0] == -kj::inf());
CASE(R"({"float64List":[4.5]})", root.getFloat64List()[0] == 4.5); CASE(R"({"float64List":[4.5]})", root.getFloat64List()[0] == 4.5);
CASE(R"({"float64List":["4.5"]})", root.getFloat64List()[0] == 4.5); CASE_NO_ROUNDTRIP(R"({"float64List":["4.5"]})", root.getFloat64List()[0] == 4.5);
CASE(R"({"float64List":[null]})", kj::isNaN(root.getFloat64List()[0])); CASE_NO_ROUNDTRIP(R"({"float64List":[null]})", kj::isNaN(root.getFloat64List()[0]));
CASE(R"({"float64List":["nan"]})", kj::isNaN(root.getFloat64List()[0])); CASE(R"({"float64List":["NaN"]})", kj::isNaN(root.getFloat64List()[0]));
CASE(R"({"float64List":["infinity"]})", root.getFloat64List()[0] == kj::inf()); CASE(R"({"float64List":["Infinity"]})", root.getFloat64List()[0] == kj::inf());
CASE(R"({"float64List":["-infinity"]})", root.getFloat64List()[0] == -kj::inf()); CASE(R"({"float64List":["-Infinity"]})", root.getFloat64List()[0] == -kj::inf());
CASE(R"({"textList":["hello"]})", kj::str("hello") == root.getTextList()[0]); CASE(R"({"textList":["hello"]})", kj::str("hello") == root.getTextList()[0]);
CASE(R"({"dataList":[[7,0,122]]})", CASE(R"({"dataList":[[7,0,122]]})",
kj::heapArray<byte>({7,0,122}).asPtr() == root.getDataList()[0]); kj::heapArray<byte>({7,0,122}).asPtr() == root.getDataList()[0]);
CASE(R"({"structList":null})", root.hasStructList() == false); CASE(R"({"structList":[{}]})", root.hasStructList() == true);
CASE(R"({"structList":[null]})", root.hasStructList() == true);
CASE(R"({"structList":[null]})", root.getStructList()[0].getBoolField() == false);
CASE(R"({"structList":[{}]})", root.getStructList()[0].getBoolField() == false); CASE(R"({"structList":[{}]})", root.getStructList()[0].getBoolField() == false);
CASE(R"({"structList":[{"boolField":false}]})", root.getStructList()[0].getBoolField() == false); CASE_NO_ROUNDTRIP(R"({"structList":[{"boolField":false}]})", root.getStructList()[0].getBoolField() == false);
CASE(R"({"structList":[{"boolField":true}]})", root.getStructList()[0].getBoolField() == true); CASE(R"({"structList":[{"boolField":true}]})", root.getStructList()[0].getBoolField() == true);
CASE(R"({"enumList":["bar"]})", root.getEnumList()[0] == TestEnum::BAR); CASE(R"({"enumList":["bar"]})", root.getEnumList()[0] == TestEnum::BAR);
#undef CASE_MAYBE_ROUNDTRIP
#undef CASE_NO_ROUNDTRIP
#undef CASE #undef CASE
#undef CASE_THROW #undef CASE_THROW
#undef CASE_THROW_RECOVERABLE #undef CASE_THROW_RECOVERABLE
...@@ -515,20 +535,6 @@ KJ_TEST("basic json decoding") { ...@@ -515,20 +535,6 @@ KJ_TEST("basic json decoding") {
KJ_EXPECT_THROW_MESSAGE("Unexpected", json.decodeRaw("+123", root)); KJ_EXPECT_THROW_MESSAGE("Unexpected", json.decodeRaw("+123", root));
} }
{
MallocMessageBuilder message;
auto root = message.initRoot<JsonValue>();
KJ_EXPECT_THROW_MESSAGE("Overflow", json.decodeRaw("1e1024", root));
}
{
MallocMessageBuilder message;
auto root = message.initRoot<JsonValue>();
KJ_EXPECT_THROW_MESSAGE("Underflow", json.decodeRaw("1e-1023", root));
}
{ {
MallocMessageBuilder message; MallocMessageBuilder message;
auto root = message.initRoot<JsonValue>(); auto root = message.initRoot<JsonValue>();
...@@ -637,7 +643,7 @@ KJ_TEST("maximum nesting depth") { ...@@ -637,7 +643,7 @@ KJ_TEST("maximum nesting depth") {
} }
} }
class TestHandler: public JsonCodec::Handler<Text> { class TestCallHandler: public JsonCodec::Handler<Text> {
public: public:
void encode(const JsonCodec& codec, Text::Reader input, void encode(const JsonCodec& codec, Text::Reader input,
JsonValue::Builder output) const override { JsonValue::Builder output) const override {
...@@ -654,31 +660,144 @@ public: ...@@ -654,31 +660,144 @@ public:
} }
}; };
KJ_TEST("register handler") { class TestDynamicStructHandler: public JsonCodec::Handler<DynamicStruct> {
MallocMessageBuilder message; public:
auto root = message.getRoot<test::TestOldVersion>(); void encode(const JsonCodec& codec, DynamicStruct::Reader input,
JsonValue::Builder output) const override {
auto fields = input.getSchema().getFields();
auto items = output.initArray(fields.size());
for (auto field: fields) {
KJ_REQUIRE(field.getIndex() < items.size());
auto item = items[field.getIndex()];
if (input.has(field)) {
codec.encode(input.get(field), field.getType(), item);
} else {
item.setNull();
}
}
}
TestHandler handler; void decode(const JsonCodec& codec, JsonValue::Reader input,
DynamicStruct::Builder output) const override {
auto orphanage = Orphanage::getForMessageContaining(output);
auto fields = output.getSchema().getFields();
auto items = input.getArray();
for (auto field: fields) {
KJ_REQUIRE(field.getIndex() < items.size());
auto item = items[field.getIndex()];
if (!item.isNull()) {
output.adopt(field, codec.decode(item, field.getType(), orphanage));
}
}
}
};
class TestStructHandler: public JsonCodec::Handler<test::TestOldVersion> {
public:
void encode(const JsonCodec& codec, test::TestOldVersion::Reader input, JsonValue::Builder output) const override {
dynamicHandler.encode(codec, input, output);
}
void decode(const JsonCodec& codec, JsonValue::Reader input, test::TestOldVersion::Builder output) const override {
dynamicHandler.decode(codec, input, output);
}
private:
TestDynamicStructHandler dynamicHandler;
};
KJ_TEST("register custom encoding handlers") {
JsonCodec json; JsonCodec json;
json.addTypeHandler(handler);
TestStructHandler structHandler;
json.addTypeHandler(structHandler);
// JSON decoder can't parse calls back, so test only encoder here
TestCallHandler callHandler;
json.addTypeHandler(callHandler);
MallocMessageBuilder message;
auto root = message.getRoot<test::TestOldVersion>();
root.setOld1(123); root.setOld1(123);
root.setOld2("foo"); root.setOld2("foo");
KJ_EXPECT(json.encode(root) == "{\"old1\":\"123\",\"old2\":Frob(123,\"foo\")}");
KJ_EXPECT(json.encode(root) == "[\"123\",Frob(123,\"foo\"),null]");
} }
KJ_TEST("register field handler") { KJ_TEST("register custom roundtrip handler") {
for (auto i = 1; i <= 2; i++) {
JsonCodec json;
TestStructHandler staticHandler;
TestDynamicStructHandler dynamicHandler;
kj::String encoded;
if (i == 1) {
// first iteration: test with explicit struct handler
json.addTypeHandler(staticHandler);
} else {
// second iteration: same checks, but with DynamicStruct handler
json.addTypeHandler(StructSchema::from<test::TestOldVersion>(), dynamicHandler);
}
{
MallocMessageBuilder message; MallocMessageBuilder message;
auto root = message.getRoot<test::TestOutOfOrder>(); auto root = message.getRoot<test::TestOldVersion>();
root.setOld1(123);
root.initOld3().setOld2("foo");
encoded = json.encode(root);
KJ_EXPECT(encoded == "[\"123\",null,[\"0\",\"foo\",null]]");
}
TestHandler handler; {
MallocMessageBuilder message;
auto root = message.getRoot<test::TestOldVersion>();
json.decode(encoded, root);
KJ_EXPECT(root.getOld1() == 123);
KJ_EXPECT(!root.hasOld2());
auto nested = root.getOld3();
KJ_EXPECT(nested.getOld1() == 0);
KJ_EXPECT("foo" == nested.getOld2());
KJ_EXPECT(!nested.hasOld3());
}
}
}
KJ_TEST("register field handler") {
TestStructHandler handler;
JsonCodec json; JsonCodec json;
json.addFieldHandler(StructSchema::from<test::TestOutOfOrder>().getFieldByName("corge"), json.addFieldHandler(StructSchema::from<test::TestOldVersion>().getFieldByName("old3"),
handler); handler);
root.setBaz("abcd"); kj::String encoded;
root.setCorge("efg");
KJ_EXPECT(json.encode(root) == "{\"corge\":Frob(123,\"efg\"),\"baz\":\"abcd\"}"); {
MallocMessageBuilder message;
auto root = message.getRoot<test::TestOldVersion>();
root.setOld1(123);
root.setOld2("foo");
auto nested = root.initOld3();
nested.setOld2("bar");
encoded = json.encode(root);
KJ_EXPECT(encoded == "{\"old1\":\"123\",\"old2\":\"foo\",\"old3\":[\"0\",\"bar\",null]}")
}
{
MallocMessageBuilder message;
auto root = message.getRoot<test::TestOldVersion>();
json.decode(encoded, root);
KJ_EXPECT(root.getOld1() == 123);
KJ_EXPECT("foo" == root.getOld2());
auto nested = root.getOld3();
KJ_EXPECT(nested.getOld1() == 0);
KJ_EXPECT("bar" == nested.getOld2());
KJ_EXPECT(!nested.hasOld3());
}
} }
class TestCapabilityHandler: public JsonCodec::Handler<test::TestInterface> { class TestCapabilityHandler: public JsonCodec::Handler<test::TestInterface> {
...@@ -703,29 +822,6 @@ KJ_TEST("register capability handler") { ...@@ -703,29 +822,6 @@ KJ_TEST("register capability handler") {
json.addTypeHandler(handler); json.addTypeHandler(handler);
} }
class TestDynamicStructHandler: public JsonCodec::Handler<DynamicStruct> {
public:
void encode(const JsonCodec& codec, DynamicStruct::Reader input,
JsonValue::Builder output) const override {
KJ_UNIMPLEMENTED("TestDynamicStructHandler::encode");
}
void decode(const JsonCodec& codec, JsonValue::Reader input,
DynamicStruct::Builder output) const override {
KJ_UNIMPLEMENTED("TestDynamicStructHandler::decode");
}
};
KJ_TEST("register DynamicStruct handler") {
// This test currently only checks that this compiles, which at one point wasn't the caes.
// TODO(test): Actually run some code here.
TestDynamicStructHandler handler;
JsonCodec json;
json.addTypeHandler(Schema::from<TestAllTypes>(), handler);
}
} // namespace } // namespace
} // namespace _ (private) } // namespace _ (private)
} // namespace capnp } // namespace capnp
...@@ -386,189 +386,152 @@ void JsonCodec::encodeField(StructSchema::Field field, DynamicValue::Reader inpu ...@@ -386,189 +386,152 @@ void JsonCodec::encodeField(StructSchema::Field field, DynamicValue::Reader inpu
encode(input, field.getType(), output); encode(input, field.getType(), output);
} }
namespace { Orphan<DynamicList> JsonCodec::decodeArray(List<JsonValue>::Reader input, ListSchema type, Orphanage orphanage) const {
auto orphan = orphanage.newOrphan(type, input.size());
auto output = orphan.get();
for (auto i: kj::indices(input)) {
output.adopt(i, decode(input[i], type.getElementType(), orphanage));
}
return orphan;
}
void JsonCodec::decodeObject(JsonValue::Reader input, StructSchema type, Orphanage orphanage, DynamicStruct::Builder output) const {
KJ_REQUIRE(input.isObject(), "Expected object value");
for (auto field: input.getObject()) {
KJ_IF_MAYBE(fieldSchema, type.findFieldByName(field.getName())) {
auto fieldValue = field.getValue();
auto fieldType = (*fieldSchema).getType();
auto iter = impl->fieldHandlers.find(*fieldSchema);
if (iter != impl->fieldHandlers.end()) {
output.adopt(*fieldSchema, iter->second->decodeBase(*this, fieldValue, fieldType, orphanage));
} else {
output.adopt(*fieldSchema, decode(fieldValue, fieldType, orphanage));
}
} else {
// Unknown json fields are ignored to allow schema evolution
}
}
}
void JsonCodec::decode(JsonValue::Reader input, DynamicStruct::Builder output) const {
auto type = output.getSchema();
auto iter = impl->typeHandlers.find(type);
if (iter != impl->typeHandlers.end()) {
return iter->second->decodeStructBase(*this, input, output);
}
decodeObject(input, type, Orphanage::getForMessageContaining(output), output);
}
Orphan<DynamicValue> JsonCodec::decode(
JsonValue::Reader input, Type type, Orphanage orphanage) const {
auto iter = impl->typeHandlers.find(type);
if (iter != impl->typeHandlers.end()) {
return iter->second->decodeBase(*this, input, type, orphanage);
}
template <typename SetFn, typename DecodeArrayFn, typename DecodeObjectFn>
void decodeField(Type type, JsonValue::Reader value, SetFn setFn, DecodeArrayFn decodeArrayFn,
DecodeObjectFn decodeObjectFn) {
// This code relies on conversions in DynamicValue::Reader::as<T>.
switch(type.which()) { switch(type.which()) {
case schema::Type::VOID: case schema::Type::VOID:
break; return capnp::VOID;
case schema::Type::BOOL: case schema::Type::BOOL:
switch (value.which()) { switch (input.which()) {
case JsonValue::BOOLEAN: case JsonValue::BOOLEAN:
setFn(value.getBoolean()); return input.getBoolean();
break;
default: default:
KJ_FAIL_REQUIRE("Expected boolean value"); KJ_FAIL_REQUIRE("Expected boolean value");
} }
break;
case schema::Type::INT8: case schema::Type::INT8:
case schema::Type::INT16: case schema::Type::INT16:
case schema::Type::INT32: case schema::Type::INT32:
case schema::Type::INT64: case schema::Type::INT64:
// Relies on range check in DynamicValue::Reader::as<IntType> // Relies on range check in DynamicValue::Reader::as<IntType>
switch (value.which()) { switch (input.which()) {
case JsonValue::NUMBER: case JsonValue::NUMBER:
setFn(value.getNumber()); return input.getNumber();
break;
case JsonValue::STRING: case JsonValue::STRING:
setFn(value.getString().parseAs<int64_t>()); return input.getString().parseAs<int64_t>();
break;
default: default:
KJ_FAIL_REQUIRE("Expected integer value"); KJ_FAIL_REQUIRE("Expected integer value");
} }
break;
case schema::Type::UINT8: case schema::Type::UINT8:
case schema::Type::UINT16: case schema::Type::UINT16:
case schema::Type::UINT32: case schema::Type::UINT32:
case schema::Type::UINT64: case schema::Type::UINT64:
// Relies on range check in DynamicValue::Reader::as<IntType> // Relies on range check in DynamicValue::Reader::as<IntType>
switch (value.which()) { switch (input.which()) {
case JsonValue::NUMBER: case JsonValue::NUMBER:
setFn(value.getNumber()); return input.getNumber();
break;
case JsonValue::STRING: case JsonValue::STRING:
setFn(value.getString().parseAs<uint64_t>()); return input.getString().parseAs<uint64_t>();
break;
default: default:
KJ_FAIL_REQUIRE("Expected integer value"); KJ_FAIL_REQUIRE("Expected integer value");
} }
break;
case schema::Type::FLOAT32: case schema::Type::FLOAT32:
case schema::Type::FLOAT64: case schema::Type::FLOAT64:
switch (value.which()) { switch (input.which()) {
case JsonValue::NULL_: case JsonValue::NULL_:
setFn(kj::nan()); return kj::nan();
break;
case JsonValue::NUMBER: case JsonValue::NUMBER:
setFn(value.getNumber()); return input.getNumber();
break;
case JsonValue::STRING: case JsonValue::STRING:
setFn(value.getString().parseAs<double>()); return input.getString().parseAs<double>();
break;
default: default:
KJ_FAIL_REQUIRE("Expected float value"); KJ_FAIL_REQUIRE("Expected float value");
} }
break;
case schema::Type::TEXT: case schema::Type::TEXT:
switch (value.which()) { switch (input.which()) {
case JsonValue::STRING: case JsonValue::STRING:
setFn(value.getString()); return orphanage.newOrphanCopy(input.getString());
break;
default: default:
KJ_FAIL_REQUIRE("Expected text value"); KJ_FAIL_REQUIRE("Expected text value");
} }
break;
case schema::Type::DATA: case schema::Type::DATA:
switch (value.which()) { switch (input.which()) {
case JsonValue::ARRAY: { case JsonValue::ARRAY: {
auto array = value.getArray(); auto array = input.getArray();
kj::Vector<byte> data(array.size()); auto orphan = orphanage.newOrphan<Data>(array.size());
for (auto arrayObject : array) { auto data = orphan.get();
auto x = arrayObject.getNumber(); for (auto i: kj::indices(array)) {
auto x = array[i].getNumber();
KJ_REQUIRE(byte(x) == x, "Number in byte array is not an integer in [0, 255]"); KJ_REQUIRE(byte(x) == x, "Number in byte array is not an integer in [0, 255]");
data.add(byte(x)); data[i] = x;
} }
setFn(Data::Reader(data.asPtr())); return kj::mv(orphan);
break;
} }
default: default:
KJ_FAIL_REQUIRE("Expected data value"); KJ_FAIL_REQUIRE("Expected data value");
} }
break;
case schema::Type::LIST: case schema::Type::LIST:
switch (value.which()) { switch (input.which()) {
case JsonValue::NULL_:
// nothing to do
break;
case JsonValue::ARRAY: case JsonValue::ARRAY:
decodeArrayFn(value.getArray()); return decodeArray(input.getArray(), type.asList(), orphanage);
break;
default: default:
KJ_FAIL_REQUIRE("Expected list value"); KJ_FAIL_REQUIRE("Expected list value");
} }
break;
case schema::Type::ENUM: case schema::Type::ENUM:
switch (value.which()) { switch (input.which()) {
case JsonValue::STRING: case JsonValue::STRING:
setFn(value.getString()); return DynamicEnum(type.asEnum().getEnumerantByName(input.getString()));
break;
default: default:
KJ_FAIL_REQUIRE("Expected enum value"); KJ_FAIL_REQUIRE("Expected enum value");
} }
break; case schema::Type::STRUCT: {
case schema::Type::STRUCT: auto structType = type.asStruct();
switch (value.which()) { auto orphan = orphanage.newOrphan(structType);
case JsonValue::NULL_: decodeObject(input, structType, orphanage, orphan.get());
// nothing to do return kj::mv(orphan);
break;
case JsonValue::OBJECT:
decodeObjectFn(value.getObject());
break;
default:
KJ_FAIL_REQUIRE("Expected object value");
} }
break;
case schema::Type::INTERFACE: case schema::Type::INTERFACE:
KJ_FAIL_REQUIRE("don't know how to JSON-decode capabilities; " KJ_FAIL_REQUIRE("don't know how to JSON-decode capabilities; "
"JsonCodec::Handler not implemented yet :("); "please register a JsonCodec::Handler for this");
case schema::Type::ANY_POINTER: case schema::Type::ANY_POINTER:
KJ_FAIL_REQUIRE("don't know how to JSON-decode AnyPointer; " KJ_FAIL_REQUIRE("don't know how to JSON-decode AnyPointer; "
"JsonCodec::Handler not implemented yet :("); "please register a JsonCodec::Handler for this");
}
}
} // namespace
void JsonCodec::decodeArray(List<JsonValue>::Reader input, DynamicList::Builder output) const {
KJ_ASSERT(input.size() == output.size(), "Builder was not initialized to input size");
auto type = output.getSchema().getElementType();
for (auto i = 0; i < input.size(); i++) {
decodeField(type, input[i],
[&](DynamicValue::Reader value) { output.set(i, value); },
[&](List<JsonValue>::Reader array) {
decodeArray(array, output.init(i, array.size()).as<DynamicList>());
},
[&](List<JsonValue::Field>::Reader object) {
decodeObject(object, output[i].as<DynamicStruct>());
});
} }
}
void JsonCodec::decodeObject(List<JsonValue::Field>::Reader input, DynamicStruct::Builder output) KJ_CLANG_KNOWS_THIS_IS_UNREACHABLE_BUT_GCC_DOESNT;
const {
for (auto field : input) {
KJ_IF_MAYBE(fieldSchema, output.getSchema().findFieldByName(field.getName())) {
decodeField((*fieldSchema).getType(), field.getValue(),
[&](DynamicValue::Reader value) { output.set(*fieldSchema, value); },
[&](List<JsonValue>::Reader array) {
decodeArray(array, output.init(*fieldSchema, array.size()).as<DynamicList>());
},
[&](List<JsonValue::Field>::Reader object) {
decodeObject(object, output.init(*fieldSchema).as<DynamicStruct>());
});
} else {
// Unknown json fields are ignored to allow schema evolution
}
}
}
void JsonCodec::decode(JsonValue::Reader input, DynamicStruct::Builder output) const {
// TODO(soon): type and field handlers
switch (input.which()) {
case JsonValue::OBJECT:
decodeObject(input.getObject(), output);
break;
default:
KJ_FAIL_REQUIRE("Top level json value must be object");
};
}
Orphan<DynamicValue> JsonCodec::decode(
JsonValue::Reader input, Type type, Orphanage orphanage) const {
// TODO(soon)
KJ_FAIL_ASSERT("JSON decode into orphanage not implement yet. :(");
} }
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
...@@ -697,19 +660,7 @@ public: ...@@ -697,19 +660,7 @@ public:
} }
void parseNumber(JsonValue::Builder& output) { void parseNumber(JsonValue::Builder& output) {
auto numberStr = consumeNumber(); output.setNumber(consumeNumber().parseAs<double>());
char *endPtr;
errno = 0;
double value = strtod(numberStr.begin(), &endPtr);
KJ_ASSERT(endPtr != numberStr.begin(), "strtod should not fail! Is consumeNumber wrong?");
KJ_REQUIRE((value != HUGE_VAL && value != -HUGE_VAL) || errno != ERANGE,
"Overflow in JSON number.");
KJ_REQUIRE(value != 0.0 || errno != ERANGE,
"Underflow in JSON number.");
output.setNumber(value);
} }
void parseString(JsonValue::Builder& output) { void parseString(JsonValue::Builder& output) {
......
...@@ -49,7 +49,7 @@ class JsonCodec { ...@@ -49,7 +49,7 @@ class JsonCodec {
// - 64-bit integers are encoded as strings, since JSON "numbers" are double-precision floating // - 64-bit integers are encoded as strings, since JSON "numbers" are double-precision floating
// points which cannot store a 64-bit integer without losing data. // points which cannot store a 64-bit integer without losing data.
// - NaNs and infinite floating point numbers are not allowed by the JSON spec, and so are encoded // - NaNs and infinite floating point numbers are not allowed by the JSON spec, and so are encoded
// as null. This matches the behavior of `JSON.stringify` in at least Firefox and Chrome. // as strings.
// - Data is encoded as an array of numbers in the range [0,255]. You probably want to register // - Data is encoded as an array of numbers in the range [0,255]. You probably want to register
// a handler that does something better, like maybe base64 encoding, but there are a zillion // a handler that does something better, like maybe base64 encoding, but there are a zillion
// different ways people do this. // different ways people do this.
...@@ -220,8 +220,8 @@ private: ...@@ -220,8 +220,8 @@ private:
void encodeField(StructSchema::Field field, DynamicValue::Reader input, void encodeField(StructSchema::Field field, DynamicValue::Reader input,
JsonValue::Builder output) const; JsonValue::Builder output) const;
void decodeArray(List<JsonValue>::Reader input, DynamicList::Builder output) const; Orphan<DynamicList> decodeArray(List<JsonValue>::Reader input, ListSchema type, Orphanage orphanage) const;
void decodeObject(List<JsonValue::Field>::Reader input, DynamicStruct::Builder output) const; void decodeObject(JsonValue::Reader input, StructSchema type, Orphanage orphanage, DynamicStruct::Builder output) const;
void addTypeHandlerImpl(Type type, HandlerBase& handler); void addTypeHandlerImpl(Type type, HandlerBase& handler);
void addFieldHandlerImpl(StructSchema::Field field, Type type, HandlerBase& handler); void addFieldHandlerImpl(StructSchema::Field field, Type type, HandlerBase& handler);
}; };
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment