From aa1b358538beb73d9b11923c70ef708010d9a92d Mon Sep 17 00:00:00 2001 From: Nick Bopp Date: Mon, 2 Mar 2020 13:15:02 -0800 Subject: [PATCH] Fix line endings (#15) --- LICENSE | 42 +- README.md | 282 +-- cmake/modules/Finduuid.cmake | 80 +- include/jsonbuilder/JsonBuilder.h | 3192 ++++++++++++++-------------- include/jsonbuilder/JsonRenderer.h | 556 ++--- src/CMakeLists.txt | 80 +- src/JsonBuilder.cpp | 2846 ++++++++++++------------- src/JsonRenderer.cpp | 970 ++++----- src/PodVector.cpp | 236 +- test/CMakeLists.txt | 20 +- test/TestBuilder.cpp | 1232 +++++------ 11 files changed, 4768 insertions(+), 4768 deletions(-) diff --git a/LICENSE b/LICENSE index 6430234..aef3b77 100644 --- a/LICENSE +++ b/LICENSE @@ -1,22 +1,22 @@ -JsonBuilder -Copyright (c) Microsoft Corporation. All rights reserved. - -MIT License - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +JsonBuilder +Copyright (c) Microsoft Corporation. All rights reserved. + +MIT License + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE \ No newline at end of file diff --git a/README.md b/README.md index f1095a2..1d2f00d 100644 --- a/README.md +++ b/README.md @@ -1,142 +1,142 @@ -[![Build Status](https://dev.azure.com/ms/JsonBuilder/_apis/build/status/microsoft.JsonBuilder?branchName=master)](https://dev.azure.com/ms/JsonBuilder/_build/latest?definitionId=148&branchName=master) - -# JsonBuilder - -JsonBuilder is a small C++ library for building a space-efficient binary representation of structured data and, when ready, rendering it to JSON. The library offers STL-like syntax for adding and finding data as well as STL-like iterators for efficiently tracking location. - -## Examples - -### Building structured data - -Let's try to build the following JSON up using the JsonBuilder interface: - -```json -{ - "e": 2.718, - "enabled": true, - "user": "john", - "resolution": { - "x": 1024, - "y": 768 - }, - "colors": [ - "Red", - "Green", - "Blue" - ] -} -``` - -The code to do so would look like this: - -```cpp -JsonBuilder jb; -jb.push_back(jb.end(), "e", 2.718); -jb.push_back(jb.end(), "enabled", true); -jb.push_back(jb.end(), "user", "john"); - -JsonIterator resolutionItr = jb.push_back(jb.end(), "resolution", JsonObject); -jb.push_back(resolutionItr, "x", 1024); -jb.push_back(resolutionItr, "y", 768); - -JsonIterator colorIterator = jb.push_back(jb.end(), "colors", JsonArray); -jb.push_back(colorIterator, "", "Red"); -jb.push_back(colorIterator, "", "Green"); -jb.push_back(colorIterator, "", "Blue"); -``` - -### Getting an iterator to existing data - -Using the built JsonBuilder object above as a starting point: - -```cpp -// Float -JsonConstIterator eItr = jb.find("e"); -float e = eItr->GetUnchecked(); -std::cout << e << std::endl; - -// Object -JsonConstIterator resolutionItr = jb.find("resolution"); -for (JsonConstIterator beginItr = resolutionItr.begin(), - endItr = resolutionItr.end(); - beginItr != endItr; - ++beginItr) -{ - std::string name(beginItr->Name().data(), beginItr->Name().length()); - std::cout << name << " " << beginItr->GetUnchecked() - << std::endl; -} - -// Array -JsonConstIterator colorsItr = jb.find("colors"); -for (JsonConstIterator beginItr = colorsItr.begin(), endItr = colorsItr.end(); - beginItr != endItr; - ++beginItr) -{ - auto color = beginItr->GetUnchecked(); - std::cout << color << std::endl; -} -``` - -### Rendering to JSON - -Using the built JsonBuilder object above as a starting point: - -```cpp -// Create a renderer and reserve 2048 bytes up front -JsonRenderer renderer; -renderer.Reserve(2048); - -// Render a json builder object to a string -std::string_view result = renderer.Render(_jsonBuilder); -std::string stl_string(result.data(), result.size()); -std::cout << stl_string.c_str() << std::endl; -``` - -## Dependencies - -This project carries a dependency on the uuid library. To develop with this project, install the development version of the library: - -```bash -sudo apt-get install uuid-dev -``` - -If you checkout with submodules, you will receive a version of Catch2 for testing that can be used automatically. If you do not checkout -this submodule, the build system will instead search for an installed version of Catch2 and use that. - -## Integration - -JsonBuilder builds as a static library and requires C++11. The project creates a CMake compatible 'jsonbuilder' target which you can use for linking against the library. - -1. Add this project as a subdirectory in your project, either as a git submodule or copying the code directly. -2. Add that directory to your top-level CMakeLists.txt with 'add_subdirectory'. This will make the 'jsonbuilder' target available. -3. Add the 'jsonbuilder' target to the target_link_libraries of any target that will use the JsonBuilder library. - -## Reporting Security Issues - -Security issues and bugs should be reported privately, via email, to the -Microsoft Security Response Center (MSRC) at <[secure@microsoft.com](mailto:secure@microsoft.com)>. -You should receive a response within 24 hours. If for some reason you do not, please follow up via -email to ensure we received your original message. Further information, including the -[MSRC PGP](https://technet.microsoft.com/en-us/security/dn606155) key, can be found in the -[Security TechCenter](https://technet.microsoft.com/en-us/security/default). - -## Code of Conduct - -This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). - -For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments. - -## Contributing - -Want to contribute? The team encourages community feedback and contributions. Please follow our [contributing guidelines](CONTRIBUTING.md). - -We also welcome [issues submitted on GitHub](https://github.com/Microsoft/JsonBuilder/issues). - -## Project Status - -This project is currently in active development. - -## Contact - +[![Build Status](https://dev.azure.com/ms/JsonBuilder/_apis/build/status/microsoft.JsonBuilder?branchName=master)](https://dev.azure.com/ms/JsonBuilder/_build/latest?definitionId=148&branchName=master) + +# JsonBuilder + +JsonBuilder is a small C++ library for building a space-efficient binary representation of structured data and, when ready, rendering it to JSON. The library offers STL-like syntax for adding and finding data as well as STL-like iterators for efficiently tracking location. + +## Examples + +### Building structured data + +Let's try to build the following JSON up using the JsonBuilder interface: + +```json +{ + "e": 2.718, + "enabled": true, + "user": "john", + "resolution": { + "x": 1024, + "y": 768 + }, + "colors": [ + "Red", + "Green", + "Blue" + ] +} +``` + +The code to do so would look like this: + +```cpp +JsonBuilder jb; +jb.push_back(jb.end(), "e", 2.718); +jb.push_back(jb.end(), "enabled", true); +jb.push_back(jb.end(), "user", "john"); + +JsonIterator resolutionItr = jb.push_back(jb.end(), "resolution", JsonObject); +jb.push_back(resolutionItr, "x", 1024); +jb.push_back(resolutionItr, "y", 768); + +JsonIterator colorIterator = jb.push_back(jb.end(), "colors", JsonArray); +jb.push_back(colorIterator, "", "Red"); +jb.push_back(colorIterator, "", "Green"); +jb.push_back(colorIterator, "", "Blue"); +``` + +### Getting an iterator to existing data + +Using the built JsonBuilder object above as a starting point: + +```cpp +// Float +JsonConstIterator eItr = jb.find("e"); +float e = eItr->GetUnchecked(); +std::cout << e << std::endl; + +// Object +JsonConstIterator resolutionItr = jb.find("resolution"); +for (JsonConstIterator beginItr = resolutionItr.begin(), + endItr = resolutionItr.end(); + beginItr != endItr; + ++beginItr) +{ + std::string name(beginItr->Name().data(), beginItr->Name().length()); + std::cout << name << " " << beginItr->GetUnchecked() + << std::endl; +} + +// Array +JsonConstIterator colorsItr = jb.find("colors"); +for (JsonConstIterator beginItr = colorsItr.begin(), endItr = colorsItr.end(); + beginItr != endItr; + ++beginItr) +{ + auto color = beginItr->GetUnchecked(); + std::cout << color << std::endl; +} +``` + +### Rendering to JSON + +Using the built JsonBuilder object above as a starting point: + +```cpp +// Create a renderer and reserve 2048 bytes up front +JsonRenderer renderer; +renderer.Reserve(2048); + +// Render a json builder object to a string +std::string_view result = renderer.Render(_jsonBuilder); +std::string stl_string(result.data(), result.size()); +std::cout << stl_string.c_str() << std::endl; +``` + +## Dependencies + +This project carries a dependency on the uuid library. To develop with this project, install the development version of the library: + +```bash +sudo apt-get install uuid-dev +``` + +If you checkout with submodules, you will receive a version of Catch2 for testing that can be used automatically. If you do not checkout +this submodule, the build system will instead search for an installed version of Catch2 and use that. + +## Integration + +JsonBuilder builds as a static library and requires C++11. The project creates a CMake compatible 'jsonbuilder' target which you can use for linking against the library. + +1. Add this project as a subdirectory in your project, either as a git submodule or copying the code directly. +2. Add that directory to your top-level CMakeLists.txt with 'add_subdirectory'. This will make the 'jsonbuilder' target available. +3. Add the 'jsonbuilder' target to the target_link_libraries of any target that will use the JsonBuilder library. + +## Reporting Security Issues + +Security issues and bugs should be reported privately, via email, to the +Microsoft Security Response Center (MSRC) at <[secure@microsoft.com](mailto:secure@microsoft.com)>. +You should receive a response within 24 hours. If for some reason you do not, please follow up via +email to ensure we received your original message. Further information, including the +[MSRC PGP](https://technet.microsoft.com/en-us/security/dn606155) key, can be found in the +[Security TechCenter](https://technet.microsoft.com/en-us/security/default). + +## Code of Conduct + +This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). + +For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments. + +## Contributing + +Want to contribute? The team encourages community feedback and contributions. Please follow our [contributing guidelines](CONTRIBUTING.md). + +We also welcome [issues submitted on GitHub](https://github.com/Microsoft/JsonBuilder/issues). + +## Project Status + +This project is currently in active development. + +## Contact + The easiest way to contact us is via the [Issues](https://github.com/microsoft/JsonBuilder/issues) page. \ No newline at end of file diff --git a/cmake/modules/Finduuid.cmake b/cmake/modules/Finduuid.cmake index e3a0bbe..6148892 100644 --- a/cmake/modules/Finduuid.cmake +++ b/cmake/modules/Finduuid.cmake @@ -1,40 +1,40 @@ -# Output target -# uuid::uuid - -include(AliasPkgConfigTarget) - -if (TARGET uuid::uuid) - return() -endif() - -# First try and find with PkgConfig -find_package(PkgConfig QUIET) -if (PKG_CONFIG_FOUND) - pkg_check_modules(uuid REQUIRED IMPORTED_TARGET uuid) - if (TARGET PkgConfig::uuid) - alias_pkg_config_target(uuid::uuid PkgConfig::uuid) - return() - endif () -endif () - -# If that doesn't work, try again with old fashioned path lookup, with some caching -if (NOT (uuid_INCLUDE_DIR AND uuid_LIBRARY)) - find_path(uuid_INCLUDE_DIR - NAMES uuid/uuid.h) - find_library(uuid_LIBRARY - NAMES uuid) - - include(FindPackageHandleStandardArgs) - find_package_handle_standard_args(uuid DEFAULT_MSG - uuid_LIBRARY - uuid_INCLUDE_DIR) - - mark_as_advanced(uuid_LIBRARY uuid_INCLUDE_DIR) -endif() - -add_library(uuid::uuid UNKNOWN IMPORTED) -set_target_properties(uuid::uuid PROPERTIES - IMPORTED_LOCATION "${uuid_LIBRARY}" - IMPORTED_INCLUDE_DIRECTORIES "${uuid_INCLUDE_DIR}") - -set(uuid_FOUND TRUE) +# Output target +# uuid::uuid + +include(AliasPkgConfigTarget) + +if (TARGET uuid::uuid) + return() +endif() + +# First try and find with PkgConfig +find_package(PkgConfig QUIET) +if (PKG_CONFIG_FOUND) + pkg_check_modules(uuid REQUIRED IMPORTED_TARGET uuid) + if (TARGET PkgConfig::uuid) + alias_pkg_config_target(uuid::uuid PkgConfig::uuid) + return() + endif () +endif () + +# If that doesn't work, try again with old fashioned path lookup, with some caching +if (NOT (uuid_INCLUDE_DIR AND uuid_LIBRARY)) + find_path(uuid_INCLUDE_DIR + NAMES uuid/uuid.h) + find_library(uuid_LIBRARY + NAMES uuid) + + include(FindPackageHandleStandardArgs) + find_package_handle_standard_args(uuid DEFAULT_MSG + uuid_LIBRARY + uuid_INCLUDE_DIR) + + mark_as_advanced(uuid_LIBRARY uuid_INCLUDE_DIR) +endif() + +add_library(uuid::uuid UNKNOWN IMPORTED) +set_target_properties(uuid::uuid PROPERTIES + IMPORTED_LOCATION "${uuid_LIBRARY}" + IMPORTED_INCLUDE_DIRECTORIES "${uuid_INCLUDE_DIR}") + +set(uuid_FOUND TRUE) diff --git a/include/jsonbuilder/JsonBuilder.h b/include/jsonbuilder/JsonBuilder.h index 8c8cc88..6d9413a 100644 --- a/include/jsonbuilder/JsonBuilder.h +++ b/include/jsonbuilder/JsonBuilder.h @@ -1,1596 +1,1596 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -/* -JsonBuilder stores data values of arbitrary type in an optimized tree -structure (i.e. values may have children). The JsonBuilder type is suitable for -storing any kind of hierarchical data. It was originally designed as a data -structure for building up a payload that will be serialized into a JSON string, -though it can be used in other scenarios that require similar organization. - -Summary: - -- enum JsonType - Indicates the type of a value that is stored in a JsonBuilder. -- class JsonValue - Interface to a value that is stored in a JsonBuilder. -- class JsonBuilder - Object that stores a tree of values. -- class JsonImplementType - Traits type used to extend JsonBuilder to work with a user-defined type. - -JsonBuilder design decisions: - -- Follows STL container design conventions (e.g. begin() and end() methods). -- Optimized for building up, lightly-manipulating, and rendering payloads. -- Less-optimized for searching through payloads, i.e. items are not indexed. - For example, finding a value with a particular name means iterating through - all of the parent's children and checking each child for a matching name. -- Nodes in the tree are one of two types. Simple nodes contain typed values - (type tag plus binary data) but have no children. Complex nodes contain no - data but may have child nodes. -- Simple nodes support arbitrary binary payloads. Any type of data can be - stored with any type tag, and the JsonBuilder itself performs no validation - of the stored data (though it does provide built-in support for creating - or accessing the contents of nodes of certain built-in types). The - JsonRenderer class and the convenience methods have built-in support for data - stored as signed-integer (1, 2, 4, or 8 bytes), unsigned-integer (1, 2, 4, or - 8 bytes), floating-point (4 or 8 bytes), boolean (true/false), null, time - (FILETIME), UUID (GUID), and string (utf-16). The convenience methods and the - renderer can be extended if other types are needed. -- Complex nodes come in two types: Object and Array. The JsonBuilder itself - makes no distinction between these two types (they have identical behavior) - but it is intended that the Object type contain named values (i.e. it is a - dictionary with string keys) and that the Array type contain anonymous values - (i.e. it is a list). -- Value name limited to 16M utf-16 characters per value. -- Value data limited to 3GB per value. -- Names are stored as utf-16. -- Memory usage for object and array values is (in bytes): - 20 + sizeof(name) + padding to a multiple of 4. -- Memory usage for all other values is (in bytes): - 12 + sizeof(name) + sizeof(data) + padding to multiple of 4. -- Total storage limited to 16GB per JsonBuilder (or available VA space). - -Error handling: - - Throw std::exception variants -*/ - -#pragma once -#include // std::min -#include -#include -#include -#include // isfinite, sqrt -#include // std::invalid_argument -#include - -#include - -namespace jsonbuilder { -struct UuidStruct -{ - uuid_t Data; -}; - -// Forward declarations - -class JsonValue; -class JsonBuilder; -template -class JsonImplementType; - -// Internal implementation details - -namespace JsonInternal { -/* -PodVector: - -Very simple vector class. For POD types only. Has the following performance -advantages over std::vector: - -- This vector uses HeapReAlloc to grow its buffer. In cases where the - HeapReAlloc can grow in-place, this avoids the overhead of allocating a - new buffer, copying the data to the new buffer, and freeing the old - buffer. -- This vector does not initialize data on resize unless specifically requested. - -In benchmarks, using PodVector instead of VC2013's std::vector improves -JsonBuilder and JsonRenderer performance by about 10%. - -In addition, using our own vector avoids creating a hard dependency on STL, -which can make it easier for customers to consume this library. -*/ - -class PodVectorBase -{ - protected: - typedef unsigned size_type; - - /* - assert(index < currentSize) - */ - static void CheckOffset(size_type index, size_type currentSize) throw(); - - /* - assert(p1 <= p2 <= p3) - */ - static void - CheckRange(void const* p1, void const* p2, void const* p3) throw(); - - /* - return checked(a + b) - */ - static size_type CheckedAdd( - size_type a, - size_type b); // may throw length_error - - /* - memcpy(pDest, pSource, cb) - */ - static void InitData(void* pDest, void const* pSource, size_t cb) throw(); - - /* - Returns a value newCapacity such that minCapacity <= newCapacity <= - maxCapacity, chosen according to a vector growth policy. The current policy - is: - - If maxCapacity < minCapacity or maxCapacity < 15, assert. - - If minCapacity < 15, return 15. - - Let cap = the smallest value 2^N-1 that is greater than or equal to - minCapacity. - - If cap < maxCapacity, return cap. Else return maxCapacity. - */ - static size_type GetNewCapacity(size_type minCapacity, size_type maxCapacity); - - /* - Calls HeapAlloc or HeapReAlloc. If allocation fails, throw bad_alloc. - */ - static void* Reallocate( - void* pb, - size_t cb, - bool zeroInitializeMemory = false); // may throw bad_alloc, length_error - - /* - Calls HeapFree. - */ - static void Deallocate(void* pb) throw(); -}; - -template -class PodVector : private PodVectorBase -{ - static size_type const - m_maxSize = ~size_t(0) / sizeof(T) > size_type(~size_type(1)) ? - size_type(~size_type(1)) : - ~size_t(0) / sizeof(T); - static_assert( - m_maxSize < size_type(m_maxSize + 1), - "Bad calculation of m_maxSize (1)"); - static_assert( - m_maxSize * sizeof(T) / sizeof(T) == m_maxSize, - "Bad calculation of m_maxSize (2)"); - - T* m_data; - size_type m_size; - size_type m_capacity; - bool m_zeroInitializeMemory; - - public: - using PodVectorBase::size_type; - - ~PodVector() throw() { Deallocate(m_data); } - - PodVector() throw() - : m_data(nullptr), m_size(0), m_capacity(0), m_zeroInitializeMemory(false) - { - return; - } - - PodVector(PodVector&& other) throw() - : m_data(other.m_data) - , m_size(other.m_size) - , m_capacity(other.m_capacity) - , m_zeroInitializeMemory(other.m_zeroInitializeMemory) - { - other.m_data = nullptr; - other.m_size = 0; - other.m_capacity = 0; - other.m_zeroInitializeMemory = false; - } - - PodVector(PodVector const& other) // may throw bad_alloc - : m_data(nullptr) - , m_size(other.m_size) - , m_capacity(other.m_size) - , m_zeroInitializeMemory(other.m_zeroInitializeMemory) - { - if (m_size != 0) - { - auto cb = m_size * sizeof(T); - m_data = - static_cast(Reallocate(nullptr, cb, m_zeroInitializeMemory)); - InitData(m_data, other.m_data, cb); - } - } - - PodVector( - T const* data, - size_type size) // may throw bad_alloc - : m_data(nullptr) - , m_size(size) - , m_capacity(size) - , m_zeroInitializeMemory(false) - { - if (m_size != 0) - { - auto cb = m_size * sizeof(T); - m_data = - static_cast(Reallocate(nullptr, cb, m_zeroInitializeMemory)); - InitData(m_data, data, cb); - } - } - - PodVector& operator=(PodVector&& other) throw() - { - PodVector(static_cast(other)).swap(*this); - return *this; - } - - PodVector& operator=(PodVector const& other) // may throw bad_alloc - { - PodVector(other).swap(*this); - return *this; - } - - static constexpr size_type max_size() throw() { return m_maxSize; } - - size_type size() const throw() { return m_size; } - - bool empty() const throw() { return m_size == 0; } - - size_type capacity() const throw() { return m_capacity; } - - T const* data() const throw() { return m_data; } - - T* data() throw() { return m_data; } - - T const& operator[](unsigned i) const throw() - { - CheckOffset(i, m_size); - return m_data[i]; - } - - T& operator[](unsigned i) throw() - { - CheckOffset(i, m_size); - return m_data[i]; - } - - void clear() { m_size = 0; } - - void push_back(T const& val) // may throw bad_alloc, length_error - { - if (m_size == m_capacity) - { - Grow(); - } - m_data[m_size++] = val; - } - - void append( - T const* pItems, - size_type cItems) // may throw bad_alloc, length_error - { - if (cItems > m_capacity - m_size) - { - GrowBy(cItems); - } - - InitData(m_data + m_size, pItems, cItems * sizeof(T)); - m_size += cItems; - } - - void append( - size_type cCopies, - T const& val) // may throw bad_alloc, length_error - { - if (cCopies > m_capacity - m_size) - { - GrowBy(cCopies); - } - - auto p = m_data + m_size; - for (size_type i = 0; i != cCopies; i += 1) - { - p[i] = val; - } - - m_size += cCopies; - } - - void reserve(size_type minCapacity) // may throw bad_alloc, length_error - { - if (m_capacity < minCapacity) - { - GrowTo(minCapacity); - } - } - - /* - NOTE: new items are uninitialized, unless m_zeroInitializeMemory is set - */ - void resize(size_type newSize) // may throw bad_alloc, length_error - { - reserve(newSize); - m_size = newSize; - } - - void swap(PodVector& other) throw() - { - auto const d = m_data; - m_data = other.m_data; - other.m_data = d; - - auto const s = m_size; - m_size = other.m_size; - other.m_size = s; - - auto const c = m_capacity; - m_capacity = other.m_capacity; - other.m_capacity = c; - - auto const z = m_zeroInitializeMemory; - m_zeroInitializeMemory = other.m_zeroInitializeMemory; - other.m_zeroInitializeMemory = z; - } - - void EnableZeroInitializeMemory() { m_zeroInitializeMemory = true; } - - /* - Ensures capacity for at least cItems additional elements, then returns - a pointer to the current end of the vector. Caller can write to this - pointer, then call SetEndPointer to include the newly-written data in - the vector. - - auto p = v.GetAppendPointer(10); - p[0] = ...; // p has room for 10 items. - v.SetEndPointer(p + 10); // Include the newly-written items in vector. - */ - T* GetAppendPointer(unsigned cItems) // may throw bad_alloc, length_error - { - if (cItems > m_capacity - m_size) - { - GrowBy(cItems); - } - return m_data + m_size; - } - - void SetEndPointer(T* pNewEnd) throw() - { - CheckRange(m_data, pNewEnd, m_data + m_capacity); - m_size = static_cast(pNewEnd - m_data); - } - - private: - void Grow() // may throw bad_alloc, length_error - { - GrowTo(m_capacity + 1); - } - - void GrowBy(size_type cItems) // may throw bad_alloc, length_error - { - GrowTo(CheckedAdd(m_size, cItems)); - } - - void GrowTo(size_type minCapacity) // may throw bad_alloc, length_error - { - size_type const newCapacity = GetNewCapacity(minCapacity, m_maxSize); - m_data = static_cast(Reallocate( - m_data, newCapacity * sizeof(T), m_zeroInitializeMemory)); - m_capacity = newCapacity; - } -}; -} // namespace JsonInternal -// namespace JsonInternal - -// JsonType - -/* -The built-in types supported by JsonBuilder and JsonRenderer. -Note that type values larger than 255 will not work correctly. -Note that JsonBuilder itself is actually only aware of types JsonHidden, -JsonArray and JsonObject. All of the other types are ignored by JsonBuilder. -There is no enforcement of any rules such as "names are not allowed on array -elements" or "names must be unique within an object". The JsonBuilder just -stores the names and data as binary blobs. - -Additional simple types can be used with JsonBuilder as necessary. (It is not -possible to add new complex types, i.e. user-defined types cannot have child -nodes.) To add new types in your own project: - -- Define JsonType constants for your types. Start numbering at 1. For example: - const JsonType JsonMyStuff = static_cast(1); -- Define a class that is derived from JsonRenderer. Override the RenderCustom - method to render values of your types. Use your derived class instead of - using JsonRenderer. -- If you want jsonBuilder.push_back(..., data), jsonValue.GetUnchecked(), - and jsonValue.ConvertTo to work for your types, - define specializations of JsonImplementType for each of your types. -*/ -enum JsonType : uint8_t -{ - // Numbering for custom types should start at 1. Custom types never have - // children. Numbering for custom types must not exceed 200. - JsonTypeReserved = 201, - JsonTypeBuiltIn = 244, - JsonUtf8, // No children. Data = utf-8 string. - JsonUInt, // No children. Data = uint (1, 2, 4, or 8 bytes). - JsonInt, // No children. Data = int (1, 2, 4, or 8 bytes). - JsonFloat, // No children. Data = float (4 or 8 bytes). - JsonBool, // No children. Data = bool (1 or 4 bytes). - JsonTime, // No children. Data = uint64 (number of 100ns intervals since - // 1601-01-01T00:00:00Z, i.e. Win32 FILETIME). - JsonUuid, // No children. Data = UUID (16 bytes universally unique - // identifier, i.e. Win32 GUID). - JsonNull, // No children. Data = void (0 bytes). - JsonHidden, // An erased or sentinel value. No data. (Calling Data() on a - // hidden value is an error.) - JsonArray, // Anonymous children. No data. (Calling Data() on an array - // value is an error.) - JsonObject, // Named children. No data. (Calling Data() on an object value - // is an error.) -}; - -// JsonValue - -/* -For internal use only. -Used for hidden erased or sentinel nodes, which do not need m_cbData. -*/ -class JsonValueBase -{ - friend class JsonBuilder; - friend class JsonValue; - typedef uint32_t Index; - - Index m_nextIndex; // The index of the "next" node. (Nodes form a - // singly-linked list). - uint32_t m_cchName : 24; - JsonType m_type : 8; -}; - -/* -Each value in a JsonBuilder is represented as a JsonValue. Each JsonValue -stores: - -- Type - a value from the JsonType enumeration, or a custom type. -- Name - utf-16 string, up to 16M characters. -- Data - binary blob, up to 3GB. - -Note that Object, Array, and hidden values do not contain data. -- It is an error to use a non-zero value for cbData when creating an -array/object. -- It is an error to call the Data() method on an array/object/hidden value. -*/ -class JsonValue : private JsonValueBase -{ - friend class JsonBuilder; - typedef unsigned StoragePod; - - union - { - uint32_t m_cbData; // Normal node only (not present for sentinel, - // array, or object) - Index m_lastChildIndex; // Array/Object node only (not present for - // sentinel or normal) - }; - - /* - Implementation details: - - The JsonBuilder stores a vector, which is just storage for - raw data. StoragePod could just be a byte, but storing at a larger - granularity gives benefits, especially in terms of data alignment. At - present StoragePod is uint32, so data is 32-bit aligned. This gives - perfect alignment for everything except float64 and int64, at a price of - up to 5 bytes per value wasted as padding (up to 2 bytes of padding after - the name, and up to 3 bytes of padding after the data). Within the vector, - information is arranged into nodes. The location within the vector where - the node begins is used as the node's index, e.g. - JsonValue& node = reinterpret_cast(storageVector[index]); - - Nodes are arranged into a tree. Only Array and Object nodes can be - interior nodes of the tree. All other nodes are leaf nodes. When an array - or object node is created, its first child is also created immediately, - directly following the Array or Object node in the storage vector. The - first child is always a hidden sentinel node. Since the first child always - immediately follows its parent array or object node, we can easily compute - the index of the first child as: - - parent.FirstChildIndex = parent.Index + DATA_OFFSET(parent.cchName). - - Since sentinel nodes are always hidden and never have data, we omit the - m_cbData member, saving 4 bytes per array/object. - - Since Array and Object nodes never have data, we reuse the m_cbData field - to store the index of the last child. If FirstChildIndex == LastChildIndex, - the parent has no visible children (its only child is the hidden sentinel). - - Nodes are arranged into a singly-linked list via the m_nextIndex member. - The end of the list is indicated by m_nextIndex == 0. All children of the - same parent are together in the list, so iteration from Node.FirstChild to - Node.LastChild covers all of the children of Node. - - If it exists, the node for the root object is stored at index 0, and its - associated hidden first child is always at index 3 (root.cchName == 0, so - DATA_OFFSET always returns 3 for root). Note that the root object is - created lazily, so it is possible for the root object to not actually be - stored (i.e. storageVector.size() might be 0). The root object is created - the first time any child node is created. The root object serves as both - the head and the tail of the singly-linked list of nodes. - - Nodes are erased by marking them as hidden. They are not removed from the - storage vector and are not removed from the linked list. (Changing this - would require several other changes such as adding a parentIndex and a - prevIndex to each node.) Hidden nodes are skipped during iterator - traversal. As long as there are no erased nodes, all iterator operations - are essentially O(1). However, iteration must skip hidden/erased nodes, - so begin(), begin(itParent), end(itParent), and operator++ operations can - potentially become O(N) if there are a large number of erased or childless - nodes. - - Note that methods in this class must never return a hidden node -- they - must return the first non-hidden node after the node they might have - otherwise returned. Because the external interface abstracts these hidden - nodes away, external implementations of count() or find() must be slightly - sub-optimal (they always have to find end(itParent), which is the first - non-hidden node AFTER itParent's last child). Internal implementations of - count(), find(), splice(), etc. do take advantage of the ability to - reference hidden nodes to become slightly more efficient -- they stop - iteration AT itParent's last child (hidden or not) instead of stopping - AFTER itParent's last child. - - Normal node format: - 0: m_nextIndex (4 bytes) - 4: m_cchName (3 bytes) - 7: m_type (1 byte) - 8: m_cbData (4 bytes) - 12: Name (cchName * 2 bytes) - xx: Padding (to a multiple of StoragePod size) - xx: Data (cbValue bytes) - xx: Padding (to a multiple of StoragePod size) - - Composite node format (array or object): - 0: m_nextIndex (4 bytes) - 4: m_cchName (3 bytes) - 7: m_type (1 byte) // JsonObject, JsonArray. - 8: m_lastChildIndex (4 bytes) - 12: Name (cchName * 2 bytes) - xx: Padding (to a multiple of StoragePod size) - xx: FirstChild (8 bytes) - a sentinel node. - - Hidden/sentinel node format: - 0: m_nextIndex (4 bytes) - 4: m_cchName (3 bytes) - 7: m_type (1 byte) // JsonHidden. - */ - - public: - JsonValue(JsonValue const&) = delete; - void operator=(JsonValue const&) = delete; - - /* - Gets the type of the value. - */ - JsonType Type() const throw(); - - /* - Gets the name of the value. - */ - std::string_view Name() const throw(); - - /* - Gets the size of the data of the value, in bytes. - Note that hidden, object, and array values do not have data, and it is an - error to call DataSize() on a value where Type() is hidden, object, or - array. - */ - unsigned DataSize() const throw(); - - /* - Reduces the size recorded for the data. - Does not change the size of the underlying buffer. - Requires: cbNew <= DataSize(). - */ - void ReduceDataSize(unsigned cbNew) throw(); - - /* - Gets a pointer to the data of the value. - Note that hidden, object, and array values do not have data, and it is an - error to call Data() on a value where Type() is hidden, object, or array. - */ - void const* Data(unsigned* pcbData = nullptr) const throw(); - - /* - Gets a pointer to the data of the value. The data can be modified, though - the length can only be reduced, never increased. - Note that hidden, object, and array values do not have data, and it is an - error to call Data() on a value where Type() is hidden, object, or array. - */ - void* Data(unsigned* pcbData = nullptr) throw(); - - /* - Returns true if Type==Null. - */ - bool IsNull() const throw(); - - /* - Returns the value's data as a T. - Requires: this->Type() is an exact match for type T. (Checked via assert.) - Requires: this->DataSize() is a valid size for this->Type(). (Checked by - assert, returns 0 in retail if size is invalid.) - - - This method asserts that this->Type() is an exact match for T. For - example, T=int matches JsonInt but does not match JsonUInt. - - This method asserts that this->DataSize() makes sense for the target type. - For example, T=int means DataSize() must be 1, 2, 4, or 8. - - This method behaves much like a reinterpret_cast, but it takes the value's - actual data size into account. For example, if T=int and DataSize() is 1, - this method returns *reinterpret_cast(Data()). - - If running in retail and data size does not make sense for the target - type, this method returns a default value of T (typically 0). For example, - if T=int and DataSize() is 3, this method will assert and then return 0. - - T must be a supported type. Supported types include: - - - For boolean data: bool. - - For string data: utl::string_view. - - For integer data: signed and unsigned char, short, int, long, long long. - - For float data: float, double, long double. - - Any user-defined type for which JsonImplementType::GetUnchecked exists. - - Detailed semantics: - assert(this->Type() is an exact match for typeof(T)); - If DataSize() is a legal size for T, return the stored data as a T; - Else assert(false), return a default value of T (e.g. 0); - */ - template - auto GetUnchecked() const throw() - -> decltype(JsonImplementType::type>::GetUnchecked( - *(JsonValue const*) 0)) - { - return JsonImplementType::type>::GetUnchecked( - *this); - } - - /* - Attempts to convert the data to T. - If the data can be converted, sets result to the data and returns true. - Else sets result to a default value (e.g. 0) and returns false. - - For integer and float conversions, performs a range check. If source value - is out of the target type's range, returns false. - - T must be a supported type. Supported types include: - - - For boolean data: bool. - - For string data: utl::string_view. - - For integer data: signed and unsigned char, short, int, long, long long. - - For float data: float, double, long double. - - Any user-defined type for which JsonImplementType::ConvertTo exists. - */ - template< - class T, - class ConvertToDoesNotSupportThisType = - decltype(JsonImplementType::type>::ConvertTo( - *(JsonValue const*) 0, - *(T*) 0))> - bool ConvertTo(T& result) const throw() - { - return JsonImplementType::type>::ConvertTo( - *this, result); - } -}; - -// JsonIterator - -/* -Implementation of JsonBuilder::const_iterator. -*/ -class JsonConstIterator -{ - friend class JsonBuilder; // JsonBuilder needs to construct const_iterators. - typedef uint32_t Index; - - JsonBuilder const* m_pContainer; - Index m_index; - - JsonConstIterator(JsonBuilder const* pContainer, Index index) throw(); - - public: - typedef std::forward_iterator_tag iterator_category; - typedef JsonValue value_type; - typedef std::ptrdiff_t difference_type; - typedef JsonValue const* pointer; - typedef JsonValue const& reference; - - JsonConstIterator() throw(); - - bool operator==(JsonConstIterator const&) const throw(); - bool operator!=(JsonConstIterator const&) const throw(); - - reference operator*() const throw(); - pointer operator->() const throw(); - - JsonConstIterator& operator++() throw(); - JsonConstIterator operator++(int) throw(); - - /* - For iterating over this value's children. - iterator.begin() is equivalent to builder.begin(iterator). - O(1), unless hidden nodes need to be skipped. - */ - JsonConstIterator begin() const throw(); - - /* - For iterating over this value's children. - iterator.end() is equivalent to builder.end(iterator). - O(1), unless hidden nodes need to be skipped. - */ - JsonConstIterator end() const throw(); - - /* - Returns true if this iterator refers to the root object of a JsonBuilder. - Note that it is an error to dereference or ++ an iterator that references - the root object (e.g. it is an error to do jsonBuilder.end()++). - */ - bool IsRoot() const throw(); -}; - -/* -Implementation of JsonBuilder::iterator. -*/ -class JsonIterator : public JsonConstIterator -{ - friend class JsonBuilder; // JsonBuilder needs to construct iterators. - - /* - A JsonIterator is created by creating a JsonConstIterator and passing it - to the constructor of a JsonIterator. This avoids the need for - JsonConstIterator to declare JsonIterator as a friend. - */ - explicit JsonIterator(JsonConstIterator const&) throw(); - - public: - typedef JsonValue& reference; - typedef JsonValue* pointer; - - JsonIterator() throw(); - - reference operator*() const throw(); - pointer operator->() const throw(); - - JsonIterator& operator++() throw(); - JsonIterator operator++(int) throw(); - - /* - For iterating over this value's children. - iterator.begin() is equivalent to builder.begin(iterator). - O(1), unless hidden nodes need to be skipped. - */ - JsonIterator begin() const throw(); - - /* - For iterating over this value's children. - iterator.end() is equivalent to builder.end(iterator). - O(1), unless hidden nodes need to be skipped. - */ - JsonIterator end() const throw(); -}; - -// JsonBuilder - -/* -Stores data in a logical tree structure. - -The tree stores values of various types. -Object values store no data but contain any number of named child values. -Array values store no data but contain any number of unnamed child values. -All other values are leaf nodes in the tree and can store arbitrary data. - -The root of the tree is an Object. This object is implicit -- it is always -present and need not be added by the user. In methods that accept a "parent -iterator" parameter, use the root() iterator to refer to the root of the tree. -*/ -class JsonBuilder -{ - friend class JsonConstIterator; - typedef JsonValue::Index Index; - typedef JsonInternal::PodVector StorageVec; - StorageVec m_storage; - - class Validator : private JsonInternal::PodVectorBase - { - // 2-bit value. - enum ValidationState : char unsigned - { - ValNone = 0, - ValTail = 1, - ValHead = 2, - ValReached = 3, - ValMax - }; - - JsonValue::StoragePod const* const m_pStorage; - size_type const m_size; - char unsigned* const m_pMap; // 2 bits per StoragePod in m_pStorage. - - public: - ~Validator(); - - explicit Validator( - JsonValue::StoragePod const* pStorage, - size_type cStorage); // may throw bad_alloc - - void Validate(); // may throw invalid_argument - - private: - void ValidateRecurse(Index index); - - void - UpdateMap(Index index, ValidationState expectedVal, ValidationState newVal); - }; - - public: - typedef JsonValue value_type; - typedef JsonValue* pointer; - typedef JsonValue const* const_pointer; - typedef size_t size_type; - typedef int difference_type; - typedef JsonIterator iterator; - typedef JsonConstIterator const_iterator; - - /* - Initializes a new instance of the JsonBuilder class. - */ - JsonBuilder() throw(); - - /* - Initializes a new instance of the JsonBuilder class using at least the - specified initial capacity (in bytes). - */ - explicit JsonBuilder(size_type cbInitialCapacity); // may throw bad_alloc, - // length_error - - /* - Initializes a new instance of the JsonBuilder class, copying its data from - other. - */ - JsonBuilder(JsonBuilder const& other); // may throw bad_alloc - - /* - Initializes a new instance of the JsonBuilder class, moving the data from - other. - NOTE: Invalidates all iterators pointing into other. - */ - JsonBuilder(JsonBuilder&& other) throw(); - - /* - Initializes a new instance of the JsonBuilder class, copying its data from - a memory buffer. Optionally runs ValidateData (i.e. for untrusted input). - */ - JsonBuilder( - void const* pbRawData, - size_type cbRawData, - bool validateData = true); // may throw bad_alloc, length_error, - // invalid_argument - - /* - Copies data from other. - */ - JsonBuilder& operator=(JsonBuilder const& other); // may throw bad_alloc - - /* - Moves the data from other. - NOTE: Invalidates all iterators pointing into other. - */ - JsonBuilder& operator=(JsonBuilder&& other) throw(); - - /* - Throws an exception if the data in this JsonBuilder is corrupt. - Mainly for use in debugging, but this can also be used when feeding - untrusted data to JsonBuilder. - */ - void ValidateData() const; // May throw bad_alloc, invalid_argument. - - iterator begin() throw(); - const_iterator begin() const throw(); - const_iterator cbegin() const throw(); - - iterator end() throw(); - const_iterator end() const throw(); - const_iterator cend() const throw(); - - iterator root() throw(); - const_iterator root() const throw(); - const_iterator croot() const throw(); - - /* - Returns a pointer to the first element in the backing raw data vector. - */ - void const* buffer_data() const throw(); - - /* - Returns the size (in bytes) of the memory currently being used by this - JsonBuilder (i.e. vector.size() of the underlying storage vector). This - value is primarily intended to help in determining the appropriate value - for the initialCapacity constructor parameter. This is also the size of - the data returned by buffer_data(). - */ - size_type buffer_size() const throw(); - - /* - Returns the size (in bytes) of the memory currently allocated by this - JsonBuilder (i.e. vector.capacity() of the underlying storage vector). - This value is primarily intended to help in determining the appropriate - value for the initialCapacity constructor parameter. - */ - size_type buffer_capacity() const throw(); - - /* - Returns the maximum size (in bytes) of the memory that could be passed - to buffer_reserve or returned from buffer_size. - On 32-bit systems, this is currently slightly less than 4GB. - On 64-bit systems, this is currently slightly less than 16GB. - */ - static constexpr size_type buffer_max_size() throw() - { - return StorageVec::max_size() * sizeof(JsonValue::StoragePod); - } - - /* - If less than the specified amount of memory is currently allocated by this - JsonBuilder, allocates additional memory so that at least the specified - amount is allocated. - */ - void buffer_reserve(size_type cbMinimumCapacity); // may throw bad_alloc, - // length_error - - /* - Removes all data from this JsonBuilder and prepares it for reuse. - Keeps the currently-allocated buffer. - O(1). - */ - void clear() throw(); - - /* - Marks the specified value as Erased. Equivalent to erase(itValue, - itValue+1). Requires: itValue+1 is valid (i.e. requires that itValue != - end()). Returns: itValue+1. Implementation detail: Erased values have their - Type() changed to Hidden, and will be skipped during iteration, but they - continue to take up space in the tree. O(1), unless there are erased values - that have to be skipped to find itValue+1. - */ - iterator erase(const_iterator itValue) throw(); - - /* - Marks the specified range as Erased. - Requires: itBegin <= itEnd (i.e. repeated itBegin++ will reach itEnd). - Returns: itEnd. - Implementation detail: Erased values have their Type() changed to Hidden, - and will be skipped during iteration, but they continue to take up space - in the tree. - O(n), where n = the number of values in the range itBegin..itEnd. - */ - iterator erase(const_iterator itBegin, const_iterator itEnd) throw(); - - /* - Replaces the contents of this with the contents of other. - NOTE: Invalidates all iterators pointing into this and other. - O(1). - */ - void swap(JsonBuilder& other) throw(); - - /* - Causes all future memory allocations by this JsonBuilder to be initialized - to zero. - */ - void EnableZeroInitializeMemory() - { - m_storage.EnableZeroInitializeMemory(); - } - - /* - Navigates to a child value, starting at the root. For each name provided, - navigates to the first child with the specified name. - Each name must be a string_view or implicitly-convertible to string_view. - Returns the first match, or end() if there are no matches. - O(n), where n is the total number of children at each level. - */ - template - iterator - find(std::string_view const& firstName, NameTys const&... additionalNames) throw() - { - return iterator( - const_iterator(this, Find(0, firstName, additionalNames...))); - } - - /* - Navigates to a child value, starting at the root. For each name provided, - navigates to the first child with the specified name. - Each name must be a string_view or implicitly-convertible to string_view. - Returns the first match, or end() if there are no matches. - O(n), where n is the total number of children at each level. - */ - template - const_iterator - find(std::string_view const& firstName, NameTys const&... additionalNames) const - throw() - { - return const_iterator(this, Find(0, firstName, additionalNames...)); - } - - /* - Navigates to a child value, starting at itParent. For each name provided, - navigates to the first child with the specified name. - Each name must be a string_view or implicitly-convertible to string_view. - Returns the first match, or end() if there are no matches. - O(n), where n is the total number of children at each level. - */ - template - iterator find( - const_iterator const& itParent, - std::string_view const& firstName, - NameTys const&... additionalNames) throw() - { - ValidateIterator(itParent); - return iterator(const_iterator( - this, Find(itParent.m_index, firstName, additionalNames...))); - } - - /* - Navigates to a child value, starting at itParent. For each name provided, - navigates to the first child with the specified name. - Each name must be a string_view or implicitly-convertible to string_view. - Returns the first match, or end() if there are no matches. - O(n), where n is the total number of children at each level. - */ - template - const_iterator find( - const_iterator const& itParent, - std::string_view const& firstName, - NameTys const&... additionalNames) const throw() - { - ValidateIterator(itParent); - return const_iterator( - this, Find(itParent.m_index, firstName, additionalNames...)); - } - - /* - Returns the number of children of itParent. - O(n), where n is the number of children of itParent. - */ - unsigned count(const_iterator const& itParent) const throw(); - - /* - Returns the first child of itParent. - If itParent has no children, returns end(itParent). - O(1), unless there are erased values that have to be skipped. - */ - iterator begin(const_iterator const& itParent) throw(); - - /* - Returns the first child of itParent. - If itParent has no children, returns end(itParent). - O(1), unless there are erased values that have to be skipped. - */ - const_iterator begin(const_iterator const& itParent) const throw(); - - /* - Returns the first child of itParent. - If itParent has no children, returns cend(itParent). - O(1), unless there are erased values that have to be skipped. - */ - const_iterator cbegin(const_iterator const& itParent) const throw(); - - /* - Returns the iterator after the last child of itParent. - O(1), unless there are erased values that have to be skipped. - */ - iterator end(const_iterator const& itParent) throw(); - - /* - Returns the iterator after the last child of itParent. - O(1), unless there are erased values that have to be skipped. - */ - const_iterator end(const_iterator const& itParent) const throw(); - - /* - Returns the iterator after the last child of itParent. - O(1), unless there are erased value that have to be skipped. - */ - const_iterator cend(const_iterator const& itParent) const throw(); - - /* - Removes all children from itOldParent. - Re-inserts them as the first children of itNewParent. - Requires: itNewParent must reference an array or an object value. - O(n), where n is the number of children of itOldParent. - */ - void splice_front( - const_iterator const& itOldParent, - const_iterator const& itNewParent) throw() - { - Splice(true, itOldParent, itNewParent, PredicateTrue()); - } - - /* - Removes all children from itOldParent. - Re-inserts them as the last children of itNewParent. - Requires: itNewParent must reference an array or an object value. - O(n), where n is the number of children of itOldParent. - */ - void splice_back( - const_iterator const& itOldParent, - const_iterator const& itNewParent) throw() - { - Splice(false, itOldParent, itNewParent, PredicateTrue()); - } - - /* - Removes all children from itOldParent where pred(itChild) returns true. - Re-inserts them as the first children of itNewParent. - Requires: itNewParent must reference an array or an object value. - O(n), where n is the number of children of itOldParent. - */ - template - void splice_front( - const_iterator const& itOldParent, - const_iterator const& itNewParent, - PredTy&& pred) throw() - { - Splice(true, itOldParent, itNewParent, std::forward(pred)); - } - - /* - Removes all children from itOldParent where pred(itChild) returns true. - Re-inserts them as the last children of itNewParent. - Requires: itNewParent must reference an array or an object value. - O(n), where n is the number of children of itOldParent. - */ - template - void splice_back( - const_iterator const& itOldParent, - const_iterator const& itNewParent, - PredTy&& pred) throw() - { - Splice(false, itOldParent, itNewParent, std::forward(pred)); - } - - /* - Creates a new value. Inserts it as the first (if front is true) or last - (if front is false) child of itParent. - If pbData is null, the new value's payload is uninitialized. - Requires: itParent must reference an array or an object value. - Requires: if type is Array or Object, cbValue must be 0 and pbValue must be - null. Returns: an iterator that references the new value. O(1). - */ - iterator AddValue( - bool front, - const_iterator const& itParent, - std::string_view const& name, - JsonType type, - unsigned cbData = 0, - void const* pbData = nullptr); // may throw bad_alloc, length_error - - /* - Creates a new value. Inserts it as the first child of itParent. - If pbData is null, the new value's payload is uninitialized. - Requires: itParent must reference an array or an object value. - Requires: if type is Array or Object, cbValue must be 0 and pbValue must be - null. Returns: an iterator that references the new value. O(1). - */ - iterator push_front( - const_iterator const& itParent, - std::string_view const& name, - JsonType type, - unsigned cbData = 0, - void const* pbData = nullptr) // may throw bad_alloc, length_error - { - return AddValue(true, itParent, name, type, cbData, pbData); - } - - /* - Creates a new value. Inserts it as the first child of itParent. - If pbData is null, the new value's payload is uninitialized. - Requires: itParent must reference an array or an object value. - Requires: if type is Array or Object, cbValue must be 0 and pbValue must be - null. Returns: an iterator that references the new value. O(1). - */ - iterator push_back( - const_iterator const& itParent, - std::string_view const& name, - JsonType type, - unsigned cbData = 0, - void const* pbData = nullptr) // may throw bad_alloc, length_error - { - return AddValue(false, itParent, name, type, cbData, pbData); - } - - /* - Creates a new value. Inserts it as the first (if front is true) or last - (if front is false) child of itParent. - - Data must be a supported type. Supported types include: - - - For boolean data: bool. - - For string data: utl::string_view, wchar_t*, __wchar_t. - - For integer data: signed and unsigned char, short, int, long, long long. - - For float data: float, double, long double. - - For time data: FILETIME. - - For UUID data: GUID. - - Any user-defined type for which JsonImplementType::AddValue exists. - - We specifically do not support char because the intent is ambiguous. - - - Convert a char to __wchar_t, signed char, or unsigned char, as - appropriate. - - Convert a char* to wchar_t* (or use JsonPushBackMbcs). - - Requires: itParent must reference an array or an object value. - Returns: an iterator that references the new value. - O(1). - */ - template< - class T, - class EnableIfType = - decltype(JsonImplementType::type>::AddValue)> - iterator AddValue( - bool front, - const_iterator const& itParent, - std::string_view const& name, - T const& data) // may throw bad_alloc, length_error - { - return JsonImplementType::type>::AddValue( - *this, front, itParent, name, data); - } - - /* - Creates a new value. Inserts it as the first child of itParent. - - Data must be a supported type. Supported types include: - - - For boolean data: bool. - - For string data: utl::string_view, wchar_t*, __wchar_t. - - For integer data: signed and unsigned char, short, int, long, long long. - - For float data: float, double, long double. - - For time data: FILETIME. - - For UUID data: GUID. - - Any user-defined type for which JsonImplementType::AddValue exists. - - We specifically do not support char because the intent is ambiguous. - - - Convert a char to __wchar_t, signed char, or unsigned char, as - appropriate. - - Convert a char* to wchar_t* (or use JsonPushBackMbcs). - - Requires: itParent must reference an array or an object value. - Returns: an iterator that references the new value. - O(1). - */ - template< - class T, - class EnableIfType = - decltype(JsonImplementType::type>::AddValue)> - iterator push_front( - const_iterator const& itParent, - std::string_view const& name, - T const& data) // may throw bad_alloc, length_error - { - return JsonImplementType::type>::AddValue( - *this, true, itParent, name, data); - } - - /* - Creates a new value. Inserts it as the last child of itParent. - - Data must be a supported type. Supported types include: - - - For boolean data: bool. - - For string data: utl::string_view, wchar_t*, __wchar_t. - - For integer data: signed and unsigned char, short, int, long, long long. - - For float data: float, double, long double. - - For time data: FILETIME. - - For UUID data: GUID. - - Any user-defined type for which JsonImplementType::AddValue exists. - - We specifically do not support char because the intent is ambiguous. - - - Convert a char to __wchar_t, signed char, or unsigned char, as - appropriate. - - Convert a char* to wchar_t* (or use JsonPushBackMbcs). - - Requires: itParent must reference an array or an object value. - Returns: an iterator that references the new value. - O(1). - */ - template< - class T, - class EnableIfType = - decltype(JsonImplementType::type>::AddValue)> - iterator push_back( - const_iterator const& itParent, - std::string_view const& name, - T const& data) // may throw bad_alloc, length_error - { - return JsonImplementType::type>::AddValue( - *this, false, itParent, name, data); - } - - private: - static void AssertNotEnd(Index) throw(); - static void AssertHidden(JsonType) throw(); - void ValidateIterator(const_iterator const&) const throw(); - void ValidateParentIterator(Index) const throw(); // Note: assumes !empty() - bool CanIterateOver(const_iterator const&) const - throw(); // False if empty() or if iterator is not a parent. - JsonValue const& GetValue(Index) const throw(); - JsonValue& GetValue(Index) throw(); - Index FirstChild(Index) const throw(); // Given array/object index, return - // index of first child. - Index LastChild(Index) const throw(); // Given array/object index, return - // index of last child. - Index NextIndex(Index) const throw(); // Given index, return next - // non-hidden index. - void EnsureRootExists(); // If root object does not exist, create it. May - // throw bad_alloc. - - unsigned - FindImpl(Index parentIndex, std::string_view const& name) const throw(); - - /* - Adds an unlinked node to the storage vector. The caller must add the new - node to the list. Returns the index of the new value. - It is always ok for pbValue to be null. If so, the data will be - uninitialized. If type is JsonArray or JsonObject, cbData must be 0. - */ - Index CreateValue( - std::string_view const& name, - JsonType type, - unsigned cbData, - void const* pbData); // may throw bad_alloc, length_error - - unsigned Find(Index parentIndex) const throw() { return parentIndex; } - - template - unsigned Find( - Index parentIndex, - std::string_view const& firstName, - NameTys const&... additionalNames) const throw() - { - Index childIndex = FindImpl(parentIndex, firstName); - if (childIndex) - { - childIndex = Find(childIndex, additionalNames...); - } - return childIndex; - } - - struct PredicateTrue - { - bool operator()(const_iterator const&) const throw() { return true; } - }; - - template - void Splice( - bool front, - const_iterator const& itOldParent, - const_iterator const& itNewParent, - PredTy&& pred) throw() - { - ValidateIterator(itOldParent); - ValidateIterator(itNewParent); - - if (CanIterateOver(itOldParent)) - { - ValidateParentIterator(itNewParent.m_index); - - auto& oldParent = GetValue(itOldParent.m_index); - auto prevIndex = FirstChild(itOldParent.m_index); - auto const lastIndex = oldParent.m_lastChildIndex; - if (prevIndex != lastIndex) - { - // Make a linked list of the items that we're moving. - Index headIndex = 0; - Index* pTailIndex = &headIndex; // pTail points at the tail's - // m_nextIndex, which is used - // to store the tail's index. - - auto pPrev = &GetValue(prevIndex); - AssertNotEnd(prevIndex); - AssertHidden(pPrev->m_type); - for (;;) - { - auto currentIndex = pPrev->m_nextIndex; - auto& current = GetValue(currentIndex); - AssertNotEnd(currentIndex); // got to end() before we got - // to oldParent.LastChild - - if (current.m_type != JsonHidden && - pred(const_iterator(this, currentIndex))) - { - pPrev->m_nextIndex = current.m_nextIndex; - *pTailIndex = currentIndex; // Link current into list - // of moved items. - pTailIndex = ¤t.m_nextIndex; - *pTailIndex = currentIndex; // Store tail's index. - - if (currentIndex == lastIndex) - { - oldParent.m_lastChildIndex = prevIndex; - break; - } - } - else - { - if (currentIndex == lastIndex) - { - break; - } - - prevIndex = currentIndex; - pPrev = ¤t; - } - } - - if (headIndex != 0) - { - // Find the right place in the linked list for the moved - // nodes. Update the parent's lastChildIndex if necessary. - auto& newParent = GetValue(itNewParent.m_index); - if (front) - { - prevIndex = FirstChild(itNewParent.m_index); - if (prevIndex == newParent.m_lastChildIndex) - { - newParent.m_lastChildIndex = *pTailIndex; - } - } - else - { - prevIndex = newParent.m_lastChildIndex; - newParent.m_lastChildIndex = *pTailIndex; - } - - // Insert the moved nodes into the linked list after prev. - pPrev = &GetValue(prevIndex); - *pTailIndex = pPrev->m_nextIndex; - pPrev->m_nextIndex = headIndex; - } - } - } - } -}; - -/* -Exchanges the contents of two JsonBuilder objects. -*/ -void swap(JsonBuilder&, JsonBuilder&) throw(); - -// JsonImplementType - -/* -The JsonImplementType class is used to implement the following methods: - -- jsonBuilder.AddValue(..., data) -- jsonBuilder.push_front(..., data) -- jsonBuilder.push_back(..., data) -- jsonValue.GetUnchecked() -- jsonValue.ConvertTo(result) - -These methods will only work if a specialization of JsonImplementType -has been defined. (More specifically, there must be a specialization of -JsonImplementType>.) The JsonBuilder.h header provides -specializations for core built-in types. If you want these methods to support -other types, you can define specializations of the JsonImplementType class for -them. -*/ -template -class JsonImplementType -{ - // Default - no special support for T. - - /* - Specializations of JsonImplementType may implement any or all of the - following: - - static T GetUnchecked( // return value may be "T" or "const T&" at your - discretion. JsonValue const& value); - - static bool ConvertTo( - JsonValue const& value, - T& result); - - static JsonIterator AddValue( - JsonBuilder& builder, - bool front, - JsonConstIterator const& itParent, - utl::string_view const& name, - T data); // data parameter may be "T" or "const T&" at your discretion. - - The specialization may leave the method undefined if the given operation - should not be supported. If the specialization does provide an - implementation for a method, it should conform to the semantics of the - corresponding methods of the JsonValue or JsonBuilder class. - */ -}; - -#define JSON_DECLARE_JsonImplementType(T, getRef) \ - template<> \ - class JsonImplementType \ - { \ - public: \ - static T getRef GetUnchecked(JsonValue const& value) throw(); \ - static bool ConvertTo(JsonValue const& value, T& result) throw(); \ - static JsonIterator AddValue( \ - JsonBuilder& builder, \ - bool front, \ - JsonConstIterator const& itParent, \ - std::string_view const& name, \ - T const& data); \ - } - -#define JSON_DECLARE_JsonImplementType_AddValue(T) \ - template<> \ - class JsonImplementType \ - { \ - public: \ - static JsonIterator AddValue( \ - JsonBuilder& builder, \ - bool front, \ - JsonConstIterator const& itParent, \ - std::string_view const& name, \ - T const& data); \ - } - -JSON_DECLARE_JsonImplementType(bool, ); - -JSON_DECLARE_JsonImplementType(unsigned char, ); -JSON_DECLARE_JsonImplementType(unsigned short, ); -JSON_DECLARE_JsonImplementType(unsigned int, ); -JSON_DECLARE_JsonImplementType(unsigned long, ); -JSON_DECLARE_JsonImplementType(unsigned long long, ); - -JSON_DECLARE_JsonImplementType(signed char, ); -JSON_DECLARE_JsonImplementType(signed short, ); -JSON_DECLARE_JsonImplementType(signed int, ); -JSON_DECLARE_JsonImplementType(signed long, ); -JSON_DECLARE_JsonImplementType(signed long long, ); - -JSON_DECLARE_JsonImplementType(float, ); -JSON_DECLARE_JsonImplementType(double, ); - -JSON_DECLARE_JsonImplementType(std::chrono::system_clock::time_point, ); -JSON_DECLARE_JsonImplementType(UuidStruct, ); -JSON_DECLARE_JsonImplementType(std::string_view, ); -JSON_DECLARE_JsonImplementType_AddValue(std::string); -JSON_DECLARE_JsonImplementType_AddValue(char); - -template<> -class JsonImplementType -{ - public: - static JsonIterator AddValue( - JsonBuilder& builder, - bool front, - JsonConstIterator const& itParent, - std::string_view const& name, - char const* psz); -}; - -template<> -class JsonImplementType : public JsonImplementType -{}; - -} // namespace jsonbuilder +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +/* +JsonBuilder stores data values of arbitrary type in an optimized tree +structure (i.e. values may have children). The JsonBuilder type is suitable for +storing any kind of hierarchical data. It was originally designed as a data +structure for building up a payload that will be serialized into a JSON string, +though it can be used in other scenarios that require similar organization. + +Summary: + +- enum JsonType + Indicates the type of a value that is stored in a JsonBuilder. +- class JsonValue + Interface to a value that is stored in a JsonBuilder. +- class JsonBuilder + Object that stores a tree of values. +- class JsonImplementType + Traits type used to extend JsonBuilder to work with a user-defined type. + +JsonBuilder design decisions: + +- Follows STL container design conventions (e.g. begin() and end() methods). +- Optimized for building up, lightly-manipulating, and rendering payloads. +- Less-optimized for searching through payloads, i.e. items are not indexed. + For example, finding a value with a particular name means iterating through + all of the parent's children and checking each child for a matching name. +- Nodes in the tree are one of two types. Simple nodes contain typed values + (type tag plus binary data) but have no children. Complex nodes contain no + data but may have child nodes. +- Simple nodes support arbitrary binary payloads. Any type of data can be + stored with any type tag, and the JsonBuilder itself performs no validation + of the stored data (though it does provide built-in support for creating + or accessing the contents of nodes of certain built-in types). The + JsonRenderer class and the convenience methods have built-in support for data + stored as signed-integer (1, 2, 4, or 8 bytes), unsigned-integer (1, 2, 4, or + 8 bytes), floating-point (4 or 8 bytes), boolean (true/false), null, time + (FILETIME), UUID (GUID), and string (utf-16). The convenience methods and the + renderer can be extended if other types are needed. +- Complex nodes come in two types: Object and Array. The JsonBuilder itself + makes no distinction between these two types (they have identical behavior) + but it is intended that the Object type contain named values (i.e. it is a + dictionary with string keys) and that the Array type contain anonymous values + (i.e. it is a list). +- Value name limited to 16M utf-16 characters per value. +- Value data limited to 3GB per value. +- Names are stored as utf-16. +- Memory usage for object and array values is (in bytes): + 20 + sizeof(name) + padding to a multiple of 4. +- Memory usage for all other values is (in bytes): + 12 + sizeof(name) + sizeof(data) + padding to multiple of 4. +- Total storage limited to 16GB per JsonBuilder (or available VA space). + +Error handling: + + Throw std::exception variants +*/ + +#pragma once +#include // std::min +#include +#include +#include +#include // isfinite, sqrt +#include // std::invalid_argument +#include + +#include + +namespace jsonbuilder { +struct UuidStruct +{ + uuid_t Data; +}; + +// Forward declarations + +class JsonValue; +class JsonBuilder; +template +class JsonImplementType; + +// Internal implementation details + +namespace JsonInternal { +/* +PodVector: + +Very simple vector class. For POD types only. Has the following performance +advantages over std::vector: + +- This vector uses HeapReAlloc to grow its buffer. In cases where the + HeapReAlloc can grow in-place, this avoids the overhead of allocating a + new buffer, copying the data to the new buffer, and freeing the old + buffer. +- This vector does not initialize data on resize unless specifically requested. + +In benchmarks, using PodVector instead of VC2013's std::vector improves +JsonBuilder and JsonRenderer performance by about 10%. + +In addition, using our own vector avoids creating a hard dependency on STL, +which can make it easier for customers to consume this library. +*/ + +class PodVectorBase +{ + protected: + typedef unsigned size_type; + + /* + assert(index < currentSize) + */ + static void CheckOffset(size_type index, size_type currentSize) throw(); + + /* + assert(p1 <= p2 <= p3) + */ + static void + CheckRange(void const* p1, void const* p2, void const* p3) throw(); + + /* + return checked(a + b) + */ + static size_type CheckedAdd( + size_type a, + size_type b); // may throw length_error + + /* + memcpy(pDest, pSource, cb) + */ + static void InitData(void* pDest, void const* pSource, size_t cb) throw(); + + /* + Returns a value newCapacity such that minCapacity <= newCapacity <= + maxCapacity, chosen according to a vector growth policy. The current policy + is: + - If maxCapacity < minCapacity or maxCapacity < 15, assert. + - If minCapacity < 15, return 15. + - Let cap = the smallest value 2^N-1 that is greater than or equal to + minCapacity. + - If cap < maxCapacity, return cap. Else return maxCapacity. + */ + static size_type GetNewCapacity(size_type minCapacity, size_type maxCapacity); + + /* + Calls HeapAlloc or HeapReAlloc. If allocation fails, throw bad_alloc. + */ + static void* Reallocate( + void* pb, + size_t cb, + bool zeroInitializeMemory = false); // may throw bad_alloc, length_error + + /* + Calls HeapFree. + */ + static void Deallocate(void* pb) throw(); +}; + +template +class PodVector : private PodVectorBase +{ + static size_type const + m_maxSize = ~size_t(0) / sizeof(T) > size_type(~size_type(1)) ? + size_type(~size_type(1)) : + ~size_t(0) / sizeof(T); + static_assert( + m_maxSize < size_type(m_maxSize + 1), + "Bad calculation of m_maxSize (1)"); + static_assert( + m_maxSize * sizeof(T) / sizeof(T) == m_maxSize, + "Bad calculation of m_maxSize (2)"); + + T* m_data; + size_type m_size; + size_type m_capacity; + bool m_zeroInitializeMemory; + + public: + using PodVectorBase::size_type; + + ~PodVector() throw() { Deallocate(m_data); } + + PodVector() throw() + : m_data(nullptr), m_size(0), m_capacity(0), m_zeroInitializeMemory(false) + { + return; + } + + PodVector(PodVector&& other) throw() + : m_data(other.m_data) + , m_size(other.m_size) + , m_capacity(other.m_capacity) + , m_zeroInitializeMemory(other.m_zeroInitializeMemory) + { + other.m_data = nullptr; + other.m_size = 0; + other.m_capacity = 0; + other.m_zeroInitializeMemory = false; + } + + PodVector(PodVector const& other) // may throw bad_alloc + : m_data(nullptr) + , m_size(other.m_size) + , m_capacity(other.m_size) + , m_zeroInitializeMemory(other.m_zeroInitializeMemory) + { + if (m_size != 0) + { + auto cb = m_size * sizeof(T); + m_data = + static_cast(Reallocate(nullptr, cb, m_zeroInitializeMemory)); + InitData(m_data, other.m_data, cb); + } + } + + PodVector( + T const* data, + size_type size) // may throw bad_alloc + : m_data(nullptr) + , m_size(size) + , m_capacity(size) + , m_zeroInitializeMemory(false) + { + if (m_size != 0) + { + auto cb = m_size * sizeof(T); + m_data = + static_cast(Reallocate(nullptr, cb, m_zeroInitializeMemory)); + InitData(m_data, data, cb); + } + } + + PodVector& operator=(PodVector&& other) throw() + { + PodVector(static_cast(other)).swap(*this); + return *this; + } + + PodVector& operator=(PodVector const& other) // may throw bad_alloc + { + PodVector(other).swap(*this); + return *this; + } + + static constexpr size_type max_size() throw() { return m_maxSize; } + + size_type size() const throw() { return m_size; } + + bool empty() const throw() { return m_size == 0; } + + size_type capacity() const throw() { return m_capacity; } + + T const* data() const throw() { return m_data; } + + T* data() throw() { return m_data; } + + T const& operator[](unsigned i) const throw() + { + CheckOffset(i, m_size); + return m_data[i]; + } + + T& operator[](unsigned i) throw() + { + CheckOffset(i, m_size); + return m_data[i]; + } + + void clear() { m_size = 0; } + + void push_back(T const& val) // may throw bad_alloc, length_error + { + if (m_size == m_capacity) + { + Grow(); + } + m_data[m_size++] = val; + } + + void append( + T const* pItems, + size_type cItems) // may throw bad_alloc, length_error + { + if (cItems > m_capacity - m_size) + { + GrowBy(cItems); + } + + InitData(m_data + m_size, pItems, cItems * sizeof(T)); + m_size += cItems; + } + + void append( + size_type cCopies, + T const& val) // may throw bad_alloc, length_error + { + if (cCopies > m_capacity - m_size) + { + GrowBy(cCopies); + } + + auto p = m_data + m_size; + for (size_type i = 0; i != cCopies; i += 1) + { + p[i] = val; + } + + m_size += cCopies; + } + + void reserve(size_type minCapacity) // may throw bad_alloc, length_error + { + if (m_capacity < minCapacity) + { + GrowTo(minCapacity); + } + } + + /* + NOTE: new items are uninitialized, unless m_zeroInitializeMemory is set + */ + void resize(size_type newSize) // may throw bad_alloc, length_error + { + reserve(newSize); + m_size = newSize; + } + + void swap(PodVector& other) throw() + { + auto const d = m_data; + m_data = other.m_data; + other.m_data = d; + + auto const s = m_size; + m_size = other.m_size; + other.m_size = s; + + auto const c = m_capacity; + m_capacity = other.m_capacity; + other.m_capacity = c; + + auto const z = m_zeroInitializeMemory; + m_zeroInitializeMemory = other.m_zeroInitializeMemory; + other.m_zeroInitializeMemory = z; + } + + void EnableZeroInitializeMemory() { m_zeroInitializeMemory = true; } + + /* + Ensures capacity for at least cItems additional elements, then returns + a pointer to the current end of the vector. Caller can write to this + pointer, then call SetEndPointer to include the newly-written data in + the vector. + + auto p = v.GetAppendPointer(10); + p[0] = ...; // p has room for 10 items. + v.SetEndPointer(p + 10); // Include the newly-written items in vector. + */ + T* GetAppendPointer(unsigned cItems) // may throw bad_alloc, length_error + { + if (cItems > m_capacity - m_size) + { + GrowBy(cItems); + } + return m_data + m_size; + } + + void SetEndPointer(T* pNewEnd) throw() + { + CheckRange(m_data, pNewEnd, m_data + m_capacity); + m_size = static_cast(pNewEnd - m_data); + } + + private: + void Grow() // may throw bad_alloc, length_error + { + GrowTo(m_capacity + 1); + } + + void GrowBy(size_type cItems) // may throw bad_alloc, length_error + { + GrowTo(CheckedAdd(m_size, cItems)); + } + + void GrowTo(size_type minCapacity) // may throw bad_alloc, length_error + { + size_type const newCapacity = GetNewCapacity(minCapacity, m_maxSize); + m_data = static_cast(Reallocate( + m_data, newCapacity * sizeof(T), m_zeroInitializeMemory)); + m_capacity = newCapacity; + } +}; +} // namespace JsonInternal +// namespace JsonInternal + +// JsonType + +/* +The built-in types supported by JsonBuilder and JsonRenderer. +Note that type values larger than 255 will not work correctly. +Note that JsonBuilder itself is actually only aware of types JsonHidden, +JsonArray and JsonObject. All of the other types are ignored by JsonBuilder. +There is no enforcement of any rules such as "names are not allowed on array +elements" or "names must be unique within an object". The JsonBuilder just +stores the names and data as binary blobs. + +Additional simple types can be used with JsonBuilder as necessary. (It is not +possible to add new complex types, i.e. user-defined types cannot have child +nodes.) To add new types in your own project: + +- Define JsonType constants for your types. Start numbering at 1. For example: + const JsonType JsonMyStuff = static_cast(1); +- Define a class that is derived from JsonRenderer. Override the RenderCustom + method to render values of your types. Use your derived class instead of + using JsonRenderer. +- If you want jsonBuilder.push_back(..., data), jsonValue.GetUnchecked(), + and jsonValue.ConvertTo to work for your types, + define specializations of JsonImplementType for each of your types. +*/ +enum JsonType : uint8_t +{ + // Numbering for custom types should start at 1. Custom types never have + // children. Numbering for custom types must not exceed 200. + JsonTypeReserved = 201, + JsonTypeBuiltIn = 244, + JsonUtf8, // No children. Data = utf-8 string. + JsonUInt, // No children. Data = uint (1, 2, 4, or 8 bytes). + JsonInt, // No children. Data = int (1, 2, 4, or 8 bytes). + JsonFloat, // No children. Data = float (4 or 8 bytes). + JsonBool, // No children. Data = bool (1 or 4 bytes). + JsonTime, // No children. Data = uint64 (number of 100ns intervals since + // 1601-01-01T00:00:00Z, i.e. Win32 FILETIME). + JsonUuid, // No children. Data = UUID (16 bytes universally unique + // identifier, i.e. Win32 GUID). + JsonNull, // No children. Data = void (0 bytes). + JsonHidden, // An erased or sentinel value. No data. (Calling Data() on a + // hidden value is an error.) + JsonArray, // Anonymous children. No data. (Calling Data() on an array + // value is an error.) + JsonObject, // Named children. No data. (Calling Data() on an object value + // is an error.) +}; + +// JsonValue + +/* +For internal use only. +Used for hidden erased or sentinel nodes, which do not need m_cbData. +*/ +class JsonValueBase +{ + friend class JsonBuilder; + friend class JsonValue; + typedef uint32_t Index; + + Index m_nextIndex; // The index of the "next" node. (Nodes form a + // singly-linked list). + uint32_t m_cchName : 24; + JsonType m_type : 8; +}; + +/* +Each value in a JsonBuilder is represented as a JsonValue. Each JsonValue +stores: + +- Type - a value from the JsonType enumeration, or a custom type. +- Name - utf-16 string, up to 16M characters. +- Data - binary blob, up to 3GB. + +Note that Object, Array, and hidden values do not contain data. +- It is an error to use a non-zero value for cbData when creating an +array/object. +- It is an error to call the Data() method on an array/object/hidden value. +*/ +class JsonValue : private JsonValueBase +{ + friend class JsonBuilder; + typedef unsigned StoragePod; + + union + { + uint32_t m_cbData; // Normal node only (not present for sentinel, + // array, or object) + Index m_lastChildIndex; // Array/Object node only (not present for + // sentinel or normal) + }; + + /* + Implementation details: + + The JsonBuilder stores a vector, which is just storage for + raw data. StoragePod could just be a byte, but storing at a larger + granularity gives benefits, especially in terms of data alignment. At + present StoragePod is uint32, so data is 32-bit aligned. This gives + perfect alignment for everything except float64 and int64, at a price of + up to 5 bytes per value wasted as padding (up to 2 bytes of padding after + the name, and up to 3 bytes of padding after the data). Within the vector, + information is arranged into nodes. The location within the vector where + the node begins is used as the node's index, e.g. + JsonValue& node = reinterpret_cast(storageVector[index]); + + Nodes are arranged into a tree. Only Array and Object nodes can be + interior nodes of the tree. All other nodes are leaf nodes. When an array + or object node is created, its first child is also created immediately, + directly following the Array or Object node in the storage vector. The + first child is always a hidden sentinel node. Since the first child always + immediately follows its parent array or object node, we can easily compute + the index of the first child as: + + parent.FirstChildIndex = parent.Index + DATA_OFFSET(parent.cchName). + + Since sentinel nodes are always hidden and never have data, we omit the + m_cbData member, saving 4 bytes per array/object. + + Since Array and Object nodes never have data, we reuse the m_cbData field + to store the index of the last child. If FirstChildIndex == LastChildIndex, + the parent has no visible children (its only child is the hidden sentinel). + + Nodes are arranged into a singly-linked list via the m_nextIndex member. + The end of the list is indicated by m_nextIndex == 0. All children of the + same parent are together in the list, so iteration from Node.FirstChild to + Node.LastChild covers all of the children of Node. + + If it exists, the node for the root object is stored at index 0, and its + associated hidden first child is always at index 3 (root.cchName == 0, so + DATA_OFFSET always returns 3 for root). Note that the root object is + created lazily, so it is possible for the root object to not actually be + stored (i.e. storageVector.size() might be 0). The root object is created + the first time any child node is created. The root object serves as both + the head and the tail of the singly-linked list of nodes. + + Nodes are erased by marking them as hidden. They are not removed from the + storage vector and are not removed from the linked list. (Changing this + would require several other changes such as adding a parentIndex and a + prevIndex to each node.) Hidden nodes are skipped during iterator + traversal. As long as there are no erased nodes, all iterator operations + are essentially O(1). However, iteration must skip hidden/erased nodes, + so begin(), begin(itParent), end(itParent), and operator++ operations can + potentially become O(N) if there are a large number of erased or childless + nodes. + + Note that methods in this class must never return a hidden node -- they + must return the first non-hidden node after the node they might have + otherwise returned. Because the external interface abstracts these hidden + nodes away, external implementations of count() or find() must be slightly + sub-optimal (they always have to find end(itParent), which is the first + non-hidden node AFTER itParent's last child). Internal implementations of + count(), find(), splice(), etc. do take advantage of the ability to + reference hidden nodes to become slightly more efficient -- they stop + iteration AT itParent's last child (hidden or not) instead of stopping + AFTER itParent's last child. + + Normal node format: + 0: m_nextIndex (4 bytes) + 4: m_cchName (3 bytes) + 7: m_type (1 byte) + 8: m_cbData (4 bytes) + 12: Name (cchName * 2 bytes) + xx: Padding (to a multiple of StoragePod size) + xx: Data (cbValue bytes) + xx: Padding (to a multiple of StoragePod size) + + Composite node format (array or object): + 0: m_nextIndex (4 bytes) + 4: m_cchName (3 bytes) + 7: m_type (1 byte) // JsonObject, JsonArray. + 8: m_lastChildIndex (4 bytes) + 12: Name (cchName * 2 bytes) + xx: Padding (to a multiple of StoragePod size) + xx: FirstChild (8 bytes) - a sentinel node. + + Hidden/sentinel node format: + 0: m_nextIndex (4 bytes) + 4: m_cchName (3 bytes) + 7: m_type (1 byte) // JsonHidden. + */ + + public: + JsonValue(JsonValue const&) = delete; + void operator=(JsonValue const&) = delete; + + /* + Gets the type of the value. + */ + JsonType Type() const throw(); + + /* + Gets the name of the value. + */ + std::string_view Name() const throw(); + + /* + Gets the size of the data of the value, in bytes. + Note that hidden, object, and array values do not have data, and it is an + error to call DataSize() on a value where Type() is hidden, object, or + array. + */ + unsigned DataSize() const throw(); + + /* + Reduces the size recorded for the data. + Does not change the size of the underlying buffer. + Requires: cbNew <= DataSize(). + */ + void ReduceDataSize(unsigned cbNew) throw(); + + /* + Gets a pointer to the data of the value. + Note that hidden, object, and array values do not have data, and it is an + error to call Data() on a value where Type() is hidden, object, or array. + */ + void const* Data(unsigned* pcbData = nullptr) const throw(); + + /* + Gets a pointer to the data of the value. The data can be modified, though + the length can only be reduced, never increased. + Note that hidden, object, and array values do not have data, and it is an + error to call Data() on a value where Type() is hidden, object, or array. + */ + void* Data(unsigned* pcbData = nullptr) throw(); + + /* + Returns true if Type==Null. + */ + bool IsNull() const throw(); + + /* + Returns the value's data as a T. + Requires: this->Type() is an exact match for type T. (Checked via assert.) + Requires: this->DataSize() is a valid size for this->Type(). (Checked by + assert, returns 0 in retail if size is invalid.) + + - This method asserts that this->Type() is an exact match for T. For + example, T=int matches JsonInt but does not match JsonUInt. + - This method asserts that this->DataSize() makes sense for the target type. + For example, T=int means DataSize() must be 1, 2, 4, or 8. + - This method behaves much like a reinterpret_cast, but it takes the value's + actual data size into account. For example, if T=int and DataSize() is 1, + this method returns *reinterpret_cast(Data()). + - If running in retail and data size does not make sense for the target + type, this method returns a default value of T (typically 0). For example, + if T=int and DataSize() is 3, this method will assert and then return 0. + + T must be a supported type. Supported types include: + + - For boolean data: bool. + - For string data: utl::string_view. + - For integer data: signed and unsigned char, short, int, long, long long. + - For float data: float, double, long double. + - Any user-defined type for which JsonImplementType::GetUnchecked exists. + + Detailed semantics: + assert(this->Type() is an exact match for typeof(T)); + If DataSize() is a legal size for T, return the stored data as a T; + Else assert(false), return a default value of T (e.g. 0); + */ + template + auto GetUnchecked() const throw() + -> decltype(JsonImplementType::type>::GetUnchecked( + *(JsonValue const*) 0)) + { + return JsonImplementType::type>::GetUnchecked( + *this); + } + + /* + Attempts to convert the data to T. + If the data can be converted, sets result to the data and returns true. + Else sets result to a default value (e.g. 0) and returns false. + + For integer and float conversions, performs a range check. If source value + is out of the target type's range, returns false. + + T must be a supported type. Supported types include: + + - For boolean data: bool. + - For string data: utl::string_view. + - For integer data: signed and unsigned char, short, int, long, long long. + - For float data: float, double, long double. + - Any user-defined type for which JsonImplementType::ConvertTo exists. + */ + template< + class T, + class ConvertToDoesNotSupportThisType = + decltype(JsonImplementType::type>::ConvertTo( + *(JsonValue const*) 0, + *(T*) 0))> + bool ConvertTo(T& result) const throw() + { + return JsonImplementType::type>::ConvertTo( + *this, result); + } +}; + +// JsonIterator + +/* +Implementation of JsonBuilder::const_iterator. +*/ +class JsonConstIterator +{ + friend class JsonBuilder; // JsonBuilder needs to construct const_iterators. + typedef uint32_t Index; + + JsonBuilder const* m_pContainer; + Index m_index; + + JsonConstIterator(JsonBuilder const* pContainer, Index index) throw(); + + public: + typedef std::forward_iterator_tag iterator_category; + typedef JsonValue value_type; + typedef std::ptrdiff_t difference_type; + typedef JsonValue const* pointer; + typedef JsonValue const& reference; + + JsonConstIterator() throw(); + + bool operator==(JsonConstIterator const&) const throw(); + bool operator!=(JsonConstIterator const&) const throw(); + + reference operator*() const throw(); + pointer operator->() const throw(); + + JsonConstIterator& operator++() throw(); + JsonConstIterator operator++(int) throw(); + + /* + For iterating over this value's children. + iterator.begin() is equivalent to builder.begin(iterator). + O(1), unless hidden nodes need to be skipped. + */ + JsonConstIterator begin() const throw(); + + /* + For iterating over this value's children. + iterator.end() is equivalent to builder.end(iterator). + O(1), unless hidden nodes need to be skipped. + */ + JsonConstIterator end() const throw(); + + /* + Returns true if this iterator refers to the root object of a JsonBuilder. + Note that it is an error to dereference or ++ an iterator that references + the root object (e.g. it is an error to do jsonBuilder.end()++). + */ + bool IsRoot() const throw(); +}; + +/* +Implementation of JsonBuilder::iterator. +*/ +class JsonIterator : public JsonConstIterator +{ + friend class JsonBuilder; // JsonBuilder needs to construct iterators. + + /* + A JsonIterator is created by creating a JsonConstIterator and passing it + to the constructor of a JsonIterator. This avoids the need for + JsonConstIterator to declare JsonIterator as a friend. + */ + explicit JsonIterator(JsonConstIterator const&) throw(); + + public: + typedef JsonValue& reference; + typedef JsonValue* pointer; + + JsonIterator() throw(); + + reference operator*() const throw(); + pointer operator->() const throw(); + + JsonIterator& operator++() throw(); + JsonIterator operator++(int) throw(); + + /* + For iterating over this value's children. + iterator.begin() is equivalent to builder.begin(iterator). + O(1), unless hidden nodes need to be skipped. + */ + JsonIterator begin() const throw(); + + /* + For iterating over this value's children. + iterator.end() is equivalent to builder.end(iterator). + O(1), unless hidden nodes need to be skipped. + */ + JsonIterator end() const throw(); +}; + +// JsonBuilder + +/* +Stores data in a logical tree structure. + +The tree stores values of various types. +Object values store no data but contain any number of named child values. +Array values store no data but contain any number of unnamed child values. +All other values are leaf nodes in the tree and can store arbitrary data. + +The root of the tree is an Object. This object is implicit -- it is always +present and need not be added by the user. In methods that accept a "parent +iterator" parameter, use the root() iterator to refer to the root of the tree. +*/ +class JsonBuilder +{ + friend class JsonConstIterator; + typedef JsonValue::Index Index; + typedef JsonInternal::PodVector StorageVec; + StorageVec m_storage; + + class Validator : private JsonInternal::PodVectorBase + { + // 2-bit value. + enum ValidationState : char unsigned + { + ValNone = 0, + ValTail = 1, + ValHead = 2, + ValReached = 3, + ValMax + }; + + JsonValue::StoragePod const* const m_pStorage; + size_type const m_size; + char unsigned* const m_pMap; // 2 bits per StoragePod in m_pStorage. + + public: + ~Validator(); + + explicit Validator( + JsonValue::StoragePod const* pStorage, + size_type cStorage); // may throw bad_alloc + + void Validate(); // may throw invalid_argument + + private: + void ValidateRecurse(Index index); + + void + UpdateMap(Index index, ValidationState expectedVal, ValidationState newVal); + }; + + public: + typedef JsonValue value_type; + typedef JsonValue* pointer; + typedef JsonValue const* const_pointer; + typedef size_t size_type; + typedef int difference_type; + typedef JsonIterator iterator; + typedef JsonConstIterator const_iterator; + + /* + Initializes a new instance of the JsonBuilder class. + */ + JsonBuilder() throw(); + + /* + Initializes a new instance of the JsonBuilder class using at least the + specified initial capacity (in bytes). + */ + explicit JsonBuilder(size_type cbInitialCapacity); // may throw bad_alloc, + // length_error + + /* + Initializes a new instance of the JsonBuilder class, copying its data from + other. + */ + JsonBuilder(JsonBuilder const& other); // may throw bad_alloc + + /* + Initializes a new instance of the JsonBuilder class, moving the data from + other. + NOTE: Invalidates all iterators pointing into other. + */ + JsonBuilder(JsonBuilder&& other) throw(); + + /* + Initializes a new instance of the JsonBuilder class, copying its data from + a memory buffer. Optionally runs ValidateData (i.e. for untrusted input). + */ + JsonBuilder( + void const* pbRawData, + size_type cbRawData, + bool validateData = true); // may throw bad_alloc, length_error, + // invalid_argument + + /* + Copies data from other. + */ + JsonBuilder& operator=(JsonBuilder const& other); // may throw bad_alloc + + /* + Moves the data from other. + NOTE: Invalidates all iterators pointing into other. + */ + JsonBuilder& operator=(JsonBuilder&& other) throw(); + + /* + Throws an exception if the data in this JsonBuilder is corrupt. + Mainly for use in debugging, but this can also be used when feeding + untrusted data to JsonBuilder. + */ + void ValidateData() const; // May throw bad_alloc, invalid_argument. + + iterator begin() throw(); + const_iterator begin() const throw(); + const_iterator cbegin() const throw(); + + iterator end() throw(); + const_iterator end() const throw(); + const_iterator cend() const throw(); + + iterator root() throw(); + const_iterator root() const throw(); + const_iterator croot() const throw(); + + /* + Returns a pointer to the first element in the backing raw data vector. + */ + void const* buffer_data() const throw(); + + /* + Returns the size (in bytes) of the memory currently being used by this + JsonBuilder (i.e. vector.size() of the underlying storage vector). This + value is primarily intended to help in determining the appropriate value + for the initialCapacity constructor parameter. This is also the size of + the data returned by buffer_data(). + */ + size_type buffer_size() const throw(); + + /* + Returns the size (in bytes) of the memory currently allocated by this + JsonBuilder (i.e. vector.capacity() of the underlying storage vector). + This value is primarily intended to help in determining the appropriate + value for the initialCapacity constructor parameter. + */ + size_type buffer_capacity() const throw(); + + /* + Returns the maximum size (in bytes) of the memory that could be passed + to buffer_reserve or returned from buffer_size. + On 32-bit systems, this is currently slightly less than 4GB. + On 64-bit systems, this is currently slightly less than 16GB. + */ + static constexpr size_type buffer_max_size() throw() + { + return StorageVec::max_size() * sizeof(JsonValue::StoragePod); + } + + /* + If less than the specified amount of memory is currently allocated by this + JsonBuilder, allocates additional memory so that at least the specified + amount is allocated. + */ + void buffer_reserve(size_type cbMinimumCapacity); // may throw bad_alloc, + // length_error + + /* + Removes all data from this JsonBuilder and prepares it for reuse. + Keeps the currently-allocated buffer. + O(1). + */ + void clear() throw(); + + /* + Marks the specified value as Erased. Equivalent to erase(itValue, + itValue+1). Requires: itValue+1 is valid (i.e. requires that itValue != + end()). Returns: itValue+1. Implementation detail: Erased values have their + Type() changed to Hidden, and will be skipped during iteration, but they + continue to take up space in the tree. O(1), unless there are erased values + that have to be skipped to find itValue+1. + */ + iterator erase(const_iterator itValue) throw(); + + /* + Marks the specified range as Erased. + Requires: itBegin <= itEnd (i.e. repeated itBegin++ will reach itEnd). + Returns: itEnd. + Implementation detail: Erased values have their Type() changed to Hidden, + and will be skipped during iteration, but they continue to take up space + in the tree. + O(n), where n = the number of values in the range itBegin..itEnd. + */ + iterator erase(const_iterator itBegin, const_iterator itEnd) throw(); + + /* + Replaces the contents of this with the contents of other. + NOTE: Invalidates all iterators pointing into this and other. + O(1). + */ + void swap(JsonBuilder& other) throw(); + + /* + Causes all future memory allocations by this JsonBuilder to be initialized + to zero. + */ + void EnableZeroInitializeMemory() + { + m_storage.EnableZeroInitializeMemory(); + } + + /* + Navigates to a child value, starting at the root. For each name provided, + navigates to the first child with the specified name. + Each name must be a string_view or implicitly-convertible to string_view. + Returns the first match, or end() if there are no matches. + O(n), where n is the total number of children at each level. + */ + template + iterator + find(std::string_view const& firstName, NameTys const&... additionalNames) throw() + { + return iterator( + const_iterator(this, Find(0, firstName, additionalNames...))); + } + + /* + Navigates to a child value, starting at the root. For each name provided, + navigates to the first child with the specified name. + Each name must be a string_view or implicitly-convertible to string_view. + Returns the first match, or end() if there are no matches. + O(n), where n is the total number of children at each level. + */ + template + const_iterator + find(std::string_view const& firstName, NameTys const&... additionalNames) const + throw() + { + return const_iterator(this, Find(0, firstName, additionalNames...)); + } + + /* + Navigates to a child value, starting at itParent. For each name provided, + navigates to the first child with the specified name. + Each name must be a string_view or implicitly-convertible to string_view. + Returns the first match, or end() if there are no matches. + O(n), where n is the total number of children at each level. + */ + template + iterator find( + const_iterator const& itParent, + std::string_view const& firstName, + NameTys const&... additionalNames) throw() + { + ValidateIterator(itParent); + return iterator(const_iterator( + this, Find(itParent.m_index, firstName, additionalNames...))); + } + + /* + Navigates to a child value, starting at itParent. For each name provided, + navigates to the first child with the specified name. + Each name must be a string_view or implicitly-convertible to string_view. + Returns the first match, or end() if there are no matches. + O(n), where n is the total number of children at each level. + */ + template + const_iterator find( + const_iterator const& itParent, + std::string_view const& firstName, + NameTys const&... additionalNames) const throw() + { + ValidateIterator(itParent); + return const_iterator( + this, Find(itParent.m_index, firstName, additionalNames...)); + } + + /* + Returns the number of children of itParent. + O(n), where n is the number of children of itParent. + */ + unsigned count(const_iterator const& itParent) const throw(); + + /* + Returns the first child of itParent. + If itParent has no children, returns end(itParent). + O(1), unless there are erased values that have to be skipped. + */ + iterator begin(const_iterator const& itParent) throw(); + + /* + Returns the first child of itParent. + If itParent has no children, returns end(itParent). + O(1), unless there are erased values that have to be skipped. + */ + const_iterator begin(const_iterator const& itParent) const throw(); + + /* + Returns the first child of itParent. + If itParent has no children, returns cend(itParent). + O(1), unless there are erased values that have to be skipped. + */ + const_iterator cbegin(const_iterator const& itParent) const throw(); + + /* + Returns the iterator after the last child of itParent. + O(1), unless there are erased values that have to be skipped. + */ + iterator end(const_iterator const& itParent) throw(); + + /* + Returns the iterator after the last child of itParent. + O(1), unless there are erased values that have to be skipped. + */ + const_iterator end(const_iterator const& itParent) const throw(); + + /* + Returns the iterator after the last child of itParent. + O(1), unless there are erased value that have to be skipped. + */ + const_iterator cend(const_iterator const& itParent) const throw(); + + /* + Removes all children from itOldParent. + Re-inserts them as the first children of itNewParent. + Requires: itNewParent must reference an array or an object value. + O(n), where n is the number of children of itOldParent. + */ + void splice_front( + const_iterator const& itOldParent, + const_iterator const& itNewParent) throw() + { + Splice(true, itOldParent, itNewParent, PredicateTrue()); + } + + /* + Removes all children from itOldParent. + Re-inserts them as the last children of itNewParent. + Requires: itNewParent must reference an array or an object value. + O(n), where n is the number of children of itOldParent. + */ + void splice_back( + const_iterator const& itOldParent, + const_iterator const& itNewParent) throw() + { + Splice(false, itOldParent, itNewParent, PredicateTrue()); + } + + /* + Removes all children from itOldParent where pred(itChild) returns true. + Re-inserts them as the first children of itNewParent. + Requires: itNewParent must reference an array or an object value. + O(n), where n is the number of children of itOldParent. + */ + template + void splice_front( + const_iterator const& itOldParent, + const_iterator const& itNewParent, + PredTy&& pred) throw() + { + Splice(true, itOldParent, itNewParent, std::forward(pred)); + } + + /* + Removes all children from itOldParent where pred(itChild) returns true. + Re-inserts them as the last children of itNewParent. + Requires: itNewParent must reference an array or an object value. + O(n), where n is the number of children of itOldParent. + */ + template + void splice_back( + const_iterator const& itOldParent, + const_iterator const& itNewParent, + PredTy&& pred) throw() + { + Splice(false, itOldParent, itNewParent, std::forward(pred)); + } + + /* + Creates a new value. Inserts it as the first (if front is true) or last + (if front is false) child of itParent. + If pbData is null, the new value's payload is uninitialized. + Requires: itParent must reference an array or an object value. + Requires: if type is Array or Object, cbValue must be 0 and pbValue must be + null. Returns: an iterator that references the new value. O(1). + */ + iterator AddValue( + bool front, + const_iterator const& itParent, + std::string_view const& name, + JsonType type, + unsigned cbData = 0, + void const* pbData = nullptr); // may throw bad_alloc, length_error + + /* + Creates a new value. Inserts it as the first child of itParent. + If pbData is null, the new value's payload is uninitialized. + Requires: itParent must reference an array or an object value. + Requires: if type is Array or Object, cbValue must be 0 and pbValue must be + null. Returns: an iterator that references the new value. O(1). + */ + iterator push_front( + const_iterator const& itParent, + std::string_view const& name, + JsonType type, + unsigned cbData = 0, + void const* pbData = nullptr) // may throw bad_alloc, length_error + { + return AddValue(true, itParent, name, type, cbData, pbData); + } + + /* + Creates a new value. Inserts it as the first child of itParent. + If pbData is null, the new value's payload is uninitialized. + Requires: itParent must reference an array or an object value. + Requires: if type is Array or Object, cbValue must be 0 and pbValue must be + null. Returns: an iterator that references the new value. O(1). + */ + iterator push_back( + const_iterator const& itParent, + std::string_view const& name, + JsonType type, + unsigned cbData = 0, + void const* pbData = nullptr) // may throw bad_alloc, length_error + { + return AddValue(false, itParent, name, type, cbData, pbData); + } + + /* + Creates a new value. Inserts it as the first (if front is true) or last + (if front is false) child of itParent. + + Data must be a supported type. Supported types include: + + - For boolean data: bool. + - For string data: utl::string_view, wchar_t*, __wchar_t. + - For integer data: signed and unsigned char, short, int, long, long long. + - For float data: float, double, long double. + - For time data: FILETIME. + - For UUID data: GUID. + - Any user-defined type for which JsonImplementType::AddValue exists. + + We specifically do not support char because the intent is ambiguous. + + - Convert a char to __wchar_t, signed char, or unsigned char, as + appropriate. + - Convert a char* to wchar_t* (or use JsonPushBackMbcs). + + Requires: itParent must reference an array or an object value. + Returns: an iterator that references the new value. + O(1). + */ + template< + class T, + class EnableIfType = + decltype(JsonImplementType::type>::AddValue)> + iterator AddValue( + bool front, + const_iterator const& itParent, + std::string_view const& name, + T const& data) // may throw bad_alloc, length_error + { + return JsonImplementType::type>::AddValue( + *this, front, itParent, name, data); + } + + /* + Creates a new value. Inserts it as the first child of itParent. + + Data must be a supported type. Supported types include: + + - For boolean data: bool. + - For string data: utl::string_view, wchar_t*, __wchar_t. + - For integer data: signed and unsigned char, short, int, long, long long. + - For float data: float, double, long double. + - For time data: FILETIME. + - For UUID data: GUID. + - Any user-defined type for which JsonImplementType::AddValue exists. + + We specifically do not support char because the intent is ambiguous. + + - Convert a char to __wchar_t, signed char, or unsigned char, as + appropriate. + - Convert a char* to wchar_t* (or use JsonPushBackMbcs). + + Requires: itParent must reference an array or an object value. + Returns: an iterator that references the new value. + O(1). + */ + template< + class T, + class EnableIfType = + decltype(JsonImplementType::type>::AddValue)> + iterator push_front( + const_iterator const& itParent, + std::string_view const& name, + T const& data) // may throw bad_alloc, length_error + { + return JsonImplementType::type>::AddValue( + *this, true, itParent, name, data); + } + + /* + Creates a new value. Inserts it as the last child of itParent. + + Data must be a supported type. Supported types include: + + - For boolean data: bool. + - For string data: utl::string_view, wchar_t*, __wchar_t. + - For integer data: signed and unsigned char, short, int, long, long long. + - For float data: float, double, long double. + - For time data: FILETIME. + - For UUID data: GUID. + - Any user-defined type for which JsonImplementType::AddValue exists. + + We specifically do not support char because the intent is ambiguous. + + - Convert a char to __wchar_t, signed char, or unsigned char, as + appropriate. + - Convert a char* to wchar_t* (or use JsonPushBackMbcs). + + Requires: itParent must reference an array or an object value. + Returns: an iterator that references the new value. + O(1). + */ + template< + class T, + class EnableIfType = + decltype(JsonImplementType::type>::AddValue)> + iterator push_back( + const_iterator const& itParent, + std::string_view const& name, + T const& data) // may throw bad_alloc, length_error + { + return JsonImplementType::type>::AddValue( + *this, false, itParent, name, data); + } + + private: + static void AssertNotEnd(Index) throw(); + static void AssertHidden(JsonType) throw(); + void ValidateIterator(const_iterator const&) const throw(); + void ValidateParentIterator(Index) const throw(); // Note: assumes !empty() + bool CanIterateOver(const_iterator const&) const + throw(); // False if empty() or if iterator is not a parent. + JsonValue const& GetValue(Index) const throw(); + JsonValue& GetValue(Index) throw(); + Index FirstChild(Index) const throw(); // Given array/object index, return + // index of first child. + Index LastChild(Index) const throw(); // Given array/object index, return + // index of last child. + Index NextIndex(Index) const throw(); // Given index, return next + // non-hidden index. + void EnsureRootExists(); // If root object does not exist, create it. May + // throw bad_alloc. + + unsigned + FindImpl(Index parentIndex, std::string_view const& name) const throw(); + + /* + Adds an unlinked node to the storage vector. The caller must add the new + node to the list. Returns the index of the new value. + It is always ok for pbValue to be null. If so, the data will be + uninitialized. If type is JsonArray or JsonObject, cbData must be 0. + */ + Index CreateValue( + std::string_view const& name, + JsonType type, + unsigned cbData, + void const* pbData); // may throw bad_alloc, length_error + + unsigned Find(Index parentIndex) const throw() { return parentIndex; } + + template + unsigned Find( + Index parentIndex, + std::string_view const& firstName, + NameTys const&... additionalNames) const throw() + { + Index childIndex = FindImpl(parentIndex, firstName); + if (childIndex) + { + childIndex = Find(childIndex, additionalNames...); + } + return childIndex; + } + + struct PredicateTrue + { + bool operator()(const_iterator const&) const throw() { return true; } + }; + + template + void Splice( + bool front, + const_iterator const& itOldParent, + const_iterator const& itNewParent, + PredTy&& pred) throw() + { + ValidateIterator(itOldParent); + ValidateIterator(itNewParent); + + if (CanIterateOver(itOldParent)) + { + ValidateParentIterator(itNewParent.m_index); + + auto& oldParent = GetValue(itOldParent.m_index); + auto prevIndex = FirstChild(itOldParent.m_index); + auto const lastIndex = oldParent.m_lastChildIndex; + if (prevIndex != lastIndex) + { + // Make a linked list of the items that we're moving. + Index headIndex = 0; + Index* pTailIndex = &headIndex; // pTail points at the tail's + // m_nextIndex, which is used + // to store the tail's index. + + auto pPrev = &GetValue(prevIndex); + AssertNotEnd(prevIndex); + AssertHidden(pPrev->m_type); + for (;;) + { + auto currentIndex = pPrev->m_nextIndex; + auto& current = GetValue(currentIndex); + AssertNotEnd(currentIndex); // got to end() before we got + // to oldParent.LastChild + + if (current.m_type != JsonHidden && + pred(const_iterator(this, currentIndex))) + { + pPrev->m_nextIndex = current.m_nextIndex; + *pTailIndex = currentIndex; // Link current into list + // of moved items. + pTailIndex = ¤t.m_nextIndex; + *pTailIndex = currentIndex; // Store tail's index. + + if (currentIndex == lastIndex) + { + oldParent.m_lastChildIndex = prevIndex; + break; + } + } + else + { + if (currentIndex == lastIndex) + { + break; + } + + prevIndex = currentIndex; + pPrev = ¤t; + } + } + + if (headIndex != 0) + { + // Find the right place in the linked list for the moved + // nodes. Update the parent's lastChildIndex if necessary. + auto& newParent = GetValue(itNewParent.m_index); + if (front) + { + prevIndex = FirstChild(itNewParent.m_index); + if (prevIndex == newParent.m_lastChildIndex) + { + newParent.m_lastChildIndex = *pTailIndex; + } + } + else + { + prevIndex = newParent.m_lastChildIndex; + newParent.m_lastChildIndex = *pTailIndex; + } + + // Insert the moved nodes into the linked list after prev. + pPrev = &GetValue(prevIndex); + *pTailIndex = pPrev->m_nextIndex; + pPrev->m_nextIndex = headIndex; + } + } + } + } +}; + +/* +Exchanges the contents of two JsonBuilder objects. +*/ +void swap(JsonBuilder&, JsonBuilder&) throw(); + +// JsonImplementType + +/* +The JsonImplementType class is used to implement the following methods: + +- jsonBuilder.AddValue(..., data) +- jsonBuilder.push_front(..., data) +- jsonBuilder.push_back(..., data) +- jsonValue.GetUnchecked() +- jsonValue.ConvertTo(result) + +These methods will only work if a specialization of JsonImplementType +has been defined. (More specifically, there must be a specialization of +JsonImplementType>.) The JsonBuilder.h header provides +specializations for core built-in types. If you want these methods to support +other types, you can define specializations of the JsonImplementType class for +them. +*/ +template +class JsonImplementType +{ + // Default - no special support for T. + + /* + Specializations of JsonImplementType may implement any or all of the + following: + + static T GetUnchecked( // return value may be "T" or "const T&" at your + discretion. JsonValue const& value); + + static bool ConvertTo( + JsonValue const& value, + T& result); + + static JsonIterator AddValue( + JsonBuilder& builder, + bool front, + JsonConstIterator const& itParent, + utl::string_view const& name, + T data); // data parameter may be "T" or "const T&" at your discretion. + + The specialization may leave the method undefined if the given operation + should not be supported. If the specialization does provide an + implementation for a method, it should conform to the semantics of the + corresponding methods of the JsonValue or JsonBuilder class. + */ +}; + +#define JSON_DECLARE_JsonImplementType(T, getRef) \ + template<> \ + class JsonImplementType \ + { \ + public: \ + static T getRef GetUnchecked(JsonValue const& value) throw(); \ + static bool ConvertTo(JsonValue const& value, T& result) throw(); \ + static JsonIterator AddValue( \ + JsonBuilder& builder, \ + bool front, \ + JsonConstIterator const& itParent, \ + std::string_view const& name, \ + T const& data); \ + } + +#define JSON_DECLARE_JsonImplementType_AddValue(T) \ + template<> \ + class JsonImplementType \ + { \ + public: \ + static JsonIterator AddValue( \ + JsonBuilder& builder, \ + bool front, \ + JsonConstIterator const& itParent, \ + std::string_view const& name, \ + T const& data); \ + } + +JSON_DECLARE_JsonImplementType(bool, ); + +JSON_DECLARE_JsonImplementType(unsigned char, ); +JSON_DECLARE_JsonImplementType(unsigned short, ); +JSON_DECLARE_JsonImplementType(unsigned int, ); +JSON_DECLARE_JsonImplementType(unsigned long, ); +JSON_DECLARE_JsonImplementType(unsigned long long, ); + +JSON_DECLARE_JsonImplementType(signed char, ); +JSON_DECLARE_JsonImplementType(signed short, ); +JSON_DECLARE_JsonImplementType(signed int, ); +JSON_DECLARE_JsonImplementType(signed long, ); +JSON_DECLARE_JsonImplementType(signed long long, ); + +JSON_DECLARE_JsonImplementType(float, ); +JSON_DECLARE_JsonImplementType(double, ); + +JSON_DECLARE_JsonImplementType(std::chrono::system_clock::time_point, ); +JSON_DECLARE_JsonImplementType(UuidStruct, ); +JSON_DECLARE_JsonImplementType(std::string_view, ); +JSON_DECLARE_JsonImplementType_AddValue(std::string); +JSON_DECLARE_JsonImplementType_AddValue(char); + +template<> +class JsonImplementType +{ + public: + static JsonIterator AddValue( + JsonBuilder& builder, + bool front, + JsonConstIterator const& itParent, + std::string_view const& name, + char const* psz); +}; + +template<> +class JsonImplementType : public JsonImplementType +{}; + +} // namespace jsonbuilder diff --git a/include/jsonbuilder/JsonRenderer.h b/include/jsonbuilder/JsonRenderer.h index 7581df8..d0c1568 100644 --- a/include/jsonbuilder/JsonRenderer.h +++ b/include/jsonbuilder/JsonRenderer.h @@ -1,278 +1,278 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -/* -JsonRenderer converts JsonBuilder trees into utf8 JSON text. - -Summary: -- JsonRenderer -- JsonRenderBool -- JsonRenderFalse -- JsonRenderTime -- JsonRenderFloat -- JsonRenderInt -- JsonRenderNull -- JsonRenderTrue -- JsonRenderUInt -- JsonRenderUuid -- JsonRenderUuidWithBraces -*/ - -#pragma once - -#include -#include - - -namespace jsonbuilder { -/* -Converts JsonBuilder data into utf-8 JSON text. -Recognizes the built-in JsonType types. To support other (custom) types, -derive from JsonRenderer and override RenderCustom. -*/ -class JsonRenderer -{ - protected: - typedef JsonInternal::PodVector RenderBuffer; - typedef JsonBuilder::const_iterator iterator; - - private: - RenderBuffer m_renderBuffer; - std::string_view m_newLine; - unsigned m_indentSpaces; - unsigned m_indent; - bool m_pretty; - - public: - typedef JsonInternal::PodVector::size_type size_type; - - /* - Initializes a new instance of the JsonRenderer class. - Optionally sets the initial value of the formatting properties. - */ - explicit JsonRenderer( - bool pretty = false, - std::string_view const& newLine = "\n", - unsigned indentSpaces = 2) throw(); - - /* - Preallocates memory in the rendering buffer (increases capacity). - */ - void Reserve(size_type cb); // may throw bad_alloc, length_error - - /* - Gets the current size of the rendering buffer, in bytes. - */ - size_type Size() const throw(); - - /* - Gets the current capacity of the rendering buffer (how large it can grow - without allocating additional memory), in bytes. - */ - size_type Capacity() const throw(); - - /* - Gets a value indicating whether the output will be formatted nicely. - If true, insignificant whitespace (spaces and newlines) will be added to - improve readability by humans and to put each value on its own line. - If false, all insignificant whitespace will be omitted. - Default value is false. - */ - bool Pretty() const throw(); - - /* - Set a value indicating whether the output will be formatted nicely. - If true, insignificant whitespace (spaces and newlines) will be added to - improve readability by humans and to put each value on its own line. - If false, all insignificant whitespace will be omitted. - Default value is false. - */ - void Pretty(bool) throw(); - - /* - Gets the string that is used for newline when Pretty() is true. - Default value is "\r\n". - */ - std::string_view const& NewLine() const throw(); - - /* - Sets the string that is used for newline when Pretty() is true. - Note that the JsonRenderer will store a copy of the string_view, but it - does not make a copy of the actual string. The string passed in here must - be valid for as long as the JsonRenderer exists. - Default value is "\r\n". - */ - void NewLine(std::string_view const&) throw(); - - /* - Gets the number of spaces per indent level. Default value is 2. - */ - unsigned IndentSpaces() const throw(); - - /* - Sets the number of spaces per indent level. Default value is 2. - */ - void IndentSpaces(unsigned) throw(); - - /* - Renders the contents of the specified JsonBuilder as utf-8 JSON, starting - at the root value. - Returns a string_view with the resulting JSON string. The returned string - is nul-terminated, but the nul is not included as part of the string_view - itself, so return.data()[return.size()] == '\0'. - The returned string_view is valid until the next call to Render or until - this JsonBuilder is destroyed. - */ - std::string_view Render(JsonBuilder const& builder); // may throw - // bad_alloc, - // length_error - - /* - Renders the contents of a JsonBuilder as utf-8 JSON, starting at the - specified value. - Returns a string_view with the resulting JSON string. The returned string - is nul-terminated, but the nul is not included as part of the string_view - itself, so return.data()[return.size()] == '\0'. - The returned string_view is valid until the next call to Render or until - this JsonBuilder is destroyed. - */ - std::string_view - Render(JsonBuilder::const_iterator const& it); // may throw bad_alloc, - // length_error - - protected: - /* - Override this method to provide rendering behavior for custom value types. - The utf-8 representation of the value referenced by itValue should be - appended to the end of buffer by calling buffer.push_back with the bytes - of the utf-8 representation. - */ - virtual void RenderCustom( - RenderBuffer& buffer, - iterator const& itValue); // may throw bad_alloc, length_error - - private: - /* - Renders any value and its children by dispatching to the appropriate - subroutine. - CANNOT RENDER THE ROOT! (Don't call this if it.IsRoot() is true.) - */ - void RenderValue(iterator const& it); - - /* - Renders an object/array value and its children. - itParent must be an array or object. - Set showNames = true for object. Set showNames = false for array. - Can be called with itParent == end() to render the root. - Example output: {"ObjectName":{"ArrayName":[7]}} - */ - void RenderStructure(iterator const& itParent, bool showNames); - - /* - Renders value as floating-point. Requires that cb be 4 or 8. Data will be - interpreted as a little-endian float or double. - Example output: 123.45 - */ - void RenderFloat(double const& value); - - /* - Renders value as signed integer. Requires that cb be 1, 2, 4 or 8. Data will - be interpreted as a little-endian signed integer. - Example output: -12345 - */ - void RenderInt(long long signed const& value); - - /* - Renders value as unsigned integer. Requires that cb be 1, 2, 4 or 8. Data - will be interpreted as a little-endian unsigned integer. - Example output: 12345 - */ - void RenderUInt(long long unsigned const& value); - - /* - Renders value as time. Requires that cb be 8. Data will be interpreted as - number of 100ns intervals since 1601-01-01T00:00:00Z. - Example output: "2015-04-02T02:09:14.7927652Z". - */ - void RenderTime(std::chrono::system_clock::time_point const& value); - - /* - Renders value as UUID. Requires that cb be 16. - Example output: "CD8D0A5E-6409-4B8E-9366-B815CEF0E35D". - */ - void RenderUuid(uuid_t const& value); - - /* - Renders value as a string. Converts pch to utf-8, escapes any control - characters, and adds quotes around the result. Example output: "String\n" - */ - void RenderString(std::string_view const& value); - - /* - If pretty-printing is disabled, has no effect. - If pretty-printing is enabled, writes m_newLine followed by - (m_indent * m_indentSpaces) space characters. - */ - void RenderNewline(); -}; - -/* -Renders the given value as an unsigned decimal integer, e.g. "123". -Returns the number of characters written, not counting the nul-termination. -*/ -unsigned JsonRenderUInt(long long unsigned n, char* pBuffer) throw(); - -/* -Renders the given value as a signed decimal integer, e.g. "-123". -Returns the number of characters written, not counting the nul-termination. -*/ -unsigned JsonRenderInt(long long signed n, char* pBuffer) throw(); - -/* -Renders the given value as a signed floating-point, e.g. "-123.1", or "null" -if the value is not finite. -Returns the number of characters written, not counting the nul-termination. -*/ -unsigned JsonRenderFloat(double n, char* pBuffer) throw(); - -/* -Renders the string "true" or "false". -Returns the number of characters written, not counting the nul-termination. -(Always returns 4 or 5.) -*/ -unsigned JsonRenderBool(bool b, char* pBuffer) throw(); - -/* -Renders the string "null". -Returns the number of characters written, not counting the nul-termination. -(Always returns 4.) -*/ -unsigned JsonRenderNull(char* pBuffer) throw(); - -/* -Renders the given FILETIME value (uint64) as an ISO 8601 string, e.g. -"2015-04-02T02:09:14.7927652Z". -Returns the number of characters written, not counting the nul-termination. -(Always returns 28.) -*/ -unsigned JsonRenderTime( - std::chrono::system_clock::time_point const& ft, - char* pBuffer) throw(); - -/* -Renders the given GUID value as a string in uppercase without braces, e.g. -"CD8D0A5E-6409-4B8E-9366-B815CEF0E35D". -Returns the number of characters written, not counting the nul-termination. -(Always returns 36.) -*/ -unsigned JsonRenderUuid(uuid_t const& g, char* pBuffer) throw(); - -/* -Renders the given GUID value as a string in uppercase with braces, e.g. -"{CD8D0A5E-6409-4B8E-9366-B815CEF0E35D}". -Returns the number of characters written, not counting the nul-termination. -(Always returns 38.) -*/ -unsigned JsonRenderUuidWithBraces(uuid_t const& g, char* pBuffer) throw(); - -} // namespace jsonbuilder +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +/* +JsonRenderer converts JsonBuilder trees into utf8 JSON text. + +Summary: +- JsonRenderer +- JsonRenderBool +- JsonRenderFalse +- JsonRenderTime +- JsonRenderFloat +- JsonRenderInt +- JsonRenderNull +- JsonRenderTrue +- JsonRenderUInt +- JsonRenderUuid +- JsonRenderUuidWithBraces +*/ + +#pragma once + +#include +#include + + +namespace jsonbuilder { +/* +Converts JsonBuilder data into utf-8 JSON text. +Recognizes the built-in JsonType types. To support other (custom) types, +derive from JsonRenderer and override RenderCustom. +*/ +class JsonRenderer +{ + protected: + typedef JsonInternal::PodVector RenderBuffer; + typedef JsonBuilder::const_iterator iterator; + + private: + RenderBuffer m_renderBuffer; + std::string_view m_newLine; + unsigned m_indentSpaces; + unsigned m_indent; + bool m_pretty; + + public: + typedef JsonInternal::PodVector::size_type size_type; + + /* + Initializes a new instance of the JsonRenderer class. + Optionally sets the initial value of the formatting properties. + */ + explicit JsonRenderer( + bool pretty = false, + std::string_view const& newLine = "\n", + unsigned indentSpaces = 2) throw(); + + /* + Preallocates memory in the rendering buffer (increases capacity). + */ + void Reserve(size_type cb); // may throw bad_alloc, length_error + + /* + Gets the current size of the rendering buffer, in bytes. + */ + size_type Size() const throw(); + + /* + Gets the current capacity of the rendering buffer (how large it can grow + without allocating additional memory), in bytes. + */ + size_type Capacity() const throw(); + + /* + Gets a value indicating whether the output will be formatted nicely. + If true, insignificant whitespace (spaces and newlines) will be added to + improve readability by humans and to put each value on its own line. + If false, all insignificant whitespace will be omitted. + Default value is false. + */ + bool Pretty() const throw(); + + /* + Set a value indicating whether the output will be formatted nicely. + If true, insignificant whitespace (spaces and newlines) will be added to + improve readability by humans and to put each value on its own line. + If false, all insignificant whitespace will be omitted. + Default value is false. + */ + void Pretty(bool) throw(); + + /* + Gets the string that is used for newline when Pretty() is true. + Default value is "\r\n". + */ + std::string_view const& NewLine() const throw(); + + /* + Sets the string that is used for newline when Pretty() is true. + Note that the JsonRenderer will store a copy of the string_view, but it + does not make a copy of the actual string. The string passed in here must + be valid for as long as the JsonRenderer exists. + Default value is "\r\n". + */ + void NewLine(std::string_view const&) throw(); + + /* + Gets the number of spaces per indent level. Default value is 2. + */ + unsigned IndentSpaces() const throw(); + + /* + Sets the number of spaces per indent level. Default value is 2. + */ + void IndentSpaces(unsigned) throw(); + + /* + Renders the contents of the specified JsonBuilder as utf-8 JSON, starting + at the root value. + Returns a string_view with the resulting JSON string. The returned string + is nul-terminated, but the nul is not included as part of the string_view + itself, so return.data()[return.size()] == '\0'. + The returned string_view is valid until the next call to Render or until + this JsonBuilder is destroyed. + */ + std::string_view Render(JsonBuilder const& builder); // may throw + // bad_alloc, + // length_error + + /* + Renders the contents of a JsonBuilder as utf-8 JSON, starting at the + specified value. + Returns a string_view with the resulting JSON string. The returned string + is nul-terminated, but the nul is not included as part of the string_view + itself, so return.data()[return.size()] == '\0'. + The returned string_view is valid until the next call to Render or until + this JsonBuilder is destroyed. + */ + std::string_view + Render(JsonBuilder::const_iterator const& it); // may throw bad_alloc, + // length_error + + protected: + /* + Override this method to provide rendering behavior for custom value types. + The utf-8 representation of the value referenced by itValue should be + appended to the end of buffer by calling buffer.push_back with the bytes + of the utf-8 representation. + */ + virtual void RenderCustom( + RenderBuffer& buffer, + iterator const& itValue); // may throw bad_alloc, length_error + + private: + /* + Renders any value and its children by dispatching to the appropriate + subroutine. + CANNOT RENDER THE ROOT! (Don't call this if it.IsRoot() is true.) + */ + void RenderValue(iterator const& it); + + /* + Renders an object/array value and its children. + itParent must be an array or object. + Set showNames = true for object. Set showNames = false for array. + Can be called with itParent == end() to render the root. + Example output: {"ObjectName":{"ArrayName":[7]}} + */ + void RenderStructure(iterator const& itParent, bool showNames); + + /* + Renders value as floating-point. Requires that cb be 4 or 8. Data will be + interpreted as a little-endian float or double. + Example output: 123.45 + */ + void RenderFloat(double const& value); + + /* + Renders value as signed integer. Requires that cb be 1, 2, 4 or 8. Data will + be interpreted as a little-endian signed integer. + Example output: -12345 + */ + void RenderInt(long long signed const& value); + + /* + Renders value as unsigned integer. Requires that cb be 1, 2, 4 or 8. Data + will be interpreted as a little-endian unsigned integer. + Example output: 12345 + */ + void RenderUInt(long long unsigned const& value); + + /* + Renders value as time. Requires that cb be 8. Data will be interpreted as + number of 100ns intervals since 1601-01-01T00:00:00Z. + Example output: "2015-04-02T02:09:14.7927652Z". + */ + void RenderTime(std::chrono::system_clock::time_point const& value); + + /* + Renders value as UUID. Requires that cb be 16. + Example output: "CD8D0A5E-6409-4B8E-9366-B815CEF0E35D". + */ + void RenderUuid(uuid_t const& value); + + /* + Renders value as a string. Converts pch to utf-8, escapes any control + characters, and adds quotes around the result. Example output: "String\n" + */ + void RenderString(std::string_view const& value); + + /* + If pretty-printing is disabled, has no effect. + If pretty-printing is enabled, writes m_newLine followed by + (m_indent * m_indentSpaces) space characters. + */ + void RenderNewline(); +}; + +/* +Renders the given value as an unsigned decimal integer, e.g. "123". +Returns the number of characters written, not counting the nul-termination. +*/ +unsigned JsonRenderUInt(long long unsigned n, char* pBuffer) throw(); + +/* +Renders the given value as a signed decimal integer, e.g. "-123". +Returns the number of characters written, not counting the nul-termination. +*/ +unsigned JsonRenderInt(long long signed n, char* pBuffer) throw(); + +/* +Renders the given value as a signed floating-point, e.g. "-123.1", or "null" +if the value is not finite. +Returns the number of characters written, not counting the nul-termination. +*/ +unsigned JsonRenderFloat(double n, char* pBuffer) throw(); + +/* +Renders the string "true" or "false". +Returns the number of characters written, not counting the nul-termination. +(Always returns 4 or 5.) +*/ +unsigned JsonRenderBool(bool b, char* pBuffer) throw(); + +/* +Renders the string "null". +Returns the number of characters written, not counting the nul-termination. +(Always returns 4.) +*/ +unsigned JsonRenderNull(char* pBuffer) throw(); + +/* +Renders the given FILETIME value (uint64) as an ISO 8601 string, e.g. +"2015-04-02T02:09:14.7927652Z". +Returns the number of characters written, not counting the nul-termination. +(Always returns 28.) +*/ +unsigned JsonRenderTime( + std::chrono::system_clock::time_point const& ft, + char* pBuffer) throw(); + +/* +Renders the given GUID value as a string in uppercase without braces, e.g. +"CD8D0A5E-6409-4B8E-9366-B815CEF0E35D". +Returns the number of characters written, not counting the nul-termination. +(Always returns 36.) +*/ +unsigned JsonRenderUuid(uuid_t const& g, char* pBuffer) throw(); + +/* +Renders the given GUID value as a string in uppercase with braces, e.g. +"{CD8D0A5E-6409-4B8E-9366-B815CEF0E35D}". +Returns the number of characters written, not counting the nul-termination. +(Always returns 38.) +*/ +unsigned JsonRenderUuidWithBraces(uuid_t const& g, char* pBuffer) throw(); + +} // namespace jsonbuilder diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 41e47bb..d1a5086 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -1,40 +1,40 @@ -cmake_minimum_required(VERSION 3.7) - -add_library(jsonbuilder - JsonBuilder.cpp - JsonRenderer.cpp - PodVector.cpp) - -target_include_directories(jsonbuilder - PUBLIC - $ - $) - -target_link_libraries(jsonbuilder PUBLIC uuid::uuid) -target_compile_features(jsonbuilder PUBLIC cxx_std_17) - -set_property(TARGET jsonbuilder PROPERTY POSITION_INDEPENDENT_CODE ON) -set_property(TARGET jsonbuilder PROPERTY SOVERSION 0) - -include(GNUInstallDirs) - -install(TARGETS jsonbuilder - EXPORT jsonbuilder-export - LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} - ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} - RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} - INCLUDES DESTINATION ${CMAKE_INSTALL_LIBDIR}) - -add_library(jsonbuilder::jsonbuilder ALIAS jsonbuilder) - -install(DIRECTORY ${PROJECT_SOURCE_DIR}/include/ DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}) - -set(JSONBUILDER_CMAKE_DIR ${CMAKE_INSTALL_LIBDIR}/cmake/jsonbuilder) - -install(EXPORT jsonbuilder-export - FILE jsonbuilderTargets.cmake - NAMESPACE jsonbuilder:: - DESTINATION ${JSONBUILDER_CMAKE_DIR}) - -install(FILES ${PROJECT_SOURCE_DIR}/cmake/modules/jsonbuilderConfig.cmake - DESTINATION ${JSONBUILDER_CMAKE_DIR}) +cmake_minimum_required(VERSION 3.7) + +add_library(jsonbuilder + JsonBuilder.cpp + JsonRenderer.cpp + PodVector.cpp) + +target_include_directories(jsonbuilder + PUBLIC + $ + $) + +target_link_libraries(jsonbuilder PUBLIC uuid::uuid) +target_compile_features(jsonbuilder PUBLIC cxx_std_17) + +set_property(TARGET jsonbuilder PROPERTY POSITION_INDEPENDENT_CODE ON) +set_property(TARGET jsonbuilder PROPERTY SOVERSION 0) + +include(GNUInstallDirs) + +install(TARGETS jsonbuilder + EXPORT jsonbuilder-export + LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} + ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} + RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} + INCLUDES DESTINATION ${CMAKE_INSTALL_LIBDIR}) + +add_library(jsonbuilder::jsonbuilder ALIAS jsonbuilder) + +install(DIRECTORY ${PROJECT_SOURCE_DIR}/include/ DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}) + +set(JSONBUILDER_CMAKE_DIR ${CMAKE_INSTALL_LIBDIR}/cmake/jsonbuilder) + +install(EXPORT jsonbuilder-export + FILE jsonbuilderTargets.cmake + NAMESPACE jsonbuilder:: + DESTINATION ${JSONBUILDER_CMAKE_DIR}) + +install(FILES ${PROJECT_SOURCE_DIR}/cmake/modules/jsonbuilderConfig.cmake + DESTINATION ${JSONBUILDER_CMAKE_DIR}) diff --git a/src/JsonBuilder.cpp b/src/JsonBuilder.cpp index 4b86621..57992ef 100644 --- a/src/JsonBuilder.cpp +++ b/src/JsonBuilder.cpp @@ -1,1423 +1,1423 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -#include - -#include - -#define StorageSize sizeof(JsonValue::StoragePod) -#define DataMax 0xf0000000 - -/* -Computes the difference between a value's index and the index at which its -data begins. Used to determine the DataIndex for a value: -value.DataIndex = value.Index + DATA_OFFSET(value.cchName). -*/ -#define DATA_OFFSET(cchName) \ - (((cchName) + sizeof(JsonValue) + (StorageSize - 1)) / StorageSize) - -#define IS_SPECIAL_TYPE(type) (JsonHidden <= (type)) -#define IS_NORMAL_TYPE(type) ((type) < JsonHidden) -#define IS_COMPOSITE_TYPE(type) (JsonArray <= (type)) - -// JsonValue - -namespace jsonbuilder { -// Verify that our conditions like (type >= JsonArray) will work. -static_assert(JsonHidden == 253, "Incorrect enum numbering for JsonHidden"); -static_assert(JsonArray == 254, "Incorrect enum numbering for JsonArray"); -static_assert(JsonObject == 255, "Incorrect enum numbering for JsonObject"); - -static_assert( - sizeof(unsigned) >= 4, - "JsonValue assumes that unsigned is at least 32 bits"); -static_assert(sizeof(JsonValueBase) == 8, "JsonValueBase changed size"); -static_assert(sizeof(JsonValue) == 12, "JsonValue changed size"); - -JsonType JsonValue::Type() const throw() -{ - return static_cast(m_type); -} - -std::string_view JsonValue::Name() const throw() -{ - return std::string_view(reinterpret_cast(this + 1), m_cchName); -} - -void const* JsonValue::Data(unsigned* pcbData) const throw() -{ - return const_cast(this)->Data(pcbData); -} - -void* JsonValue::Data(unsigned* pcbData) throw() -{ - assert(!IS_SPECIAL_TYPE(m_type)); // Can't call Data() on hidden, - // object, or array values. - if (pcbData != nullptr) - { - *pcbData = m_cbData; - } - - return reinterpret_cast(this) + DATA_OFFSET(m_cchName); -} - -unsigned JsonValue::DataSize() const throw() -{ - assert(!IS_SPECIAL_TYPE(m_type)); // Can't call DataSize() on hidden, - // object, or array values. - return m_cbData; -} - -void JsonValue::ReduceDataSize(unsigned cbNew) throw() -{ - if (IS_SPECIAL_TYPE(m_type) || cbNew > m_cbData) - { - assert(!"JsonBuilder: invalid use of ReduceDataSize()."); - std::terminate(); - } - - m_cbData = cbNew; -} - -bool JsonValue::IsNull() const throw() -{ - return m_type == JsonNull; -} - -// JsonConstIterator - -JsonConstIterator::JsonConstIterator() throw() : m_pContainer(), m_index() -{ - return; -} - -JsonConstIterator::JsonConstIterator(JsonBuilder const* pContainer, Index index) throw() - : m_pContainer(pContainer), m_index(index) -{ - return; -} - -bool JsonConstIterator::operator==(JsonConstIterator const& other) const throw() -{ - assert(m_pContainer == other.m_pContainer); - return m_index == other.m_index; -} - -bool JsonConstIterator::operator!=(JsonConstIterator const& other) const throw() -{ - assert(m_pContainer == other.m_pContainer); - return m_index != other.m_index; -} - -JsonConstIterator::reference JsonConstIterator::operator*() const throw() -{ - m_pContainer->AssertNotEnd(m_index); // Do not dereference the end() - // iterator. - return m_pContainer->GetValue(m_index); -} - -JsonConstIterator::pointer JsonConstIterator::operator->() const throw() -{ - m_pContainer->AssertNotEnd(m_index); // Do not dereference the end() - // iterator. - return &m_pContainer->GetValue(m_index); -} - -JsonConstIterator& JsonConstIterator::operator++() throw() -{ - m_index = m_pContainer->NextIndex(m_index); // Implicitly asserts !end(). - return *this; -} - -JsonConstIterator JsonConstIterator::operator++(int) throw() -{ - auto old = *this; - m_index = m_pContainer->NextIndex(m_index); // Implicitly asserts !end(). - return old; -} - -JsonConstIterator JsonConstIterator::begin() const throw() -{ - return m_pContainer->begin(*this); -} - -JsonConstIterator JsonConstIterator::end() const throw() -{ - return m_pContainer->end(*this); -} - -bool JsonConstIterator::IsRoot() const throw() -{ - return m_index == 0; -} - -// JsonIterator - -JsonIterator::JsonIterator() throw() : JsonConstIterator() -{ - return; -} - -JsonIterator::JsonIterator(JsonConstIterator const& other) throw() - : JsonConstIterator(other) -{ - return; -} - -JsonIterator::reference JsonIterator::operator*() const throw() -{ - return const_cast(JsonConstIterator::operator*()); -} - -JsonIterator::pointer JsonIterator::operator->() const throw() -{ - return const_cast(JsonConstIterator::operator->()); -} - -JsonIterator& JsonIterator::operator++() throw() -{ - JsonConstIterator::operator++(); - return *this; -} - -JsonIterator JsonIterator::operator++(int) throw() -{ - JsonIterator old(*this); - JsonConstIterator::operator++(); - return old; -} - -JsonIterator JsonIterator::begin() const throw() -{ - return JsonIterator(JsonConstIterator::begin()); -} - -JsonIterator JsonIterator::end() const throw() -{ - return JsonIterator(JsonConstIterator::end()); -} - -// JsonBuilder::Validator - -constexpr unsigned char MapBits = 2; -constexpr unsigned char MapMask = (1 << MapBits) - 1; -constexpr unsigned char MapPerByte = 8 / MapBits; -#define MapSize(cStorage) ((cStorage) / MapPerByte + 1u) - -JsonBuilder::Validator::~Validator() -{ - Deallocate(m_pMap); -} - -JsonBuilder::Validator::Validator( - JsonValue::StoragePod const* pStorage, - size_type cStorage) - : m_pStorage(pStorage) - , m_size(cStorage) - , m_pMap(static_cast(Reallocate(nullptr, MapSize(cStorage)))) -{ - static_assert( - ValMax <= (1 << MapBits), "Too many ValidationStates for MapBits"); - assert(m_size != 0); // No need to validate an empty builder. - return; -} - -void JsonBuilder::Validator::Validate() -{ - memset(m_pMap, 0, MapSize(m_size)); - - // Traverse the linked list, starting at head. - // Ensure that items fit in storage without overlap. - // Ensure that there are no loops. - // Mark all valid heads. - Index index = 0; - do - { - static_assert( - sizeof(JsonValueBase) % StorageSize == 0, - "JsonValueBase not a multiple of StorageSize"); - static_assert( - sizeof(JsonValue) % StorageSize == 0, - "JsonValue not a multiple of StorageSize"); - - // Note: don't dereference pValue until the appropriate part has been - // validated. - JsonValue const* const pValue = - reinterpret_cast(m_pStorage + index); - - // Mark start of JsonValueBase as head. - UpdateMap(index, ValNone, ValHead); - - // Mark remainder of JsonValueBase as tail. - Index const baseEnd = sizeof(JsonValueBase) / StorageSize; - for (Index i = 1; i != baseEnd; i++) - { - UpdateMap(index + i, ValNone, ValTail); - } - - // Now safe to dereference: m_nextIndex, m_cchName, m_type. - - if (pValue->m_type != JsonHidden) - { - // Mark m_cbData/m_lastChildIndex, Name as tail. - Index const nameEnd = DATA_OFFSET(pValue->m_cchName); - for (Index i = baseEnd; i != nameEnd; i++) - { - UpdateMap(index + i, ValNone, ValTail); - } - - // Now safe to dereference: m_cbData/m_lastChildIndex, Name. - - if (IS_NORMAL_TYPE(pValue->m_type)) - { - if (pValue->m_cbData > DataMax) - { - throw std::invalid_argument("JsonBuilder - corrupt data"); - } - - // Mark Data as tail. - Index const dataEnd = nameEnd + - (pValue->m_cbData + StorageSize - 1) / StorageSize; - for (Index i = nameEnd; i != dataEnd; i++) - { - UpdateMap(index + i, ValNone, ValTail); - } - } - } - - index = pValue->m_nextIndex; - } while (index != 0); - - // Validate root. - UpdateMap(0, ValHead, ValReached); - if (reinterpret_cast(m_pStorage)->m_cchName != 0 || - reinterpret_cast(m_pStorage)->m_type != JsonObject) - { - throw std::invalid_argument("JsonBuilder - corrupt data"); - } - - // Traverse the tree, starting at root. - // Ensure that all reachable indexes are valid heads. - // Ensure that there are no child->parent loops (no head reached more than - // once). - ValidateRecurse(0); - - return; -} - -void JsonBuilder::Validator::ValidateRecurse(Index parent) -{ - JsonValue const* const pParent = - reinterpret_cast(m_pStorage + parent); - - // Validate first child (always hidden/sentinel). - - Index child = parent + DATA_OFFSET(pParent->m_cchName); - UpdateMap(child, ValHead, ValReached); - JsonValue const* pChild = - reinterpret_cast(m_pStorage + child); - - if (pChild->m_type != JsonHidden) - { - throw std::invalid_argument("JsonBuilder - corrupt data"); - } - - // Validate remaining children. - - while (child != pParent->m_lastChildIndex) - { - child = pChild->m_nextIndex; - UpdateMap(child, ValHead, ValReached); - pChild = reinterpret_cast(m_pStorage + child); - - if (IS_COMPOSITE_TYPE(pChild->m_type)) - { - ValidateRecurse(child); - } - } - - return; -} - -void JsonBuilder::Validator::UpdateMap( - Index index, - ValidationState expectedVal, - ValidationState newVal) -{ - assert(newVal == (expectedVal | newVal)); - Index i = index / MapPerByte; - char shift = (index % MapPerByte) * MapBits; - if (m_size <= index || ((m_pMap[i] >> shift) & MapMask) != expectedVal) - { - throw std::invalid_argument("JsonBuilder - corrupt data"); - } - - m_pMap[i] |= newVal << shift; - assert(newVal == ((m_pMap[i] >> shift) & MapMask)); -} - -// JsonBuilder - -JsonBuilder::JsonBuilder() throw() -{ - return; -} - -JsonBuilder::JsonBuilder(size_type cbInitialCapacity) -{ - buffer_reserve(cbInitialCapacity); -} - -JsonBuilder::JsonBuilder(JsonBuilder const& other) : m_storage(other.m_storage) -{ - return; -} - -JsonBuilder::JsonBuilder(JsonBuilder&& other) throw() - : m_storage(std::move(other.m_storage)) -{ - return; -} - -JsonBuilder::JsonBuilder(void const* pbRawData, size_type cbRawData, bool validateData) - : m_storage( - static_cast(pbRawData), - static_cast(cbRawData / StorageSize)) -{ - if (cbRawData % StorageSize != 0 || - cbRawData / StorageSize > StorageVec::max_size()) - { - throw std::invalid_argument("cbRawData invalid"); - } - else if (validateData) - { - ValidateData(); - } -} - -JsonBuilder& JsonBuilder::operator=(JsonBuilder const& other) -{ - m_storage = other.m_storage; - return *this; -} - -JsonBuilder& JsonBuilder::operator=(JsonBuilder&& other) throw() -{ - m_storage = std::move(other.m_storage); - return *this; -} - -void JsonBuilder::ValidateData() const -{ - if (!m_storage.empty()) - { - Validator(m_storage.data(), m_storage.size()).Validate(); - } -} - -JsonBuilder::iterator JsonBuilder::begin() throw() -{ - return iterator(cbegin()); -} - -JsonBuilder::const_iterator JsonBuilder::begin() const throw() -{ - return cbegin(); -} - -JsonBuilder::const_iterator JsonBuilder::cbegin() const throw() -{ - Index index = 0; - if (!m_storage.empty()) - { - // Return index of first non-hidden node after root. - index = GetValue(0).m_nextIndex; - for (;;) - { - auto& value = GetValue(index); - if (value.m_type != JsonHidden) - { - break; - } - - AssertNotEnd(index); // end() should never be hidden. - index = value.m_nextIndex; - } - } - return const_iterator(this, index); -} - -JsonBuilder::iterator JsonBuilder::end() throw() -{ - return iterator(cend()); -} - -JsonBuilder::const_iterator JsonBuilder::end() const throw() -{ - return cend(); -} - -JsonBuilder::const_iterator JsonBuilder::cend() const throw() -{ - return const_iterator(this, 0); -} - -JsonBuilder::iterator JsonBuilder::root() throw() -{ - return end(); -} - -JsonBuilder::const_iterator JsonBuilder::root() const throw() -{ - return end(); -} - -JsonBuilder::const_iterator JsonBuilder::croot() const throw() -{ - return end(); -} - -JsonBuilder::size_type JsonBuilder::buffer_size() const throw() -{ - return m_storage.size() * StorageSize; -} - -void const* JsonBuilder::buffer_data() const throw() -{ - return m_storage.data(); -} - -JsonBuilder::size_type JsonBuilder::buffer_capacity() const throw() -{ - return m_storage.capacity() * StorageSize; -} - -void JsonBuilder::buffer_reserve(size_type cbMinimumCapacity) -{ - if (cbMinimumCapacity > StorageVec::max_size() * StorageSize) - { - throw std::length_error("requested capacity is too large"); - } - auto const cItems = (cbMinimumCapacity + StorageSize - 1) / StorageSize; - m_storage.reserve(static_cast(cItems)); -} - -void JsonBuilder::clear() throw() -{ - m_storage.clear(); -} - -JsonBuilder::iterator JsonBuilder::erase(const_iterator itValue) throw() -{ - ValidateIterator(itValue); - if (itValue.m_index == 0) - { - assert(!"JsonBuilder: cannot erase end()"); - std::terminate(); - } - - GetValue(itValue.m_index).m_type = JsonHidden; - return iterator(const_iterator(this, NextIndex(itValue.m_index))); -} - -JsonBuilder::iterator -JsonBuilder::erase(const_iterator itBegin, const_iterator itEnd) throw() -{ - ValidateIterator(itBegin); - ValidateIterator(itEnd); - auto index = itBegin.m_index; - while (index != itEnd.m_index) - { - if (index == 0) - { - assert(!"JsonBuilder: invalid use of ReduceDataSize()."); - std::terminate(); - } - - auto& value = GetValue(index); - value.m_type = JsonHidden; - index = value.m_nextIndex; - } - return iterator(itEnd); -} - -void JsonBuilder::swap(JsonBuilder& other) throw() -{ - m_storage.swap(other.m_storage); -} - -unsigned -JsonBuilder::FindImpl(Index parentIndex, std::string_view const& name) const - throw() -{ - Index result = 0; - if (!m_storage.empty() && IS_COMPOSITE_TYPE(GetValue(parentIndex).m_type)) - { - auto index = FirstChild(parentIndex); - auto const lastIndex = LastChild(parentIndex); - if (index != lastIndex) - { - AssertNotEnd(index); - auto& sentinelValue = GetValue(index); - assert(sentinelValue.m_type == JsonHidden); - index = sentinelValue.m_nextIndex; - for (;;) - { - AssertNotEnd(index); - auto& value = GetValue(index); - - if (value.m_type != JsonHidden && value.Name() == name) - { - result = index; - break; - } - - if (index == lastIndex) - { - break; - } - - index = value.m_nextIndex; - } - } - } - return result; -} - -unsigned JsonBuilder::count(const_iterator const& itParent) const throw() -{ - ValidateIterator(itParent); - - unsigned result = 0; - if (CanIterateOver(itParent)) - { - auto index = FirstChild(itParent.m_index); - auto const lastIndex = LastChild(itParent.m_index); - if (index != lastIndex) - { - AssertNotEnd(index); - auto& sentinelValue = GetValue(index); - assert(sentinelValue.m_type == JsonHidden); - index = sentinelValue.m_nextIndex; - for (;;) - { - AssertNotEnd(index); - auto& value = GetValue(index); - - if (value.m_type != JsonHidden) - { - ++result; - } - - if (index == lastIndex) - { - break; - } - - index = value.m_nextIndex; - } - } - } - return result; -} - -JsonBuilder::iterator JsonBuilder::begin(const_iterator const& itParent) throw() -{ - return iterator(cbegin(itParent)); -} - -JsonBuilder::const_iterator -JsonBuilder::begin(const_iterator const& itParent) const throw() -{ - return cbegin(itParent); -} - -JsonBuilder::const_iterator -JsonBuilder::cbegin(const_iterator const& itParent) const throw() -{ - ValidateIterator(itParent); - Index index = 0; - if (CanIterateOver(itParent)) - { - index = NextIndex(FirstChild(itParent.m_index)); - } - return const_iterator(this, index); -} - -JsonBuilder::iterator JsonBuilder::end(const_iterator const& itParent) throw() -{ - return iterator(cend(itParent)); -} - -JsonBuilder::const_iterator -JsonBuilder::end(const_iterator const& itParent) const throw() -{ - return cend(itParent); -} - -JsonBuilder::const_iterator -JsonBuilder::cend(const_iterator const& itParent) const throw() -{ - ValidateIterator(itParent); - Index index = 0; - if (CanIterateOver(itParent)) - { - index = NextIndex(LastChild(itParent.m_index)); - } - return const_iterator(this, index); -} - -JsonBuilder::iterator JsonBuilder::AddValue( - bool front, - const_iterator const& itParent, - std::string_view const& name, - JsonType type, - unsigned cbData, - void const* pbData) -{ - ValidateIterator(itParent); - EnsureRootExists(); - ValidateParentIterator(itParent.m_index); - Index const newIndex = CreateValue(name, type, cbData, pbData); - - // Find the right place in the linked list for the new node. - // Update the parent's lastChildIndex if necessary. - - auto& parentValue = GetValue(itParent.m_index); // GetValue(parent) must be - // AFTER the CreateValue. - Index prevIndex; // The node that the new node goes after. - if (front) - { - prevIndex = FirstChild(itParent.m_index); - if (prevIndex == parentValue.m_lastChildIndex) - { - parentValue.m_lastChildIndex = newIndex; - } - } - else - { - prevIndex = parentValue.m_lastChildIndex; - parentValue.m_lastChildIndex = newIndex; - } - - // Insert the new node into the linked list after prev. - - auto& prevValue = GetValue(prevIndex); - auto& newValue = GetValue(newIndex); - newValue.m_nextIndex = prevValue.m_nextIndex; - prevValue.m_nextIndex = newIndex; - - return iterator(const_iterator(this, newIndex)); -} - -void JsonBuilder::AssertNotEnd(Index index) throw() -{ - (void) index; // Unreferenced parameter in release builds. - assert(index != 0); -} - -void JsonBuilder::AssertHidden(JsonType type) throw() -{ - (void) type; // Unreferenced parameter in release builds. - assert(type == JsonHidden); -} - -void JsonBuilder::ValidateIterator(const_iterator const& it) const throw() -{ - assert(it.m_index == 0 || it.m_index < m_storage.size()); - - if (it.m_pContainer != this) - { - assert(!"JsonBuilder: iterator is from a different container"); - std::terminate(); - } -} - -void JsonBuilder::ValidateParentIterator(Index index) const throw() -{ - assert(!m_storage.empty()); - - if (!IS_COMPOSITE_TYPE(GetValue(index).m_type)) - { - assert(!"JsonBuilder: destination must be an array or object"); - std::terminate(); - } -} - -bool JsonBuilder::CanIterateOver(const_iterator const& it) const throw() -{ - return !m_storage.empty() && IS_COMPOSITE_TYPE(GetValue(it.m_index).m_type); -} - -JsonValue const& JsonBuilder::GetValue(Index index) const throw() -{ - return reinterpret_cast(m_storage[index]); -} - -JsonValue& JsonBuilder::GetValue(Index index) throw() -{ - return reinterpret_cast(m_storage[index]); -} - -JsonBuilder::Index JsonBuilder::FirstChild(Index index) const throw() -{ - auto cchName = reinterpret_cast(m_storage[index]).m_cchName; - return index + DATA_OFFSET(cchName); -} - -JsonBuilder::Index JsonBuilder::LastChild(Index index) const throw() -{ - return reinterpret_cast(m_storage[index]).m_lastChildIndex; -} - -JsonBuilder::Index JsonBuilder::NextIndex(Index index) const throw() -{ - assert(index < m_storage.size()); - auto pValue = reinterpret_cast(m_storage.data() + index); - for (;;) - { - assert(index != 0); // assert(it != end()) - index = pValue->m_nextIndex; - pValue = reinterpret_cast(m_storage.data() + index); - if (pValue->m_type != JsonHidden) - { - break; - } - } - return index; -} - -void JsonBuilder::EnsureRootExists() -{ - if (m_storage.empty()) - { - unsigned index = - CreateValue(std::string_view(), JsonObject, 0, nullptr); - if (index != 0) - { - std::terminate(); - } - } -} - -JsonBuilder::Index JsonBuilder::CreateValue( - std::string_view const& name, - JsonType type, - unsigned cbData, - void const* pbData) -{ - if (name.size() > 0xffffff) - { - throw std::invalid_argument("JsonBuilder - cchName too large"); - } - - if (cbData > DataMax) - { - throw std::invalid_argument("JsonBuilder - cbValue too large"); - } - - if (IS_COMPOSITE_TYPE(type)) - { - assert(cbData == 0); - cbData = sizeof(JsonValueBase); - } - - unsigned const cchName = static_cast(name.size()); - unsigned const valueIndex = static_cast(m_storage.size()); - unsigned const dataIndex = valueIndex + DATA_OFFSET(cchName); - unsigned const newStorageSize = - dataIndex + (cbData + StorageSize - 1) / StorageSize; - auto const pOldStorageData = reinterpret_cast(m_storage.data()); - - if (newStorageSize <= valueIndex) - { - throw std::invalid_argument("JsonBuilder - too much data"); - } - - m_storage.resize(newStorageSize); - - JsonValue* const pValue = - reinterpret_cast(m_storage.data() + valueIndex); - pValue->m_nextIndex = 0; - pValue->m_cchName = cchName; - pValue->m_type = type; - - auto pNameData = reinterpret_cast(name.data()); - auto const pNewStorageData = reinterpret_cast(m_storage.data()); - if (pOldStorageData != pNewStorageData && pOldStorageData < pNameData && - pNameData < pOldStorageData + (valueIndex * StorageSize)) - { - // They're copying the name from within the vector, and we just resized - // out from under them. Technically, we could consider this a bug in the - // caller, but it's an easy mistake to make, easy to miss in testing, - // and hard to diagnose if it hits. Dealing with this is easy for us, so - // just fix up the problem instead of making it an error. - pNameData = pNewStorageData + (pNameData - pOldStorageData); - } - - // Write into the memory beyond the JsonValue's end - std::memcpy(reinterpret_cast(pValue + 1), pNameData, cchName); - - if (IS_COMPOSITE_TYPE(type)) - { - pValue->m_lastChildIndex = dataIndex; - - // Set up sentinel node. Insert it into the linked list. - auto pRootValue = reinterpret_cast(m_storage.data()); - auto pSentinel = - reinterpret_cast(m_storage.data() + dataIndex); - pSentinel->m_nextIndex = pRootValue->m_nextIndex; - pSentinel->m_cchName = 0; - pSentinel->m_type = JsonHidden; - pRootValue->m_nextIndex = dataIndex; - } - else - { - pValue->m_cbData = cbData; - - // Set up data. - if (pbData != nullptr) - { - auto pData = static_cast(pbData); - if (pOldStorageData != pNewStorageData && pOldStorageData < pData && - pData < pOldStorageData + (valueIndex * StorageSize)) - { - // They're copying the data from within the vector, and we just - // resized out from under them. Technically, we could consider - // this a bug in the caller, but it's an easy mistake to make, - // easy to miss in testing, and hard to diagnose when it hits. - // Dealing with this is easy for us, so just fix up the problem - // instead of making it an error. - pData = pNewStorageData + (pData - pOldStorageData); - } - - memcpy(m_storage.data() + dataIndex, pData, cbData); - } - } - - return valueIndex; -} - -void swap(JsonBuilder& a, JsonBuilder& b) throw() -{ - a.swap(b); -} - -// JsonImplementType - -/* -The macro-based GetUnchecked and ConvertTo (for f32, u8, u16, u32, i8, i16, -and i32) aren't perfectly optimal... But they're probably close enough. -*/ - -#define IMPLEMENT_AddValue(DataType, DataSize, DataPtr, ValueType) \ - JsonIterator JsonImplementType::AddValue( \ - JsonBuilder& builder, \ - bool front, \ - JsonConstIterator const& itParent, \ - std::string_view const& name, \ - DataType const& data) \ - { \ - return builder.AddValue( \ - front, itParent, name, ValueType, DataSize, DataPtr); \ - } - -#define IMPLEMENT_GetUnchecked(DataType, ValueType) \ - DataType JsonImplementType::GetUnchecked( \ - JsonValue const& value) throw() \ - { \ - return static_cast(GetUnchecked##ValueType(value)); \ - } - -#define IMPLEMENT_JsonImplementType(DataType, ValueType) \ - IMPLEMENT_AddValue(DataType, sizeof(data), &data, ValueType); \ - IMPLEMENT_GetUnchecked(DataType, ValueType); \ - \ - bool JsonImplementType::ConvertTo( \ - JsonValue const& value, DataType& result) throw() \ - { \ - return ConvertTo##ValueType(value, result); \ - } - -// JsonBool - -bool JsonImplementType::GetUnchecked(JsonValue const& value) throw() -{ - assert(value.Type() == JsonBool); - bool result; - unsigned cb; - void const* pb = value.Data(&cb); - switch (cb) - { - case 1: - result = *static_cast(pb) != 0; - break; - case 4: - result = *static_cast(pb) != 0; - break; - default: - result = 0; - assert(!"Invalid size for JsonBool"); - break; - } - return result; -} - -bool JsonImplementType::ConvertTo(JsonValue const& value, bool& result) throw() -{ - bool success; - switch (value.Type()) - { - case JsonBool: - result = GetUnchecked(value); - success = true; - break; - default: - result = false; - success = false; - break; - } - return success; -} - -IMPLEMENT_AddValue(bool, sizeof(data), &data, JsonBool); - -// JsonUInt - -bool JsonImplementType::ConvertTo( - JsonValue const& value, - unsigned long long& result) throw() -{ - static double const UnsignedHuge = 18446744073709551616.0; - - bool success; - switch (value.Type()) - { - case JsonUInt: - result = JsonImplementType::GetUnchecked(value); - success = true; - goto Done; - - case JsonInt: - result = static_cast( - JsonImplementType::GetUnchecked(value)); - if (result < 0x8000000000000000) - { - success = true; - goto Done; - } - break; - - case JsonFloat: { - auto f = JsonImplementType::GetUnchecked(value); - if (0.0 <= f && f < UnsignedHuge) - { - result = static_cast(f); - success = true; - goto Done; - } - break; - } - - default: - break; - } - - result = 0; - success = false; - -Done: - - return success; -} - -static uint64_t GetUncheckedJsonUInt(JsonValue const& value) -{ - assert(value.Type() == JsonUInt); - uint64_t result; - unsigned cb; - void const* pb = value.Data(&cb); - switch (cb) - { - case 1: - result = *static_cast(pb); - break; - case 2: - result = *static_cast(pb); - break; - case 4: - result = *static_cast(pb); - break; - case 8: - result = *static_cast(pb); - break; - default: - result = 0; - assert(!"Invalid size for JsonUInt"); - break; - } - return result; -} - -IMPLEMENT_AddValue(unsigned long long, sizeof(data), &data, JsonUInt); -IMPLEMENT_GetUnchecked(unsigned long long, JsonUInt); - -template -static bool ConvertToJsonUInt(JsonValue const& value, T& result) -{ - unsigned long long implResult; - bool success; - if (JsonImplementType::ConvertTo(value, implResult) && - implResult <= (0xffffffffffffffff >> (64 - sizeof(T) * 8))) - { - result = static_cast(implResult); - success = true; - } - else - { - result = 0; - success = false; - } - return success; -} - -IMPLEMENT_JsonImplementType(unsigned char, JsonUInt); -IMPLEMENT_JsonImplementType(unsigned short, JsonUInt); -IMPLEMENT_JsonImplementType(unsigned int, JsonUInt); -IMPLEMENT_JsonImplementType(unsigned long, JsonUInt); - -// JsonInt - -bool JsonImplementType::ConvertTo( - JsonValue const& value, - signed long long& result) throw() -{ - static double const SignedHuge = 9223372036854775808.0; - - bool success; - switch (value.Type()) - { - case JsonInt: - result = JsonImplementType::GetUnchecked(value); - success = true; - goto Done; - - case JsonUInt: - result = static_cast( - JsonImplementType::GetUnchecked(value)); - if (result >= 0) - { - success = true; - goto Done; - } - break; - - case JsonFloat: { - auto f = JsonImplementType::GetUnchecked(value); - if (-SignedHuge <= f && f < SignedHuge) - { - result = static_cast(f); - success = true; - goto Done; - } - break; - } - - default: - break; - } - - result = 0; - success = false; - -Done: - - return success; -} - -static int64_t GetUncheckedJsonInt(JsonValue const& value) -{ - assert(value.Type() == JsonInt); - int64_t result; - unsigned cb; - void const* pb = value.Data(&cb); - switch (cb) - { - case 1: - result = *static_cast(pb); - break; - case 2: - result = *static_cast(pb); - break; - case 4: - result = *static_cast(pb); - break; - case 8: - result = *static_cast(pb); - break; - default: - result = 0; - assert(!"Invalid size for JsonInt"); - break; - } - return result; -} - -IMPLEMENT_AddValue(signed long long, sizeof(data), &data, JsonInt); -IMPLEMENT_GetUnchecked(signed long long, JsonInt); - -template -static bool ConvertToJsonInt(JsonValue const& value, T& result) -{ - signed long long implResult; - bool success; - - if (JsonImplementType::ConvertTo(value, implResult) && - (sizeof(T) == sizeof(signed long long) || - (implResult < (1ll << (sizeof(T) * 8 - 1)) && - implResult >= -(1ll << (sizeof(T) * 8 - 1))))) - { - result = static_cast(implResult); - success = true; - } - else - { - result = 0; - success = false; - } - return success; -} - -IMPLEMENT_JsonImplementType(signed char, JsonInt); -IMPLEMENT_JsonImplementType(signed short, JsonInt); -IMPLEMENT_JsonImplementType(signed int, JsonInt); -IMPLEMENT_JsonImplementType(signed long, JsonInt); - -// JsonFloat - -double JsonImplementType::GetUnchecked(JsonValue const& value) throw() -{ - assert(value.Type() == JsonFloat); - double result; - unsigned cb; - void const* pb = value.Data(&cb); - switch (cb) - { - case 4: - result = *static_cast(pb); - break; - case 8: - result = *static_cast(pb); - break; - default: - result = 0; - assert(!"Invalid size for JsonFloat"); - break; - } - return result; -} - -bool JsonImplementType::ConvertTo( - JsonValue const& value, - double& result) throw() -{ - bool success; - - switch (value.Type()) - { - case JsonUInt: - result = static_cast( - JsonImplementType::GetUnchecked(value)); - success = true; - break; - - case JsonInt: - result = static_cast( - JsonImplementType::GetUnchecked(value)); - success = true; - break; - - case JsonFloat: - result = GetUnchecked(value); - success = true; - break; - - default: - result = 0.0; - success = false; - break; - } - - return success; -} - -IMPLEMENT_AddValue(double, sizeof(data), &data, JsonFloat); - -#define GetUncheckedJsonFloat(value) \ - JsonImplementType::GetUnchecked(value) - -template -static bool ConvertToJsonFloat(JsonValue const& value, T& result) -{ - double implResult; - bool success = JsonImplementType::ConvertTo(value, implResult); - result = static_cast(implResult); - return success; -} - -IMPLEMENT_JsonImplementType(float, JsonFloat); - -// JsonUtf8 - -JsonIterator JsonImplementType::AddValue( - JsonBuilder& builder, - bool front, - JsonConstIterator const& itParent, - std::string_view const& name, - char const* psz) -{ - return builder.AddValue( - front, itParent, name, JsonUtf8, static_cast(strlen(psz)), psz); -} - -IMPLEMENT_AddValue(char, sizeof(data), &data, JsonUtf8); -IMPLEMENT_AddValue(std::string, data.size(), data.data(), JsonUtf8); - -std::string_view -JsonImplementType::GetUnchecked(JsonValue const& value) throw() -{ - assert(value.Type() == JsonUtf8); - unsigned cb; - void const* pb = value.Data(&cb); - return std::string_view(static_cast(pb), cb); -} - -bool JsonImplementType::ConvertTo( - JsonValue const& value, - std::string_view& result) throw() -{ - bool success; - - if (value.Type() == JsonUtf8) - { - result = GetUnchecked(value); - success = true; - } - else - { - result = std::string_view(); - success = false; - } - - return success; -} - -JsonIterator JsonImplementType::AddValue( - JsonBuilder& builder, - bool front, - JsonConstIterator const& itParent, - std::string_view const& name, - std::string_view const& data) -{ - return builder.AddValue( - front, - itParent, - name, - JsonUtf8, - static_cast(data.size()), - data.data()); -} - -// JsonTime - -JsonIterator JsonImplementType::AddValue( - JsonBuilder& builder, - bool front, - JsonConstIterator const& itParent, - std::string_view const& name, - std::chrono::system_clock::time_point const& data) -{ - std::chrono::nanoseconds nanosSinceEpoch = - std::chrono::duration_cast( - data.time_since_epoch()); - - int64_t nanosInt64 = nanosSinceEpoch.count(); - return builder.AddValue( - front, itParent, name, JsonTime, sizeof(nanosInt64), &nanosInt64); -} - -bool JsonImplementType::ConvertTo( - JsonValue const& jsonValue, - std::chrono::system_clock::time_point& value) throw() -{ - bool success; - - if (jsonValue.Type() == JsonTime) - { - value = jsonValue.GetUnchecked(); - success = true; - } - else - { - value = std::chrono::system_clock::time_point{}; - success = false; - } - - return success; -} - -std::chrono::system_clock::time_point -JsonImplementType::GetUnchecked( - JsonValue const& jsonValue) throw() -{ - assert(jsonValue.Type() == JsonTime); - assert(jsonValue.DataSize() == 8); - if (jsonValue.DataSize() == 8) - { - int64_t nanosSinceEpoch = *static_cast(jsonValue.Data()); - return std::chrono::system_clock::time_point{ std::chrono::nanoseconds{ - nanosSinceEpoch } }; - } - else - { - return {}; - } -} - -// JsonUuid - -IMPLEMENT_AddValue(UuidStruct, sizeof(UuidStruct), &data, JsonUuid); - -bool JsonImplementType::ConvertTo( - JsonValue const& jsonValue, - UuidStruct& value) throw() -{ - bool success; - - if (jsonValue.Type() == JsonUuid) - { - assert(jsonValue.DataSize() == 16); - value = *static_cast(jsonValue.Data()); - success = true; - } - else - { - value = UuidStruct(); - success = false; - } - - return success; -} - -UuidStruct -JsonImplementType::GetUnchecked(JsonValue const& jsonValue) throw() -{ - assert(jsonValue.Type() == JsonUuid); - assert(jsonValue.DataSize() == 16); - - return jsonValue.DataSize() == 16 ? - *static_cast(jsonValue.Data()) : - UuidStruct(); -} - -} // namespace jsonbuilder +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +#include + +#include + +#define StorageSize sizeof(JsonValue::StoragePod) +#define DataMax 0xf0000000 + +/* +Computes the difference between a value's index and the index at which its +data begins. Used to determine the DataIndex for a value: +value.DataIndex = value.Index + DATA_OFFSET(value.cchName). +*/ +#define DATA_OFFSET(cchName) \ + (((cchName) + sizeof(JsonValue) + (StorageSize - 1)) / StorageSize) + +#define IS_SPECIAL_TYPE(type) (JsonHidden <= (type)) +#define IS_NORMAL_TYPE(type) ((type) < JsonHidden) +#define IS_COMPOSITE_TYPE(type) (JsonArray <= (type)) + +// JsonValue + +namespace jsonbuilder { +// Verify that our conditions like (type >= JsonArray) will work. +static_assert(JsonHidden == 253, "Incorrect enum numbering for JsonHidden"); +static_assert(JsonArray == 254, "Incorrect enum numbering for JsonArray"); +static_assert(JsonObject == 255, "Incorrect enum numbering for JsonObject"); + +static_assert( + sizeof(unsigned) >= 4, + "JsonValue assumes that unsigned is at least 32 bits"); +static_assert(sizeof(JsonValueBase) == 8, "JsonValueBase changed size"); +static_assert(sizeof(JsonValue) == 12, "JsonValue changed size"); + +JsonType JsonValue::Type() const throw() +{ + return static_cast(m_type); +} + +std::string_view JsonValue::Name() const throw() +{ + return std::string_view(reinterpret_cast(this + 1), m_cchName); +} + +void const* JsonValue::Data(unsigned* pcbData) const throw() +{ + return const_cast(this)->Data(pcbData); +} + +void* JsonValue::Data(unsigned* pcbData) throw() +{ + assert(!IS_SPECIAL_TYPE(m_type)); // Can't call Data() on hidden, + // object, or array values. + if (pcbData != nullptr) + { + *pcbData = m_cbData; + } + + return reinterpret_cast(this) + DATA_OFFSET(m_cchName); +} + +unsigned JsonValue::DataSize() const throw() +{ + assert(!IS_SPECIAL_TYPE(m_type)); // Can't call DataSize() on hidden, + // object, or array values. + return m_cbData; +} + +void JsonValue::ReduceDataSize(unsigned cbNew) throw() +{ + if (IS_SPECIAL_TYPE(m_type) || cbNew > m_cbData) + { + assert(!"JsonBuilder: invalid use of ReduceDataSize()."); + std::terminate(); + } + + m_cbData = cbNew; +} + +bool JsonValue::IsNull() const throw() +{ + return m_type == JsonNull; +} + +// JsonConstIterator + +JsonConstIterator::JsonConstIterator() throw() : m_pContainer(), m_index() +{ + return; +} + +JsonConstIterator::JsonConstIterator(JsonBuilder const* pContainer, Index index) throw() + : m_pContainer(pContainer), m_index(index) +{ + return; +} + +bool JsonConstIterator::operator==(JsonConstIterator const& other) const throw() +{ + assert(m_pContainer == other.m_pContainer); + return m_index == other.m_index; +} + +bool JsonConstIterator::operator!=(JsonConstIterator const& other) const throw() +{ + assert(m_pContainer == other.m_pContainer); + return m_index != other.m_index; +} + +JsonConstIterator::reference JsonConstIterator::operator*() const throw() +{ + m_pContainer->AssertNotEnd(m_index); // Do not dereference the end() + // iterator. + return m_pContainer->GetValue(m_index); +} + +JsonConstIterator::pointer JsonConstIterator::operator->() const throw() +{ + m_pContainer->AssertNotEnd(m_index); // Do not dereference the end() + // iterator. + return &m_pContainer->GetValue(m_index); +} + +JsonConstIterator& JsonConstIterator::operator++() throw() +{ + m_index = m_pContainer->NextIndex(m_index); // Implicitly asserts !end(). + return *this; +} + +JsonConstIterator JsonConstIterator::operator++(int) throw() +{ + auto old = *this; + m_index = m_pContainer->NextIndex(m_index); // Implicitly asserts !end(). + return old; +} + +JsonConstIterator JsonConstIterator::begin() const throw() +{ + return m_pContainer->begin(*this); +} + +JsonConstIterator JsonConstIterator::end() const throw() +{ + return m_pContainer->end(*this); +} + +bool JsonConstIterator::IsRoot() const throw() +{ + return m_index == 0; +} + +// JsonIterator + +JsonIterator::JsonIterator() throw() : JsonConstIterator() +{ + return; +} + +JsonIterator::JsonIterator(JsonConstIterator const& other) throw() + : JsonConstIterator(other) +{ + return; +} + +JsonIterator::reference JsonIterator::operator*() const throw() +{ + return const_cast(JsonConstIterator::operator*()); +} + +JsonIterator::pointer JsonIterator::operator->() const throw() +{ + return const_cast(JsonConstIterator::operator->()); +} + +JsonIterator& JsonIterator::operator++() throw() +{ + JsonConstIterator::operator++(); + return *this; +} + +JsonIterator JsonIterator::operator++(int) throw() +{ + JsonIterator old(*this); + JsonConstIterator::operator++(); + return old; +} + +JsonIterator JsonIterator::begin() const throw() +{ + return JsonIterator(JsonConstIterator::begin()); +} + +JsonIterator JsonIterator::end() const throw() +{ + return JsonIterator(JsonConstIterator::end()); +} + +// JsonBuilder::Validator + +constexpr unsigned char MapBits = 2; +constexpr unsigned char MapMask = (1 << MapBits) - 1; +constexpr unsigned char MapPerByte = 8 / MapBits; +#define MapSize(cStorage) ((cStorage) / MapPerByte + 1u) + +JsonBuilder::Validator::~Validator() +{ + Deallocate(m_pMap); +} + +JsonBuilder::Validator::Validator( + JsonValue::StoragePod const* pStorage, + size_type cStorage) + : m_pStorage(pStorage) + , m_size(cStorage) + , m_pMap(static_cast(Reallocate(nullptr, MapSize(cStorage)))) +{ + static_assert( + ValMax <= (1 << MapBits), "Too many ValidationStates for MapBits"); + assert(m_size != 0); // No need to validate an empty builder. + return; +} + +void JsonBuilder::Validator::Validate() +{ + memset(m_pMap, 0, MapSize(m_size)); + + // Traverse the linked list, starting at head. + // Ensure that items fit in storage without overlap. + // Ensure that there are no loops. + // Mark all valid heads. + Index index = 0; + do + { + static_assert( + sizeof(JsonValueBase) % StorageSize == 0, + "JsonValueBase not a multiple of StorageSize"); + static_assert( + sizeof(JsonValue) % StorageSize == 0, + "JsonValue not a multiple of StorageSize"); + + // Note: don't dereference pValue until the appropriate part has been + // validated. + JsonValue const* const pValue = + reinterpret_cast(m_pStorage + index); + + // Mark start of JsonValueBase as head. + UpdateMap(index, ValNone, ValHead); + + // Mark remainder of JsonValueBase as tail. + Index const baseEnd = sizeof(JsonValueBase) / StorageSize; + for (Index i = 1; i != baseEnd; i++) + { + UpdateMap(index + i, ValNone, ValTail); + } + + // Now safe to dereference: m_nextIndex, m_cchName, m_type. + + if (pValue->m_type != JsonHidden) + { + // Mark m_cbData/m_lastChildIndex, Name as tail. + Index const nameEnd = DATA_OFFSET(pValue->m_cchName); + for (Index i = baseEnd; i != nameEnd; i++) + { + UpdateMap(index + i, ValNone, ValTail); + } + + // Now safe to dereference: m_cbData/m_lastChildIndex, Name. + + if (IS_NORMAL_TYPE(pValue->m_type)) + { + if (pValue->m_cbData > DataMax) + { + throw std::invalid_argument("JsonBuilder - corrupt data"); + } + + // Mark Data as tail. + Index const dataEnd = nameEnd + + (pValue->m_cbData + StorageSize - 1) / StorageSize; + for (Index i = nameEnd; i != dataEnd; i++) + { + UpdateMap(index + i, ValNone, ValTail); + } + } + } + + index = pValue->m_nextIndex; + } while (index != 0); + + // Validate root. + UpdateMap(0, ValHead, ValReached); + if (reinterpret_cast(m_pStorage)->m_cchName != 0 || + reinterpret_cast(m_pStorage)->m_type != JsonObject) + { + throw std::invalid_argument("JsonBuilder - corrupt data"); + } + + // Traverse the tree, starting at root. + // Ensure that all reachable indexes are valid heads. + // Ensure that there are no child->parent loops (no head reached more than + // once). + ValidateRecurse(0); + + return; +} + +void JsonBuilder::Validator::ValidateRecurse(Index parent) +{ + JsonValue const* const pParent = + reinterpret_cast(m_pStorage + parent); + + // Validate first child (always hidden/sentinel). + + Index child = parent + DATA_OFFSET(pParent->m_cchName); + UpdateMap(child, ValHead, ValReached); + JsonValue const* pChild = + reinterpret_cast(m_pStorage + child); + + if (pChild->m_type != JsonHidden) + { + throw std::invalid_argument("JsonBuilder - corrupt data"); + } + + // Validate remaining children. + + while (child != pParent->m_lastChildIndex) + { + child = pChild->m_nextIndex; + UpdateMap(child, ValHead, ValReached); + pChild = reinterpret_cast(m_pStorage + child); + + if (IS_COMPOSITE_TYPE(pChild->m_type)) + { + ValidateRecurse(child); + } + } + + return; +} + +void JsonBuilder::Validator::UpdateMap( + Index index, + ValidationState expectedVal, + ValidationState newVal) +{ + assert(newVal == (expectedVal | newVal)); + Index i = index / MapPerByte; + char shift = (index % MapPerByte) * MapBits; + if (m_size <= index || ((m_pMap[i] >> shift) & MapMask) != expectedVal) + { + throw std::invalid_argument("JsonBuilder - corrupt data"); + } + + m_pMap[i] |= newVal << shift; + assert(newVal == ((m_pMap[i] >> shift) & MapMask)); +} + +// JsonBuilder + +JsonBuilder::JsonBuilder() throw() +{ + return; +} + +JsonBuilder::JsonBuilder(size_type cbInitialCapacity) +{ + buffer_reserve(cbInitialCapacity); +} + +JsonBuilder::JsonBuilder(JsonBuilder const& other) : m_storage(other.m_storage) +{ + return; +} + +JsonBuilder::JsonBuilder(JsonBuilder&& other) throw() + : m_storage(std::move(other.m_storage)) +{ + return; +} + +JsonBuilder::JsonBuilder(void const* pbRawData, size_type cbRawData, bool validateData) + : m_storage( + static_cast(pbRawData), + static_cast(cbRawData / StorageSize)) +{ + if (cbRawData % StorageSize != 0 || + cbRawData / StorageSize > StorageVec::max_size()) + { + throw std::invalid_argument("cbRawData invalid"); + } + else if (validateData) + { + ValidateData(); + } +} + +JsonBuilder& JsonBuilder::operator=(JsonBuilder const& other) +{ + m_storage = other.m_storage; + return *this; +} + +JsonBuilder& JsonBuilder::operator=(JsonBuilder&& other) throw() +{ + m_storage = std::move(other.m_storage); + return *this; +} + +void JsonBuilder::ValidateData() const +{ + if (!m_storage.empty()) + { + Validator(m_storage.data(), m_storage.size()).Validate(); + } +} + +JsonBuilder::iterator JsonBuilder::begin() throw() +{ + return iterator(cbegin()); +} + +JsonBuilder::const_iterator JsonBuilder::begin() const throw() +{ + return cbegin(); +} + +JsonBuilder::const_iterator JsonBuilder::cbegin() const throw() +{ + Index index = 0; + if (!m_storage.empty()) + { + // Return index of first non-hidden node after root. + index = GetValue(0).m_nextIndex; + for (;;) + { + auto& value = GetValue(index); + if (value.m_type != JsonHidden) + { + break; + } + + AssertNotEnd(index); // end() should never be hidden. + index = value.m_nextIndex; + } + } + return const_iterator(this, index); +} + +JsonBuilder::iterator JsonBuilder::end() throw() +{ + return iterator(cend()); +} + +JsonBuilder::const_iterator JsonBuilder::end() const throw() +{ + return cend(); +} + +JsonBuilder::const_iterator JsonBuilder::cend() const throw() +{ + return const_iterator(this, 0); +} + +JsonBuilder::iterator JsonBuilder::root() throw() +{ + return end(); +} + +JsonBuilder::const_iterator JsonBuilder::root() const throw() +{ + return end(); +} + +JsonBuilder::const_iterator JsonBuilder::croot() const throw() +{ + return end(); +} + +JsonBuilder::size_type JsonBuilder::buffer_size() const throw() +{ + return m_storage.size() * StorageSize; +} + +void const* JsonBuilder::buffer_data() const throw() +{ + return m_storage.data(); +} + +JsonBuilder::size_type JsonBuilder::buffer_capacity() const throw() +{ + return m_storage.capacity() * StorageSize; +} + +void JsonBuilder::buffer_reserve(size_type cbMinimumCapacity) +{ + if (cbMinimumCapacity > StorageVec::max_size() * StorageSize) + { + throw std::length_error("requested capacity is too large"); + } + auto const cItems = (cbMinimumCapacity + StorageSize - 1) / StorageSize; + m_storage.reserve(static_cast(cItems)); +} + +void JsonBuilder::clear() throw() +{ + m_storage.clear(); +} + +JsonBuilder::iterator JsonBuilder::erase(const_iterator itValue) throw() +{ + ValidateIterator(itValue); + if (itValue.m_index == 0) + { + assert(!"JsonBuilder: cannot erase end()"); + std::terminate(); + } + + GetValue(itValue.m_index).m_type = JsonHidden; + return iterator(const_iterator(this, NextIndex(itValue.m_index))); +} + +JsonBuilder::iterator +JsonBuilder::erase(const_iterator itBegin, const_iterator itEnd) throw() +{ + ValidateIterator(itBegin); + ValidateIterator(itEnd); + auto index = itBegin.m_index; + while (index != itEnd.m_index) + { + if (index == 0) + { + assert(!"JsonBuilder: invalid use of ReduceDataSize()."); + std::terminate(); + } + + auto& value = GetValue(index); + value.m_type = JsonHidden; + index = value.m_nextIndex; + } + return iterator(itEnd); +} + +void JsonBuilder::swap(JsonBuilder& other) throw() +{ + m_storage.swap(other.m_storage); +} + +unsigned +JsonBuilder::FindImpl(Index parentIndex, std::string_view const& name) const + throw() +{ + Index result = 0; + if (!m_storage.empty() && IS_COMPOSITE_TYPE(GetValue(parentIndex).m_type)) + { + auto index = FirstChild(parentIndex); + auto const lastIndex = LastChild(parentIndex); + if (index != lastIndex) + { + AssertNotEnd(index); + auto& sentinelValue = GetValue(index); + assert(sentinelValue.m_type == JsonHidden); + index = sentinelValue.m_nextIndex; + for (;;) + { + AssertNotEnd(index); + auto& value = GetValue(index); + + if (value.m_type != JsonHidden && value.Name() == name) + { + result = index; + break; + } + + if (index == lastIndex) + { + break; + } + + index = value.m_nextIndex; + } + } + } + return result; +} + +unsigned JsonBuilder::count(const_iterator const& itParent) const throw() +{ + ValidateIterator(itParent); + + unsigned result = 0; + if (CanIterateOver(itParent)) + { + auto index = FirstChild(itParent.m_index); + auto const lastIndex = LastChild(itParent.m_index); + if (index != lastIndex) + { + AssertNotEnd(index); + auto& sentinelValue = GetValue(index); + assert(sentinelValue.m_type == JsonHidden); + index = sentinelValue.m_nextIndex; + for (;;) + { + AssertNotEnd(index); + auto& value = GetValue(index); + + if (value.m_type != JsonHidden) + { + ++result; + } + + if (index == lastIndex) + { + break; + } + + index = value.m_nextIndex; + } + } + } + return result; +} + +JsonBuilder::iterator JsonBuilder::begin(const_iterator const& itParent) throw() +{ + return iterator(cbegin(itParent)); +} + +JsonBuilder::const_iterator +JsonBuilder::begin(const_iterator const& itParent) const throw() +{ + return cbegin(itParent); +} + +JsonBuilder::const_iterator +JsonBuilder::cbegin(const_iterator const& itParent) const throw() +{ + ValidateIterator(itParent); + Index index = 0; + if (CanIterateOver(itParent)) + { + index = NextIndex(FirstChild(itParent.m_index)); + } + return const_iterator(this, index); +} + +JsonBuilder::iterator JsonBuilder::end(const_iterator const& itParent) throw() +{ + return iterator(cend(itParent)); +} + +JsonBuilder::const_iterator +JsonBuilder::end(const_iterator const& itParent) const throw() +{ + return cend(itParent); +} + +JsonBuilder::const_iterator +JsonBuilder::cend(const_iterator const& itParent) const throw() +{ + ValidateIterator(itParent); + Index index = 0; + if (CanIterateOver(itParent)) + { + index = NextIndex(LastChild(itParent.m_index)); + } + return const_iterator(this, index); +} + +JsonBuilder::iterator JsonBuilder::AddValue( + bool front, + const_iterator const& itParent, + std::string_view const& name, + JsonType type, + unsigned cbData, + void const* pbData) +{ + ValidateIterator(itParent); + EnsureRootExists(); + ValidateParentIterator(itParent.m_index); + Index const newIndex = CreateValue(name, type, cbData, pbData); + + // Find the right place in the linked list for the new node. + // Update the parent's lastChildIndex if necessary. + + auto& parentValue = GetValue(itParent.m_index); // GetValue(parent) must be + // AFTER the CreateValue. + Index prevIndex; // The node that the new node goes after. + if (front) + { + prevIndex = FirstChild(itParent.m_index); + if (prevIndex == parentValue.m_lastChildIndex) + { + parentValue.m_lastChildIndex = newIndex; + } + } + else + { + prevIndex = parentValue.m_lastChildIndex; + parentValue.m_lastChildIndex = newIndex; + } + + // Insert the new node into the linked list after prev. + + auto& prevValue = GetValue(prevIndex); + auto& newValue = GetValue(newIndex); + newValue.m_nextIndex = prevValue.m_nextIndex; + prevValue.m_nextIndex = newIndex; + + return iterator(const_iterator(this, newIndex)); +} + +void JsonBuilder::AssertNotEnd(Index index) throw() +{ + (void) index; // Unreferenced parameter in release builds. + assert(index != 0); +} + +void JsonBuilder::AssertHidden(JsonType type) throw() +{ + (void) type; // Unreferenced parameter in release builds. + assert(type == JsonHidden); +} + +void JsonBuilder::ValidateIterator(const_iterator const& it) const throw() +{ + assert(it.m_index == 0 || it.m_index < m_storage.size()); + + if (it.m_pContainer != this) + { + assert(!"JsonBuilder: iterator is from a different container"); + std::terminate(); + } +} + +void JsonBuilder::ValidateParentIterator(Index index) const throw() +{ + assert(!m_storage.empty()); + + if (!IS_COMPOSITE_TYPE(GetValue(index).m_type)) + { + assert(!"JsonBuilder: destination must be an array or object"); + std::terminate(); + } +} + +bool JsonBuilder::CanIterateOver(const_iterator const& it) const throw() +{ + return !m_storage.empty() && IS_COMPOSITE_TYPE(GetValue(it.m_index).m_type); +} + +JsonValue const& JsonBuilder::GetValue(Index index) const throw() +{ + return reinterpret_cast(m_storage[index]); +} + +JsonValue& JsonBuilder::GetValue(Index index) throw() +{ + return reinterpret_cast(m_storage[index]); +} + +JsonBuilder::Index JsonBuilder::FirstChild(Index index) const throw() +{ + auto cchName = reinterpret_cast(m_storage[index]).m_cchName; + return index + DATA_OFFSET(cchName); +} + +JsonBuilder::Index JsonBuilder::LastChild(Index index) const throw() +{ + return reinterpret_cast(m_storage[index]).m_lastChildIndex; +} + +JsonBuilder::Index JsonBuilder::NextIndex(Index index) const throw() +{ + assert(index < m_storage.size()); + auto pValue = reinterpret_cast(m_storage.data() + index); + for (;;) + { + assert(index != 0); // assert(it != end()) + index = pValue->m_nextIndex; + pValue = reinterpret_cast(m_storage.data() + index); + if (pValue->m_type != JsonHidden) + { + break; + } + } + return index; +} + +void JsonBuilder::EnsureRootExists() +{ + if (m_storage.empty()) + { + unsigned index = + CreateValue(std::string_view(), JsonObject, 0, nullptr); + if (index != 0) + { + std::terminate(); + } + } +} + +JsonBuilder::Index JsonBuilder::CreateValue( + std::string_view const& name, + JsonType type, + unsigned cbData, + void const* pbData) +{ + if (name.size() > 0xffffff) + { + throw std::invalid_argument("JsonBuilder - cchName too large"); + } + + if (cbData > DataMax) + { + throw std::invalid_argument("JsonBuilder - cbValue too large"); + } + + if (IS_COMPOSITE_TYPE(type)) + { + assert(cbData == 0); + cbData = sizeof(JsonValueBase); + } + + unsigned const cchName = static_cast(name.size()); + unsigned const valueIndex = static_cast(m_storage.size()); + unsigned const dataIndex = valueIndex + DATA_OFFSET(cchName); + unsigned const newStorageSize = + dataIndex + (cbData + StorageSize - 1) / StorageSize; + auto const pOldStorageData = reinterpret_cast(m_storage.data()); + + if (newStorageSize <= valueIndex) + { + throw std::invalid_argument("JsonBuilder - too much data"); + } + + m_storage.resize(newStorageSize); + + JsonValue* const pValue = + reinterpret_cast(m_storage.data() + valueIndex); + pValue->m_nextIndex = 0; + pValue->m_cchName = cchName; + pValue->m_type = type; + + auto pNameData = reinterpret_cast(name.data()); + auto const pNewStorageData = reinterpret_cast(m_storage.data()); + if (pOldStorageData != pNewStorageData && pOldStorageData < pNameData && + pNameData < pOldStorageData + (valueIndex * StorageSize)) + { + // They're copying the name from within the vector, and we just resized + // out from under them. Technically, we could consider this a bug in the + // caller, but it's an easy mistake to make, easy to miss in testing, + // and hard to diagnose if it hits. Dealing with this is easy for us, so + // just fix up the problem instead of making it an error. + pNameData = pNewStorageData + (pNameData - pOldStorageData); + } + + // Write into the memory beyond the JsonValue's end + std::memcpy(reinterpret_cast(pValue + 1), pNameData, cchName); + + if (IS_COMPOSITE_TYPE(type)) + { + pValue->m_lastChildIndex = dataIndex; + + // Set up sentinel node. Insert it into the linked list. + auto pRootValue = reinterpret_cast(m_storage.data()); + auto pSentinel = + reinterpret_cast(m_storage.data() + dataIndex); + pSentinel->m_nextIndex = pRootValue->m_nextIndex; + pSentinel->m_cchName = 0; + pSentinel->m_type = JsonHidden; + pRootValue->m_nextIndex = dataIndex; + } + else + { + pValue->m_cbData = cbData; + + // Set up data. + if (pbData != nullptr) + { + auto pData = static_cast(pbData); + if (pOldStorageData != pNewStorageData && pOldStorageData < pData && + pData < pOldStorageData + (valueIndex * StorageSize)) + { + // They're copying the data from within the vector, and we just + // resized out from under them. Technically, we could consider + // this a bug in the caller, but it's an easy mistake to make, + // easy to miss in testing, and hard to diagnose when it hits. + // Dealing with this is easy for us, so just fix up the problem + // instead of making it an error. + pData = pNewStorageData + (pData - pOldStorageData); + } + + memcpy(m_storage.data() + dataIndex, pData, cbData); + } + } + + return valueIndex; +} + +void swap(JsonBuilder& a, JsonBuilder& b) throw() +{ + a.swap(b); +} + +// JsonImplementType + +/* +The macro-based GetUnchecked and ConvertTo (for f32, u8, u16, u32, i8, i16, +and i32) aren't perfectly optimal... But they're probably close enough. +*/ + +#define IMPLEMENT_AddValue(DataType, DataSize, DataPtr, ValueType) \ + JsonIterator JsonImplementType::AddValue( \ + JsonBuilder& builder, \ + bool front, \ + JsonConstIterator const& itParent, \ + std::string_view const& name, \ + DataType const& data) \ + { \ + return builder.AddValue( \ + front, itParent, name, ValueType, DataSize, DataPtr); \ + } + +#define IMPLEMENT_GetUnchecked(DataType, ValueType) \ + DataType JsonImplementType::GetUnchecked( \ + JsonValue const& value) throw() \ + { \ + return static_cast(GetUnchecked##ValueType(value)); \ + } + +#define IMPLEMENT_JsonImplementType(DataType, ValueType) \ + IMPLEMENT_AddValue(DataType, sizeof(data), &data, ValueType); \ + IMPLEMENT_GetUnchecked(DataType, ValueType); \ + \ + bool JsonImplementType::ConvertTo( \ + JsonValue const& value, DataType& result) throw() \ + { \ + return ConvertTo##ValueType(value, result); \ + } + +// JsonBool + +bool JsonImplementType::GetUnchecked(JsonValue const& value) throw() +{ + assert(value.Type() == JsonBool); + bool result; + unsigned cb; + void const* pb = value.Data(&cb); + switch (cb) + { + case 1: + result = *static_cast(pb) != 0; + break; + case 4: + result = *static_cast(pb) != 0; + break; + default: + result = 0; + assert(!"Invalid size for JsonBool"); + break; + } + return result; +} + +bool JsonImplementType::ConvertTo(JsonValue const& value, bool& result) throw() +{ + bool success; + switch (value.Type()) + { + case JsonBool: + result = GetUnchecked(value); + success = true; + break; + default: + result = false; + success = false; + break; + } + return success; +} + +IMPLEMENT_AddValue(bool, sizeof(data), &data, JsonBool); + +// JsonUInt + +bool JsonImplementType::ConvertTo( + JsonValue const& value, + unsigned long long& result) throw() +{ + static double const UnsignedHuge = 18446744073709551616.0; + + bool success; + switch (value.Type()) + { + case JsonUInt: + result = JsonImplementType::GetUnchecked(value); + success = true; + goto Done; + + case JsonInt: + result = static_cast( + JsonImplementType::GetUnchecked(value)); + if (result < 0x8000000000000000) + { + success = true; + goto Done; + } + break; + + case JsonFloat: { + auto f = JsonImplementType::GetUnchecked(value); + if (0.0 <= f && f < UnsignedHuge) + { + result = static_cast(f); + success = true; + goto Done; + } + break; + } + + default: + break; + } + + result = 0; + success = false; + +Done: + + return success; +} + +static uint64_t GetUncheckedJsonUInt(JsonValue const& value) +{ + assert(value.Type() == JsonUInt); + uint64_t result; + unsigned cb; + void const* pb = value.Data(&cb); + switch (cb) + { + case 1: + result = *static_cast(pb); + break; + case 2: + result = *static_cast(pb); + break; + case 4: + result = *static_cast(pb); + break; + case 8: + result = *static_cast(pb); + break; + default: + result = 0; + assert(!"Invalid size for JsonUInt"); + break; + } + return result; +} + +IMPLEMENT_AddValue(unsigned long long, sizeof(data), &data, JsonUInt); +IMPLEMENT_GetUnchecked(unsigned long long, JsonUInt); + +template +static bool ConvertToJsonUInt(JsonValue const& value, T& result) +{ + unsigned long long implResult; + bool success; + if (JsonImplementType::ConvertTo(value, implResult) && + implResult <= (0xffffffffffffffff >> (64 - sizeof(T) * 8))) + { + result = static_cast(implResult); + success = true; + } + else + { + result = 0; + success = false; + } + return success; +} + +IMPLEMENT_JsonImplementType(unsigned char, JsonUInt); +IMPLEMENT_JsonImplementType(unsigned short, JsonUInt); +IMPLEMENT_JsonImplementType(unsigned int, JsonUInt); +IMPLEMENT_JsonImplementType(unsigned long, JsonUInt); + +// JsonInt + +bool JsonImplementType::ConvertTo( + JsonValue const& value, + signed long long& result) throw() +{ + static double const SignedHuge = 9223372036854775808.0; + + bool success; + switch (value.Type()) + { + case JsonInt: + result = JsonImplementType::GetUnchecked(value); + success = true; + goto Done; + + case JsonUInt: + result = static_cast( + JsonImplementType::GetUnchecked(value)); + if (result >= 0) + { + success = true; + goto Done; + } + break; + + case JsonFloat: { + auto f = JsonImplementType::GetUnchecked(value); + if (-SignedHuge <= f && f < SignedHuge) + { + result = static_cast(f); + success = true; + goto Done; + } + break; + } + + default: + break; + } + + result = 0; + success = false; + +Done: + + return success; +} + +static int64_t GetUncheckedJsonInt(JsonValue const& value) +{ + assert(value.Type() == JsonInt); + int64_t result; + unsigned cb; + void const* pb = value.Data(&cb); + switch (cb) + { + case 1: + result = *static_cast(pb); + break; + case 2: + result = *static_cast(pb); + break; + case 4: + result = *static_cast(pb); + break; + case 8: + result = *static_cast(pb); + break; + default: + result = 0; + assert(!"Invalid size for JsonInt"); + break; + } + return result; +} + +IMPLEMENT_AddValue(signed long long, sizeof(data), &data, JsonInt); +IMPLEMENT_GetUnchecked(signed long long, JsonInt); + +template +static bool ConvertToJsonInt(JsonValue const& value, T& result) +{ + signed long long implResult; + bool success; + + if (JsonImplementType::ConvertTo(value, implResult) && + (sizeof(T) == sizeof(signed long long) || + (implResult < (1ll << (sizeof(T) * 8 - 1)) && + implResult >= -(1ll << (sizeof(T) * 8 - 1))))) + { + result = static_cast(implResult); + success = true; + } + else + { + result = 0; + success = false; + } + return success; +} + +IMPLEMENT_JsonImplementType(signed char, JsonInt); +IMPLEMENT_JsonImplementType(signed short, JsonInt); +IMPLEMENT_JsonImplementType(signed int, JsonInt); +IMPLEMENT_JsonImplementType(signed long, JsonInt); + +// JsonFloat + +double JsonImplementType::GetUnchecked(JsonValue const& value) throw() +{ + assert(value.Type() == JsonFloat); + double result; + unsigned cb; + void const* pb = value.Data(&cb); + switch (cb) + { + case 4: + result = *static_cast(pb); + break; + case 8: + result = *static_cast(pb); + break; + default: + result = 0; + assert(!"Invalid size for JsonFloat"); + break; + } + return result; +} + +bool JsonImplementType::ConvertTo( + JsonValue const& value, + double& result) throw() +{ + bool success; + + switch (value.Type()) + { + case JsonUInt: + result = static_cast( + JsonImplementType::GetUnchecked(value)); + success = true; + break; + + case JsonInt: + result = static_cast( + JsonImplementType::GetUnchecked(value)); + success = true; + break; + + case JsonFloat: + result = GetUnchecked(value); + success = true; + break; + + default: + result = 0.0; + success = false; + break; + } + + return success; +} + +IMPLEMENT_AddValue(double, sizeof(data), &data, JsonFloat); + +#define GetUncheckedJsonFloat(value) \ + JsonImplementType::GetUnchecked(value) + +template +static bool ConvertToJsonFloat(JsonValue const& value, T& result) +{ + double implResult; + bool success = JsonImplementType::ConvertTo(value, implResult); + result = static_cast(implResult); + return success; +} + +IMPLEMENT_JsonImplementType(float, JsonFloat); + +// JsonUtf8 + +JsonIterator JsonImplementType::AddValue( + JsonBuilder& builder, + bool front, + JsonConstIterator const& itParent, + std::string_view const& name, + char const* psz) +{ + return builder.AddValue( + front, itParent, name, JsonUtf8, static_cast(strlen(psz)), psz); +} + +IMPLEMENT_AddValue(char, sizeof(data), &data, JsonUtf8); +IMPLEMENT_AddValue(std::string, data.size(), data.data(), JsonUtf8); + +std::string_view +JsonImplementType::GetUnchecked(JsonValue const& value) throw() +{ + assert(value.Type() == JsonUtf8); + unsigned cb; + void const* pb = value.Data(&cb); + return std::string_view(static_cast(pb), cb); +} + +bool JsonImplementType::ConvertTo( + JsonValue const& value, + std::string_view& result) throw() +{ + bool success; + + if (value.Type() == JsonUtf8) + { + result = GetUnchecked(value); + success = true; + } + else + { + result = std::string_view(); + success = false; + } + + return success; +} + +JsonIterator JsonImplementType::AddValue( + JsonBuilder& builder, + bool front, + JsonConstIterator const& itParent, + std::string_view const& name, + std::string_view const& data) +{ + return builder.AddValue( + front, + itParent, + name, + JsonUtf8, + static_cast(data.size()), + data.data()); +} + +// JsonTime + +JsonIterator JsonImplementType::AddValue( + JsonBuilder& builder, + bool front, + JsonConstIterator const& itParent, + std::string_view const& name, + std::chrono::system_clock::time_point const& data) +{ + std::chrono::nanoseconds nanosSinceEpoch = + std::chrono::duration_cast( + data.time_since_epoch()); + + int64_t nanosInt64 = nanosSinceEpoch.count(); + return builder.AddValue( + front, itParent, name, JsonTime, sizeof(nanosInt64), &nanosInt64); +} + +bool JsonImplementType::ConvertTo( + JsonValue const& jsonValue, + std::chrono::system_clock::time_point& value) throw() +{ + bool success; + + if (jsonValue.Type() == JsonTime) + { + value = jsonValue.GetUnchecked(); + success = true; + } + else + { + value = std::chrono::system_clock::time_point{}; + success = false; + } + + return success; +} + +std::chrono::system_clock::time_point +JsonImplementType::GetUnchecked( + JsonValue const& jsonValue) throw() +{ + assert(jsonValue.Type() == JsonTime); + assert(jsonValue.DataSize() == 8); + if (jsonValue.DataSize() == 8) + { + int64_t nanosSinceEpoch = *static_cast(jsonValue.Data()); + return std::chrono::system_clock::time_point{ std::chrono::nanoseconds{ + nanosSinceEpoch } }; + } + else + { + return {}; + } +} + +// JsonUuid + +IMPLEMENT_AddValue(UuidStruct, sizeof(UuidStruct), &data, JsonUuid); + +bool JsonImplementType::ConvertTo( + JsonValue const& jsonValue, + UuidStruct& value) throw() +{ + bool success; + + if (jsonValue.Type() == JsonUuid) + { + assert(jsonValue.DataSize() == 16); + value = *static_cast(jsonValue.Data()); + success = true; + } + else + { + value = UuidStruct(); + success = false; + } + + return success; +} + +UuidStruct +JsonImplementType::GetUnchecked(JsonValue const& jsonValue) throw() +{ + assert(jsonValue.Type() == JsonUuid); + assert(jsonValue.DataSize() == 16); + + return jsonValue.DataSize() == 16 ? + *static_cast(jsonValue.Data()) : + UuidStruct(); +} + +} // namespace jsonbuilder diff --git a/src/JsonRenderer.cpp b/src/JsonRenderer.cpp index cf3497a..f27409f 100644 --- a/src/JsonRenderer.cpp +++ b/src/JsonRenderer.cpp @@ -1,486 +1,486 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -#include -#include -#include - -#include -#include - -#define WriteChar(ch) m_renderBuffer.push_back(ch) -#define WriteChars(pch, cch) m_renderBuffer.append(pch, cch) - -namespace jsonbuilder { -/* -Efficiently multiply two 32-bit unsigned integers to get a 64-bit result. -(The current VC compiler does not optimize this -- if we don't use an -intrinsic, it makes a call to _aullmul.) -*/ -#if defined(_M_ARM) || defined(_M_ARM64) -# define UMul64(a, b) _arm_umull(a, b) -#elif defined(_M_IX86) || defined(_M_X64) -# define UMul64(a, b) __emulu(a, b) -#else -static long long unsigned UMul64(unsigned a, unsigned b) -{ - return static_cast(a) * b; -} -#endif - -/* - Given a number from 0..15, returns the corresponding uppercase hex digit, - i.e. '0'..'F'. Note: it is an error to pass a value larger than 15. -*/ -static inline constexpr char u4_to_hex_upper(char unsigned _Val4) -{ - return ("0123456789ABCDEF")[_Val4]; -} - -/* -Multiply two 64-bit unsigned integers to get a 128-bit result. Return the high -64 bits of the answer. -*/ -__attribute__((unused)) static long long unsigned -UMul128Hi(long long unsigned a, long long unsigned b) -{ -#if defined(_M_X64) || defined(_M_ARM64) - long long unsigned const high = __umulh(a, b); -#else - long long unsigned const mid = - UMul64(static_cast(a), static_cast(b >> 32)) + - (UMul64(static_cast(a), static_cast(b)) >> 32); - long long unsigned const high = - UMul64(static_cast(a >> 32), static_cast(b >> 32)) + - (mid >> 32) + - ((UMul64(static_cast(a >> 32), static_cast(b)) + - static_cast(mid)) >> - 32); -#endif - return high; -} - -/* -Formats a uint32 with leading 0s. Always writes cch characters. -*/ -static void FormatUint(unsigned n, char* pch, unsigned cch) -{ - do - { - --cch; - pch[cch] = '0' + (n % 10); - n = n / 10; - } while (cch != 0); -} - -template -static unsigned JsonRenderXInt(N const& n, char* pBuffer) -{ - // TODO: Why can't we use std::to_wchars when we're using c++17? - std::string result = std::to_string(n); - std::size_t const cch = - std::min(result.size(), static_cast(CB - 1)); - strncpy(pBuffer, result.c_str(), cch); - pBuffer[cch] = 0; - - pBuffer[cch] = 0; - return static_cast(cch); -} - -unsigned JsonRenderUInt(long long unsigned n, char* pBuffer) throw() -{ - return JsonRenderXInt<21>(n, pBuffer); -} - -unsigned JsonRenderInt(long long signed n, char* pBuffer) throw() -{ - return JsonRenderXInt<21>(n, pBuffer); -} - -unsigned JsonRenderFloat(double n, char* pBuffer) throw() -{ - unsigned cch; - - if (std::isfinite(n)) - { - cch = static_cast(snprintf(pBuffer, 31, "%.17g", n)); - if (cch > 31) - { - cch = 31; - } - pBuffer[cch] = 0; - } - else - { - cch = JsonRenderNull(pBuffer); - } - - return cch; -} - -unsigned JsonRenderBool(bool b, char* pBuffer) throw() -{ - unsigned cch; - if (b) - { - strcpy(pBuffer, "true"); - cch = 4; - } - else - { - strcpy(pBuffer, "false"); - cch = 5; - } - return cch; -} - -unsigned JsonRenderNull(char* pBuffer) throw() -{ - strcpy(pBuffer, "null"); - return 4; -} - -unsigned JsonRenderTime( - std::chrono::system_clock::time_point const& timePoint, - char* pBuffer) throw() -{ - time_t printableTime = std::chrono::system_clock::to_time_t(timePoint); - tm timeStruct = tm(); - gmtime_r(&printableTime, &timeStruct); - - auto subsecondDuration = - timePoint.time_since_epoch() % std::chrono::seconds{ 1 }; - auto subsecondNanos = - std::chrono::duration_cast(subsecondDuration); - - FormatUint(static_cast(timeStruct.tm_year + 1900), pBuffer + 0, 4); - pBuffer[4] = '-'; - FormatUint(static_cast(timeStruct.tm_mon + 1), pBuffer + 5, 2); - pBuffer[7] = '-'; - FormatUint(static_cast(timeStruct.tm_mday), pBuffer + 8, 2); - pBuffer[10] = 'T'; - FormatUint(static_cast(timeStruct.tm_hour), pBuffer + 11, 2); - pBuffer[13] = ':'; - FormatUint(static_cast(timeStruct.tm_min), pBuffer + 14, 2); - pBuffer[16] = ':'; - FormatUint(static_cast(timeStruct.tm_sec), pBuffer + 17, 2); - pBuffer[19] = '.'; - FormatUint( - static_cast(subsecondNanos.count() / 100), pBuffer + 20, 7); - pBuffer[27] = 'Z'; - pBuffer[28] = 0; - return 28; -} - -unsigned JsonRenderUuid(uuid_t const& g, char* pBuffer) throw() -{ - uuid_unparse_upper(g, pBuffer); - return 36; -} - -unsigned JsonRenderUuidWithBraces(uuid_t const& g, char* pBuffer) throw() -{ - pBuffer[0] = '{'; - JsonRenderUuid(g, pBuffer + 1); - pBuffer[37] = '}'; - pBuffer[38] = 0; - return 38; -} - -JsonRenderer::JsonRenderer( - bool pretty, - std::string_view const& newLine, - unsigned indentSpaces) throw() - : m_newLine(newLine), m_indentSpaces(indentSpaces), m_pretty(pretty) -{ - return; -} - -void JsonRenderer::Reserve(size_type cb) -{ - m_renderBuffer.reserve(cb); -} - -JsonRenderer::size_type JsonRenderer::Size() const throw() -{ - return m_renderBuffer.size(); -} - -JsonRenderer::size_type JsonRenderer::Capacity() const throw() -{ - return m_renderBuffer.capacity(); -} - -bool JsonRenderer::Pretty() const throw() -{ - return m_pretty; -} - -void JsonRenderer::Pretty(bool value) throw() -{ - m_pretty = value; -} - -std::string_view const& JsonRenderer::NewLine() const throw() -{ - return m_newLine; -} - -void JsonRenderer::NewLine(std::string_view const& value) throw() -{ - m_newLine = value; -} - -unsigned JsonRenderer::IndentSpaces() const throw() -{ - return m_indentSpaces; -} - -void JsonRenderer::IndentSpaces(unsigned value) throw() -{ - m_indentSpaces = value; -} - -std::string_view JsonRenderer::Render(JsonBuilder const& builder) -{ - auto itRoot = builder.root(); - m_renderBuffer.clear(); - m_indent = 0; - RenderStructure(itRoot, true); - WriteChar('\0'); - return std::string_view(m_renderBuffer.data(), m_renderBuffer.size() - 1); -} - -std::string_view JsonRenderer::Render(JsonBuilder::const_iterator const& it) -{ - m_renderBuffer.clear(); - m_indent = 0; - if (it.IsRoot()) - { - RenderStructure(it, true); - } - else - { - RenderValue(it); - } - WriteChar('\0'); - return std::string_view(m_renderBuffer.data(), m_renderBuffer.size() - 1); -} - -void JsonRenderer::RenderCustom(RenderBuffer&, iterator const& it) -{ - auto const cchMax = 32u; - auto pch = m_renderBuffer.GetAppendPointer(cchMax); - unsigned cch = - static_cast(snprintf(pch, cchMax, "\"Custom#%u\"", it->Type())); - pch += cch > cchMax ? cchMax : cch; - m_renderBuffer.SetEndPointer(pch); -} - -void JsonRenderer::RenderValue(iterator const& it) -{ - assert(!it.IsRoot()); - switch (it->Type()) - { - case JsonObject: - RenderStructure(it, true); - break; - case JsonArray: - RenderStructure(it, false); - break; - case JsonNull: - WriteChars("null", 4); - break; - case JsonBool: - if (it->GetUnchecked()) - { - WriteChars("true", 4); - } - else - { - WriteChars("false", 5); - } - break; - case JsonUtf8: - RenderString(it->GetUnchecked()); - break; - case JsonFloat: - RenderFloat(it->GetUnchecked()); - break; - case JsonInt: - RenderInt(it->GetUnchecked()); - break; - case JsonUInt: - RenderUInt(it->GetUnchecked()); - break; - case JsonTime: - RenderTime(it->GetUnchecked()); - break; - case JsonUuid: - RenderUuid(it->GetUnchecked().Data); - break; - default: - RenderCustom(m_renderBuffer, it); - break; - } -} - -void JsonRenderer::RenderStructure(iterator const& itParent, bool showNames) -{ - WriteChar(showNames ? '{' : '['); - - auto it = itParent.begin(); - auto itEnd = itParent.end(); - if (it != itEnd) - { - m_indent += m_indentSpaces; - - for (;;) - { - if (m_pretty) - { - RenderNewline(); - } - - if (showNames) - { - RenderString(it->Name()); - WriteChar(':'); - - if (m_pretty) - { - WriteChar(' '); - } - } - - RenderValue(it); - - ++it; - if (it == itEnd) - { - break; - } - - WriteChar(','); - } - - m_indent -= m_indentSpaces; - - if (m_pretty) - { - RenderNewline(); - } - } - - WriteChar(showNames ? '}' : ']'); -} - -void JsonRenderer::RenderFloat(double const& value) -{ - auto pch = m_renderBuffer.GetAppendPointer(32); - pch += JsonRenderFloat(value, pch); - m_renderBuffer.SetEndPointer(pch); -} - -void JsonRenderer::RenderInt(long long signed const& value) -{ - auto const cchMax = 20u; - auto pch = m_renderBuffer.GetAppendPointer(cchMax); - - // TODO: Why can't we use std::to_wchars when we're using c++17? - unsigned cch = static_cast(snprintf(pch, cchMax, "%lld", value)); - pch += cch > cchMax ? cchMax : cch; - m_renderBuffer.SetEndPointer(pch); -} - -void JsonRenderer::RenderUInt(long long unsigned const& value) -{ - auto const cchMax = 20u; - auto pch = m_renderBuffer.GetAppendPointer(cchMax); - - // TODO: Why can't we use std::to_wchars when we're using c++17? - unsigned cch = static_cast(snprintf(pch, cchMax, "%llu", value)); - pch += cch > cchMax ? cchMax : cch; - m_renderBuffer.SetEndPointer(pch); -} - -void JsonRenderer::RenderTime(std::chrono::system_clock::time_point const& value) -{ - auto pch = m_renderBuffer.GetAppendPointer(32); - *pch++ = '"'; - pch += JsonRenderTime(value, pch); - *pch++ = '"'; - m_renderBuffer.SetEndPointer(pch); -} - -void JsonRenderer::RenderUuid(uuid_t const& value) -{ - auto pch = m_renderBuffer.GetAppendPointer(38); - *pch++ = '"'; - pch += JsonRenderUuid(value, pch); - *pch++ = '"'; - m_renderBuffer.SetEndPointer(pch); -} - -void JsonRenderer::RenderString(std::string_view const& value) -{ - WriteChar('"'); - for (auto ch : value) - { - if (static_cast(ch) < 0x20) - { - // Control character - must be escaped. - switch (ch) - { - case 8: - WriteChar('\\'); - WriteChar('b'); - break; - case 9: - WriteChar('\\'); - WriteChar('t'); - break; - case 10: - WriteChar('\\'); - WriteChar('n'); - break; - case 12: - WriteChar('\\'); - WriteChar('f'); - break; - case 13: - WriteChar('\\'); - WriteChar('r'); - break; - default: - auto p = m_renderBuffer.GetAppendPointer(6); - *p++ = '\\'; - *p++ = 'u'; - *p++ = '0'; - *p++ = '0'; - *p++ = u4_to_hex_upper((ch >> 4) & 0xf); - *p++ = u4_to_hex_upper(ch & 0xf); - m_renderBuffer.SetEndPointer(p); - break; - } - } - else if (ch == '"' || ch == '\\') - { - // ASCII character - pass through (escape quote and backslash) - WriteChar('\\'); - WriteChar(ch); - } - else - { - WriteChar(ch); - } - } - WriteChar('"'); -} - -void JsonRenderer::RenderNewline() -{ - WriteChars(m_newLine.data(), static_cast(m_newLine.size())); - m_renderBuffer.append(m_indent, ' '); -} +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +#include +#include +#include + +#include +#include + +#define WriteChar(ch) m_renderBuffer.push_back(ch) +#define WriteChars(pch, cch) m_renderBuffer.append(pch, cch) + +namespace jsonbuilder { +/* +Efficiently multiply two 32-bit unsigned integers to get a 64-bit result. +(The current VC compiler does not optimize this -- if we don't use an +intrinsic, it makes a call to _aullmul.) +*/ +#if defined(_M_ARM) || defined(_M_ARM64) +# define UMul64(a, b) _arm_umull(a, b) +#elif defined(_M_IX86) || defined(_M_X64) +# define UMul64(a, b) __emulu(a, b) +#else +static long long unsigned UMul64(unsigned a, unsigned b) +{ + return static_cast(a) * b; +} +#endif + +/* + Given a number from 0..15, returns the corresponding uppercase hex digit, + i.e. '0'..'F'. Note: it is an error to pass a value larger than 15. +*/ +static inline constexpr char u4_to_hex_upper(char unsigned _Val4) +{ + return ("0123456789ABCDEF")[_Val4]; +} + +/* +Multiply two 64-bit unsigned integers to get a 128-bit result. Return the high +64 bits of the answer. +*/ +__attribute__((unused)) static long long unsigned +UMul128Hi(long long unsigned a, long long unsigned b) +{ +#if defined(_M_X64) || defined(_M_ARM64) + long long unsigned const high = __umulh(a, b); +#else + long long unsigned const mid = + UMul64(static_cast(a), static_cast(b >> 32)) + + (UMul64(static_cast(a), static_cast(b)) >> 32); + long long unsigned const high = + UMul64(static_cast(a >> 32), static_cast(b >> 32)) + + (mid >> 32) + + ((UMul64(static_cast(a >> 32), static_cast(b)) + + static_cast(mid)) >> + 32); +#endif + return high; +} + +/* +Formats a uint32 with leading 0s. Always writes cch characters. +*/ +static void FormatUint(unsigned n, char* pch, unsigned cch) +{ + do + { + --cch; + pch[cch] = '0' + (n % 10); + n = n / 10; + } while (cch != 0); +} + +template +static unsigned JsonRenderXInt(N const& n, char* pBuffer) +{ + // TODO: Why can't we use std::to_wchars when we're using c++17? + std::string result = std::to_string(n); + std::size_t const cch = + std::min(result.size(), static_cast(CB - 1)); + strncpy(pBuffer, result.c_str(), cch); + pBuffer[cch] = 0; + + pBuffer[cch] = 0; + return static_cast(cch); +} + +unsigned JsonRenderUInt(long long unsigned n, char* pBuffer) throw() +{ + return JsonRenderXInt<21>(n, pBuffer); +} + +unsigned JsonRenderInt(long long signed n, char* pBuffer) throw() +{ + return JsonRenderXInt<21>(n, pBuffer); +} + +unsigned JsonRenderFloat(double n, char* pBuffer) throw() +{ + unsigned cch; + + if (std::isfinite(n)) + { + cch = static_cast(snprintf(pBuffer, 31, "%.17g", n)); + if (cch > 31) + { + cch = 31; + } + pBuffer[cch] = 0; + } + else + { + cch = JsonRenderNull(pBuffer); + } + + return cch; +} + +unsigned JsonRenderBool(bool b, char* pBuffer) throw() +{ + unsigned cch; + if (b) + { + strcpy(pBuffer, "true"); + cch = 4; + } + else + { + strcpy(pBuffer, "false"); + cch = 5; + } + return cch; +} + +unsigned JsonRenderNull(char* pBuffer) throw() +{ + strcpy(pBuffer, "null"); + return 4; +} + +unsigned JsonRenderTime( + std::chrono::system_clock::time_point const& timePoint, + char* pBuffer) throw() +{ + time_t printableTime = std::chrono::system_clock::to_time_t(timePoint); + tm timeStruct = tm(); + gmtime_r(&printableTime, &timeStruct); + + auto subsecondDuration = + timePoint.time_since_epoch() % std::chrono::seconds{ 1 }; + auto subsecondNanos = + std::chrono::duration_cast(subsecondDuration); + + FormatUint(static_cast(timeStruct.tm_year + 1900), pBuffer + 0, 4); + pBuffer[4] = '-'; + FormatUint(static_cast(timeStruct.tm_mon + 1), pBuffer + 5, 2); + pBuffer[7] = '-'; + FormatUint(static_cast(timeStruct.tm_mday), pBuffer + 8, 2); + pBuffer[10] = 'T'; + FormatUint(static_cast(timeStruct.tm_hour), pBuffer + 11, 2); + pBuffer[13] = ':'; + FormatUint(static_cast(timeStruct.tm_min), pBuffer + 14, 2); + pBuffer[16] = ':'; + FormatUint(static_cast(timeStruct.tm_sec), pBuffer + 17, 2); + pBuffer[19] = '.'; + FormatUint( + static_cast(subsecondNanos.count() / 100), pBuffer + 20, 7); + pBuffer[27] = 'Z'; + pBuffer[28] = 0; + return 28; +} + +unsigned JsonRenderUuid(uuid_t const& g, char* pBuffer) throw() +{ + uuid_unparse_upper(g, pBuffer); + return 36; +} + +unsigned JsonRenderUuidWithBraces(uuid_t const& g, char* pBuffer) throw() +{ + pBuffer[0] = '{'; + JsonRenderUuid(g, pBuffer + 1); + pBuffer[37] = '}'; + pBuffer[38] = 0; + return 38; +} + +JsonRenderer::JsonRenderer( + bool pretty, + std::string_view const& newLine, + unsigned indentSpaces) throw() + : m_newLine(newLine), m_indentSpaces(indentSpaces), m_pretty(pretty) +{ + return; +} + +void JsonRenderer::Reserve(size_type cb) +{ + m_renderBuffer.reserve(cb); +} + +JsonRenderer::size_type JsonRenderer::Size() const throw() +{ + return m_renderBuffer.size(); +} + +JsonRenderer::size_type JsonRenderer::Capacity() const throw() +{ + return m_renderBuffer.capacity(); +} + +bool JsonRenderer::Pretty() const throw() +{ + return m_pretty; +} + +void JsonRenderer::Pretty(bool value) throw() +{ + m_pretty = value; +} + +std::string_view const& JsonRenderer::NewLine() const throw() +{ + return m_newLine; +} + +void JsonRenderer::NewLine(std::string_view const& value) throw() +{ + m_newLine = value; +} + +unsigned JsonRenderer::IndentSpaces() const throw() +{ + return m_indentSpaces; +} + +void JsonRenderer::IndentSpaces(unsigned value) throw() +{ + m_indentSpaces = value; +} + +std::string_view JsonRenderer::Render(JsonBuilder const& builder) +{ + auto itRoot = builder.root(); + m_renderBuffer.clear(); + m_indent = 0; + RenderStructure(itRoot, true); + WriteChar('\0'); + return std::string_view(m_renderBuffer.data(), m_renderBuffer.size() - 1); +} + +std::string_view JsonRenderer::Render(JsonBuilder::const_iterator const& it) +{ + m_renderBuffer.clear(); + m_indent = 0; + if (it.IsRoot()) + { + RenderStructure(it, true); + } + else + { + RenderValue(it); + } + WriteChar('\0'); + return std::string_view(m_renderBuffer.data(), m_renderBuffer.size() - 1); +} + +void JsonRenderer::RenderCustom(RenderBuffer&, iterator const& it) +{ + auto const cchMax = 32u; + auto pch = m_renderBuffer.GetAppendPointer(cchMax); + unsigned cch = + static_cast(snprintf(pch, cchMax, "\"Custom#%u\"", it->Type())); + pch += cch > cchMax ? cchMax : cch; + m_renderBuffer.SetEndPointer(pch); +} + +void JsonRenderer::RenderValue(iterator const& it) +{ + assert(!it.IsRoot()); + switch (it->Type()) + { + case JsonObject: + RenderStructure(it, true); + break; + case JsonArray: + RenderStructure(it, false); + break; + case JsonNull: + WriteChars("null", 4); + break; + case JsonBool: + if (it->GetUnchecked()) + { + WriteChars("true", 4); + } + else + { + WriteChars("false", 5); + } + break; + case JsonUtf8: + RenderString(it->GetUnchecked()); + break; + case JsonFloat: + RenderFloat(it->GetUnchecked()); + break; + case JsonInt: + RenderInt(it->GetUnchecked()); + break; + case JsonUInt: + RenderUInt(it->GetUnchecked()); + break; + case JsonTime: + RenderTime(it->GetUnchecked()); + break; + case JsonUuid: + RenderUuid(it->GetUnchecked().Data); + break; + default: + RenderCustom(m_renderBuffer, it); + break; + } +} + +void JsonRenderer::RenderStructure(iterator const& itParent, bool showNames) +{ + WriteChar(showNames ? '{' : '['); + + auto it = itParent.begin(); + auto itEnd = itParent.end(); + if (it != itEnd) + { + m_indent += m_indentSpaces; + + for (;;) + { + if (m_pretty) + { + RenderNewline(); + } + + if (showNames) + { + RenderString(it->Name()); + WriteChar(':'); + + if (m_pretty) + { + WriteChar(' '); + } + } + + RenderValue(it); + + ++it; + if (it == itEnd) + { + break; + } + + WriteChar(','); + } + + m_indent -= m_indentSpaces; + + if (m_pretty) + { + RenderNewline(); + } + } + + WriteChar(showNames ? '}' : ']'); +} + +void JsonRenderer::RenderFloat(double const& value) +{ + auto pch = m_renderBuffer.GetAppendPointer(32); + pch += JsonRenderFloat(value, pch); + m_renderBuffer.SetEndPointer(pch); +} + +void JsonRenderer::RenderInt(long long signed const& value) +{ + auto const cchMax = 20u; + auto pch = m_renderBuffer.GetAppendPointer(cchMax); + + // TODO: Why can't we use std::to_wchars when we're using c++17? + unsigned cch = static_cast(snprintf(pch, cchMax, "%lld", value)); + pch += cch > cchMax ? cchMax : cch; + m_renderBuffer.SetEndPointer(pch); +} + +void JsonRenderer::RenderUInt(long long unsigned const& value) +{ + auto const cchMax = 20u; + auto pch = m_renderBuffer.GetAppendPointer(cchMax); + + // TODO: Why can't we use std::to_wchars when we're using c++17? + unsigned cch = static_cast(snprintf(pch, cchMax, "%llu", value)); + pch += cch > cchMax ? cchMax : cch; + m_renderBuffer.SetEndPointer(pch); +} + +void JsonRenderer::RenderTime(std::chrono::system_clock::time_point const& value) +{ + auto pch = m_renderBuffer.GetAppendPointer(32); + *pch++ = '"'; + pch += JsonRenderTime(value, pch); + *pch++ = '"'; + m_renderBuffer.SetEndPointer(pch); +} + +void JsonRenderer::RenderUuid(uuid_t const& value) +{ + auto pch = m_renderBuffer.GetAppendPointer(38); + *pch++ = '"'; + pch += JsonRenderUuid(value, pch); + *pch++ = '"'; + m_renderBuffer.SetEndPointer(pch); +} + +void JsonRenderer::RenderString(std::string_view const& value) +{ + WriteChar('"'); + for (auto ch : value) + { + if (static_cast(ch) < 0x20) + { + // Control character - must be escaped. + switch (ch) + { + case 8: + WriteChar('\\'); + WriteChar('b'); + break; + case 9: + WriteChar('\\'); + WriteChar('t'); + break; + case 10: + WriteChar('\\'); + WriteChar('n'); + break; + case 12: + WriteChar('\\'); + WriteChar('f'); + break; + case 13: + WriteChar('\\'); + WriteChar('r'); + break; + default: + auto p = m_renderBuffer.GetAppendPointer(6); + *p++ = '\\'; + *p++ = 'u'; + *p++ = '0'; + *p++ = '0'; + *p++ = u4_to_hex_upper((ch >> 4) & 0xf); + *p++ = u4_to_hex_upper(ch & 0xf); + m_renderBuffer.SetEndPointer(p); + break; + } + } + else if (ch == '"' || ch == '\\') + { + // ASCII character - pass through (escape quote and backslash) + WriteChar('\\'); + WriteChar(ch); + } + else + { + WriteChar(ch); + } + } + WriteChar('"'); +} + +void JsonRenderer::RenderNewline() +{ + WriteChars(m_newLine.data(), static_cast(m_newLine.size())); + m_renderBuffer.append(m_indent, ' '); +} } // namespace jsonbuilder \ No newline at end of file diff --git a/src/PodVector.cpp b/src/PodVector.cpp index 86321a6..94d712e 100644 --- a/src/PodVector.cpp +++ b/src/PodVector.cpp @@ -1,118 +1,118 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -#include - -#include - -namespace jsonbuilder { namespace JsonInternal { - -static inline unsigned char -BitScanReverse(unsigned long* Index, unsigned long Mask) -{ - if (Mask == 0 || Index == 0) - return 0; - - int ii = 0; - for (ii = ((sizeof(Mask) * 8) - 1); ii >= 0; --ii) - { - unsigned long tempMask = 1ul << ii; - if ((Mask & tempMask) != 0) - { - *Index = ii; - break; - } - } - return (ii >= 0 ? (unsigned char) 1 : (unsigned char) 0); -} - -void PodVectorBase::CheckOffset(size_type index, size_type currentSize) throw() -{ - (void) index; // Unreferenced parameter - (void) currentSize; // Unreferenced parameter - assert(index < currentSize); -} - -void PodVectorBase::CheckRange(void const* p1, void const* p2, void const* p3) throw() -{ - (void) p1; // Unreferenced parameter - (void) p2; // Unreferenced parameter - (void) p3; // Unreferenced parameter - assert(p1 <= p2 && p2 <= p3); -} - -PodVectorBase::size_type PodVectorBase::CheckedAdd(size_type a, size_type b) -{ - size_type c = a + b; - if (c < a) - { - throw std::length_error("JsonVector - exceeded maximum capacity"); - } - return c; -} - -void PodVectorBase::InitData(void* pDest, void const* pSource, size_t cb) throw() -{ - memcpy(pDest, pSource, cb); -} - -PodVectorBase::size_type -PodVectorBase::GetNewCapacity(size_type minCapacity, size_type maxCapacity) -{ - size_type cap; - if (minCapacity <= 15) - { - cap = 15; - } - else - { - long unsigned index = 0; - if (!BitScanReverse(&index, minCapacity)) - { - std::terminate(); - } - cap = static_cast((2 << index) - 1); - } - - if (maxCapacity < cap) - { - if (maxCapacity < minCapacity) - { - throw std::length_error("JsonVector - exceeded maximum capacity"); - } - - cap = maxCapacity; - } - - assert(15 <= cap); - assert(minCapacity <= cap); - assert(cap <= maxCapacity); - return cap; -} - -void* PodVectorBase::Reallocate(void* pb, size_t cb, bool zeroInitializeMemory) -{ - void* const pbNew = pb ? realloc(pb, cb) : malloc(cb); - - if (pbNew == nullptr) - { - throw std::bad_alloc(); - } - - if (zeroInitializeMemory) - { - memset(pbNew, 0, cb); - } - - return pbNew; -} - -void PodVectorBase::Deallocate(void* pb) throw() -{ - if (pb) - { - free(pb); - } -} - -}} +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +#include + +#include + +namespace jsonbuilder { namespace JsonInternal { + +static inline unsigned char +BitScanReverse(unsigned long* Index, unsigned long Mask) +{ + if (Mask == 0 || Index == 0) + return 0; + + int ii = 0; + for (ii = ((sizeof(Mask) * 8) - 1); ii >= 0; --ii) + { + unsigned long tempMask = 1ul << ii; + if ((Mask & tempMask) != 0) + { + *Index = ii; + break; + } + } + return (ii >= 0 ? (unsigned char) 1 : (unsigned char) 0); +} + +void PodVectorBase::CheckOffset(size_type index, size_type currentSize) throw() +{ + (void) index; // Unreferenced parameter + (void) currentSize; // Unreferenced parameter + assert(index < currentSize); +} + +void PodVectorBase::CheckRange(void const* p1, void const* p2, void const* p3) throw() +{ + (void) p1; // Unreferenced parameter + (void) p2; // Unreferenced parameter + (void) p3; // Unreferenced parameter + assert(p1 <= p2 && p2 <= p3); +} + +PodVectorBase::size_type PodVectorBase::CheckedAdd(size_type a, size_type b) +{ + size_type c = a + b; + if (c < a) + { + throw std::length_error("JsonVector - exceeded maximum capacity"); + } + return c; +} + +void PodVectorBase::InitData(void* pDest, void const* pSource, size_t cb) throw() +{ + memcpy(pDest, pSource, cb); +} + +PodVectorBase::size_type +PodVectorBase::GetNewCapacity(size_type minCapacity, size_type maxCapacity) +{ + size_type cap; + if (minCapacity <= 15) + { + cap = 15; + } + else + { + long unsigned index = 0; + if (!BitScanReverse(&index, minCapacity)) + { + std::terminate(); + } + cap = static_cast((2 << index) - 1); + } + + if (maxCapacity < cap) + { + if (maxCapacity < minCapacity) + { + throw std::length_error("JsonVector - exceeded maximum capacity"); + } + + cap = maxCapacity; + } + + assert(15 <= cap); + assert(minCapacity <= cap); + assert(cap <= maxCapacity); + return cap; +} + +void* PodVectorBase::Reallocate(void* pb, size_t cb, bool zeroInitializeMemory) +{ + void* const pbNew = pb ? realloc(pb, cb) : malloc(cb); + + if (pbNew == nullptr) + { + throw std::bad_alloc(); + } + + if (zeroInitializeMemory) + { + memset(pbNew, 0, cb); + } + + return pbNew; +} + +void PodVectorBase::Deallocate(void* pb) throw() +{ + if (pb) + { + free(pb); + } +} + +}} diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index c65070d..03935b7 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -1,10 +1,10 @@ -cmake_minimum_required(VERSION 3.7) - -# Add source to this project's executable. -add_executable(jsonbuilderTest CatchMain.cpp TestBuilder.cpp TestRenderer.cpp) -target_compile_features(jsonbuilderTest PRIVATE cxx_std_17) -target_link_libraries(jsonbuilderTest PRIVATE jsonbuilder Catch2::Catch2) - -include(CTest) -include(Catch) -catch_discover_tests(jsonbuilderTest) +cmake_minimum_required(VERSION 3.7) + +# Add source to this project's executable. +add_executable(jsonbuilderTest CatchMain.cpp TestBuilder.cpp TestRenderer.cpp) +target_compile_features(jsonbuilderTest PRIVATE cxx_std_17) +target_link_libraries(jsonbuilderTest PRIVATE jsonbuilder Catch2::Catch2) + +include(CTest) +include(Catch) +catch_discover_tests(jsonbuilderTest) diff --git a/test/TestBuilder.cpp b/test/TestBuilder.cpp index 67363ca..7284d83 100644 --- a/test/TestBuilder.cpp +++ b/test/TestBuilder.cpp @@ -1,616 +1,616 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -#include -#include - -using namespace jsonbuilder; - -TEST_CASE("JsonBuilder buffer reserve", "[builder]") -{ - constexpr auto c_maxSize = JsonBuilder::buffer_max_size(); - - JsonBuilder b; - - SECTION("Buffer begins empty") - { - REQUIRE(b.buffer_size() == 0); - REQUIRE(b.buffer_capacity() == 0); - } - - SECTION("Buffer reserving 0 remains empty") - { - b.buffer_reserve(0); - REQUIRE(b.buffer_size() == 0); - REQUIRE(b.buffer_capacity() == 0); - } - - SECTION("Buffer reserving 1 keeps size 0 and makes a capacity of at least " - "the size of one element") - { - b.buffer_reserve(1); - REQUIRE(b.buffer_size() == 0); - REQUIRE(b.buffer_capacity() >= 4); - } - - SECTION("Buffer reserving 2 keeps size 0 and makes a capacity of at least " - "the size of one element") - { - b.buffer_reserve(2); - REQUIRE(b.buffer_size() == 0); - REQUIRE(b.buffer_capacity() >= 4); - } - - SECTION("Buffer reserving 5 keeps size 0 and makes a capacity of at least " - "the size of two elements") - { - b.buffer_reserve(5); - REQUIRE(b.buffer_size() == 0); - REQUIRE(b.buffer_capacity() >= 8); - } - - SECTION("Buffer reserving max succeeds or throws std::bad_alloc") - { - auto reserveMax = [&]() { - try - { - b.buffer_reserve(c_maxSize); - } - catch (const std::bad_alloc&) - {} - }; - - REQUIRE_NOTHROW(reserveMax()); - } - - SECTION("Buffer reserving one more than max throws std::length_error") - { - REQUIRE_THROWS_AS(b.buffer_reserve(c_maxSize + 1), std::length_error); - } - - SECTION("Buffer reserving maximum of size_type throws std::length_error") - { - auto maxSizeType = ~JsonBuilder::size_type(0); - REQUIRE_THROWS_AS(b.buffer_reserve(maxSizeType), std::length_error); - } -} - -template -static void TestInputOutputScalar() -{ - using InputLimits = std::numeric_limits; - using OutputLimits = std::numeric_limits; - - JsonBuilder b; - - b.push_back(b.root(), "", InputLimits::lowest()); - b.push_back(b.root(), "", InputLimits::min()); - b.push_back(b.root(), "", InputLimits::max()); - b.push_back(b.root(), "", OutputLimits::lowest()); - b.push_back(b.root(), "", OutputLimits::min()); - b.push_back(b.root(), "", OutputLimits::max()); - - REQUIRE_NOTHROW(b.ValidateData()); - - InputType i; - - auto it = b.begin(); - REQUIRE(it->GetUnchecked() == InputLimits::lowest()); - REQUIRE(it->ConvertTo(i)); - REQUIRE(i == InputLimits::lowest()); - - ++it; - REQUIRE(it->GetUnchecked() == InputLimits::min()); - REQUIRE(it->ConvertTo(i)); - REQUIRE(i == InputLimits::min()); - - ++it; - REQUIRE(it->GetUnchecked() == InputLimits::max()); - REQUIRE(it->ConvertTo(i)); - REQUIRE(i == InputLimits::max()); - - ++it; - REQUIRE( - it->GetUnchecked() == - static_cast(OutputLimits::lowest())); - if (it->ConvertTo(i)) - { - REQUIRE(i == static_cast(OutputLimits::lowest())); - } - else - { - REQUIRE(i == 0); - REQUIRE(it->GetUnchecked() != OutputLimits::lowest()); - } - - ++it; - REQUIRE( - it->GetUnchecked() == - static_cast(OutputLimits::min())); - if (it->ConvertTo(i)) - { - REQUIRE(i == static_cast(OutputLimits::min())); - } - else - { - REQUIRE(i == 0); - REQUIRE(it->GetUnchecked() != OutputLimits::min()); - } - - ++it; - REQUIRE( - it->GetUnchecked() == - static_cast(OutputLimits::max())); - if (it->ConvertTo(i)) - { - REQUIRE(i == static_cast(OutputLimits::max())); - } - else - { - REQUIRE(i == 0); - REQUIRE(it->GetUnchecked() != OutputLimits::max()); - } -} - -TEST_CASE("JsonBuilder numeric limits", "[builder]") -{ - SECTION("signed char") { TestInputOutputScalar(); } - SECTION("signed short") { TestInputOutputScalar(); } - SECTION("signed int") { TestInputOutputScalar(); } - SECTION("signed long") { TestInputOutputScalar(); } - SECTION("signed long long") - { - TestInputOutputScalar(); - } - - SECTION("unsigned char") - { - TestInputOutputScalar(); - } - SECTION("unsigned short") - { - TestInputOutputScalar(); - } - SECTION("unsigned int") { TestInputOutputScalar(); } - SECTION("unsigned long") - { - TestInputOutputScalar(); - } - SECTION("unsigned long long") - { - TestInputOutputScalar(); - } - - SECTION("float") { TestInputOutputScalar(); } - SECTION("double") { TestInputOutputScalar(); } -} - -TEST_CASE("JsonBuilder string push_back") -{ - JsonBuilder b; - - SECTION("push_back std::string_view") - { - auto itr = b.push_back(b.root(), "", std::string_view{ "ABCDE" }); - REQUIRE(itr->GetUnchecked() == "ABCDE"); - } - - SECTION("push_back std::string") - { - auto itr = b.push_back(b.root(), "", std::string{ "ABCDE" }); - REQUIRE(itr->GetUnchecked() == "ABCDE"); - } - - SECTION("push_back char") - { - auto itr = b.push_back(b.root(), "", ' '); - REQUIRE(itr->GetUnchecked() == " "); - } - - SECTION("push_back char*") - { - auto itr = b.push_back(b.root(), "", const_cast("ABC")); - REQUIRE(itr->GetUnchecked() == "ABC"); - } - - SECTION("push_back const char*") - { - auto itr = b.push_back(b.root(), "", static_cast("DEF")); - REQUIRE(itr->GetUnchecked() == "DEF"); - } - - SECTION("push_back const char[]") - { - auto itr = b.push_back(b.root(), "", "HIJ"); - REQUIRE(itr->GetUnchecked() == "HIJ"); - } -} - -TEST_CASE("JsonBuilder chrono push_back", "[builder]") -{ - auto now = std::chrono::system_clock::now(); - - JsonBuilder b; - auto itr = b.push_back(b.root(), "CurrentTime", now); - auto retrieved = itr->GetUnchecked(); - REQUIRE(retrieved == now); -} - -TEST_CASE("JsonBuilder uuid push_back", "[builder]") -{ - UuidStruct uuid; - uuid_generate(uuid.Data); - - JsonBuilder b; - auto itr = b.push_back(b.root(), "Uuid", uuid); - - auto retrieved = itr->GetUnchecked(); - REQUIRE(uuid_compare(retrieved.Data, uuid.Data) == 0); -} - -TEST_CASE("JsonBuilder find", "[builder]") -{ - JsonBuilder b; - - // Empty object - REQUIRE(b.find("a1") == b.end()); - REQUIRE(b.find("a1", "a2") == b.end()); - - // Single object (a1) - auto itA1 = b.push_back(b.root(), "a1", JsonObject); - REQUIRE(b.find("a1") == itA1); - REQUIRE(b.find(b.root(), "a1") == itA1); - REQUIRE(b.find("b1") == b.end()); - REQUIRE(b.find(b.root(), "b1") == b.end()); - REQUIRE(b.find("a1", "a2") == b.end()); - - // Second object b2, sibling of a1 - auto itB1 = b.push_back(b.root(), "b1", JsonObject); - REQUIRE(b.find("a1") == itA1); - REQUIRE(b.find("a1", "a2") == b.end()); - REQUIRE(b.find("b1") == itB1); - REQUIRE(b.find("c1") == b.end()); - - // First child of a1, a2 - auto itA1A2 = b.push_back(itA1, "a2", JsonObject); - REQUIRE(b.find("a1") == itA1); - REQUIRE(b.find("a1", "a2") == itA1A2); - REQUIRE(b.find(b.root(), "a1", "a2") == itA1A2); - REQUIRE(b.find("a1", "a2", "a3") == b.end()); - REQUIRE(b.find("b1") == itB1); - REQUIRE(b.find("c1") == b.end()); - - // First child of a2, a3 - auto itA1A2A3 = b.push_back(itA1A2, "a3", 0); - REQUIRE(b.find("a1", "a2", "a3") == itA1A2A3); - REQUIRE(b.find(itA1, "a2") == itA1A2); - REQUIRE(b.find(itB1, "a2") == b.end()); - - REQUIRE_NOTHROW(b.ValidateData()); -} - -TEST_CASE("JsonBuilder constructors", "[builder]") -{ - JsonBuilder b; - b.push_back(b.root(), "aname", "ava"); - b.push_back(b.root(), "bname", "bva"); - REQUIRE_NOTHROW(b.ValidateData()); - - SECTION("Copy constructor") - { - JsonBuilder copy{ b }; - REQUIRE_NOTHROW(copy.ValidateData()); - - auto it = copy.begin(); - REQUIRE(it->Name() == "aname"); - REQUIRE(it->GetUnchecked() == "ava"); - - ++it; - REQUIRE(it->Name() == "bname"); - REQUIRE(it->GetUnchecked() == "bva"); - } - - SECTION("Move constructor") - { - JsonBuilder move{ std::move(b) }; - REQUIRE_NOTHROW(move.ValidateData()); - - auto it = move.begin(); - REQUIRE(it->Name() == "aname"); - REQUIRE(it->GetUnchecked() == "ava"); - - ++it; - REQUIRE(it->Name() == "bname"); - REQUIRE(it->GetUnchecked() == "bva"); - } -} - -TEST_CASE("JsonBuilder erase", "[builder]") -{ - JsonBuilder b; - b.push_back(b.root(), "aname", "ava"); - b.push_back(b.root(), "bname", "bva"); - REQUIRE_NOTHROW(b.ValidateData()); - - SECTION("erase a single child element") - { - auto itr = b.erase(b.begin()); - REQUIRE_NOTHROW(b.ValidateData()); - REQUIRE(itr == b.begin()); - REQUIRE(b.count(b.root()) == 1); - } - - SECTION("erase all children") - { - auto itr = b.erase(b.begin(), b.end()); - REQUIRE_NOTHROW(b.ValidateData()); - REQUIRE(itr == b.end()); - REQUIRE(b.begin() == b.end()); - REQUIRE(b.count(itr) == 0); - } -} - -TEST_CASE("JsonBuilder conversions", "[builder]") -{ - int64_t ival; - uint64_t uval; - double fval; - std::string_view sval; - bool bval; - std::chrono::system_clock::time_point tval; - UuidStruct uuidval; - - JsonBuilder b; - - SECTION("JsonNull") - { - auto itr = b.push_back(b.root(), "FirstItem", JsonNull); - - REQUIRE(itr->IsNull()); - REQUIRE(!itr->ConvertTo(bval)); - REQUIRE(!itr->ConvertTo(fval)); - REQUIRE(!itr->ConvertTo(ival)); - REQUIRE(!itr->ConvertTo(uval)); - REQUIRE(!itr->ConvertTo(sval)); - REQUIRE(!itr->ConvertTo(tval)); - REQUIRE(!itr->ConvertTo(uuidval)); - } - - SECTION("false") - { - auto itr = b.push_back(b.root(), "", false); - - REQUIRE(itr->GetUnchecked() == false); - REQUIRE((itr->ConvertTo(bval) && !bval)); - REQUIRE(!itr->ConvertTo(fval)); - REQUIRE(!itr->ConvertTo(ival)); - REQUIRE(!itr->ConvertTo(uval)); - REQUIRE(!itr->ConvertTo(sval)); - REQUIRE(!itr->ConvertTo(tval)); - REQUIRE(!itr->ConvertTo(uuidval)); - } - - SECTION("true") - { - auto itr = b.push_back(b.root(), "", true); - - REQUIRE(itr->GetUnchecked() == true); - REQUIRE((itr->ConvertTo(bval) && bval)); - REQUIRE(!itr->ConvertTo(fval)); - REQUIRE(!itr->ConvertTo(ival)); - REQUIRE(!itr->ConvertTo(uval)); - REQUIRE(!itr->ConvertTo(sval)); - REQUIRE(!itr->ConvertTo(tval)); - REQUIRE(!itr->ConvertTo(uuidval)); - } - - SECTION("int64_t") - { - auto itr = b.push_back(b.root(), "", 123); - - REQUIRE(itr->GetUnchecked() == 123); - REQUIRE(!itr->ConvertTo(bval)); - REQUIRE((itr->ConvertTo(fval) && fval == 123)); - REQUIRE((itr->ConvertTo(ival) && ival == 123)); - REQUIRE((itr->ConvertTo(uval) && uval == 123)); - REQUIRE(!itr->ConvertTo(sval)); - REQUIRE(!itr->ConvertTo(tval)); - REQUIRE(!itr->ConvertTo(uuidval)); - } - - SECTION("uint64_t") - { - auto itr = b.push_back(b.root(), "", 123u); - - REQUIRE(itr->GetUnchecked() == 123u); - REQUIRE(!itr->ConvertTo(bval)); - REQUIRE((itr->ConvertTo(fval) && fval == 123)); - REQUIRE((itr->ConvertTo(ival) && ival == 123)); - REQUIRE((itr->ConvertTo(uval) && uval == 123)); - REQUIRE(!itr->ConvertTo(sval)); - REQUIRE(!itr->ConvertTo(tval)); - REQUIRE(!itr->ConvertTo(uuidval)); - } - - SECTION("double") - { - auto itr = b.push_back(b.root(), "", 123.0); - - REQUIRE(itr->GetUnchecked() == 123.0); - REQUIRE(!itr->ConvertTo(bval)); - REQUIRE((itr->ConvertTo(fval) && fval == 123)); - REQUIRE((itr->ConvertTo(ival) && ival == 123)); - REQUIRE((itr->ConvertTo(uval) && uval == 123)); - REQUIRE(!itr->ConvertTo(sval)); - REQUIRE(!itr->ConvertTo(tval)); - REQUIRE(!itr->ConvertTo(uuidval)); - } - - SECTION("string") - { - auto itr = b.push_back(b.root(), "", "ABC"); - - REQUIRE(itr->GetUnchecked() == "ABC"); - REQUIRE(!itr->ConvertTo(bval)); - REQUIRE(!itr->ConvertTo(fval)); - REQUIRE(!itr->ConvertTo(ival)); - REQUIRE(!itr->ConvertTo(uval)); - REQUIRE((itr->ConvertTo(sval) && sval == "ABC")); - REQUIRE(!itr->ConvertTo(tval)); - REQUIRE(!itr->ConvertTo(uuidval)); - } - - SECTION("less than int64_t min as double") - { - auto itr = b.push_back(b.root(), "", -9223372036854777856.0); - - REQUIRE(!itr->ConvertTo(bval)); - REQUIRE((itr->ConvertTo(fval) && fval == -9223372036854777856.0)); - REQUIRE(!itr->ConvertTo(ival)); - REQUIRE(!itr->ConvertTo(uval)); - REQUIRE(!itr->ConvertTo(sval)); - REQUIRE(!itr->ConvertTo(tval)); - REQUIRE(!itr->ConvertTo(uuidval)); - } - - SECTION("int64_t min") - { - // This value of -9223372036854775808 cannot be described as a literal - // due to negative literals being positive literals with a unary minus - // applied. - constexpr int64_t c_int64min = (-9223372036854775807ll - 1); - - auto itr = b.push_back(b.root(), "", c_int64min); - - REQUIRE(!itr->ConvertTo(bval)); - REQUIRE((itr->ConvertTo(fval) && fval == -9223372036854775808.0)); - REQUIRE((itr->ConvertTo(ival) && ival == c_int64min)); - REQUIRE(!itr->ConvertTo(uval)); - REQUIRE(!itr->ConvertTo(sval)); - REQUIRE(!itr->ConvertTo(tval)); - REQUIRE(!itr->ConvertTo(uuidval)); - } - - SECTION("-1") - { - auto itr = b.push_back(b.root(), "", -1); - - REQUIRE(!itr->ConvertTo(bval)); - REQUIRE((itr->ConvertTo(fval) && fval == -1.0)); - REQUIRE((itr->ConvertTo(ival) && ival == -1)); - REQUIRE(!itr->ConvertTo(uval)); - REQUIRE(!itr->ConvertTo(sval)); - REQUIRE(!itr->ConvertTo(tval)); - REQUIRE(!itr->ConvertTo(uuidval)); - } - - SECTION("0") - { - auto itr = b.push_back(b.root(), "", 0); - - REQUIRE(!itr->ConvertTo(bval)); - REQUIRE((itr->ConvertTo(fval) && fval == 0.0)); - REQUIRE((itr->ConvertTo(ival) && ival == 0)); - REQUIRE((itr->ConvertTo(uval) && uval == 0)); - REQUIRE(!itr->ConvertTo(sval)); - REQUIRE(!itr->ConvertTo(tval)); - REQUIRE(!itr->ConvertTo(uuidval)); - } - - SECTION("int64_t max") - { - auto itr = b.push_back(b.root(), "", 9223372036854775807); - - REQUIRE(!itr->ConvertTo(bval)); - REQUIRE((itr->ConvertTo(fval) && fval == 9223372036854775807.0)); - REQUIRE((itr->ConvertTo(ival) && ival == 9223372036854775807)); - REQUIRE((itr->ConvertTo(uval) && uval == 9223372036854775807)); - REQUIRE(!itr->ConvertTo(sval)); - REQUIRE(!itr->ConvertTo(tval)); - REQUIRE(!itr->ConvertTo(uuidval)); - } - - SECTION("greater than int64_t max and less than uint64_t max") - { - auto itr = b.push_back(b.root(), "", 9223372036854775808ull); - - REQUIRE(!itr->ConvertTo(bval)); - REQUIRE((itr->ConvertTo(fval) && fval == 9223372036854775808.0)); - REQUIRE(!itr->ConvertTo(ival)); - REQUIRE((itr->ConvertTo(uval) && uval == 9223372036854775808ull)); - REQUIRE(!itr->ConvertTo(sval)); - REQUIRE(!itr->ConvertTo(tval)); - REQUIRE(!itr->ConvertTo(uuidval)); - } - - SECTION("greater than int64_t max and less than uint64_t max as double") - { - auto itr = b.push_back(b.root(), "", 9223372036854777856.0); - - REQUIRE(!itr->ConvertTo(bval)); - REQUIRE((itr->ConvertTo(fval) && fval == 9223372036854777856.0)); - REQUIRE(!itr->ConvertTo(ival)); - REQUIRE((itr->ConvertTo(uval) && uval == 9223372036854777856ull)); - REQUIRE(!itr->ConvertTo(sval)); - REQUIRE(!itr->ConvertTo(tval)); - REQUIRE(!itr->ConvertTo(uuidval)); - } - - SECTION("uint64_t max") - { - auto itr = b.push_back(b.root(), "", 18446744073709551615ull); - - REQUIRE(!itr->ConvertTo(bval)); - REQUIRE((itr->ConvertTo(fval) && fval == 18446744073709551615.0)); - REQUIRE(!itr->ConvertTo(ival)); - REQUIRE((itr->ConvertTo(uval) && uval == 18446744073709551615ull)); - REQUIRE(!itr->ConvertTo(sval)); - REQUIRE(!itr->ConvertTo(tval)); - REQUIRE(!itr->ConvertTo(uuidval)); - } - - SECTION("greater than uint64_t max") - { - auto itr = b.push_back(b.root(), "", 18446744073709551616.0); - - REQUIRE(!itr->ConvertTo(bval)); - REQUIRE((itr->ConvertTo(fval) && fval == 18446744073709551615.0)); - REQUIRE(!itr->ConvertTo(ival)); - REQUIRE(!itr->ConvertTo(uval)); - REQUIRE(!itr->ConvertTo(sval)); - REQUIRE(!itr->ConvertTo(tval)); - REQUIRE(!itr->ConvertTo(uuidval)); - } - - SECTION("time") - { - auto now = std::chrono::system_clock::now(); - auto itr = b.push_back(b.root(), "", now); - - REQUIRE(!itr->ConvertTo(bval)); - REQUIRE(!itr->ConvertTo(fval)); - REQUIRE(!itr->ConvertTo(ival)); - REQUIRE(!itr->ConvertTo(uval)); - REQUIRE(!itr->ConvertTo(sval)); - REQUIRE((itr->ConvertTo(tval) && tval == now)); - REQUIRE(!itr->ConvertTo(uuidval)); - } - - SECTION("uuid") - { - UuidStruct uuid; - uuid_generate(uuid.Data); - - auto itr = b.push_back(b.root(), "", uuid); - - REQUIRE(!itr->ConvertTo(bval)); - REQUIRE(!itr->ConvertTo(fval)); - REQUIRE(!itr->ConvertTo(ival)); - REQUIRE(!itr->ConvertTo(uval)); - REQUIRE(!itr->ConvertTo(sval)); - REQUIRE(!itr->ConvertTo(tval)); - REQUIRE( - (itr->ConvertTo(uuidval) && - 0 == uuid_compare(uuidval.Data, uuid.Data))); - } -} +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +#include +#include + +using namespace jsonbuilder; + +TEST_CASE("JsonBuilder buffer reserve", "[builder]") +{ + constexpr auto c_maxSize = JsonBuilder::buffer_max_size(); + + JsonBuilder b; + + SECTION("Buffer begins empty") + { + REQUIRE(b.buffer_size() == 0); + REQUIRE(b.buffer_capacity() == 0); + } + + SECTION("Buffer reserving 0 remains empty") + { + b.buffer_reserve(0); + REQUIRE(b.buffer_size() == 0); + REQUIRE(b.buffer_capacity() == 0); + } + + SECTION("Buffer reserving 1 keeps size 0 and makes a capacity of at least " + "the size of one element") + { + b.buffer_reserve(1); + REQUIRE(b.buffer_size() == 0); + REQUIRE(b.buffer_capacity() >= 4); + } + + SECTION("Buffer reserving 2 keeps size 0 and makes a capacity of at least " + "the size of one element") + { + b.buffer_reserve(2); + REQUIRE(b.buffer_size() == 0); + REQUIRE(b.buffer_capacity() >= 4); + } + + SECTION("Buffer reserving 5 keeps size 0 and makes a capacity of at least " + "the size of two elements") + { + b.buffer_reserve(5); + REQUIRE(b.buffer_size() == 0); + REQUIRE(b.buffer_capacity() >= 8); + } + + SECTION("Buffer reserving max succeeds or throws std::bad_alloc") + { + auto reserveMax = [&]() { + try + { + b.buffer_reserve(c_maxSize); + } + catch (const std::bad_alloc&) + {} + }; + + REQUIRE_NOTHROW(reserveMax()); + } + + SECTION("Buffer reserving one more than max throws std::length_error") + { + REQUIRE_THROWS_AS(b.buffer_reserve(c_maxSize + 1), std::length_error); + } + + SECTION("Buffer reserving maximum of size_type throws std::length_error") + { + auto maxSizeType = ~JsonBuilder::size_type(0); + REQUIRE_THROWS_AS(b.buffer_reserve(maxSizeType), std::length_error); + } +} + +template +static void TestInputOutputScalar() +{ + using InputLimits = std::numeric_limits; + using OutputLimits = std::numeric_limits; + + JsonBuilder b; + + b.push_back(b.root(), "", InputLimits::lowest()); + b.push_back(b.root(), "", InputLimits::min()); + b.push_back(b.root(), "", InputLimits::max()); + b.push_back(b.root(), "", OutputLimits::lowest()); + b.push_back(b.root(), "", OutputLimits::min()); + b.push_back(b.root(), "", OutputLimits::max()); + + REQUIRE_NOTHROW(b.ValidateData()); + + InputType i; + + auto it = b.begin(); + REQUIRE(it->GetUnchecked() == InputLimits::lowest()); + REQUIRE(it->ConvertTo(i)); + REQUIRE(i == InputLimits::lowest()); + + ++it; + REQUIRE(it->GetUnchecked() == InputLimits::min()); + REQUIRE(it->ConvertTo(i)); + REQUIRE(i == InputLimits::min()); + + ++it; + REQUIRE(it->GetUnchecked() == InputLimits::max()); + REQUIRE(it->ConvertTo(i)); + REQUIRE(i == InputLimits::max()); + + ++it; + REQUIRE( + it->GetUnchecked() == + static_cast(OutputLimits::lowest())); + if (it->ConvertTo(i)) + { + REQUIRE(i == static_cast(OutputLimits::lowest())); + } + else + { + REQUIRE(i == 0); + REQUIRE(it->GetUnchecked() != OutputLimits::lowest()); + } + + ++it; + REQUIRE( + it->GetUnchecked() == + static_cast(OutputLimits::min())); + if (it->ConvertTo(i)) + { + REQUIRE(i == static_cast(OutputLimits::min())); + } + else + { + REQUIRE(i == 0); + REQUIRE(it->GetUnchecked() != OutputLimits::min()); + } + + ++it; + REQUIRE( + it->GetUnchecked() == + static_cast(OutputLimits::max())); + if (it->ConvertTo(i)) + { + REQUIRE(i == static_cast(OutputLimits::max())); + } + else + { + REQUIRE(i == 0); + REQUIRE(it->GetUnchecked() != OutputLimits::max()); + } +} + +TEST_CASE("JsonBuilder numeric limits", "[builder]") +{ + SECTION("signed char") { TestInputOutputScalar(); } + SECTION("signed short") { TestInputOutputScalar(); } + SECTION("signed int") { TestInputOutputScalar(); } + SECTION("signed long") { TestInputOutputScalar(); } + SECTION("signed long long") + { + TestInputOutputScalar(); + } + + SECTION("unsigned char") + { + TestInputOutputScalar(); + } + SECTION("unsigned short") + { + TestInputOutputScalar(); + } + SECTION("unsigned int") { TestInputOutputScalar(); } + SECTION("unsigned long") + { + TestInputOutputScalar(); + } + SECTION("unsigned long long") + { + TestInputOutputScalar(); + } + + SECTION("float") { TestInputOutputScalar(); } + SECTION("double") { TestInputOutputScalar(); } +} + +TEST_CASE("JsonBuilder string push_back") +{ + JsonBuilder b; + + SECTION("push_back std::string_view") + { + auto itr = b.push_back(b.root(), "", std::string_view{ "ABCDE" }); + REQUIRE(itr->GetUnchecked() == "ABCDE"); + } + + SECTION("push_back std::string") + { + auto itr = b.push_back(b.root(), "", std::string{ "ABCDE" }); + REQUIRE(itr->GetUnchecked() == "ABCDE"); + } + + SECTION("push_back char") + { + auto itr = b.push_back(b.root(), "", ' '); + REQUIRE(itr->GetUnchecked() == " "); + } + + SECTION("push_back char*") + { + auto itr = b.push_back(b.root(), "", const_cast("ABC")); + REQUIRE(itr->GetUnchecked() == "ABC"); + } + + SECTION("push_back const char*") + { + auto itr = b.push_back(b.root(), "", static_cast("DEF")); + REQUIRE(itr->GetUnchecked() == "DEF"); + } + + SECTION("push_back const char[]") + { + auto itr = b.push_back(b.root(), "", "HIJ"); + REQUIRE(itr->GetUnchecked() == "HIJ"); + } +} + +TEST_CASE("JsonBuilder chrono push_back", "[builder]") +{ + auto now = std::chrono::system_clock::now(); + + JsonBuilder b; + auto itr = b.push_back(b.root(), "CurrentTime", now); + auto retrieved = itr->GetUnchecked(); + REQUIRE(retrieved == now); +} + +TEST_CASE("JsonBuilder uuid push_back", "[builder]") +{ + UuidStruct uuid; + uuid_generate(uuid.Data); + + JsonBuilder b; + auto itr = b.push_back(b.root(), "Uuid", uuid); + + auto retrieved = itr->GetUnchecked(); + REQUIRE(uuid_compare(retrieved.Data, uuid.Data) == 0); +} + +TEST_CASE("JsonBuilder find", "[builder]") +{ + JsonBuilder b; + + // Empty object + REQUIRE(b.find("a1") == b.end()); + REQUIRE(b.find("a1", "a2") == b.end()); + + // Single object (a1) + auto itA1 = b.push_back(b.root(), "a1", JsonObject); + REQUIRE(b.find("a1") == itA1); + REQUIRE(b.find(b.root(), "a1") == itA1); + REQUIRE(b.find("b1") == b.end()); + REQUIRE(b.find(b.root(), "b1") == b.end()); + REQUIRE(b.find("a1", "a2") == b.end()); + + // Second object b2, sibling of a1 + auto itB1 = b.push_back(b.root(), "b1", JsonObject); + REQUIRE(b.find("a1") == itA1); + REQUIRE(b.find("a1", "a2") == b.end()); + REQUIRE(b.find("b1") == itB1); + REQUIRE(b.find("c1") == b.end()); + + // First child of a1, a2 + auto itA1A2 = b.push_back(itA1, "a2", JsonObject); + REQUIRE(b.find("a1") == itA1); + REQUIRE(b.find("a1", "a2") == itA1A2); + REQUIRE(b.find(b.root(), "a1", "a2") == itA1A2); + REQUIRE(b.find("a1", "a2", "a3") == b.end()); + REQUIRE(b.find("b1") == itB1); + REQUIRE(b.find("c1") == b.end()); + + // First child of a2, a3 + auto itA1A2A3 = b.push_back(itA1A2, "a3", 0); + REQUIRE(b.find("a1", "a2", "a3") == itA1A2A3); + REQUIRE(b.find(itA1, "a2") == itA1A2); + REQUIRE(b.find(itB1, "a2") == b.end()); + + REQUIRE_NOTHROW(b.ValidateData()); +} + +TEST_CASE("JsonBuilder constructors", "[builder]") +{ + JsonBuilder b; + b.push_back(b.root(), "aname", "ava"); + b.push_back(b.root(), "bname", "bva"); + REQUIRE_NOTHROW(b.ValidateData()); + + SECTION("Copy constructor") + { + JsonBuilder copy{ b }; + REQUIRE_NOTHROW(copy.ValidateData()); + + auto it = copy.begin(); + REQUIRE(it->Name() == "aname"); + REQUIRE(it->GetUnchecked() == "ava"); + + ++it; + REQUIRE(it->Name() == "bname"); + REQUIRE(it->GetUnchecked() == "bva"); + } + + SECTION("Move constructor") + { + JsonBuilder move{ std::move(b) }; + REQUIRE_NOTHROW(move.ValidateData()); + + auto it = move.begin(); + REQUIRE(it->Name() == "aname"); + REQUIRE(it->GetUnchecked() == "ava"); + + ++it; + REQUIRE(it->Name() == "bname"); + REQUIRE(it->GetUnchecked() == "bva"); + } +} + +TEST_CASE("JsonBuilder erase", "[builder]") +{ + JsonBuilder b; + b.push_back(b.root(), "aname", "ava"); + b.push_back(b.root(), "bname", "bva"); + REQUIRE_NOTHROW(b.ValidateData()); + + SECTION("erase a single child element") + { + auto itr = b.erase(b.begin()); + REQUIRE_NOTHROW(b.ValidateData()); + REQUIRE(itr == b.begin()); + REQUIRE(b.count(b.root()) == 1); + } + + SECTION("erase all children") + { + auto itr = b.erase(b.begin(), b.end()); + REQUIRE_NOTHROW(b.ValidateData()); + REQUIRE(itr == b.end()); + REQUIRE(b.begin() == b.end()); + REQUIRE(b.count(itr) == 0); + } +} + +TEST_CASE("JsonBuilder conversions", "[builder]") +{ + int64_t ival; + uint64_t uval; + double fval; + std::string_view sval; + bool bval; + std::chrono::system_clock::time_point tval; + UuidStruct uuidval; + + JsonBuilder b; + + SECTION("JsonNull") + { + auto itr = b.push_back(b.root(), "FirstItem", JsonNull); + + REQUIRE(itr->IsNull()); + REQUIRE(!itr->ConvertTo(bval)); + REQUIRE(!itr->ConvertTo(fval)); + REQUIRE(!itr->ConvertTo(ival)); + REQUIRE(!itr->ConvertTo(uval)); + REQUIRE(!itr->ConvertTo(sval)); + REQUIRE(!itr->ConvertTo(tval)); + REQUIRE(!itr->ConvertTo(uuidval)); + } + + SECTION("false") + { + auto itr = b.push_back(b.root(), "", false); + + REQUIRE(itr->GetUnchecked() == false); + REQUIRE((itr->ConvertTo(bval) && !bval)); + REQUIRE(!itr->ConvertTo(fval)); + REQUIRE(!itr->ConvertTo(ival)); + REQUIRE(!itr->ConvertTo(uval)); + REQUIRE(!itr->ConvertTo(sval)); + REQUIRE(!itr->ConvertTo(tval)); + REQUIRE(!itr->ConvertTo(uuidval)); + } + + SECTION("true") + { + auto itr = b.push_back(b.root(), "", true); + + REQUIRE(itr->GetUnchecked() == true); + REQUIRE((itr->ConvertTo(bval) && bval)); + REQUIRE(!itr->ConvertTo(fval)); + REQUIRE(!itr->ConvertTo(ival)); + REQUIRE(!itr->ConvertTo(uval)); + REQUIRE(!itr->ConvertTo(sval)); + REQUIRE(!itr->ConvertTo(tval)); + REQUIRE(!itr->ConvertTo(uuidval)); + } + + SECTION("int64_t") + { + auto itr = b.push_back(b.root(), "", 123); + + REQUIRE(itr->GetUnchecked() == 123); + REQUIRE(!itr->ConvertTo(bval)); + REQUIRE((itr->ConvertTo(fval) && fval == 123)); + REQUIRE((itr->ConvertTo(ival) && ival == 123)); + REQUIRE((itr->ConvertTo(uval) && uval == 123)); + REQUIRE(!itr->ConvertTo(sval)); + REQUIRE(!itr->ConvertTo(tval)); + REQUIRE(!itr->ConvertTo(uuidval)); + } + + SECTION("uint64_t") + { + auto itr = b.push_back(b.root(), "", 123u); + + REQUIRE(itr->GetUnchecked() == 123u); + REQUIRE(!itr->ConvertTo(bval)); + REQUIRE((itr->ConvertTo(fval) && fval == 123)); + REQUIRE((itr->ConvertTo(ival) && ival == 123)); + REQUIRE((itr->ConvertTo(uval) && uval == 123)); + REQUIRE(!itr->ConvertTo(sval)); + REQUIRE(!itr->ConvertTo(tval)); + REQUIRE(!itr->ConvertTo(uuidval)); + } + + SECTION("double") + { + auto itr = b.push_back(b.root(), "", 123.0); + + REQUIRE(itr->GetUnchecked() == 123.0); + REQUIRE(!itr->ConvertTo(bval)); + REQUIRE((itr->ConvertTo(fval) && fval == 123)); + REQUIRE((itr->ConvertTo(ival) && ival == 123)); + REQUIRE((itr->ConvertTo(uval) && uval == 123)); + REQUIRE(!itr->ConvertTo(sval)); + REQUIRE(!itr->ConvertTo(tval)); + REQUIRE(!itr->ConvertTo(uuidval)); + } + + SECTION("string") + { + auto itr = b.push_back(b.root(), "", "ABC"); + + REQUIRE(itr->GetUnchecked() == "ABC"); + REQUIRE(!itr->ConvertTo(bval)); + REQUIRE(!itr->ConvertTo(fval)); + REQUIRE(!itr->ConvertTo(ival)); + REQUIRE(!itr->ConvertTo(uval)); + REQUIRE((itr->ConvertTo(sval) && sval == "ABC")); + REQUIRE(!itr->ConvertTo(tval)); + REQUIRE(!itr->ConvertTo(uuidval)); + } + + SECTION("less than int64_t min as double") + { + auto itr = b.push_back(b.root(), "", -9223372036854777856.0); + + REQUIRE(!itr->ConvertTo(bval)); + REQUIRE((itr->ConvertTo(fval) && fval == -9223372036854777856.0)); + REQUIRE(!itr->ConvertTo(ival)); + REQUIRE(!itr->ConvertTo(uval)); + REQUIRE(!itr->ConvertTo(sval)); + REQUIRE(!itr->ConvertTo(tval)); + REQUIRE(!itr->ConvertTo(uuidval)); + } + + SECTION("int64_t min") + { + // This value of -9223372036854775808 cannot be described as a literal + // due to negative literals being positive literals with a unary minus + // applied. + constexpr int64_t c_int64min = (-9223372036854775807ll - 1); + + auto itr = b.push_back(b.root(), "", c_int64min); + + REQUIRE(!itr->ConvertTo(bval)); + REQUIRE((itr->ConvertTo(fval) && fval == -9223372036854775808.0)); + REQUIRE((itr->ConvertTo(ival) && ival == c_int64min)); + REQUIRE(!itr->ConvertTo(uval)); + REQUIRE(!itr->ConvertTo(sval)); + REQUIRE(!itr->ConvertTo(tval)); + REQUIRE(!itr->ConvertTo(uuidval)); + } + + SECTION("-1") + { + auto itr = b.push_back(b.root(), "", -1); + + REQUIRE(!itr->ConvertTo(bval)); + REQUIRE((itr->ConvertTo(fval) && fval == -1.0)); + REQUIRE((itr->ConvertTo(ival) && ival == -1)); + REQUIRE(!itr->ConvertTo(uval)); + REQUIRE(!itr->ConvertTo(sval)); + REQUIRE(!itr->ConvertTo(tval)); + REQUIRE(!itr->ConvertTo(uuidval)); + } + + SECTION("0") + { + auto itr = b.push_back(b.root(), "", 0); + + REQUIRE(!itr->ConvertTo(bval)); + REQUIRE((itr->ConvertTo(fval) && fval == 0.0)); + REQUIRE((itr->ConvertTo(ival) && ival == 0)); + REQUIRE((itr->ConvertTo(uval) && uval == 0)); + REQUIRE(!itr->ConvertTo(sval)); + REQUIRE(!itr->ConvertTo(tval)); + REQUIRE(!itr->ConvertTo(uuidval)); + } + + SECTION("int64_t max") + { + auto itr = b.push_back(b.root(), "", 9223372036854775807); + + REQUIRE(!itr->ConvertTo(bval)); + REQUIRE((itr->ConvertTo(fval) && fval == 9223372036854775807.0)); + REQUIRE((itr->ConvertTo(ival) && ival == 9223372036854775807)); + REQUIRE((itr->ConvertTo(uval) && uval == 9223372036854775807)); + REQUIRE(!itr->ConvertTo(sval)); + REQUIRE(!itr->ConvertTo(tval)); + REQUIRE(!itr->ConvertTo(uuidval)); + } + + SECTION("greater than int64_t max and less than uint64_t max") + { + auto itr = b.push_back(b.root(), "", 9223372036854775808ull); + + REQUIRE(!itr->ConvertTo(bval)); + REQUIRE((itr->ConvertTo(fval) && fval == 9223372036854775808.0)); + REQUIRE(!itr->ConvertTo(ival)); + REQUIRE((itr->ConvertTo(uval) && uval == 9223372036854775808ull)); + REQUIRE(!itr->ConvertTo(sval)); + REQUIRE(!itr->ConvertTo(tval)); + REQUIRE(!itr->ConvertTo(uuidval)); + } + + SECTION("greater than int64_t max and less than uint64_t max as double") + { + auto itr = b.push_back(b.root(), "", 9223372036854777856.0); + + REQUIRE(!itr->ConvertTo(bval)); + REQUIRE((itr->ConvertTo(fval) && fval == 9223372036854777856.0)); + REQUIRE(!itr->ConvertTo(ival)); + REQUIRE((itr->ConvertTo(uval) && uval == 9223372036854777856ull)); + REQUIRE(!itr->ConvertTo(sval)); + REQUIRE(!itr->ConvertTo(tval)); + REQUIRE(!itr->ConvertTo(uuidval)); + } + + SECTION("uint64_t max") + { + auto itr = b.push_back(b.root(), "", 18446744073709551615ull); + + REQUIRE(!itr->ConvertTo(bval)); + REQUIRE((itr->ConvertTo(fval) && fval == 18446744073709551615.0)); + REQUIRE(!itr->ConvertTo(ival)); + REQUIRE((itr->ConvertTo(uval) && uval == 18446744073709551615ull)); + REQUIRE(!itr->ConvertTo(sval)); + REQUIRE(!itr->ConvertTo(tval)); + REQUIRE(!itr->ConvertTo(uuidval)); + } + + SECTION("greater than uint64_t max") + { + auto itr = b.push_back(b.root(), "", 18446744073709551616.0); + + REQUIRE(!itr->ConvertTo(bval)); + REQUIRE((itr->ConvertTo(fval) && fval == 18446744073709551615.0)); + REQUIRE(!itr->ConvertTo(ival)); + REQUIRE(!itr->ConvertTo(uval)); + REQUIRE(!itr->ConvertTo(sval)); + REQUIRE(!itr->ConvertTo(tval)); + REQUIRE(!itr->ConvertTo(uuidval)); + } + + SECTION("time") + { + auto now = std::chrono::system_clock::now(); + auto itr = b.push_back(b.root(), "", now); + + REQUIRE(!itr->ConvertTo(bval)); + REQUIRE(!itr->ConvertTo(fval)); + REQUIRE(!itr->ConvertTo(ival)); + REQUIRE(!itr->ConvertTo(uval)); + REQUIRE(!itr->ConvertTo(sval)); + REQUIRE((itr->ConvertTo(tval) && tval == now)); + REQUIRE(!itr->ConvertTo(uuidval)); + } + + SECTION("uuid") + { + UuidStruct uuid; + uuid_generate(uuid.Data); + + auto itr = b.push_back(b.root(), "", uuid); + + REQUIRE(!itr->ConvertTo(bval)); + REQUIRE(!itr->ConvertTo(fval)); + REQUIRE(!itr->ConvertTo(ival)); + REQUIRE(!itr->ConvertTo(uval)); + REQUIRE(!itr->ConvertTo(sval)); + REQUIRE(!itr->ConvertTo(tval)); + REQUIRE( + (itr->ConvertTo(uuidval) && + 0 == uuid_compare(uuidval.Data, uuid.Data))); + } +}