diff --git a/core/json/src/JsonReader.cpp b/core/json/src/JsonReader.cpp index d786721..e174993 100644 --- a/core/json/src/JsonReader.cpp +++ b/core/json/src/JsonReader.cpp @@ -78,6 +78,7 @@ class JsonReaderImpl { return start; } +#ifdef NGREST_JSON_NO_QUOTE_STRING inline char* tokenString() { ++curr; // skip '"' char* start = curr; @@ -99,6 +100,94 @@ class JsonReaderImpl { ++curr; return start; } +#else + + inline char fromHexChar(short hex) + { + static const char table[256] = { + 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, // x00 + 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, // x10 + 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, // x20 + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, // x30 + 0x10, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, // x40 + 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, // x50 + 0x10, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, // x60 + 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, // x70 + 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, // x80 + 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, // x90 + 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, // xA0 + 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, // xB0 + 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, // xC0 + 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, // xD0 + 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, // xE0 + 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10 // xF0 + }; + NGREST_ASSERT(table[hex] != 0x10, "Unexpected character while reading unicode hex digit"); + return table[hex]; + } + + inline char* tokenString() { + ++curr; // skip '"' + char* start = curr; + char* out = nullptr; + while (*curr != '"') { + if (*curr == '\\') { // parse quoted characters + if (!out) { + out = curr; + } + ++curr; + switch (*curr) { + case 'b': + *out = '\b'; + break; + case 'f': + *out = '\f'; + break; + case 'n': + *out = '\n'; + break; + case 'r': + *out = '\r'; + break; + case 't': + *out = '\t'; + break; + case 'u': + ++curr; + *out = fromHexChar(*curr) << 4; + ++curr; + *out |= fromHexChar(*curr); + if (*out) { // \u00XX + ++out; + } + ++curr; + *out = fromHexChar(*curr) << 4; + ++curr; + *out |= fromHexChar(*curr); + break; + default: + *out = *curr; + break; + } + ++out; + } else { + NGREST_ASSERT(*curr >= 0x20, "Unexpected control character while reading string"); + if (out) { + *out = *curr; + ++out; + } + } + ++curr; + } + if (out) { + *out = '\0'; + } else { + *curr = '\0'; + } + ++curr; + return start; + } +#endif inline JsonReaderImpl(char* buff, MemPool* memPool): @@ -141,7 +230,7 @@ class JsonReaderImpl { if (!strncmp(token, "NaN", len)) return pool->alloc(ValueType::NaN); - NGREST_THROW_ASSERT("Unexpected token"); + NGREST_THROW_ASSERT(std::string("Unexpected token: [") + token + "]"); } inline Node* readArrayOrObject() @@ -155,7 +244,7 @@ class JsonReaderImpl { return readObject(); } - NGREST_THROW_ASSERT("Unexpected symbol"); + NGREST_THROW_ASSERT(std::string("Unexpected symbol: [") + *curr + "]"); } inline Array* readArray() diff --git a/core/json/src/JsonWriter.cpp b/core/json/src/JsonWriter.cpp index 914685f..dcc67e7 100644 --- a/core/json/src/JsonWriter.cpp +++ b/core/json/src/JsonWriter.cpp @@ -41,6 +41,73 @@ class JsonWriterImpl { { } + inline char toHexChar(short dec) + { + static char hexTable[] = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'}; + return hexTable[dec]; + } + + inline void putString(const char* str) + { + pool->putChar('"'); +#ifdef NGREST_JSON_NO_QUOTE_STRING + pool->putCString(str); +#else + size_t len = strlen(str); + pool->reserve(len); + const char* start = str; + const char* curr = str; + for (; *curr; ++curr) { + if (*curr < 0x20) { + if (start) { + if (curr > start) { + pool->putData(start, curr - start); + } + } + switch (*curr) { + case '\b': + pool->putData("\\b", 2); + break; + case '\f': + pool->putData("\\f", 2); + break; + case '\n': + pool->putData("\\n", 2); + break; + case '\r': + pool->putData("\\r", 2); + break; + case '\t': + pool->putData("\\t", 2); + break; + default: + pool->putData("\\u00", 4); + pool->putChar(toHexChar((*curr >> 4) & 0x0f)); + pool->putChar(toHexChar(*curr & 0x0f)); + break; + } + start = curr + 1; + } else { + if (*curr == '"' || *curr == '\\') { + if (start) { + if (curr > start) { + pool->putData(start, curr - start); + } + start = curr + 1; + } + + pool->putChar('\\'); + pool->putChar(*curr); + } + } + } + if (start && curr > start) { + pool->putData(start, curr - start); + } +#endif + pool->putChar('"'); + } + inline void writeNode(const Node* node) { if (!node) { @@ -56,9 +123,7 @@ class JsonWriterImpl { for (const NamedNode* child = object->firstChild; child; child = child->nextSibling) { if (child != object->firstChild) pool->putChar(','); - pool->putChar('"'); - pool->putCString(child->name); - pool->putChar('"'); + putString(child->name); pool->putChar(':'); writeNode(child->node); } @@ -86,9 +151,7 @@ class JsonWriterImpl { break; case ValueType::String: - pool->putChar('"'); - pool->putCString(value->value); - pool->putChar('"'); + putString(value->value); break; case ValueType::Number: diff --git a/tests/json/src/jsontest.cpp b/tests/json/src/jsontest.cpp index 496fbeb..fbc03a0 100644 --- a/tests/json/src/jsontest.cpp +++ b/tests/json/src/jsontest.cpp @@ -38,7 +38,7 @@ int main() const int testsCount = 8; char test1[] = "{}"; char test2[] = "[]"; - char test3[] = "{\"abc\": 1}"; + char test3[] = "{\"ab c\": 1}"; char test4[] = "{ \"q\": [ ] }"; char test5[] = "{\"x\": {\"abc\": 1}}"; char test6[] = "{ \"x\" : {\"abc\": 1 }, \"y\": [1, 2e2, \"3\", null , NaN]}"; @@ -55,26 +55,78 @@ int main() test8 }; + const char* testsOut[testsCount] = { + "{}", + "[]", + "{\"ab c\":1}", + "{\"q\":[]}", + "{\"x\":{\"abc\":1}}", + "{\"x\":{\"abc\":1},\"y\":[1,2e2,\"3\",null,NaN]}", + "[{},{\"\":\"\"}]", + "[[[],[]],{\"1\":[]}]" + }; + for (int t = 0; t < testsCount; ++t) { + std::cout << "Basic test: #" << t << std::endl; ngrest::MemPool pool; ngrest::MemPool poolOut; ngrest::Node* root = ngrest::json::JsonReader::read(tests[t], &pool); ngrest::json::JsonWriter::write(root, &poolOut, 2); - int chunksCount = poolOut.getChunkCount(); - const ngrest::MemPool::Chunk* chunks = poolOut.getChunks(); - printf("test #%d \n----------\n", t + 1); - for (int i = 0; i < chunksCount; ++i) { - const ngrest::MemPool::Chunk* chunk = chunks + i; - fwrite(chunk->buffer, chunk->size, 1, stdout); - } - printf("\n----------\n"); + const ngrest::MemPool::Chunk* chunk = poolOut.flatten(); + NGREST_ASSERT(!strcmp(chunk->buffer, testsOut[t]), std::string("Basic Test failed. Expected [") + + testsOut[t] + "] found [" + chunk->buffer + "].") } } catch (const std::exception& e) { std::cerr << e.what() << std::endl; + return 1; } + + + // quoting test + try { + const char* json = R"(["no special chars","\b\f\n\r\t\"\\\/\u0008\u000a","__\n\r__","\t__","__\t"])"; + const char* values[] = { + "no special chars", "\b\f\n\r\t\"\\/\x08\x0a", "__\n\r__", "\t__", "__\t" + }; + const char* jsonOut = R"(["no special chars","\b\f\n\r\t\"\\/\b\n","__\n\r__","\t__","__\t"])"; + ngrest::MemPool poolIn; + + // test reader + char* jsonIn = poolIn.putCString(json, true); + const ngrest::Node* root = ngrest::json::JsonReader::read(jsonIn, &poolIn); + NGREST_ASSERT(root->type == ngrest::NodeType::Array, "Read node is not array"); + const ngrest::Array* arr = static_cast(root); + int index = 0; + for (const ngrest::LinkedNode* node = arr->firstChild; node; node = node->nextSibling, ++index) { + std::cout << "Reader test: #" << index << std::endl; + NGREST_ASSERT_NULL(node->node); + NGREST_ASSERT(node->node->type == ngrest::NodeType::Value, "Child is not Value"); + const ngrest::Value* value = static_cast(node->node); + NGREST_ASSERT_NULL(value->value); + NGREST_ASSERT(!strcmp(value->value, values[index]), std::string("Reader Test failed. Expected [") + + values[index] + "] found [" + value->value + "].") + } + + + std::cout << "Writer test" << std::endl; + // test writer + ngrest::MemPool poolOut; + ngrest::json::JsonWriter::write(root, &poolOut); + ngrest::MemPool::Chunk* res = poolOut.flatten(); + NGREST_ASSERT_NULL(res); + NGREST_ASSERT(!strcmp(res->buffer, jsonOut), std::string("Writer Test failed. Expected:\n===================\n") + + jsonOut + "\n===================\nfound:\n===================\n" + + res->buffer + "\n===================\n") + } catch (const std::exception& e) { + std::cerr << e.what() << std::endl; + return 1; + } + + std::cout << "All json tests passed" << std::endl; + return 0; }