├── .clang-format ├── .gitignore ├── .gitmodules ├── Android.bp ├── CMakeLists.txt ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── SECURITY.md ├── azure-pipelines.yml ├── cmake └── modules │ ├── AliasPkgConfigTarget.cmake │ ├── Finduuid.cmake │ └── jsonbuilderConfig.cmake ├── include └── jsonbuilder │ ├── JsonBuilder.h │ └── JsonRenderer.h ├── src ├── CMakeLists.txt ├── JsonBuilder.cpp ├── JsonExceptions.cpp ├── JsonRenderer.cpp └── PodVector.cpp └── test ├── CMakeLists.txt ├── CatchMain.cpp ├── TestBuilder.cpp └── TestRenderer.cpp /.clang-format: -------------------------------------------------------------------------------- 1 | --- 2 | Language: Cpp 3 | # BasedOnStyle: WebKit 4 | AccessModifierOffset: -2 5 | AlignAfterOpenBracket: AlwaysBreak 6 | AlignConsecutiveAssignments: false 7 | AlignConsecutiveDeclarations: false 8 | AlignEscapedNewlines: Left 9 | AlignOperands: false 10 | AlignTrailingComments: true 11 | AllowAllParametersOfDeclarationOnNextLine: false 12 | AllowShortBlocksOnASingleLine: true 13 | AllowShortCaseLabelsOnASingleLine: false 14 | AllowShortFunctionsOnASingleLine: Inline 15 | AllowShortIfStatementsOnASingleLine: false 16 | AllowShortLoopsOnASingleLine: false 17 | AlwaysBreakAfterDefinitionReturnType: None 18 | AlwaysBreakAfterReturnType: None 19 | AlwaysBreakBeforeMultilineStrings: false 20 | AlwaysBreakTemplateDeclarations: true 21 | BinPackArguments: false 22 | BinPackParameters: false 23 | BraceWrapping: 24 | AfterClass: true 25 | AfterControlStatement: true 26 | AfterEnum: true 27 | AfterFunction: true 28 | AfterNamespace: false 29 | AfterObjCDeclaration: false 30 | AfterStruct: true 31 | AfterUnion: true 32 | AfterExternBlock: true 33 | BeforeCatch: true 34 | BeforeElse: true 35 | IndentBraces: false 36 | SplitEmptyFunction: false 37 | SplitEmptyRecord: false 38 | SplitEmptyNamespace: false 39 | BreakBeforeBinaryOperators: None 40 | BreakBeforeBraces: Custom 41 | BreakBeforeInheritanceComma: false 42 | BreakBeforeTernaryOperators: false 43 | BreakConstructorInitializers: BeforeComma 44 | BreakAfterJavaFieldAnnotations: false 45 | BreakStringLiterals: false 46 | ColumnLimit: 80 47 | CommentPragmas: '/\*\+\+(.+\n.+)+\*/' 48 | CompactNamespaces: true 49 | ConstructorInitializerAllOnOneLineOrOnePerLine: true 50 | ConstructorInitializerIndentWidth: 4 51 | ContinuationIndentWidth: 4 52 | Cpp11BracedListStyle: false 53 | DerivePointerAlignment: false 54 | DisableFormat: false 55 | ExperimentalAutoDetectBinPacking: false 56 | FixNamespaceComments: false 57 | ForEachMacros: 58 | - foreach 59 | - Q_FOREACH 60 | - BOOST_FOREACH 61 | IncludeBlocks: Regroup 62 | IncludeCategories: 63 | - Regex: '<.+\/.+>' 64 | Priority: 2 65 | - Regex: '<.+>' 66 | Priority: 1 67 | - Regex: '".+"' 68 | Priority: 3 69 | IncludeIsMainRegex: '(Test)?$' 70 | IndentCaseLabels: false 71 | IndentPPDirectives: AfterHash 72 | IndentWidth: 4 73 | IndentWrappedFunctionNames: false 74 | JavaScriptQuotes: Leave 75 | JavaScriptWrapImports: true 76 | KeepEmptyLinesAtTheStartOfBlocks: false 77 | MacroBlockBegin: '' 78 | MacroBlockEnd: '' 79 | MaxEmptyLinesToKeep: 1 80 | NamespaceIndentation: None 81 | ObjCBlockIndentWidth: 4 82 | ObjCSpaceAfterProperty: true 83 | ObjCSpaceBeforeProtocolList: true 84 | PenaltyBreakAssignment: 5 85 | PenaltyBreakBeforeFirstCallParameter: 5 86 | PenaltyBreakComment: 15 87 | PenaltyBreakFirstLessLess: 5 88 | PenaltyBreakString: 15 89 | PenaltyExcessCharacter: 10 90 | PenaltyReturnTypeOnItsOwnLine: 1 91 | PointerAlignment: Left 92 | ReflowComments: true 93 | SortIncludes: true 94 | SortUsingDeclarations: true 95 | SpaceAfterCStyleCast: true 96 | SpaceAfterTemplateKeyword: false 97 | SpaceBeforeAssignmentOperators: true 98 | SpaceBeforeCpp11BracedList: false 99 | SpaceBeforeCtorInitializerColon: true 100 | SpaceBeforeInheritanceColon: true 101 | SpaceBeforeParens: ControlStatements 102 | SpaceBeforeRangeBasedForLoopColon: true 103 | SpaceInEmptyParentheses: false 104 | SpacesBeforeTrailingComments: 2 105 | SpacesInAngles: false 106 | SpacesInContainerLiterals: false 107 | SpacesInCStyleCastParentheses: false 108 | SpacesInParentheses: false 109 | SpacesInSquareBrackets: false 110 | Standard: Cpp11 111 | TabWidth: 4 112 | UseTab: Never 113 | ... 114 | 115 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | build 2 | .vscode -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "external/Catch2"] 2 | path = external/Catch2 3 | url = https://github.com/catchorg/Catch2 4 | -------------------------------------------------------------------------------- /Android.bp: -------------------------------------------------------------------------------- 1 | cc_defaults { 2 | name: "jsonbuilder_defaults", 3 | } 4 | 5 | cc_defaults { 6 | name: "jsonbuilder_library_defaults", 7 | defaults: ["jsonbuilder_defaults"], 8 | proprietary: true, 9 | } 10 | 11 | cc_library_static { 12 | name: "libjsonbuilder", 13 | defaults: ["jsonbuilder_library_defaults"], 14 | 15 | cflags: [ 16 | "-Wall", 17 | "-Werror", 18 | "-Wno-implicit-fallthrough", 19 | "-fexceptions" 20 | ], 21 | 22 | srcs: [ 23 | "src/JsonBuilder.cpp", 24 | "src/JsonRenderer.cpp", 25 | "src/PodVector.cpp", 26 | ], 27 | 28 | static_libs: [ 29 | "libext2_uuid", 30 | ], 31 | 32 | local_include_dirs: [ 33 | "include", 34 | ], 35 | 36 | export_static_lib_headers: [ 37 | "libext2_uuid", 38 | ], 39 | 40 | export_include_dirs: [ 41 | "include", 42 | ] 43 | } -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.15) 2 | 3 | project(jsonbuilder VERSION 0.2) 4 | 5 | if(WIN32) 6 | add_compile_options(/W4 /WX /permissive-) 7 | else() 8 | add_compile_options( 9 | -Wall 10 | -Wextra 11 | -Wformat 12 | -Wformat-security 13 | -Werror=format-security 14 | -Wstack-protector 15 | -Werror=stack-protector) 16 | if(NOT CMAKE_BUILD_TYPE STREQUAL "Debug") 17 | add_compile_options(-D_FORTIFY_SOURCE=2) 18 | endif() 19 | endif() 20 | 21 | add_subdirectory(src) 22 | 23 | # Only include testing stuff if we are the top level 24 | if (${CMAKE_PROJECT_NAME} STREQUAL ${PROJECT_NAME}) 25 | enable_testing() 26 | 27 | list(APPEND CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/cmake/modules) 28 | if (EXISTS ${PROJECT_SOURCE_DIR}/external/Catch2/CMakeLists.txt) 29 | list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_LIST_DIR}/external/Catch2/contrib") 30 | add_subdirectory(external/Catch2) 31 | else () 32 | find_package(Catch2 REQUIRED) 33 | endif () 34 | 35 | add_subdirectory(test) 36 | endif () 37 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to JsonBuilder 2 | 3 | ## Reporting Issues 4 | 5 | If you find a bug, please file an issue in GitHub via the [issues](https://github.com/Microsoft/JsonBuilder/issues) page. 6 | 7 | # Contributing 8 | 9 | This project welcomes contributions and suggestions. Most contributions require you to 10 | agree to a Contributor License Agreement (CLA) declaring that you have the right to, 11 | and actually do, grant us the rights to use your contribution. For details, visit 12 | https://cla.microsoft.com. 13 | 14 | When you submit a pull request, a CLA-bot will automatically determine whether you need 15 | to provide a CLA and decorate the PR appropriately (e.g., label, comment). Simply follow the 16 | instructions provided by the bot. You will only need to do this once across all repositories using our CLA. 17 | 18 | This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). 19 | For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) 20 | or contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments. 21 | 22 | # Release 23 | 24 | This repository uses the [GitHub Flow](https://guides.github.com/introduction/flow/) model. 25 | 26 | This means that any commit which reaches master is considered 'released' since projects which depend on this library as a submodule may choose to update at any time. The master branch is policy-protected and does not accept direct pushes. 27 | 28 | To get code into master: 29 | 30 | 1. Create a topic branch from master. 31 | 2. Commit code to your topic. 32 | 3. Open a Pull Request into master. 33 | 4. Once approved, squash your branch into master. 34 | 35 | Pull requests must use squash commit to keep master history clean. 36 | 37 | Pull Requests must be approved by a member of the [Device Health Services Team](https://github.com/orgs/microsoft/teams/device-health-services-team). 38 | 39 | Pull Requests must be up to date with master and must pass the CI status check. 40 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | JsonBuilder 2 | Copyright (c) Microsoft Corporation. All rights reserved. 3 | 4 | MIT License 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![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) 2 | 3 | # JsonBuilder 4 | 5 | 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. 6 | 7 | ## Examples 8 | 9 | ### Building structured data 10 | 11 | Let's try to build the following JSON up using the JsonBuilder interface: 12 | 13 | ```json 14 | { 15 | "e": 2.718, 16 | "enabled": true, 17 | "user": "john", 18 | "resolution": { 19 | "x": 1024, 20 | "y": 768 21 | }, 22 | "colors": [ 23 | "Red", 24 | "Green", 25 | "Blue" 26 | ] 27 | } 28 | ``` 29 | 30 | The code to do so would look like this: 31 | 32 | ```cpp 33 | JsonBuilder jb; 34 | jb.push_back(jb.end(), "e", 2.718); 35 | jb.push_back(jb.end(), "enabled", true); 36 | jb.push_back(jb.end(), "user", "john"); 37 | 38 | JsonIterator resolutionItr = jb.push_back(jb.end(), "resolution", JsonObject); 39 | jb.push_back(resolutionItr, "x", 1024); 40 | jb.push_back(resolutionItr, "y", 768); 41 | 42 | JsonIterator colorIterator = jb.push_back(jb.end(), "colors", JsonArray); 43 | jb.push_back(colorIterator, "", "Red"); 44 | jb.push_back(colorIterator, "", "Green"); 45 | jb.push_back(colorIterator, "", "Blue"); 46 | ``` 47 | 48 | ### Getting an iterator to existing data 49 | 50 | Using the built JsonBuilder object above as a starting point: 51 | 52 | ```cpp 53 | // Float 54 | JsonConstIterator eItr = jb.find("e"); 55 | float e = eItr->GetUnchecked(); 56 | std::cout << e << std::endl; 57 | 58 | // Object 59 | JsonConstIterator resolutionItr = jb.find("resolution"); 60 | for (JsonConstIterator beginItr = resolutionItr.begin(), 61 | endItr = resolutionItr.end(); 62 | beginItr != endItr; 63 | ++beginItr) 64 | { 65 | std::string name(beginItr->Name().data(), beginItr->Name().length()); 66 | std::cout << name << " " << beginItr->GetUnchecked() 67 | << std::endl; 68 | } 69 | 70 | // Array 71 | JsonConstIterator colorsItr = jb.find("colors"); 72 | for (JsonConstIterator beginItr = colorsItr.begin(), endItr = colorsItr.end(); 73 | beginItr != endItr; 74 | ++beginItr) 75 | { 76 | auto color = beginItr->GetUnchecked(); 77 | std::cout << color << std::endl; 78 | } 79 | ``` 80 | 81 | ### Rendering to JSON 82 | 83 | Using the built JsonBuilder object above as a starting point: 84 | 85 | ```cpp 86 | // Create a renderer and reserve 2048 bytes up front 87 | JsonRenderer renderer; 88 | renderer.Reserve(2048); 89 | 90 | // Render a json builder object to a string 91 | std::string_view result = renderer.Render(_jsonBuilder); 92 | std::string stl_string(result.data(), result.size()); 93 | std::cout << stl_string.c_str() << std::endl; 94 | ``` 95 | 96 | ## Dependencies 97 | 98 | The Linux tests for this project depend on the uuid library. To develop with this project, install the development version of the library: 99 | 100 | ```bash 101 | sudo apt-get install uuid-dev 102 | ``` 103 | 104 | If you checkout with submodules, you will receive a version of Catch2 for testing that can be used automatically. If you do not checkout 105 | this submodule, the build system will instead search for an installed version of Catch2 and use that to build the tests. 106 | 107 | ## Integration 108 | 109 | JsonBuilder builds as a static library and requires C++17. The project creates a CMake compatible 'jsonbuilder' target which you can use for linking against the library. 110 | 111 | 1. Add this project as a subdirectory in your project, either as a git submodule or copying the code directly. 112 | 2. Add that directory to your top-level CMakeLists.txt with 'add_subdirectory'. This will make the 'jsonbuilder' target available. 113 | 3. Add the 'jsonbuilder' target to the target_link_libraries of any target that will use the JsonBuilder library. 114 | 115 | ## Reporting Security Issues 116 | 117 | Security issues and bugs should be reported privately, via email, to the 118 | Microsoft Security Response Center (MSRC) at <[secure@microsoft.com](mailto:secure@microsoft.com)>. 119 | You should receive a response within 24 hours. If for some reason you do not, please follow up via 120 | email to ensure we received your original message. Further information, including the 121 | [MSRC PGP](https://technet.microsoft.com/en-us/security/dn606155) key, can be found in the 122 | [Security TechCenter](https://technet.microsoft.com/en-us/security/default). 123 | 124 | ## Code of Conduct 125 | 126 | This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). 127 | 128 | 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. 129 | 130 | ## Contributing 131 | 132 | Want to contribute? The team encourages community feedback and contributions. Please follow our [contributing guidelines](CONTRIBUTING.md). 133 | 134 | We also welcome [issues submitted on GitHub](https://github.com/Microsoft/JsonBuilder/issues). 135 | 136 | ## Project Status 137 | 138 | This project is currently in active development. 139 | 140 | ## Contact 141 | 142 | The easiest way to contact us is via the [Issues](https://github.com/microsoft/JsonBuilder/issues) page. 143 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | ## Security 4 | 5 | Microsoft takes the security of our software products and services seriously, which includes all source code repositories managed through our GitHub organizations, which include [Microsoft](https://github.com/microsoft), [Azure](https://github.com/Azure), [DotNet](https://github.com/dotnet), [AspNet](https://github.com/aspnet), [Xamarin](https://github.com/xamarin), and [our GitHub organizations](https://opensource.microsoft.com/). 6 | 7 | If you believe you have found a security vulnerability in any Microsoft-owned repository that meets [Microsoft's definition of a security vulnerability](https://aka.ms/opensource/security/definition), please report it to us as described below. 8 | 9 | ## Reporting Security Issues 10 | 11 | **Please do not report security vulnerabilities through public GitHub issues.** 12 | 13 | Instead, please report them to the Microsoft Security Response Center (MSRC) at [https://msrc.microsoft.com/create-report](https://aka.ms/opensource/security/create-report). 14 | 15 | If you prefer to submit without logging in, send email to [secure@microsoft.com](mailto:secure@microsoft.com). If possible, encrypt your message with our PGP key; please download it from the [Microsoft Security Response Center PGP Key page](https://aka.ms/opensource/security/pgpkey). 16 | 17 | 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. Additional information can be found at [microsoft.com/msrc](https://aka.ms/opensource/security/msrc). 18 | 19 | Please include the requested information listed below (as much as you can provide) to help us better understand the nature and scope of the possible issue: 20 | 21 | * Type of issue (e.g. buffer overflow, SQL injection, cross-site scripting, etc.) 22 | * Full paths of source file(s) related to the manifestation of the issue 23 | * The location of the affected source code (tag/branch/commit or direct URL) 24 | * Any special configuration required to reproduce the issue 25 | * Step-by-step instructions to reproduce the issue 26 | * Proof-of-concept or exploit code (if possible) 27 | * Impact of the issue, including how an attacker might exploit the issue 28 | 29 | This information will help us triage your report more quickly. 30 | 31 | If you are reporting for a bug bounty, more complete reports can contribute to a higher bounty award. Please visit our [Microsoft Bug Bounty Program](https://aka.ms/opensource/security/bounty) page for more details about our active programs. 32 | 33 | ## Preferred Languages 34 | 35 | We prefer all communications to be in English. 36 | 37 | ## Policy 38 | 39 | Microsoft follows the principle of [Coordinated Vulnerability Disclosure](https://aka.ms/opensource/security/cvd). 40 | 41 | 42 | -------------------------------------------------------------------------------- /azure-pipelines.yml: -------------------------------------------------------------------------------- 1 | jobs: 2 | - job: Linux 3 | pool: 4 | vmImage: 'Ubuntu 18.04' 5 | steps: 6 | - bash: | 7 | sudo apt update 8 | sudo apt install uuid-dev -y 9 | displayName: 'Install dependency packages' 10 | 11 | - task: CMake@1 12 | displayName: 'CMake Configure+Generate' 13 | inputs: 14 | cmakeArgs: '..' 15 | 16 | - task: CMake@1 17 | displayName: 'CMake Build' 18 | inputs: 19 | cmakeArgs: '--build . -- -j2' 20 | 21 | - bash: | 22 | cd build/test 23 | mkdir testResults 24 | ./jsonbuilderTest -o testResults/TEST-JsonBuilderTest.xml -r junit 25 | displayName: 'Run test binary' 26 | continueOnError: true 27 | 28 | - task: PublishTestResults@2 29 | displayName: 'Publish test results' 30 | inputs: 31 | testResultsFiles: '**/TEST-*.xml' 32 | -------------------------------------------------------------------------------- /cmake/modules/AliasPkgConfigTarget.cmake: -------------------------------------------------------------------------------- 1 | function(alias_pkg_config_target newTarget targetToClone) 2 | add_library(${newTarget} INTERFACE IMPORTED) 3 | foreach(name INTERFACE_LINK_LIBRARIES INTERFACE_INCLUDE_DIRECTORIES INTERFACE_COMPILE_DEFINITIONS INTERFACE_COMPILE_OPTIONS) 4 | get_property(value TARGET ${targetToClone} PROPERTY ${name}) 5 | set_property(TARGET ${newTarget} PROPERTY ${name} ${value}) 6 | endforeach() 7 | endfunction() 8 | -------------------------------------------------------------------------------- /cmake/modules/Finduuid.cmake: -------------------------------------------------------------------------------- 1 | # Output target 2 | # uuid::uuid 3 | 4 | include(AliasPkgConfigTarget) 5 | 6 | if (TARGET uuid::uuid) 7 | return() 8 | endif() 9 | 10 | # First try and find with PkgConfig 11 | find_package(PkgConfig QUIET) 12 | if (PKG_CONFIG_FOUND) 13 | pkg_check_modules(uuid REQUIRED IMPORTED_TARGET uuid) 14 | if (TARGET PkgConfig::uuid) 15 | alias_pkg_config_target(uuid::uuid PkgConfig::uuid) 16 | return() 17 | endif () 18 | endif () 19 | 20 | # If that doesn't work, try again with old fashioned path lookup, with some caching 21 | if (NOT (uuid_INCLUDE_DIR AND uuid_LIBRARY)) 22 | find_path(uuid_INCLUDE_DIR 23 | NAMES uuid/uuid.h) 24 | find_library(uuid_LIBRARY 25 | NAMES uuid) 26 | 27 | include(FindPackageHandleStandardArgs) 28 | find_package_handle_standard_args(uuid DEFAULT_MSG 29 | uuid_LIBRARY 30 | uuid_INCLUDE_DIR) 31 | 32 | mark_as_advanced(uuid_LIBRARY uuid_INCLUDE_DIR) 33 | endif() 34 | 35 | add_library(uuid::uuid UNKNOWN IMPORTED) 36 | set_target_properties(uuid::uuid PROPERTIES 37 | IMPORTED_LOCATION "${uuid_LIBRARY}" 38 | IMPORTED_INCLUDE_DIRECTORIES "${uuid_INCLUDE_DIR}") 39 | 40 | set(uuid_FOUND TRUE) 41 | -------------------------------------------------------------------------------- /cmake/modules/jsonbuilderConfig.cmake: -------------------------------------------------------------------------------- 1 | get_filename_component(JSONBUILDER_CMAKE_DIR "${CMAKE_CURRENT_LIST_FILE}" PATH) 2 | include(CMakeFindDependencyMacro) 3 | 4 | find_dependency(uuid REQUIRED) 5 | 6 | if (NOT TARGET jsonbuilder::jsonbuilder) 7 | include("${JSONBUILDER_CMAKE_DIR}/jsonbuilderTargets.cmake") 8 | endif () 9 | 10 | -------------------------------------------------------------------------------- /include/jsonbuilder/JsonRenderer.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. All rights reserved. 2 | // Licensed under the MIT License. 3 | 4 | /* 5 | JsonRenderer converts JsonBuilder trees into utf8 JSON text. 6 | 7 | Summary: 8 | - JsonRenderer 9 | - JsonRenderBool 10 | - JsonRenderFalse 11 | - JsonRenderTime 12 | - JsonRenderFloat 13 | - JsonRenderInt 14 | - JsonRenderNull 15 | - JsonRenderTrue 16 | - JsonRenderUInt 17 | - JsonRenderUuid 18 | - JsonRenderUuidWithBraces 19 | */ 20 | 21 | #pragma once 22 | 23 | #include 24 | 25 | #ifndef _Out_writes_z_ 26 | #define _Out_writes_z_(c) 27 | #endif 28 | 29 | namespace jsonbuilder { 30 | /* 31 | Converts JsonBuilder data into utf-8 JSON text. 32 | Recognizes the built-in JsonType types. To support other (custom) types, 33 | derive from JsonRenderer and override RenderCustom. 34 | */ 35 | class JsonRenderer 36 | { 37 | protected: 38 | typedef JsonInternal::PodVector RenderBuffer; 39 | typedef JsonBuilder::const_iterator iterator; 40 | 41 | private: 42 | RenderBuffer m_renderBuffer; 43 | std::string_view m_newLine; 44 | unsigned m_indentSpaces; 45 | unsigned m_indent; 46 | bool m_pretty; 47 | 48 | public: 49 | typedef JsonInternal::PodVector::size_type size_type; 50 | 51 | virtual ~JsonRenderer(); 52 | 53 | /* 54 | Initializes a new instance of the JsonRenderer class. 55 | Optionally sets the initial value of the formatting properties. 56 | */ 57 | explicit JsonRenderer( 58 | bool pretty = false, 59 | std::string_view newLine = "\n", 60 | unsigned indentSpaces = 2) noexcept; 61 | 62 | /* 63 | Preallocates memory in the rendering buffer (increases capacity). 64 | */ 65 | void Reserve(size_type cb) 66 | noexcept(false); // may throw bad_alloc, length_error 67 | 68 | /* 69 | Gets the current size of the rendering buffer, in bytes. 70 | */ 71 | size_type Size() const noexcept; 72 | 73 | /* 74 | Gets the current capacity of the rendering buffer (how large it can grow 75 | without allocating additional memory), in bytes. 76 | */ 77 | size_type Capacity() const noexcept; 78 | 79 | /* 80 | Gets a value indicating whether the output will be formatted nicely. 81 | If true, insignificant whitespace (spaces and newlines) will be added to 82 | improve readability by humans and to put each value on its own line. 83 | If false, all insignificant whitespace will be omitted. 84 | Default value is false. 85 | */ 86 | bool Pretty() const noexcept; 87 | 88 | /* 89 | Set a value indicating whether the output will be formatted nicely. 90 | If true, insignificant whitespace (spaces and newlines) will be added to 91 | improve readability by humans and to put each value on its own line. 92 | If false, all insignificant whitespace will be omitted. 93 | Default value is false. 94 | */ 95 | void Pretty(bool) noexcept; 96 | 97 | /* 98 | Gets the string that is used for newline when Pretty() is true. 99 | Default value is "\n". 100 | */ 101 | std::string_view NewLine() const noexcept; 102 | 103 | /* 104 | Sets the string that is used for newline when Pretty() is true. 105 | Note that the JsonRenderer does not make a copy of the string that the 106 | specified string_view references. The string passed in here must be valid 107 | for as long as the JsonRenderer exists (string literal is ok). 108 | Default value is "\n". 109 | */ 110 | void NewLine(std::string_view value) noexcept; 111 | 112 | /* 113 | Gets the number of spaces per indent level. Default value is 2. 114 | */ 115 | unsigned IndentSpaces() const noexcept; 116 | 117 | /* 118 | Sets the number of spaces per indent level. Default value is 2. 119 | */ 120 | void IndentSpaces(unsigned value) noexcept; 121 | 122 | /* 123 | Renders the contents of the specified JsonBuilder as utf-8 JSON, starting 124 | at the root value. 125 | Returns a string_view with the resulting JSON string. The returned string 126 | is nul-terminated, but the nul is not included as part of the string_view 127 | itself, so return.data()[return.size()] == '\0'. 128 | The returned string_view is valid until the next call to Render or until 129 | this JsonBuilder is destroyed. 130 | */ 131 | std::string_view Render(JsonBuilder const& builder) 132 | noexcept(false); // may throw bad_alloc, length_error 133 | 134 | /* 135 | Renders the contents of a JsonBuilder as utf-8 JSON, starting at the 136 | specified value. 137 | Returns a string_view with the resulting JSON string. The returned string 138 | is nul-terminated, but the nul is not included as part of the string_view 139 | itself, so return.data()[return.size()] == '\0'. 140 | The returned string_view is valid until the next call to Render or until 141 | this JsonBuilder is destroyed. 142 | */ 143 | std::string_view 144 | Render(JsonBuilder::const_iterator const& it) 145 | noexcept(false); // may throw bad_alloc, length_error 146 | 147 | protected: 148 | /* 149 | Override this method to provide rendering behavior for custom value types. 150 | The utf-8 representation of the value referenced by itValue should be 151 | appended to the end of buffer by calling buffer.push_back with the bytes 152 | of the utf-8 representation. 153 | */ 154 | virtual void RenderCustom( 155 | RenderBuffer& buffer, 156 | iterator const& itValue) 157 | noexcept(false); // may throw bad_alloc, length_error 158 | 159 | private: 160 | /* 161 | Renders any value and its children by dispatching to the appropriate 162 | subroutine. 163 | CANNOT RENDER THE ROOT! (Don't call this if it.IsRoot() is true.) 164 | */ 165 | void RenderValue(iterator const& it); 166 | 167 | /* 168 | Renders an object/array value and its children. 169 | itParent must be an array or object. 170 | Set showNames = true for object. Set showNames = false for array. 171 | Can be called with itParent == end() to render the root. 172 | Example output: {"ObjectName":{"ArrayName":[7]}} 173 | */ 174 | void RenderStructure(iterator const& itParent, bool showNames); 175 | 176 | /* 177 | Renders value as floating-point. Requires that cb be 4 or 8. Data will be 178 | interpreted as a little-endian float or double. 179 | Example output: 123.45 180 | */ 181 | void RenderFloat(double value); 182 | 183 | /* 184 | Renders value as signed integer. Requires that cb be 1, 2, 4 or 8. Data will 185 | be interpreted as a little-endian signed integer. 186 | Example output: -12345 187 | */ 188 | void RenderInt(long long signed value); 189 | 190 | /* 191 | Renders value as unsigned integer. Requires that cb be 1, 2, 4 or 8. Data 192 | will be interpreted as a little-endian unsigned integer. 193 | Example output: 12345 194 | */ 195 | void RenderUInt(long long unsigned value); 196 | 197 | /* 198 | Renders value as time. Requires that cb be 8. Data will be interpreted as 199 | number of 100ns intervals since 1601-01-01T00:00:00Z. 200 | Example output: "2015-04-02T02:09:14.7927652Z". 201 | */ 202 | void RenderTime(TimeStruct value); 203 | 204 | /* 205 | Renders big-endian value as UUID. Compatible with uuid_t from libuuid. 206 | Example output: "CD8D0A5E-6409-4B8E-9366-B815CEF0E35D". 207 | */ 208 | void RenderUuid(_In_reads_(16) char unsigned const* value); 209 | 210 | /* 211 | Renders value as a string. Escapes any control characters and adds quotes 212 | around the result. Example output: "String\n" 213 | */ 214 | void RenderString(std::string_view value); 215 | 216 | /* 217 | If pretty-printing is disabled, has no effect. 218 | If pretty-printing is enabled, writes m_newLine followed by 219 | (m_indent * m_indentSpaces) space characters. 220 | */ 221 | void RenderNewline(); 222 | }; 223 | 224 | /* 225 | Renders the given value as an unsigned decimal integer, e.g. "123". 226 | Returns the number of characters written, not counting the nul-termination. 227 | */ 228 | unsigned JsonRenderUInt(long long unsigned n, _Out_writes_z_(21) char* pBuffer) noexcept; 229 | 230 | /* 231 | Renders the given value as a signed decimal integer, e.g. "-123". 232 | Returns the number of characters written, not counting the nul-termination. 233 | */ 234 | unsigned JsonRenderInt(long long signed n, _Out_writes_z_(21) char* pBuffer) noexcept; 235 | 236 | /* 237 | Renders the given value as a signed floating-point, e.g. "-123.1", or "null" 238 | if the value is not finite. 239 | Returns the number of characters written, not counting the nul-termination. 240 | */ 241 | unsigned JsonRenderFloat(double n, _Out_writes_z_(32) char* pBuffer) noexcept; 242 | 243 | /* 244 | Renders the string "true" or "false". 245 | Returns the number of characters written, not counting the nul-termination. 246 | (Always returns 4 or 5.) 247 | */ 248 | unsigned JsonRenderBool(bool b, _Out_writes_z_(6) char* pBuffer) noexcept; 249 | 250 | /* 251 | Renders the string "null". 252 | Returns the number of characters written, not counting the nul-termination. 253 | (Always returns 4.) 254 | */ 255 | unsigned JsonRenderNull(_Out_writes_z_(5) char* pBuffer) noexcept; 256 | 257 | /* 258 | Renders the given date/time value as an ISO 8601 string, e.g. 259 | "2015-04-02T02:09:14.7927652Z". 260 | Returns the number of characters written, not counting the nul-termination. 261 | (Always returns 28.) 262 | */ 263 | unsigned JsonRenderTime( 264 | TimeStruct t, 265 | _Out_writes_z_(29) char* pBuffer) noexcept; 266 | 267 | /* 268 | Renders the given time_point value as an ISO 8601 string, e.g. 269 | "2015-04-02T02:09:14.7927652Z". 270 | Returns the number of characters written, not counting the nul-termination. 271 | (Always returns 28.) 272 | */ 273 | unsigned JsonRenderTime( 274 | std::chrono::system_clock::time_point t, 275 | _Out_writes_z_(29) char* pBuffer) noexcept; 276 | 277 | /* 278 | Renders the given big-endian uuid_t value as a string in uppercase without 279 | braces, e.g. "CD8D0A5E-6409-4B8E-9366-B815CEF0E35D". 280 | Returns the number of characters written, not counting the nul-termination. 281 | (Always returns 36.) 282 | */ 283 | unsigned JsonRenderUuid(_In_reads_(16) char unsigned const* g, _Out_writes_z_(37) char* pBuffer) noexcept; 284 | 285 | /* 286 | Renders the given big-endian uuid_t value as a string in uppercase with braces, 287 | e.g. "{CD8D0A5E-6409-4B8E-9366-B815CEF0E35D}". 288 | Returns the number of characters written, not counting the nul-termination. 289 | (Always returns 38.) 290 | */ 291 | unsigned JsonRenderUuidWithBraces(_In_reads_(16) char unsigned const* g, _Out_writes_z_(39) char* pBuffer) noexcept; 292 | 293 | } // namespace jsonbuilder 294 | -------------------------------------------------------------------------------- /src/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.15) 2 | 3 | add_library(jsonbuilder 4 | JsonBuilder.cpp 5 | JsonExceptions.cpp 6 | JsonRenderer.cpp 7 | PodVector.cpp) 8 | 9 | target_include_directories(jsonbuilder 10 | PUBLIC 11 | $ 12 | $) 13 | 14 | target_compile_features(jsonbuilder PUBLIC cxx_std_17) 15 | 16 | set_property(TARGET jsonbuilder PROPERTY POSITION_INDEPENDENT_CODE ON) 17 | set_property(TARGET jsonbuilder PROPERTY SOVERSION 0) 18 | 19 | include(GNUInstallDirs) 20 | 21 | install(TARGETS jsonbuilder 22 | EXPORT jsonbuilder-export 23 | LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} 24 | ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} 25 | RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} 26 | INCLUDES DESTINATION ${CMAKE_INSTALL_LIBDIR}) 27 | 28 | add_library(jsonbuilder::jsonbuilder ALIAS jsonbuilder) 29 | 30 | install(DIRECTORY ${PROJECT_SOURCE_DIR}/include/ DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}) 31 | 32 | set(JSONBUILDER_CMAKE_DIR ${CMAKE_INSTALL_LIBDIR}/cmake/jsonbuilder) 33 | 34 | install(EXPORT jsonbuilder-export 35 | FILE jsonbuilderTargets.cmake 36 | NAMESPACE jsonbuilder:: 37 | DESTINATION ${JSONBUILDER_CMAKE_DIR}) 38 | 39 | install(FILES ${PROJECT_SOURCE_DIR}/cmake/modules/jsonbuilderConfig.cmake 40 | DESTINATION ${JSONBUILDER_CMAKE_DIR}) 41 | -------------------------------------------------------------------------------- /src/JsonBuilder.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. All rights reserved. 2 | // Licensed under the MIT License. 3 | 4 | #include 5 | 6 | #include 7 | #include 8 | 9 | #ifndef _Out_writes_to_ 10 | #define _Out_writes_to_(count, written) 11 | #endif 12 | 13 | #define StorageSize sizeof(JsonValue::StoragePod) 14 | 15 | /* 16 | Computes the difference between a value's index and the index at which its 17 | data begins. Used to determine the DataIndex for a value: 18 | value.DataIndex = value.Index + DATA_OFFSET(value.cchName). 19 | */ 20 | #define DATA_OFFSET(cchName) ( \ 21 | ((cchName) + static_cast(sizeof(JsonValue) + (StorageSize - 1))) \ 22 | / static_cast(StorageSize) \ 23 | ) 24 | 25 | #define IS_SPECIAL_TYPE(type) (JsonHidden <= (type)) 26 | #define IS_NORMAL_TYPE(type) ((type) < JsonHidden) 27 | #define IS_COMPOSITE_TYPE(type) (JsonArray <= (type)) 28 | 29 | auto constexpr NameMax = 0xFFFFFFu; 30 | auto constexpr DataMax = 0xF0000000u; 31 | 32 | auto constexpr TicksPerSecond = 10'000'000u; 33 | auto constexpr FileTime1970Ticks = 116444736000000000u; 34 | using ticks = std::chrono::duration>; 35 | 36 | static unsigned 37 | Utf16ToUtf8( 38 | _Out_writes_to_(cchSrc * 3, return) char unsigned* pchDest, 39 | _In_reads_(cchSrc) char16_t const* pchSrc, 40 | unsigned cchSrc) 41 | noexcept 42 | { 43 | unsigned iDest = 0; 44 | for (unsigned iSrc = 0; iSrc != cchSrc; iSrc += 1) 45 | { 46 | auto const ch = pchSrc[iSrc]; 47 | if (ch < 0x80) 48 | { 49 | pchDest[iDest++] = static_cast(ch); 50 | } 51 | else if (ch < 0x800) 52 | { 53 | pchDest[iDest++] = static_cast(0xC0 | (ch >> 6)); 54 | pchDest[iDest++] = static_cast(0x80 | (ch & 0x3F)); 55 | } 56 | else if (ch >= 0xD800 && ch < 0xDC00 && 57 | iSrc + 1 != cchSrc && 58 | pchSrc[iSrc + 1] >= 0xDC00 && pchSrc[iSrc + 1] < 0xE000) 59 | { 60 | // Surrogate pair. 61 | iSrc += 1; 62 | auto const ch32 = ((ch - 0xD800) << 10) + (pchSrc[iSrc] - 0xDC00) + 0x10000; 63 | pchDest[iDest++] = static_cast(0xF0 | (ch32 >> 18)); 64 | pchDest[iDest++] = static_cast(0x80 | ((ch32 >> 12) & 0x3F)); 65 | pchDest[iDest++] = static_cast(0x80 | ((ch32 >> 6) & 0x3F)); 66 | pchDest[iDest++] = static_cast(0x80 | (ch32 & 0x3F)); 67 | } 68 | else 69 | { 70 | pchDest[iDest++] = static_cast(0xE0 | (ch >> 12)); 71 | pchDest[iDest++] = static_cast(0x80 | ((ch >> 6) & 0x3F)); 72 | pchDest[iDest++] = static_cast(0x80 | (ch & 0x3F)); 73 | } 74 | } 75 | 76 | return iDest; 77 | } 78 | 79 | static unsigned 80 | Utf32ToUtf8( 81 | _Out_writes_to_(cchSrc * 4, return) char unsigned* pchDest, 82 | _In_reads_(cchSrc) char32_t const* pchSrc, 83 | unsigned cchSrc) 84 | noexcept 85 | { 86 | unsigned iDest = 0; 87 | for (unsigned iSrc = 0; iSrc != cchSrc; iSrc += 1) 88 | { 89 | auto const ch = pchSrc[iSrc]; 90 | if (ch < 0x80) 91 | { 92 | pchDest[iDest++] = static_cast(ch); 93 | } 94 | else if (ch < 0x800) 95 | { 96 | pchDest[iDest++] = static_cast(0xC0 | (ch >> 6)); 97 | pchDest[iDest++] = static_cast(0x80 | (ch & 0x3F)); 98 | } 99 | else if (ch < 0x10000) 100 | { 101 | pchDest[iDest++] = static_cast(0xE0 | (ch >> 12)); 102 | pchDest[iDest++] = static_cast(0x80 | ((ch >> 6) & 0x3F)); 103 | pchDest[iDest++] = static_cast(0x80 | (ch & 0x3F)); 104 | } 105 | else 106 | { 107 | // If ch is outside the Unicode range, we'll ignore the high bits. 108 | pchDest[iDest++] = static_cast(0xF0 | (ch >> 18)); 109 | pchDest[iDest++] = static_cast(0x80 | ((ch >> 12) & 0x3F)); 110 | pchDest[iDest++] = static_cast(0x80 | ((ch >> 6) & 0x3F)); 111 | pchDest[iDest++] = static_cast(0x80 | (ch & 0x3F)); 112 | } 113 | } 114 | 115 | return iDest; 116 | } 117 | 118 | static unsigned 119 | SbcsToUtf8( 120 | _Out_writes_to_(cchSrc * 3, return) char unsigned* pchDest, 121 | _In_reads_(cchSrc) char const* pchSrc, 122 | unsigned cchSrc, 123 | _In_reads_(128) char16_t const* high128) 124 | noexcept 125 | { 126 | unsigned iDest = 0; 127 | for (unsigned iSrc = 0; iSrc != cchSrc; iSrc += 1) 128 | { 129 | char unsigned const ch8 = pchSrc[iSrc]; 130 | if (ch8 < 0x80) 131 | { 132 | // ASCII character - don't use lookup table. 133 | pchDest[iDest++] = static_cast(ch8); 134 | continue; 135 | } 136 | 137 | char16_t const ch = high128[ch8 - 0x80]; 138 | if (ch < 0x80) 139 | { 140 | pchDest[iDest++] = static_cast(ch); 141 | } 142 | else if (ch < 0x800) 143 | { 144 | pchDest[iDest++] = static_cast(0xC0 | (ch >> 6)); 145 | pchDest[iDest++] = static_cast(0x80 | (ch & 0x3F)); 146 | } 147 | else 148 | { 149 | // It could be in the surrogate range, but we don't care. 150 | pchDest[iDest++] = static_cast(0xE0 | (ch >> 12)); 151 | pchDest[iDest++] = static_cast(0x80 | ((ch >> 6) & 0x3F)); 152 | pchDest[iDest++] = static_cast(0x80 | (ch & 0x3F)); 153 | } 154 | } 155 | 156 | return iDest; 157 | } 158 | 159 | // JsonValue 160 | 161 | namespace jsonbuilder { 162 | // Verify that our conditions like (type >= JsonArray) will work. 163 | static_assert(JsonHidden == 253, "Incorrect enum numbering for JsonHidden"); 164 | static_assert(JsonArray == 254, "Incorrect enum numbering for JsonArray"); 165 | static_assert(JsonObject == 255, "Incorrect enum numbering for JsonObject"); 166 | 167 | static_assert( 168 | sizeof(unsigned) >= 4, 169 | "JsonValue assumes that unsigned is at least 32 bits"); 170 | static_assert(sizeof(JsonValueBase) == 8, "JsonValueBase changed size"); 171 | static_assert(sizeof(JsonValue) == 12, "JsonValue changed size"); 172 | 173 | JsonType JsonValue::Type() const noexcept 174 | { 175 | return static_cast(m_type); 176 | } 177 | 178 | std::string_view JsonValue::Name() const noexcept 179 | { 180 | return std::string_view(reinterpret_cast(this + 1), m_cchName); 181 | } 182 | 183 | void const* JsonValue::Data(_Out_opt_ unsigned* pcbData) const noexcept 184 | { 185 | return const_cast(this)->Data(pcbData); 186 | } 187 | 188 | void* JsonValue::Data(_Out_opt_ unsigned* pcbData) noexcept 189 | { 190 | assert(!IS_SPECIAL_TYPE(m_type)); // Can't call Data() on hidden, 191 | // object, or array values. 192 | if (pcbData != nullptr) 193 | { 194 | *pcbData = m_cbData; 195 | } 196 | 197 | return reinterpret_cast(this) + DATA_OFFSET(m_cchName); 198 | } 199 | 200 | unsigned JsonValue::DataSize() const noexcept 201 | { 202 | assert(!IS_SPECIAL_TYPE(m_type)); // Can't call DataSize() on hidden, 203 | // object, or array values. 204 | return m_cbData; 205 | } 206 | 207 | void JsonValue::ReduceDataSize(unsigned cbNew) noexcept 208 | { 209 | if (IS_SPECIAL_TYPE(m_type) || cbNew > m_cbData) 210 | { 211 | assert(!"JsonBuilder: invalid use of ReduceDataSize()."); 212 | std::terminate(); 213 | } 214 | 215 | m_cbData = cbNew; 216 | } 217 | 218 | bool JsonValue::IsNull() const noexcept 219 | { 220 | return m_type == JsonNull; 221 | } 222 | 223 | // JsonConstIterator 224 | 225 | JsonConstIterator::JsonConstIterator() noexcept : m_pContainer(), m_index() 226 | { 227 | return; 228 | } 229 | 230 | JsonConstIterator::JsonConstIterator(JsonBuilder const* pContainer, Index index) noexcept 231 | : m_pContainer(pContainer), m_index(index) 232 | { 233 | return; 234 | } 235 | 236 | bool JsonConstIterator::operator==(JsonConstIterator const& other) const noexcept 237 | { 238 | assert(m_pContainer == other.m_pContainer); 239 | return m_index == other.m_index; 240 | } 241 | 242 | bool JsonConstIterator::operator!=(JsonConstIterator const& other) const noexcept 243 | { 244 | assert(m_pContainer == other.m_pContainer); 245 | return m_index != other.m_index; 246 | } 247 | 248 | JsonConstIterator::reference JsonConstIterator::operator*() const noexcept 249 | { 250 | m_pContainer->AssertNotEnd(m_index); // Do not dereference the end() 251 | // iterator. 252 | return m_pContainer->GetValue(m_index); 253 | } 254 | 255 | JsonConstIterator::pointer JsonConstIterator::operator->() const noexcept 256 | { 257 | m_pContainer->AssertNotEnd(m_index); // Do not dereference the end() 258 | // iterator. 259 | return &m_pContainer->GetValue(m_index); 260 | } 261 | 262 | JsonConstIterator& JsonConstIterator::operator++() noexcept 263 | { 264 | m_index = m_pContainer->NextIndex(m_index); // Implicitly asserts !end(). 265 | return *this; 266 | } 267 | 268 | JsonConstIterator JsonConstIterator::operator++(int) noexcept 269 | { 270 | auto old = *this; 271 | m_index = m_pContainer->NextIndex(m_index); // Implicitly asserts !end(). 272 | return old; 273 | } 274 | 275 | JsonConstIterator JsonConstIterator::begin() const noexcept 276 | { 277 | return m_pContainer->begin(*this); 278 | } 279 | 280 | JsonConstIterator JsonConstIterator::end() const noexcept 281 | { 282 | return m_pContainer->end(*this); 283 | } 284 | 285 | bool JsonConstIterator::IsRoot() const noexcept 286 | { 287 | return m_index == 0; 288 | } 289 | 290 | // JsonIterator 291 | 292 | JsonIterator::JsonIterator() noexcept : JsonConstIterator() 293 | { 294 | return; 295 | } 296 | 297 | JsonIterator::JsonIterator(JsonConstIterator const& other) noexcept 298 | : JsonConstIterator(other) 299 | { 300 | return; 301 | } 302 | 303 | JsonIterator::reference JsonIterator::operator*() const noexcept 304 | { 305 | return const_cast(JsonConstIterator::operator*()); 306 | } 307 | 308 | JsonIterator::pointer JsonIterator::operator->() const noexcept 309 | { 310 | return const_cast(JsonConstIterator::operator->()); 311 | } 312 | 313 | JsonIterator& JsonIterator::operator++() noexcept 314 | { 315 | JsonConstIterator::operator++(); 316 | return *this; 317 | } 318 | 319 | JsonIterator JsonIterator::operator++(int) noexcept 320 | { 321 | JsonIterator old(*this); 322 | JsonConstIterator::operator++(); 323 | return old; 324 | } 325 | 326 | JsonIterator JsonIterator::begin() const noexcept 327 | { 328 | return JsonIterator(JsonConstIterator::begin()); 329 | } 330 | 331 | JsonIterator JsonIterator::end() const noexcept 332 | { 333 | return JsonIterator(JsonConstIterator::end()); 334 | } 335 | 336 | // JsonBuilder::RestoreOldSize 337 | 338 | class JsonBuilder::RestoreOldSize 339 | { 340 | StorageVec::size_type const m_oldSize; 341 | StorageVec* m_pVec; 342 | 343 | public: 344 | 345 | ~RestoreOldSize() 346 | { 347 | if (m_pVec != nullptr) 348 | { 349 | m_pVec->resize(m_oldSize); 350 | } 351 | } 352 | 353 | explicit 354 | RestoreOldSize(StorageVec& vec) noexcept : m_oldSize(vec.size()), m_pVec(&vec) 355 | { 356 | return; 357 | } 358 | 359 | void 360 | Dismiss() noexcept 361 | { 362 | m_pVec = nullptr; 363 | } 364 | }; 365 | 366 | // JsonBuilder::Validator 367 | 368 | constexpr unsigned char MapBits = 2; 369 | constexpr unsigned char MapMask = (1 << MapBits) - 1; 370 | constexpr unsigned char MapPerByte = 8 / MapBits; 371 | #define MapSize(cStorage) ((cStorage) / MapPerByte + 1u) 372 | 373 | class JsonBuilder::Validator 374 | : private JsonInternal::PodVectorBase 375 | { 376 | // 2-bit value. 377 | enum ValidationState : char unsigned 378 | { 379 | ValNone = 0, 380 | ValTail = 1, 381 | ValHead = 2, 382 | ValReached = 3, 383 | ValMax 384 | }; 385 | 386 | JsonValue::StoragePod const* const m_pStorage; 387 | size_type const m_size; 388 | char unsigned* const m_pMap; // 2 bits per StoragePod in m_pStorage. 389 | 390 | public: 391 | ~Validator(); 392 | 393 | explicit Validator( 394 | _In_reads_(cStorage) JsonValue::StoragePod const* pStorage, 395 | size_type cStorage) 396 | noexcept(false); // may throw bad_alloc 397 | 398 | void Validate() 399 | noexcept(false); // may throw invalid_argument 400 | 401 | private: 402 | void ValidateRecurse(Index index) noexcept(false); 403 | 404 | void 405 | UpdateMap(Index index, ValidationState expectedVal, ValidationState newVal) noexcept(false); 406 | }; 407 | 408 | JsonBuilder::Validator::~Validator() 409 | { 410 | Deallocate(m_pMap); 411 | } 412 | 413 | JsonBuilder::Validator::Validator( 414 | _In_reads_(cStorage) JsonValue::StoragePod const* pStorage, 415 | size_type cStorage) 416 | : m_pStorage(pStorage) 417 | , m_size(cStorage) 418 | , m_pMap(static_cast(Allocate(MapSize(cStorage), false))) 419 | { 420 | static_assert( 421 | ValMax <= (1 << MapBits), "Too many ValidationStates for MapBits"); 422 | assert(m_size != 0); // No need to validate an empty builder. 423 | return; 424 | } 425 | 426 | void JsonBuilder::Validator::Validate() 427 | { 428 | memset(m_pMap, 0, MapSize(m_size)); 429 | 430 | // Traverse the linked list, starting at head. 431 | // Ensure that items fit in storage without overlap. 432 | // Ensure that there are no loops. 433 | // Mark all valid heads. 434 | Index index = 0; 435 | do 436 | { 437 | static_assert( 438 | sizeof(JsonValueBase) % StorageSize == 0, 439 | "JsonValueBase not a multiple of StorageSize"); 440 | static_assert( 441 | sizeof(JsonValue) % StorageSize == 0, 442 | "JsonValue not a multiple of StorageSize"); 443 | 444 | // Note: don't dereference pValue until the appropriate part has been 445 | // validated. 446 | JsonValue const* const pValue = 447 | reinterpret_cast(m_pStorage + index); 448 | 449 | // Mark start of JsonValueBase as head. 450 | UpdateMap(index, ValNone, ValHead); 451 | 452 | // Mark remainder of JsonValueBase as tail. 453 | Index const baseEnd = sizeof(JsonValueBase) / StorageSize; 454 | for (Index i = 1; i != baseEnd; i++) 455 | { 456 | UpdateMap(index + i, ValNone, ValTail); 457 | } 458 | 459 | // Now safe to dereference: m_nextIndex, m_cchName, m_type. 460 | 461 | if (pValue->m_type != JsonHidden) 462 | { 463 | // Mark m_cbData/m_lastChildIndex, Name as tail. 464 | Index const nameEnd = DATA_OFFSET(pValue->m_cchName); 465 | for (Index i = baseEnd; i != nameEnd; i++) 466 | { 467 | UpdateMap(index + i, ValNone, ValTail); 468 | } 469 | 470 | // Now safe to dereference: m_cbData/m_lastChildIndex, Name. 471 | 472 | if (IS_NORMAL_TYPE(pValue->m_type)) 473 | { 474 | if (pValue->m_cbData > DataMax) 475 | { 476 | JsonThrowInvalidArgument("JsonBuilder - corrupt data"); 477 | } 478 | 479 | // Mark Data as tail. 480 | Index const dataEnd = nameEnd + 481 | (pValue->m_cbData + StorageSize - 1) / StorageSize; 482 | for (Index i = nameEnd; i != dataEnd; i++) 483 | { 484 | UpdateMap(index + i, ValNone, ValTail); 485 | } 486 | } 487 | } 488 | 489 | index = pValue->m_nextIndex; 490 | } while (index != 0); 491 | 492 | // Validate root. 493 | UpdateMap(0, ValHead, ValReached); 494 | if (reinterpret_cast(m_pStorage)->m_cchName != 0 || 495 | reinterpret_cast(m_pStorage)->m_type != JsonObject) 496 | { 497 | JsonThrowInvalidArgument("JsonBuilder - corrupt data"); 498 | } 499 | 500 | // Traverse the tree, starting at root. 501 | // Ensure that all reachable indexes are valid heads. 502 | // Ensure that there are no child->parent loops (no head reached more than 503 | // once). 504 | ValidateRecurse(0); 505 | 506 | return; 507 | } 508 | 509 | void JsonBuilder::Validator::ValidateRecurse(Index parent) 510 | { 511 | JsonValue const* const pParent = 512 | reinterpret_cast(m_pStorage + parent); 513 | 514 | // Validate first child (always hidden/sentinel). 515 | 516 | Index child = parent + DATA_OFFSET(pParent->m_cchName); 517 | UpdateMap(child, ValHead, ValReached); 518 | JsonValue const* pChild = 519 | reinterpret_cast(m_pStorage + child); 520 | 521 | if (pChild->m_type != JsonHidden) 522 | { 523 | JsonThrowInvalidArgument("JsonBuilder - corrupt data"); 524 | } 525 | 526 | // Validate remaining children. 527 | 528 | while (child != pParent->m_lastChildIndex) 529 | { 530 | child = pChild->m_nextIndex; 531 | UpdateMap(child, ValHead, ValReached); 532 | pChild = reinterpret_cast(m_pStorage + child); 533 | 534 | if (IS_COMPOSITE_TYPE(pChild->m_type)) 535 | { 536 | ValidateRecurse(child); 537 | } 538 | } 539 | 540 | return; 541 | } 542 | 543 | void JsonBuilder::Validator::UpdateMap( 544 | Index index, 545 | ValidationState expectedVal, 546 | ValidationState newVal) 547 | { 548 | assert(newVal == (expectedVal | newVal)); 549 | Index i = index / MapPerByte; 550 | char shift = (index % MapPerByte) * MapBits; 551 | if (m_size <= index || ((m_pMap[i] >> shift) & MapMask) != expectedVal) 552 | { 553 | JsonThrowInvalidArgument("JsonBuilder - corrupt data"); 554 | } 555 | 556 | m_pMap[i] |= newVal << shift; 557 | assert(newVal == ((m_pMap[i] >> shift) & MapMask)); 558 | } 559 | 560 | // JsonBuilder 561 | 562 | JsonBuilder::JsonBuilder() noexcept 563 | { 564 | return; 565 | } 566 | 567 | JsonBuilder::JsonBuilder(size_type cbInitialCapacity) 568 | { 569 | buffer_reserve(cbInitialCapacity); 570 | } 571 | 572 | JsonBuilder::JsonBuilder(JsonBuilder const& other) : m_storage(other.m_storage) 573 | { 574 | return; 575 | } 576 | 577 | JsonBuilder::JsonBuilder(JsonBuilder&& other) noexcept 578 | : m_storage(std::move(other.m_storage)) 579 | { 580 | return; 581 | } 582 | 583 | JsonBuilder::JsonBuilder( 584 | _In_reads_bytes_(cbRawData) void const* pbRawData, 585 | size_type cbRawData, 586 | bool validateData) 587 | : m_storage( 588 | static_cast(pbRawData), 589 | static_cast(cbRawData / StorageSize)) 590 | { 591 | if (cbRawData % StorageSize != 0 || 592 | cbRawData / StorageSize > StorageVec::max_size()) 593 | { 594 | JsonThrowInvalidArgument("cbRawData invalid"); 595 | } 596 | else if (validateData) 597 | { 598 | ValidateData(); 599 | } 600 | } 601 | 602 | JsonBuilder& JsonBuilder::operator=(JsonBuilder const& other) 603 | { 604 | m_storage = other.m_storage; 605 | return *this; 606 | } 607 | 608 | JsonBuilder& JsonBuilder::operator=(JsonBuilder&& other) noexcept 609 | { 610 | m_storage = std::move(other.m_storage); 611 | return *this; 612 | } 613 | 614 | void JsonBuilder::ValidateData() const 615 | { 616 | if (!m_storage.empty()) 617 | { 618 | Validator(m_storage.data(), m_storage.size()).Validate(); 619 | } 620 | } 621 | 622 | JsonBuilder::iterator JsonBuilder::begin() noexcept 623 | { 624 | return iterator(cbegin()); 625 | } 626 | 627 | JsonBuilder::const_iterator JsonBuilder::begin() const noexcept 628 | { 629 | return cbegin(); 630 | } 631 | 632 | JsonBuilder::const_iterator JsonBuilder::cbegin() const noexcept 633 | { 634 | Index index = 0; 635 | if (!m_storage.empty()) 636 | { 637 | // Return index of first non-hidden node after root. 638 | index = GetValue(0).m_nextIndex; 639 | for (;;) 640 | { 641 | auto& value = GetValue(index); 642 | if (value.m_type != JsonHidden) 643 | { 644 | break; 645 | } 646 | 647 | AssertNotEnd(index); // end() should never be hidden. 648 | index = value.m_nextIndex; 649 | } 650 | } 651 | return const_iterator(this, index); 652 | } 653 | 654 | JsonBuilder::iterator JsonBuilder::end() noexcept 655 | { 656 | return iterator(cend()); 657 | } 658 | 659 | JsonBuilder::const_iterator JsonBuilder::end() const noexcept 660 | { 661 | return cend(); 662 | } 663 | 664 | JsonBuilder::const_iterator JsonBuilder::cend() const noexcept 665 | { 666 | return const_iterator(this, 0); 667 | } 668 | 669 | JsonBuilder::iterator JsonBuilder::root() noexcept 670 | { 671 | return end(); 672 | } 673 | 674 | JsonBuilder::const_iterator JsonBuilder::root() const noexcept 675 | { 676 | return end(); 677 | } 678 | 679 | JsonBuilder::const_iterator JsonBuilder::croot() const noexcept 680 | { 681 | return end(); 682 | } 683 | 684 | JsonBuilder::size_type JsonBuilder::buffer_size() const noexcept 685 | { 686 | return m_storage.size() * StorageSize; 687 | } 688 | 689 | void const* JsonBuilder::buffer_data() const noexcept 690 | { 691 | return m_storage.data(); 692 | } 693 | 694 | JsonBuilder::size_type JsonBuilder::buffer_capacity() const noexcept 695 | { 696 | return m_storage.capacity() * StorageSize; 697 | } 698 | 699 | void JsonBuilder::buffer_reserve(size_type cbMinimumCapacity) 700 | { 701 | if (cbMinimumCapacity > StorageVec::max_size() * StorageSize) 702 | { 703 | JsonThrowLengthError("requested capacity is too large"); 704 | } 705 | auto const cItems = (cbMinimumCapacity + StorageSize - 1) / StorageSize; 706 | m_storage.reserve(static_cast(cItems)); 707 | } 708 | 709 | void JsonBuilder::clear() noexcept 710 | { 711 | m_storage.clear(); 712 | } 713 | 714 | JsonBuilder::iterator JsonBuilder::erase(const_iterator itValue) noexcept 715 | { 716 | ValidateIterator(itValue); 717 | if (itValue.m_index == 0) 718 | { 719 | assert(!"JsonBuilder: cannot erase end()"); 720 | std::terminate(); 721 | } 722 | 723 | GetValue(itValue.m_index).m_type = JsonHidden; 724 | return iterator(const_iterator(this, NextIndex(itValue.m_index))); 725 | } 726 | 727 | JsonBuilder::iterator 728 | JsonBuilder::erase(const_iterator itBegin, const_iterator itEnd) noexcept 729 | { 730 | ValidateIterator(itBegin); 731 | ValidateIterator(itEnd); 732 | auto index = itBegin.m_index; 733 | while (index != itEnd.m_index) 734 | { 735 | if (index == 0) 736 | { 737 | assert(!"JsonBuilder: invalid use of ReduceDataSize()."); 738 | std::terminate(); 739 | } 740 | 741 | auto& value = GetValue(index); 742 | value.m_type = JsonHidden; 743 | index = value.m_nextIndex; 744 | } 745 | return iterator(itEnd); 746 | } 747 | 748 | void JsonBuilder::swap(JsonBuilder& other) noexcept 749 | { 750 | m_storage.swap(other.m_storage); 751 | } 752 | 753 | unsigned 754 | JsonBuilder::FindImpl(Index parentIndex, std::string_view const& name) const noexcept 755 | { 756 | Index result = 0; 757 | if (!m_storage.empty() && IS_COMPOSITE_TYPE(GetValue(parentIndex).m_type)) 758 | { 759 | auto index = FirstChild(parentIndex); 760 | auto const lastIndex = LastChild(parentIndex); 761 | if (index != lastIndex) 762 | { 763 | AssertNotEnd(index); 764 | auto& sentinelValue = GetValue(index); 765 | assert(sentinelValue.m_type == JsonHidden); 766 | index = sentinelValue.m_nextIndex; 767 | for (;;) 768 | { 769 | AssertNotEnd(index); 770 | auto& value = GetValue(index); 771 | 772 | if (value.m_type != JsonHidden && value.Name() == name) 773 | { 774 | result = index; 775 | break; 776 | } 777 | 778 | if (index == lastIndex) 779 | { 780 | break; 781 | } 782 | 783 | index = value.m_nextIndex; 784 | } 785 | } 786 | } 787 | return result; 788 | } 789 | 790 | unsigned JsonBuilder::count(const_iterator const& itParent) const noexcept 791 | { 792 | ValidateIterator(itParent); 793 | 794 | unsigned result = 0; 795 | if (CanIterateOver(itParent)) 796 | { 797 | auto index = FirstChild(itParent.m_index); 798 | auto const lastIndex = LastChild(itParent.m_index); 799 | if (index != lastIndex) 800 | { 801 | AssertNotEnd(index); 802 | auto& sentinelValue = GetValue(index); 803 | assert(sentinelValue.m_type == JsonHidden); 804 | index = sentinelValue.m_nextIndex; 805 | for (;;) 806 | { 807 | AssertNotEnd(index); 808 | auto& value = GetValue(index); 809 | 810 | if (value.m_type != JsonHidden) 811 | { 812 | ++result; 813 | } 814 | 815 | if (index == lastIndex) 816 | { 817 | break; 818 | } 819 | 820 | index = value.m_nextIndex; 821 | } 822 | } 823 | } 824 | return result; 825 | } 826 | 827 | JsonBuilder::iterator JsonBuilder::begin(const_iterator const& itParent) noexcept 828 | { 829 | return iterator(cbegin(itParent)); 830 | } 831 | 832 | JsonBuilder::const_iterator 833 | JsonBuilder::begin(const_iterator const& itParent) const noexcept 834 | { 835 | return cbegin(itParent); 836 | } 837 | 838 | JsonBuilder::const_iterator 839 | JsonBuilder::cbegin(const_iterator const& itParent) const noexcept 840 | { 841 | ValidateIterator(itParent); 842 | Index index = 0; 843 | if (CanIterateOver(itParent)) 844 | { 845 | index = NextIndex(FirstChild(itParent.m_index)); 846 | } 847 | return const_iterator(this, index); 848 | } 849 | 850 | JsonBuilder::iterator JsonBuilder::end(const_iterator const& itParent) noexcept 851 | { 852 | return iterator(cend(itParent)); 853 | } 854 | 855 | JsonBuilder::const_iterator 856 | JsonBuilder::end(const_iterator const& itParent) const noexcept 857 | { 858 | return cend(itParent); 859 | } 860 | 861 | JsonBuilder::const_iterator 862 | JsonBuilder::cend(const_iterator const& itParent) const noexcept 863 | { 864 | ValidateIterator(itParent); 865 | Index index = 0; 866 | if (CanIterateOver(itParent)) 867 | { 868 | index = NextIndex(LastChild(itParent.m_index)); 869 | } 870 | return const_iterator(this, index); 871 | } 872 | 873 | void 874 | JsonBuilder::CreateRoot() noexcept(false) 875 | { 876 | unsigned constexpr RootIndex = 0u; 877 | unsigned constexpr SentinelIndex = RootIndex + DATA_OFFSET(0u); 878 | m_storage.resize(RootSize); 879 | auto const pStorage = m_storage.data(); 880 | 881 | auto const pRootValue = reinterpret_cast(pStorage + RootIndex); 882 | pRootValue->m_nextIndex = SentinelIndex; 883 | pRootValue->m_cchName = 0u; 884 | pRootValue->m_type = JsonObject; 885 | pRootValue->m_lastChildIndex = SentinelIndex; 886 | 887 | auto const pSentinel = reinterpret_cast(pStorage + SentinelIndex); 888 | pSentinel->m_nextIndex = RootIndex; 889 | pSentinel->m_cchName = 0; 890 | pSentinel->m_type = JsonHidden; 891 | } 892 | 893 | void const* 894 | JsonBuilder::NewValueInitImpl( 895 | bool front, 896 | const_iterator const& itParent, 897 | void const* pName, 898 | unsigned cbNameReserve, 899 | unsigned cbDataHint) 900 | noexcept(false) // may throw bad_alloc, length_error 901 | { 902 | ValidateIterator(itParent); 903 | 904 | if (cbDataHint < sizeof(void*) || cbDataHint > DataMax) 905 | { 906 | if (cbDataHint > DataMax) 907 | { 908 | JsonThrowLengthError("JsonBuilder - cbData too large"); 909 | } 910 | 911 | // We need at least enough room for a pointer. 912 | cbDataHint = sizeof(void*); 913 | } 914 | 915 | unsigned const valueIndex = static_cast(m_storage.size()); 916 | unsigned const dataIndex = valueIndex + DATA_OFFSET(cbNameReserve); 917 | unsigned const newStorageSize = dataIndex + (cbDataHint + StorageSize - 1) / StorageSize; 918 | 919 | if (dataIndex <= valueIndex || newStorageSize < dataIndex) 920 | { 921 | // Integer overflow. 922 | JsonThrowLengthError("JsonBuilder - too much data"); 923 | } 924 | 925 | void const* pNameAfterAlloc; 926 | if (m_storage.empty()) 927 | { 928 | // Validate itParent. 929 | // Since builder is empty, the only possible parent is the root. 930 | if (itParent.m_index != 0) 931 | { 932 | assert(!"JsonBuilder: destination must be an array or object"); 933 | std::terminate(); 934 | } 935 | 936 | if (newStorageSize + RootSize < RootSize) 937 | { 938 | // Integer overflow. 939 | JsonThrowLengthError("JsonBuilder - too much data"); 940 | } 941 | 942 | // Reserve room for root and new value (avoid extra reallocation). 943 | m_storage.reserve(newStorageSize + RootSize); // Reserve, not Resize. 944 | CreateRoot(); 945 | 946 | // Since builder was empty, it's not possible for pName to be inside storage. 947 | pNameAfterAlloc = pName; 948 | } 949 | else 950 | { 951 | // Validate itParent. 952 | ValidateParentIterator(itParent.m_index); 953 | 954 | // Reserve room for new value. 955 | if (m_storage.capacity() >= newStorageSize) 956 | { 957 | // No reallocation, so pName is still valid. 958 | pNameAfterAlloc = pName; 959 | } 960 | else 961 | { 962 | auto const oldDataBegin = reinterpret_cast(m_storage.data()); 963 | auto const oldDataEnd = reinterpret_cast(m_storage.data() + m_storage.size()); 964 | m_storage.reserve(newStorageSize); // Reserve, not Resize. 965 | 966 | if (static_cast(pName) > oldDataBegin && 967 | static_cast(pName) < oldDataEnd) 968 | { 969 | // They're copying the name from within the vector, and we just resized 970 | // out from under them. Technically, we could consider this a bug in the 971 | // caller, but it's an easy mistake to make, easy to miss in testing, 972 | // and hard to diagnose if it hits. Dealing with this is easy for us, so 973 | // just fix up the problem instead of making it an error. 974 | pNameAfterAlloc = reinterpret_cast(m_storage.data()) + 975 | (static_cast(pName) - oldDataBegin); 976 | } 977 | else 978 | { 979 | pNameAfterAlloc = pName; 980 | } 981 | } 982 | } 983 | 984 | auto const pValue = reinterpret_cast(m_storage.data() + m_storage.size()); 985 | pValue->m_nextIndex = itParent.m_index; // Stash itParent for use by _newValueCommit. 986 | pValue->m_type = static_cast(front); // Stash front for use by _newValueCommit. 987 | 988 | return pNameAfterAlloc; 989 | } 990 | 991 | void 992 | JsonBuilder::NewValueInit( 993 | bool front, 994 | const_iterator const& itParent, 995 | _In_reads_(cchName) char const* pchNameUtf8, 996 | size_type cchName, 997 | unsigned cbDataHint) 998 | noexcept(false) // may throw bad_alloc, length_error 999 | { 1000 | auto constexpr WorstCaseMultiplier = 1u; 1001 | if (cchName > NameMax / WorstCaseMultiplier) 1002 | { 1003 | JsonThrowLengthError("JsonBuilder - cchName too large"); 1004 | } 1005 | 1006 | auto const cchSrc = static_cast(cchName); 1007 | auto const cbNameReserve = cchSrc * WorstCaseMultiplier; 1008 | auto const pOldStorageData = m_storage.data(); 1009 | auto const pchSrc = static_cast( 1010 | NewValueInitImpl(front, itParent, pchNameUtf8, cbNameReserve, cbDataHint)); 1011 | auto const pValue = reinterpret_cast(m_storage.data() + m_storage.size()); 1012 | auto const pchDest = reinterpret_cast(pValue + 1); 1013 | 1014 | // Stash the name for use by _newValueCommit. 1015 | auto const cchDest = cchSrc; 1016 | memcpy(pchDest, pchSrc, cchSrc); // No conversion needed. 1017 | pValue->m_cchName = cchDest; 1018 | 1019 | // Stash the old pointer for use by _newValueCommit. 1020 | memcpy(reinterpret_cast(pValue) + DATA_OFFSET(cchDest), 1021 | &pOldStorageData, sizeof(pOldStorageData)); 1022 | } 1023 | 1024 | void 1025 | JsonBuilder::NewValueInit( 1026 | bool front, 1027 | const_iterator const& itParent, 1028 | _In_reads_(cchName) char16_t const* pchNameUtf16, 1029 | size_type cchName, 1030 | unsigned cbDataHint) 1031 | noexcept(false) // may throw bad_alloc, length_error 1032 | { 1033 | auto constexpr WorstCaseMultiplier = 3u; // 1 UTF-16 code unit -> 3 UTF-8 code units. 1034 | if (cchName > NameMax / WorstCaseMultiplier) 1035 | { 1036 | JsonThrowLengthError("JsonBuilder - cchName too large"); 1037 | } 1038 | 1039 | auto const cchSrc = static_cast(cchName); 1040 | auto const cbNameReserve = cchSrc * WorstCaseMultiplier; 1041 | auto const pOldStorageData = m_storage.data(); 1042 | auto const pchSrc = static_cast( 1043 | NewValueInitImpl(front, itParent, pchNameUtf16, cbNameReserve, cbDataHint)); 1044 | auto const pValue = reinterpret_cast(m_storage.data() + m_storage.size()); 1045 | auto const pchDest = reinterpret_cast(pValue + 1); 1046 | 1047 | // Stash the name for use by _newValueCommit. 1048 | auto const cchDest = Utf16ToUtf8(pchDest, pchSrc, cchSrc); 1049 | pValue->m_cchName = cchDest; 1050 | 1051 | // Stash the old pointer for use by _newValueCommit. 1052 | memcpy(reinterpret_cast(pValue) + DATA_OFFSET(cchDest), 1053 | &pOldStorageData, sizeof(pOldStorageData)); 1054 | } 1055 | 1056 | void 1057 | JsonBuilder::NewValueInit( 1058 | bool front, 1059 | const_iterator const& itParent, 1060 | _In_reads_(cchName) char32_t const* pchNameUtf32, 1061 | size_type cchName, 1062 | unsigned cbDataHint) 1063 | noexcept(false) // may throw bad_alloc, length_error 1064 | { 1065 | auto constexpr WorstCaseMultiplier = 4u; // 1 UTF-32 code unit -> 4 UTF-8 code units. 1066 | if (cchName > NameMax / WorstCaseMultiplier) 1067 | { 1068 | JsonThrowLengthError("JsonBuilder - cchName too large"); 1069 | } 1070 | 1071 | auto const cchSrc = static_cast(cchName); 1072 | auto const cbNameReserve = cchSrc * WorstCaseMultiplier; 1073 | auto const pOldStorageData = m_storage.data(); 1074 | auto const pchSrc = static_cast( 1075 | NewValueInitImpl(front, itParent, pchNameUtf32, cbNameReserve, cbDataHint)); 1076 | auto const pValue = reinterpret_cast(m_storage.data() + m_storage.size()); 1077 | auto const pchDest = reinterpret_cast(pValue + 1); 1078 | 1079 | // Stash the name for use by _newValueCommit. 1080 | auto const cchDest = Utf32ToUtf8(pchDest, pchSrc, cchSrc); 1081 | pValue->m_cchName = cchDest; 1082 | 1083 | // Stash the old pointer for use by _newValueCommit. 1084 | memcpy(reinterpret_cast(pValue) + DATA_OFFSET(cchDest), 1085 | &pOldStorageData, 1086 | sizeof(pOldStorageData)); 1087 | } 1088 | 1089 | JsonBuilder::iterator 1090 | JsonBuilder::_newValueCommit( 1091 | JsonType type, 1092 | unsigned cbData, 1093 | _In_reads_bytes_opt_(cbData) void const* pbData) 1094 | noexcept(false) // may throw bad_alloc, length_error 1095 | { 1096 | if (cbData > DataMax) 1097 | { 1098 | JsonThrowLengthError("JsonBuilder - cbValue too large"); 1099 | } 1100 | 1101 | auto const newIndex = static_cast(m_storage.size()); 1102 | 1103 | // We expect front, parentIndex, name, and pOldStorageData to have been 1104 | // stashed by NewValueInit. 1105 | 1106 | auto pValue = reinterpret_cast(m_storage.data() + newIndex); 1107 | assert(m_storage.capacity() - m_storage.size() >= sizeof(JsonValue) / StorageSize); 1108 | 1109 | auto const dataIndex = newIndex + DATA_OFFSET(pValue->m_cchName); 1110 | assert(m_storage.capacity() >= dataIndex + (sizeof(void*) + StorageSize - 1) / StorageSize); 1111 | 1112 | auto const parentIndex = pValue->m_nextIndex; 1113 | assert(parentIndex < newIndex); 1114 | assert(IS_COMPOSITE_TYPE(GetValue(parentIndex).m_type)); 1115 | 1116 | auto const front = pValue->m_type != 0; 1117 | assert(pValue->m_type == 0 || pValue->m_type == 1); 1118 | 1119 | StoragePod const* pOldStorageData; 1120 | memcpy(&pOldStorageData, m_storage.data() + dataIndex, sizeof(pOldStorageData)); 1121 | 1122 | if (IS_COMPOSITE_TYPE(type)) 1123 | { 1124 | assert(cbData == 0); 1125 | cbData = sizeof(JsonValueBase); 1126 | } 1127 | 1128 | unsigned const newStorageSize = dataIndex + (cbData + StorageSize - 1) / StorageSize; 1129 | if (newStorageSize < dataIndex) 1130 | { 1131 | JsonThrowLengthError("JsonBuilder - too much data"); 1132 | } 1133 | 1134 | if (m_storage.capacity() < newStorageSize) 1135 | { 1136 | RestoreOldSize restoreOldSize(m_storage); // In case resize(newStorageSize) throws. 1137 | m_storage.resize(dataIndex); // Does not reallocate. Ensures that name gets copied during reallocation. 1138 | m_storage.resize(newStorageSize); // Reallocate. 1139 | 1140 | // Commit. 1141 | restoreOldSize.Dismiss(); 1142 | pValue = reinterpret_cast(m_storage.data() + newIndex); 1143 | } 1144 | else 1145 | { 1146 | // Commit. 1147 | m_storage.resize(newStorageSize); 1148 | } 1149 | 1150 | pValue->m_nextIndex = 0; 1151 | pValue->m_type = type; 1152 | 1153 | if (IS_COMPOSITE_TYPE(type)) 1154 | { 1155 | pValue->m_lastChildIndex = dataIndex; 1156 | 1157 | // Set up sentinel node. Insert it into the linked list. 1158 | auto pRootValue = reinterpret_cast(m_storage.data()); 1159 | auto pSentinel = reinterpret_cast(m_storage.data() + dataIndex); 1160 | pSentinel->m_nextIndex = pRootValue->m_nextIndex; 1161 | pSentinel->m_cchName = 0; 1162 | pSentinel->m_type = JsonHidden; 1163 | pRootValue->m_nextIndex = dataIndex; 1164 | } 1165 | else 1166 | { 1167 | pValue->m_cbData = cbData; 1168 | 1169 | if (pbData != nullptr) 1170 | { 1171 | if (pOldStorageData != m_storage.data() && // We reallocated. 1172 | pOldStorageData != nullptr && // Wasn't empty. 1173 | pbData > static_cast(pOldStorageData) && 1174 | pbData < static_cast(pOldStorageData + newIndex)) 1175 | { 1176 | // They're copying the data from within the vector, and we just resized 1177 | // out from under them. Technically, we could consider this a bug in the 1178 | // caller, but it's an easy mistake to make, easy to miss in testing, 1179 | // and hard to diagnose if it hits. Dealing with this is easy for us, so 1180 | // just fix up the problem instead of making it an error. 1181 | pbData = reinterpret_cast(m_storage.data()) + 1182 | (static_cast(pbData) - reinterpret_cast(pOldStorageData)); 1183 | } 1184 | 1185 | memcpy(m_storage.data() + dataIndex, pbData, cbData); 1186 | } 1187 | } 1188 | 1189 | // Find the right place in the linked list for the new node. 1190 | // Update the parent's lastChildIndex if necessary. 1191 | 1192 | auto& parentValue = GetValue(parentIndex); 1193 | Index prevIndex; // The node that the new node goes after. 1194 | if (front) 1195 | { 1196 | prevIndex = FirstChild(parentIndex); 1197 | if (prevIndex == parentValue.m_lastChildIndex) 1198 | { 1199 | parentValue.m_lastChildIndex = newIndex; 1200 | } 1201 | } 1202 | else 1203 | { 1204 | prevIndex = parentValue.m_lastChildIndex; 1205 | parentValue.m_lastChildIndex = newIndex; 1206 | } 1207 | 1208 | // Insert the new node into the linked list after prev. 1209 | 1210 | auto& prevValue = GetValue(prevIndex); 1211 | auto& newValue = GetValue(newIndex); 1212 | newValue.m_nextIndex = prevValue.m_nextIndex; 1213 | prevValue.m_nextIndex = newIndex; 1214 | 1215 | return iterator(const_iterator(this, newIndex)); 1216 | } 1217 | 1218 | JsonBuilder::iterator 1219 | JsonBuilder::_newValueCommitSbcsAsUtf8( 1220 | JsonType type, 1221 | std::string_view sbcsData, 1222 | _In_reads_(128) char16_t const* high128) 1223 | noexcept(false) // may throw bad_alloc, length_error 1224 | { 1225 | auto constexpr WorstCaseMultiplier = 3u; // 1 UTF-16 code unit -> 3 UTF-8 code units. 1226 | if (sbcsData.size() > DataMax / WorstCaseMultiplier) 1227 | { 1228 | JsonThrowLengthError("JsonBuilder - cchData too large"); 1229 | } 1230 | 1231 | // Create node. Reserve worst-case size for data. 1232 | auto const cchSrc = static_cast(sbcsData.size()); 1233 | auto const valueIt = _newValueCommit(type, cchSrc * WorstCaseMultiplier, nullptr); 1234 | 1235 | // Copy data into node, converting to UTF-8. 1236 | auto& value = GetValue(valueIt.m_index); 1237 | auto const valueDataIndex = valueIt.m_index + DATA_OFFSET(value.m_cchName); 1238 | assert(m_storage.size() - valueDataIndex >= (cchSrc * WorstCaseMultiplier + StorageSize - 1u) / StorageSize); 1239 | auto const cbDest = SbcsToUtf8(reinterpret_cast(m_storage.data() + valueDataIndex), sbcsData.data(), cchSrc, high128); 1240 | 1241 | // Shrink to fit actual data size. 1242 | value.m_cbData = cbDest; // Shrink 1243 | m_storage.resize(valueDataIndex + (cbDest + StorageSize - 1) / StorageSize); // Shrink 1244 | 1245 | return valueIt; 1246 | } 1247 | 1248 | JsonBuilder::iterator 1249 | JsonBuilder::NewValueCommitUtfAsUtf8Impl( 1250 | JsonType type, 1251 | JsonInternal::JSON_SIZE_T cchData, 1252 | _In_reads_(cchData) char const* pchDataUtf8) 1253 | noexcept(false) 1254 | { 1255 | if (cchData > DataMax) 1256 | { 1257 | JsonThrowLengthError("JsonBuilder - cchData too large"); 1258 | } 1259 | return _newValueCommit(type, static_cast(cchData), pchDataUtf8); 1260 | } 1261 | 1262 | JsonBuilder::iterator 1263 | JsonBuilder::NewValueCommitUtfAsUtf8Impl( 1264 | JsonType type, 1265 | JsonInternal::JSON_SIZE_T cchData, 1266 | _In_reads_(cchData) char16_t const* pchDataUtf16) 1267 | noexcept(false) // may throw bad_alloc, length_error 1268 | { 1269 | auto constexpr WorstCaseMultiplier = 3u; // 1 UTF-16 code unit -> 3 UTF-8 code units. 1270 | if (cchData > DataMax / WorstCaseMultiplier) 1271 | { 1272 | JsonThrowLengthError("JsonBuilder - cchData too large"); 1273 | } 1274 | 1275 | // Create node. Reserve worst-case size for data. 1276 | auto const cchSrc = static_cast(cchData); 1277 | auto const valueIt = _newValueCommit(type, cchSrc * WorstCaseMultiplier, nullptr); 1278 | 1279 | // Copy data into node, converting to UTF-8. 1280 | auto& value = GetValue(valueIt.m_index); 1281 | auto const valueDataIndex = valueIt.m_index + DATA_OFFSET(value.m_cchName); 1282 | assert(m_storage.size() - valueDataIndex >= (cchSrc * WorstCaseMultiplier + StorageSize - 1u) / StorageSize); 1283 | auto const cbDest = Utf16ToUtf8(reinterpret_cast(m_storage.data() + valueDataIndex), pchDataUtf16, cchSrc); 1284 | 1285 | // Shrink to fit actual data size. 1286 | value.m_cbData = cbDest; // Shrink 1287 | m_storage.resize(valueDataIndex + (cbDest + StorageSize - 1) / StorageSize); // Shrink 1288 | 1289 | return valueIt; 1290 | } 1291 | 1292 | JsonBuilder::iterator 1293 | JsonBuilder::NewValueCommitUtfAsUtf8Impl( 1294 | JsonType type, 1295 | JsonInternal::JSON_SIZE_T cchData, 1296 | _In_reads_(cchData) char32_t const* pchDataUtf32) 1297 | noexcept(false) // may throw bad_alloc, length_error 1298 | { 1299 | auto constexpr WorstCaseMultiplier = 4u; // 1 UTF-32 code unit -> 4 UTF-8 code units. 1300 | if (cchData > DataMax / WorstCaseMultiplier) 1301 | { 1302 | JsonThrowLengthError("JsonBuilder - cchData too large"); 1303 | } 1304 | 1305 | // Create node. Reserve worst-case size for data. 1306 | auto const cchSrc = static_cast(cchData); 1307 | auto const valueIt = _newValueCommit(type, cchSrc * WorstCaseMultiplier, nullptr); 1308 | 1309 | // Copy data into node, converting to UTF-8. 1310 | auto& value = GetValue(valueIt.m_index); 1311 | auto const valueDataIndex = valueIt.m_index + DATA_OFFSET(value.m_cchName); 1312 | assert(m_storage.size() - valueDataIndex >= (cchSrc * WorstCaseMultiplier + StorageSize - 1u) / StorageSize); 1313 | auto const cbDest = Utf32ToUtf8(reinterpret_cast(m_storage.data() + valueDataIndex), pchDataUtf32, cchSrc); 1314 | 1315 | // Shrink to fit actual data size. 1316 | value.m_cbData = cbDest; // Shrink 1317 | m_storage.resize(valueDataIndex + (cbDest + StorageSize - 1) / StorageSize); // Shrink 1318 | 1319 | return valueIt; 1320 | } 1321 | 1322 | void JsonBuilder::AssertNotEnd(Index index) noexcept 1323 | { 1324 | (void) index; // Unreferenced parameter in release builds. 1325 | assert(index != 0); 1326 | } 1327 | 1328 | void JsonBuilder::AssertHidden(JsonType type) noexcept 1329 | { 1330 | (void) type; // Unreferenced parameter in release builds. 1331 | assert(type == JsonHidden); 1332 | } 1333 | 1334 | void JsonBuilder::ValidateIterator(const_iterator const& it) const noexcept 1335 | { 1336 | assert(it.m_index == 0 || it.m_index < m_storage.size()); 1337 | 1338 | if (it.m_pContainer != this) 1339 | { 1340 | assert(!"JsonBuilder: iterator is from a different container"); 1341 | std::terminate(); 1342 | } 1343 | } 1344 | 1345 | void JsonBuilder::ValidateParentIterator(Index index) const noexcept 1346 | { 1347 | assert(!m_storage.empty()); 1348 | 1349 | if (!IS_COMPOSITE_TYPE(GetValue(index).m_type)) 1350 | { 1351 | assert(!"JsonBuilder: destination must be an array or object"); 1352 | std::terminate(); 1353 | } 1354 | } 1355 | 1356 | bool JsonBuilder::CanIterateOver(const_iterator const& it) const noexcept 1357 | { 1358 | return !m_storage.empty() && IS_COMPOSITE_TYPE(GetValue(it.m_index).m_type); 1359 | } 1360 | 1361 | JsonValue const& JsonBuilder::GetValue(Index index) const noexcept 1362 | { 1363 | return reinterpret_cast(m_storage[index]); 1364 | } 1365 | 1366 | JsonValue& JsonBuilder::GetValue(Index index) noexcept 1367 | { 1368 | return reinterpret_cast(m_storage[index]); 1369 | } 1370 | 1371 | JsonBuilder::Index JsonBuilder::FirstChild(Index index) const noexcept 1372 | { 1373 | auto cchName = reinterpret_cast(m_storage[index]).m_cchName; 1374 | return index + DATA_OFFSET(cchName); 1375 | } 1376 | 1377 | JsonBuilder::Index JsonBuilder::LastChild(Index index) const noexcept 1378 | { 1379 | return reinterpret_cast(m_storage[index]).m_lastChildIndex; 1380 | } 1381 | 1382 | JsonBuilder::Index JsonBuilder::NextIndex(Index index) const noexcept 1383 | { 1384 | assert(index < m_storage.size()); 1385 | auto pValue = reinterpret_cast(m_storage.data() + index); 1386 | for (;;) 1387 | { 1388 | assert(index != 0); // assert(it != end()) 1389 | index = pValue->m_nextIndex; 1390 | pValue = reinterpret_cast(m_storage.data() + index); 1391 | if (pValue->m_type != JsonHidden) 1392 | { 1393 | break; 1394 | } 1395 | } 1396 | return index; 1397 | } 1398 | 1399 | void JsonBuilder::EnsureRootExists() 1400 | { 1401 | if (m_storage.empty()) 1402 | { 1403 | CreateRoot(); 1404 | } 1405 | } 1406 | 1407 | void swap(JsonBuilder& a, JsonBuilder& b) noexcept 1408 | { 1409 | a.swap(b); 1410 | } 1411 | 1412 | // JsonImplementType 1413 | 1414 | /* 1415 | The macro-based GetUnchecked and ConvertTo (for f32, u8, u16, u32, i8, i16, 1416 | and i32) aren't perfectly optimal... But they're probably close enough. 1417 | */ 1418 | 1419 | #define IMPLEMENT_AddValue(DataType, DataSize, DataPtr, ValueType, InRef) \ 1420 | JsonIterator JsonImplementType::AddValueCommit( \ 1421 | JsonBuilder& builder, \ 1422 | DataType InRef data) \ 1423 | { \ 1424 | return builder._newValueCommit(ValueType, DataSize, DataPtr);\ 1425 | } 1426 | 1427 | #define IMPLEMENT_GetUnchecked(DataType, ValueType) \ 1428 | DataType JsonImplementType::GetUnchecked( \ 1429 | JsonValue const& value) noexcept \ 1430 | { \ 1431 | return static_cast(GetUnchecked##ValueType(value)); \ 1432 | } 1433 | 1434 | #define IMPLEMENT_JsonImplementType(DataType, ValueType, InRef) \ 1435 | IMPLEMENT_AddValue(DataType, sizeof(data), &data, ValueType, InRef); \ 1436 | IMPLEMENT_GetUnchecked(DataType, ValueType); \ 1437 | \ 1438 | bool JsonImplementType::ConvertTo( \ 1439 | JsonValue const& value, DataType& result) noexcept \ 1440 | { \ 1441 | return ConvertTo##ValueType(value, result); \ 1442 | } 1443 | 1444 | // JsonBool 1445 | 1446 | bool JsonImplementType::GetUnchecked(JsonValue const& value) noexcept 1447 | { 1448 | assert(value.Type() == JsonBool); 1449 | bool result; 1450 | unsigned cb; 1451 | void const* pb = value.Data(&cb); 1452 | switch (cb) 1453 | { 1454 | case 1: 1455 | result = *static_cast(pb) != 0; 1456 | break; 1457 | case 4: 1458 | result = *static_cast(pb) != 0; 1459 | break; 1460 | default: 1461 | result = 0; 1462 | assert(!"Invalid size for JsonBool"); 1463 | break; 1464 | } 1465 | return result; 1466 | } 1467 | 1468 | bool JsonImplementType::ConvertTo(JsonValue const& value, bool& result) noexcept 1469 | { 1470 | bool success; 1471 | switch (value.Type()) 1472 | { 1473 | case JsonBool: 1474 | result = GetUnchecked(value); 1475 | success = true; 1476 | break; 1477 | default: 1478 | result = false; 1479 | success = false; 1480 | break; 1481 | } 1482 | return success; 1483 | } 1484 | 1485 | IMPLEMENT_AddValue(bool, sizeof(data), &data, JsonBool, ); 1486 | 1487 | // JsonUInt 1488 | 1489 | bool JsonImplementType::ConvertTo( 1490 | JsonValue const& value, 1491 | unsigned long long& result) noexcept 1492 | { 1493 | static double const UnsignedHuge = 18446744073709551616.0; 1494 | 1495 | bool success; 1496 | switch (value.Type()) 1497 | { 1498 | case JsonUInt: 1499 | result = JsonImplementType::GetUnchecked(value); 1500 | success = true; 1501 | goto Done; 1502 | 1503 | case JsonInt: 1504 | result = static_cast( 1505 | JsonImplementType::GetUnchecked(value)); 1506 | if (result < 0x8000000000000000) 1507 | { 1508 | success = true; 1509 | goto Done; 1510 | } 1511 | break; 1512 | 1513 | case JsonFloat: { 1514 | auto f = JsonImplementType::GetUnchecked(value); 1515 | if (0.0 <= f && f < UnsignedHuge) 1516 | { 1517 | result = static_cast(f); 1518 | success = true; 1519 | goto Done; 1520 | } 1521 | break; 1522 | } 1523 | 1524 | default: 1525 | break; 1526 | } 1527 | 1528 | result = 0; 1529 | success = false; 1530 | 1531 | Done: 1532 | 1533 | return success; 1534 | } 1535 | 1536 | static uint64_t GetUncheckedJsonUInt(JsonValue const& value) 1537 | { 1538 | assert(value.Type() == JsonUInt); 1539 | uint64_t result; 1540 | unsigned cb; 1541 | void const* pb = value.Data(&cb); 1542 | switch (cb) 1543 | { 1544 | case 1: 1545 | result = *static_cast(pb); 1546 | break; 1547 | case 2: 1548 | result = *static_cast(pb); 1549 | break; 1550 | case 4: 1551 | result = *static_cast(pb); 1552 | break; 1553 | case 8: 1554 | result = *static_cast(pb); 1555 | break; 1556 | default: 1557 | result = 0; 1558 | assert(!"Invalid size for JsonUInt"); 1559 | break; 1560 | } 1561 | return result; 1562 | } 1563 | 1564 | IMPLEMENT_AddValue(unsigned long long, sizeof(data), &data, JsonUInt, ); 1565 | IMPLEMENT_GetUnchecked(unsigned long long, JsonUInt); 1566 | 1567 | template 1568 | static bool ConvertToJsonUInt(JsonValue const& value, T& result) 1569 | { 1570 | unsigned long long implResult; 1571 | bool success; 1572 | if (JsonImplementType::ConvertTo(value, implResult) && 1573 | implResult <= (0xffffffffffffffff >> (64 - sizeof(T) * 8))) 1574 | { 1575 | result = static_cast(implResult); 1576 | success = true; 1577 | } 1578 | else 1579 | { 1580 | result = 0; 1581 | success = false; 1582 | } 1583 | return success; 1584 | } 1585 | 1586 | IMPLEMENT_JsonImplementType(unsigned char, JsonUInt, ); 1587 | IMPLEMENT_JsonImplementType(unsigned short, JsonUInt, ); 1588 | IMPLEMENT_JsonImplementType(unsigned int, JsonUInt, ); 1589 | IMPLEMENT_JsonImplementType(unsigned long, JsonUInt, ); 1590 | 1591 | // JsonInt 1592 | 1593 | bool JsonImplementType::ConvertTo( 1594 | JsonValue const& value, 1595 | signed long long& result) noexcept 1596 | { 1597 | static double const SignedHuge = 9223372036854775808.0; 1598 | 1599 | bool success; 1600 | switch (value.Type()) 1601 | { 1602 | case JsonInt: 1603 | result = JsonImplementType::GetUnchecked(value); 1604 | success = true; 1605 | goto Done; 1606 | 1607 | case JsonUInt: 1608 | result = static_cast( 1609 | JsonImplementType::GetUnchecked(value)); 1610 | if (result >= 0) 1611 | { 1612 | success = true; 1613 | goto Done; 1614 | } 1615 | break; 1616 | 1617 | case JsonFloat: { 1618 | auto f = JsonImplementType::GetUnchecked(value); 1619 | if (-SignedHuge <= f && f < SignedHuge) 1620 | { 1621 | result = static_cast(f); 1622 | success = true; 1623 | goto Done; 1624 | } 1625 | break; 1626 | } 1627 | 1628 | default: 1629 | break; 1630 | } 1631 | 1632 | result = 0; 1633 | success = false; 1634 | 1635 | Done: 1636 | 1637 | return success; 1638 | } 1639 | 1640 | static int64_t GetUncheckedJsonInt(JsonValue const& value) 1641 | { 1642 | assert(value.Type() == JsonInt); 1643 | int64_t result; 1644 | unsigned cb; 1645 | void const* pb = value.Data(&cb); 1646 | switch (cb) 1647 | { 1648 | case 1: 1649 | result = *static_cast(pb); 1650 | break; 1651 | case 2: 1652 | result = *static_cast(pb); 1653 | break; 1654 | case 4: 1655 | result = *static_cast(pb); 1656 | break; 1657 | case 8: 1658 | result = *static_cast(pb); 1659 | break; 1660 | default: 1661 | result = 0; 1662 | assert(!"Invalid size for JsonInt"); 1663 | break; 1664 | } 1665 | return result; 1666 | } 1667 | 1668 | IMPLEMENT_AddValue(signed long long, sizeof(data), &data, JsonInt, ); 1669 | IMPLEMENT_GetUnchecked(signed long long, JsonInt); 1670 | 1671 | template 1672 | static bool ConvertToJsonInt(JsonValue const& value, T& result) 1673 | { 1674 | signed long long implResult; 1675 | bool success; 1676 | 1677 | if (JsonImplementType::ConvertTo(value, implResult) && 1678 | (sizeof(T) == sizeof(signed long long) || 1679 | (implResult < (1ll << (sizeof(T) * 8 - 1)) && 1680 | implResult >= -(1ll << (sizeof(T) * 8 - 1))))) 1681 | { 1682 | result = static_cast(implResult); 1683 | success = true; 1684 | } 1685 | else 1686 | { 1687 | result = 0; 1688 | success = false; 1689 | } 1690 | return success; 1691 | } 1692 | 1693 | IMPLEMENT_JsonImplementType(signed char, JsonInt, ); 1694 | IMPLEMENT_JsonImplementType(signed short, JsonInt, ); 1695 | IMPLEMENT_JsonImplementType(signed int, JsonInt, ); 1696 | IMPLEMENT_JsonImplementType(signed long, JsonInt, ); 1697 | 1698 | // JsonFloat 1699 | 1700 | double JsonImplementType::GetUnchecked(JsonValue const& value) noexcept 1701 | { 1702 | assert(value.Type() == JsonFloat); 1703 | double result; 1704 | unsigned cb; 1705 | void const* pb = value.Data(&cb); 1706 | switch (cb) 1707 | { 1708 | case 4: 1709 | result = *static_cast(pb); 1710 | break; 1711 | case 8: 1712 | result = *static_cast(pb); 1713 | break; 1714 | default: 1715 | result = 0; 1716 | assert(!"Invalid size for JsonFloat"); 1717 | break; 1718 | } 1719 | return result; 1720 | } 1721 | 1722 | bool JsonImplementType::ConvertTo( 1723 | JsonValue const& value, 1724 | double& result) noexcept 1725 | { 1726 | bool success; 1727 | 1728 | switch (value.Type()) 1729 | { 1730 | case JsonUInt: 1731 | result = static_cast( 1732 | JsonImplementType::GetUnchecked(value)); 1733 | success = true; 1734 | break; 1735 | 1736 | case JsonInt: 1737 | result = static_cast( 1738 | JsonImplementType::GetUnchecked(value)); 1739 | success = true; 1740 | break; 1741 | 1742 | case JsonFloat: 1743 | result = GetUnchecked(value); 1744 | success = true; 1745 | break; 1746 | 1747 | default: 1748 | result = 0.0; 1749 | success = false; 1750 | break; 1751 | } 1752 | 1753 | return success; 1754 | } 1755 | 1756 | IMPLEMENT_AddValue(double, sizeof(data), &data, JsonFloat, ); 1757 | 1758 | #define GetUncheckedJsonFloat(value) \ 1759 | JsonImplementType::GetUnchecked(value) 1760 | 1761 | template 1762 | static bool ConvertToJsonFloat(JsonValue const& value, T& result) 1763 | { 1764 | double implResult; 1765 | bool success = JsonImplementType::ConvertTo(value, implResult); 1766 | result = static_cast(implResult); 1767 | return success; 1768 | } 1769 | 1770 | IMPLEMENT_JsonImplementType(float, JsonFloat, ); 1771 | 1772 | // JsonUtf8 1773 | 1774 | JsonIterator 1775 | JsonImplementType::AddValueCommit( 1776 | JsonBuilder& builder, 1777 | _In_z_ char const* psz) 1778 | { 1779 | return builder._newValueCommitUtfAsUtf8(JsonUtf8, psz); 1780 | } 1781 | 1782 | JsonIterator 1783 | JsonImplementType::AddValueCommit( 1784 | JsonBuilder& builder, 1785 | _In_z_ wchar_t const* psz) 1786 | { 1787 | return builder._newValueCommitUtfAsUtf8(JsonUtf8, psz); 1788 | } 1789 | 1790 | JsonIterator 1791 | JsonImplementType::AddValueCommit( 1792 | JsonBuilder& builder, 1793 | _In_z_ char16_t const* psz) 1794 | { 1795 | return builder._newValueCommitUtfAsUtf8(JsonUtf8, psz); 1796 | } 1797 | 1798 | JsonIterator 1799 | JsonImplementType::AddValueCommit( 1800 | JsonBuilder& builder, 1801 | _In_z_ char32_t const* psz) 1802 | { 1803 | return builder._newValueCommitUtfAsUtf8(JsonUtf8, psz); 1804 | } 1805 | 1806 | std::string_view 1807 | JsonImplementType::GetUnchecked(JsonValue const& value) noexcept 1808 | { 1809 | assert(value.Type() == JsonUtf8); 1810 | unsigned cb; 1811 | void const* pb = value.Data(&cb); 1812 | return std::string_view(static_cast(pb), cb); 1813 | } 1814 | 1815 | bool JsonImplementType::ConvertTo( 1816 | JsonValue const& value, 1817 | std::string_view& result) noexcept 1818 | { 1819 | bool success; 1820 | 1821 | if (value.Type() == JsonUtf8) 1822 | { 1823 | result = GetUnchecked(value); 1824 | success = true; 1825 | } 1826 | else 1827 | { 1828 | result = std::string_view(); 1829 | success = false; 1830 | } 1831 | 1832 | return success; 1833 | } 1834 | 1835 | JsonIterator 1836 | JsonImplementType::AddValueCommit( 1837 | JsonBuilder& builder, 1838 | std::string_view data) 1839 | { 1840 | return builder._newValueCommitUtfAsUtf8(JsonUtf8, data); 1841 | } 1842 | 1843 | JsonIterator 1844 | JsonImplementType::AddValueCommit( 1845 | JsonBuilder& builder, 1846 | std::wstring_view data) 1847 | { 1848 | return builder._newValueCommitUtfAsUtf8(JsonUtf8, data); 1849 | } 1850 | 1851 | JsonIterator 1852 | JsonImplementType::AddValueCommit( 1853 | JsonBuilder& builder, 1854 | std::u16string_view data) 1855 | { 1856 | return builder._newValueCommitUtfAsUtf8(JsonUtf8, data); 1857 | } 1858 | 1859 | JsonIterator 1860 | JsonImplementType::AddValueCommit( 1861 | JsonBuilder& builder, 1862 | std::u32string_view data) 1863 | { 1864 | return builder._newValueCommitUtfAsUtf8(JsonUtf8, data); 1865 | } 1866 | 1867 | JsonIterator 1868 | JsonImplementType::AddValueCommit( 1869 | JsonBuilder& builder, 1870 | latin1_view data) 1871 | { 1872 | static constexpr char16_t High128[] = { 1873 | 0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8A, 0x8B, 0x8C, 0x8D, 0x8E, 0x8F, 1874 | 0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9A, 0x9B, 0x9C, 0x9D, 0x9E, 0x9F, 1875 | 0xA0, 0xA1, 0xA2, 0xA3, 0xA4, 0xA5, 0xA6, 0xA7, 0xA8, 0xA9, 0xAA, 0xAB, 0xAC, 0xAD, 0xAE, 0xAF, 1876 | 0xB0, 0xB1, 0xB2, 0xB3, 0xB4, 0xB5, 0xB6, 0xB7, 0xB8, 0xB9, 0xBA, 0xBB, 0xBC, 0xBD, 0xBE, 0xBF, 1877 | 0xC0, 0xC1, 0xC2, 0xC3, 0xC4, 0xC5, 0xC6, 0xC7, 0xC8, 0xC9, 0xCA, 0xCB, 0xCC, 0xCD, 0xCE, 0xCF, 1878 | 0xD0, 0xD1, 0xD2, 0xD3, 0xD4, 0xD5, 0xD6, 0xD7, 0xD8, 0xD9, 0xDA, 0xDB, 0xDC, 0xDD, 0xDE, 0xDF, 1879 | 0xE0, 0xE1, 0xE2, 0xE3, 0xE4, 0xE5, 0xE6, 0xE7, 0xE8, 0xE9, 0xEA, 0xEB, 0xEC, 0xED, 0xEE, 0xEF, 1880 | 0xF0, 0xF1, 0xF2, 0xF3, 0xF4, 0xF5, 0xF6, 0xF7, 0xF8, 0xF9, 0xFA, 0xFB, 0xFC, 0xFD, 0xFE, 0xFF, 1881 | }; 1882 | static_assert(sizeof(High128) == 128 * sizeof(High128[0]), "High128 size"); 1883 | return builder._newValueCommitSbcsAsUtf8(JsonUtf8, data, High128); 1884 | } 1885 | 1886 | JsonIterator 1887 | JsonImplementType::AddValueCommit( 1888 | JsonBuilder& builder, 1889 | cp1252_view data) 1890 | { 1891 | static constexpr char16_t High128[] = { 1892 | 0x20AC, 0x0081, 0x201A, 0x0192, 0x201E, 0x2026, 0x2020, 0x2021, 0x02C6, 0x2030, 0x0160, 0x2039, 0x0152, 0x008D, 0x017D, 0x008F, 1893 | 0x0090, 0x2018, 0x2019, 0x201C, 0x201D, 0x2022, 0x2013, 0x2014, 0x02DC, 0x2122, 0x0161, 0x203A, 0x0153, 0x009D, 0x017E, 0x0178, 1894 | 0xA0, 0xA1, 0xA2, 0xA3, 0xA4, 0xA5, 0xA6, 0xA7, 0xA8, 0xA9, 0xAA, 0xAB, 0xAC, 0xAD, 0xAE, 0xAF, 1895 | 0xB0, 0xB1, 0xB2, 0xB3, 0xB4, 0xB5, 0xB6, 0xB7, 0xB8, 0xB9, 0xBA, 0xBB, 0xBC, 0xBD, 0xBE, 0xBF, 1896 | 0xC0, 0xC1, 0xC2, 0xC3, 0xC4, 0xC5, 0xC6, 0xC7, 0xC8, 0xC9, 0xCA, 0xCB, 0xCC, 0xCD, 0xCE, 0xCF, 1897 | 0xD0, 0xD1, 0xD2, 0xD3, 0xD4, 0xD5, 0xD6, 0xD7, 0xD8, 0xD9, 0xDA, 0xDB, 0xDC, 0xDD, 0xDE, 0xDF, 1898 | 0xE0, 0xE1, 0xE2, 0xE3, 0xE4, 0xE5, 0xE6, 0xE7, 0xE8, 0xE9, 0xEA, 0xEB, 0xEC, 0xED, 0xEE, 0xEF, 1899 | 0xF0, 0xF1, 0xF2, 0xF3, 0xF4, 0xF5, 0xF6, 0xF7, 0xF8, 0xF9, 0xFA, 0xFB, 0xFC, 0xFD, 0xFE, 0xFF, 1900 | }; 1901 | static_assert(sizeof(High128) == 128 * sizeof(High128[0]), "High128 size"); 1902 | return builder._newValueCommitSbcsAsUtf8(JsonUtf8, data, High128); 1903 | } 1904 | 1905 | // JsonTime 1906 | 1907 | JsonIterator 1908 | JsonImplementType::AddValueCommit( 1909 | JsonBuilder& builder, 1910 | std::chrono::system_clock::time_point data) 1911 | { 1912 | uint64_t const ft = FileTime1970Ticks + std::chrono::duration_cast(data.time_since_epoch()).count(); 1913 | auto const timeStruct = TimeStruct::FromValue(ft); 1914 | return builder._newValueCommit(JsonTime, sizeof(timeStruct), &timeStruct); 1915 | } 1916 | 1917 | bool JsonImplementType::ConvertTo( 1918 | JsonValue const& jsonValue, 1919 | std::chrono::system_clock::time_point& value) noexcept 1920 | { 1921 | bool success; 1922 | 1923 | if (jsonValue.Type() == JsonTime) 1924 | { 1925 | value = jsonValue.GetUnchecked(); 1926 | success = true; 1927 | } 1928 | else 1929 | { 1930 | value = std::chrono::system_clock::time_point{}; 1931 | success = false; 1932 | } 1933 | 1934 | return success; 1935 | } 1936 | 1937 | std::chrono::system_clock::time_point 1938 | JsonImplementType::GetUnchecked( 1939 | JsonValue const& jsonValue) noexcept 1940 | { 1941 | assert(jsonValue.Type() == JsonTime); 1942 | assert(jsonValue.DataSize() == sizeof(TimeStruct)); 1943 | if (jsonValue.DataSize() == sizeof(TimeStruct)) 1944 | { 1945 | auto constexpr SystemClock1970Ticks = -std::chrono::duration_cast( 1946 | std::chrono::system_clock::time_point().time_since_epoch() 1947 | ).count(); 1948 | auto const ft = jsonValue.GetUnchecked().Value(); 1949 | auto const ticksSinceEpoch = ticks(ft + (SystemClock1970Ticks - FileTime1970Ticks)); 1950 | return std::chrono::system_clock::time_point(ticksSinceEpoch); 1951 | } 1952 | else 1953 | { 1954 | return {}; 1955 | } 1956 | } 1957 | 1958 | IMPLEMENT_AddValue(TimeStruct, sizeof(TimeStruct), &data, JsonTime, ); 1959 | 1960 | bool JsonImplementType::ConvertTo( 1961 | JsonValue const& jsonValue, 1962 | TimeStruct& value) noexcept 1963 | { 1964 | bool success; 1965 | 1966 | if (jsonValue.Type() == JsonTime) 1967 | { 1968 | assert(jsonValue.DataSize() == sizeof(TimeStruct)); 1969 | value = *static_cast(jsonValue.Data()); 1970 | success = true; 1971 | } 1972 | else 1973 | { 1974 | value = TimeStruct(); 1975 | success = false; 1976 | } 1977 | 1978 | return success; 1979 | } 1980 | 1981 | TimeStruct 1982 | JsonImplementType::GetUnchecked(JsonValue const& jsonValue) noexcept 1983 | { 1984 | assert(jsonValue.Type() == JsonTime); 1985 | assert(jsonValue.DataSize() == sizeof(TimeStruct)); 1986 | 1987 | return jsonValue.DataSize() == sizeof(TimeStruct) ? 1988 | *static_cast(jsonValue.Data()) : 1989 | TimeStruct(); 1990 | } 1991 | 1992 | // JsonUuid 1993 | 1994 | IMPLEMENT_AddValue(UuidStruct, sizeof(UuidStruct), &data, JsonUuid, const&); 1995 | 1996 | bool JsonImplementType::ConvertTo( 1997 | JsonValue const& jsonValue, 1998 | UuidStruct& value) noexcept 1999 | { 2000 | bool success; 2001 | 2002 | if (jsonValue.Type() == JsonUuid) 2003 | { 2004 | assert(jsonValue.DataSize() == sizeof(UuidStruct)); 2005 | value = *static_cast(jsonValue.Data()); 2006 | success = true; 2007 | } 2008 | else 2009 | { 2010 | value = UuidStruct(); 2011 | success = false; 2012 | } 2013 | 2014 | return success; 2015 | } 2016 | 2017 | UuidStruct const& 2018 | JsonImplementType::GetUnchecked(JsonValue const& jsonValue) noexcept 2019 | { 2020 | assert(jsonValue.Type() == JsonUuid); 2021 | assert(jsonValue.DataSize() == sizeof(UuidStruct)); 2022 | 2023 | static constexpr UuidStruct emptyUuid = { 0 }; 2024 | return jsonValue.DataSize() == sizeof(UuidStruct) ? 2025 | *static_cast(jsonValue.Data()) : 2026 | emptyUuid; 2027 | } 2028 | 2029 | // JsonType 2030 | 2031 | JsonIterator 2032 | JsonImplementType::AddValueCommit( 2033 | JsonBuilder& builder, 2034 | JsonType type) 2035 | { 2036 | // This method is only for 0-size values like null, array, or object. 2037 | // The following types cannot have 0-size values and should not call this: 2038 | assert(type != JsonUInt); 2039 | assert(type != JsonInt); 2040 | assert(type != JsonFloat); 2041 | assert(type != JsonBool); 2042 | assert(type != JsonTime); 2043 | assert(type != JsonUuid); 2044 | return builder._newValueCommit(type, 0, nullptr); 2045 | } 2046 | 2047 | } // namespace jsonbuilder 2048 | -------------------------------------------------------------------------------- /src/JsonExceptions.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. All rights reserved. 2 | // Licensed under the MIT License. 3 | 4 | #include 5 | namespace jsonbuilder 6 | { 7 | [[noreturn]] void _jsonbuilderDecl JsonThrowBadAlloc() noexcept(false) 8 | { 9 | throw std::bad_alloc(); 10 | } 11 | [[noreturn]] void _jsonbuilderDecl JsonThrowLengthError(_In_z_ const char* what) noexcept(false) 12 | { 13 | throw std::length_error(what); 14 | } 15 | [[noreturn]] void _jsonbuilderDecl JsonThrowInvalidArgument(_In_z_ const char* what) noexcept(false) 16 | { 17 | throw std::invalid_argument(what); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/JsonRenderer.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. All rights reserved. 2 | // Licensed under the MIT License. 3 | 4 | #include 5 | 6 | #ifdef _WIN32 7 | #include 8 | #else 9 | #define __USE_TIME_BITS64 10 | #include 11 | static_assert(sizeof(time_t) == 8, "time_t must be 64 bits"); 12 | #endif 13 | 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | 21 | #ifdef __cpp_lib_to_chars 22 | #define FORMAT_DOUBLE_USING_TO_CHARS 1 23 | #else 24 | #define FORMAT_DOUBLE_USING_TO_CHARS 0 25 | #endif 26 | 27 | #if !FORMAT_DOUBLE_USING_TO_CHARS 28 | #include 29 | #endif 30 | 31 | #ifndef _Out_writes_ 32 | #define _Out_writes_(c) 33 | #endif 34 | 35 | #define WriteChar(ch) m_renderBuffer.push_back(ch) 36 | #define WriteChars(pch, cch) m_renderBuffer.append(pch, cch) 37 | 38 | auto constexpr TicksPerSecond = 10'000'000u; 39 | auto constexpr FileTime1970Ticks = 116444736000000000u; 40 | auto constexpr FileTime10000Ticks = 2650467744000000000u; 41 | using ticks = std::chrono::duration>; 42 | 43 | namespace jsonbuilder { 44 | 45 | static constexpr unsigned Log10Ceil(unsigned n) 46 | { 47 | return n < 10 ? 1 : 1 + Log10Ceil(n / 10); 48 | } 49 | 50 | template 51 | static unsigned 52 | MemCpyFromLiteral(_Out_writes_(N) char* dest, char const (&src)[N]) noexcept 53 | { 54 | memcpy(dest, src, N); 55 | return N - 1; 56 | } 57 | 58 | /* 59 | Given a number from 0..15, returns the corresponding uppercase hex digit, 60 | i.e. '0'..'F'. Note: it is an error to pass a value larger than 15. 61 | */ 62 | static constexpr char u4_to_hex_upper(char unsigned val4) 63 | { 64 | assert(val4 < 16); 65 | return ("0123456789ABCDEF")[val4]; 66 | } 67 | 68 | static unsigned u8_to_hex_upper(char unsigned val8, _Out_writes_(2) char* pch) 69 | { 70 | pch[0] = u4_to_hex_upper((val8 >> 4) & 0xf); 71 | pch[1] = u4_to_hex_upper(val8 & 0xf); 72 | return 2; 73 | } 74 | 75 | /* 76 | Formats a uint32 with leading 0s. Always writes cch characters. 77 | */ 78 | static void FormatUint(unsigned n, _Out_writes_(cch) char* pch, unsigned cch) 79 | { 80 | do 81 | { 82 | --cch; 83 | pch[cch] = '0' + (n % 10); 84 | n = n / 10; 85 | } while (cch != 0); 86 | } 87 | 88 | template 89 | static unsigned JsonRenderXInt(N const& n, _Out_writes_z_(CB) char* pBuffer) 90 | { 91 | auto const result = std::to_chars(pBuffer, pBuffer + CB - 1, n); 92 | unsigned const cch = static_cast(result.ptr - pBuffer); 93 | assert(result.ec == std::errc()); 94 | assert(cch < CB); 95 | 96 | pBuffer[cch] = 0; 97 | return cch; 98 | } 99 | 100 | unsigned JsonRenderUInt(long long unsigned n, _Out_writes_z_(21) char* pBuffer) noexcept 101 | { 102 | return JsonRenderXInt<21>(n, pBuffer); 103 | } 104 | 105 | unsigned JsonRenderInt(long long signed n, _Out_writes_z_(21) char* pBuffer) noexcept 106 | { 107 | return JsonRenderXInt<21>(n, pBuffer); 108 | } 109 | 110 | unsigned JsonRenderFloat(double n, _Out_writes_z_(32) char* pBuffer) noexcept 111 | { 112 | auto const CB = 32u; 113 | unsigned cch; 114 | 115 | if (std::isfinite(n)) 116 | { 117 | // "-1.234e+56\0" 118 | auto constexpr cchMax = 119 | sizeof("-.e+") + // Non-digit chars, including NUL. 120 | std::numeric_limits::max_digits10 + 121 | Log10Ceil(std::numeric_limits::max_exponent10); 122 | static_assert(cchMax <= CB, "Unexpected max_digits10"); 123 | 124 | #if FORMAT_DOUBLE_USING_TO_CHARS 125 | 126 | auto const result = std::to_chars(pBuffer, pBuffer + CB - 1, n); 127 | cch = static_cast(result.ptr - pBuffer); 128 | assert(result.ec == std::errc()); 129 | assert(cch < CB); 130 | pBuffer[cch] = 0; 131 | 132 | #else // FORMAT_DOUBLE_USING_TO_CHARS 133 | 134 | cch = static_cast(std::snprintf(pBuffer, CB, "%.17g", n)); 135 | if (cch >= CB) 136 | { 137 | // Unexpected - should always fit. 138 | assert(false); 139 | cch = CB - 1; 140 | pBuffer[cch] = 0; 141 | } 142 | 143 | #endif // FORMAT_DOUBLE_USING_TO_CHARS 144 | } 145 | else 146 | { 147 | cch = JsonRenderNull(pBuffer); 148 | } 149 | 150 | return cch; 151 | } 152 | 153 | unsigned JsonRenderBool(bool b, _Out_writes_z_(6) char* pBuffer) noexcept 154 | { 155 | return b ? MemCpyFromLiteral(pBuffer, "true") : MemCpyFromLiteral(pBuffer, "false"); 156 | } 157 | 158 | unsigned JsonRenderNull(_Out_writes_z_(5) char* pBuffer) noexcept 159 | { 160 | return MemCpyFromLiteral(pBuffer, "null"); 161 | } 162 | 163 | static unsigned 164 | RenderFileTime(uint64_t ft, _Out_writes_z_(29) char* pBuffer) noexcept 165 | { 166 | // Only attempt to render years between 1601 and 9999. 167 | if (ft < FileTime10000Ticks) 168 | { 169 | auto const subsecondTicks = static_cast(ft % TicksPerSecond); 170 | 171 | #ifdef _WIN32 172 | SYSTEMTIME st; 173 | if (!FileTimeToSystemTime(reinterpret_cast(&ft), &st)) 174 | { 175 | assert(false); // Unexpected - should succeed for all values 1601..32000. 176 | goto NotOk; 177 | } 178 | 179 | auto const year = st.wYear; 180 | auto const month = st.wMonth; 181 | auto const day = st.wDay; 182 | auto const hour = st.wHour; 183 | auto const minute = st.wMinute; 184 | auto const second = st.wSecond; 185 | #else 186 | auto const seconds1601 = ft / TicksPerSecond; 187 | time_t const seconds1970 = static_cast(seconds1601) - (FileTime1970Ticks / TicksPerSecond); 188 | 189 | tm timeStruct; 190 | if (0 == gmtime_r(&seconds1970, &timeStruct)) 191 | { 192 | goto NotOk; 193 | } 194 | 195 | auto const year = static_cast(timeStruct.tm_year + 1900); 196 | auto const month = static_cast(timeStruct.tm_mon + 1); 197 | auto const day = static_cast(timeStruct.tm_mday); 198 | auto const hour = static_cast(timeStruct.tm_hour); 199 | auto const minute = static_cast(timeStruct.tm_min); 200 | auto const second = static_cast(timeStruct.tm_sec); 201 | #endif 202 | 203 | FormatUint(year, pBuffer + 0, 4); 204 | pBuffer[4] = '-'; 205 | FormatUint(month, pBuffer + 5, 2); 206 | pBuffer[7] = '-'; 207 | FormatUint(day, pBuffer + 8, 2); 208 | pBuffer[10] = 'T'; 209 | FormatUint(hour, pBuffer + 11, 2); 210 | pBuffer[13] = ':'; 211 | FormatUint(minute, pBuffer + 14, 2); 212 | pBuffer[16] = ':'; 213 | FormatUint(second, pBuffer + 17, 2); 214 | pBuffer[19] = '.'; 215 | FormatUint(subsecondTicks, pBuffer + 20, 7); 216 | pBuffer[27] = 'Z'; 217 | pBuffer[28] = 0; 218 | } 219 | else 220 | { 221 | NotOk: 222 | 223 | // Unable to print a nice date-time. Print the raw FILETIME instead, 224 | // something like "FILETIME(0x0000000000000000)" (still 28 chars). 225 | auto p = pBuffer; 226 | p += MemCpyFromLiteral(p, "FILETIME(0x"); 227 | 228 | auto const pData = reinterpret_cast(&ft); 229 | for (unsigned i = 0; i != 8; i += 1) 230 | { 231 | p += u8_to_hex_upper(pData[7u - i], p); 232 | } 233 | 234 | p += MemCpyFromLiteral(p, ")"); 235 | assert(p == pBuffer + 28); 236 | assert(pBuffer[28] == 0); 237 | } 238 | 239 | return 28; 240 | } 241 | 242 | unsigned JsonRenderTime( 243 | TimeStruct const t, 244 | _Out_writes_z_(29) char* pBuffer) noexcept 245 | { 246 | return RenderFileTime(t.Value(), pBuffer); 247 | } 248 | 249 | unsigned JsonRenderTime( 250 | std::chrono::system_clock::time_point const timePoint, 251 | _Out_writes_z_(29) char* pBuffer) noexcept 252 | { 253 | uint64_t const ft = FileTime1970Ticks + std::chrono::duration_cast(timePoint.time_since_epoch()).count(); 254 | return RenderFileTime(ft, pBuffer); 255 | } 256 | 257 | unsigned JsonRenderUuid(_In_reads_(16) char unsigned const* g, _Out_writes_z_(37) char* pBuffer) noexcept 258 | { 259 | u8_to_hex_upper(g[0], pBuffer + 0); 260 | u8_to_hex_upper(g[1], pBuffer + 2); 261 | u8_to_hex_upper(g[2], pBuffer + 4); 262 | u8_to_hex_upper(g[3], pBuffer + 6); 263 | pBuffer[8] = '-'; 264 | u8_to_hex_upper(g[4], pBuffer + 9); 265 | u8_to_hex_upper(g[5], pBuffer + 11); 266 | pBuffer[13] = '-'; 267 | u8_to_hex_upper(g[6], pBuffer + 14); 268 | u8_to_hex_upper(g[7], pBuffer + 16); 269 | pBuffer[18] = '-'; 270 | u8_to_hex_upper(g[8], pBuffer + 19); 271 | u8_to_hex_upper(g[9], pBuffer + 21); 272 | pBuffer[23] = '-'; 273 | u8_to_hex_upper(g[10], pBuffer + 24); 274 | u8_to_hex_upper(g[11], pBuffer + 26); 275 | u8_to_hex_upper(g[12], pBuffer + 28); 276 | u8_to_hex_upper(g[13], pBuffer + 30); 277 | u8_to_hex_upper(g[14], pBuffer + 32); 278 | u8_to_hex_upper(g[15], pBuffer + 34); 279 | pBuffer[36] = 0; 280 | return 36; 281 | } 282 | 283 | unsigned JsonRenderUuidWithBraces(_In_reads_(16) char unsigned const* g, _Out_writes_z_(39) char* pBuffer) noexcept 284 | { 285 | pBuffer[0] = '{'; 286 | JsonRenderUuid(g, pBuffer + 1); 287 | pBuffer[37] = '}'; 288 | pBuffer[38] = 0; 289 | return 38; 290 | } 291 | 292 | JsonRenderer::~JsonRenderer() 293 | { 294 | return; 295 | } 296 | 297 | JsonRenderer::JsonRenderer( 298 | bool pretty, 299 | std::string_view newLine, 300 | unsigned indentSpaces) noexcept 301 | : m_newLine(newLine), m_indentSpaces(indentSpaces), m_indent(0), m_pretty(pretty) 302 | { 303 | return; 304 | } 305 | 306 | void JsonRenderer::Reserve(size_type cb) 307 | { 308 | m_renderBuffer.reserve(cb); 309 | } 310 | 311 | JsonRenderer::size_type JsonRenderer::Size() const noexcept 312 | { 313 | return m_renderBuffer.size(); 314 | } 315 | 316 | JsonRenderer::size_type JsonRenderer::Capacity() const noexcept 317 | { 318 | return m_renderBuffer.capacity(); 319 | } 320 | 321 | bool JsonRenderer::Pretty() const noexcept 322 | { 323 | return m_pretty; 324 | } 325 | 326 | void JsonRenderer::Pretty(bool value) noexcept 327 | { 328 | m_pretty = value; 329 | } 330 | 331 | std::string_view JsonRenderer::NewLine() const noexcept 332 | { 333 | return m_newLine; 334 | } 335 | 336 | void JsonRenderer::NewLine(std::string_view const value) noexcept 337 | { 338 | m_newLine = value; 339 | } 340 | 341 | unsigned JsonRenderer::IndentSpaces() const noexcept 342 | { 343 | return m_indentSpaces; 344 | } 345 | 346 | void JsonRenderer::IndentSpaces(unsigned value) noexcept 347 | { 348 | m_indentSpaces = value; 349 | } 350 | 351 | std::string_view JsonRenderer::Render(JsonBuilder const& builder) 352 | { 353 | auto itRoot = builder.root(); 354 | m_renderBuffer.clear(); 355 | m_indent = 0; 356 | RenderStructure(itRoot, true); 357 | WriteChar('\0'); 358 | return std::string_view(m_renderBuffer.data(), m_renderBuffer.size() - 1); 359 | } 360 | 361 | std::string_view JsonRenderer::Render(JsonBuilder::const_iterator const& it) 362 | { 363 | m_renderBuffer.clear(); 364 | m_indent = 0; 365 | if (it.IsRoot()) 366 | { 367 | RenderStructure(it, true); 368 | } 369 | else 370 | { 371 | RenderValue(it); 372 | } 373 | WriteChar('\0'); 374 | return std::string_view(m_renderBuffer.data(), m_renderBuffer.size() - 1); 375 | } 376 | 377 | void JsonRenderer::RenderCustom(RenderBuffer&, iterator const& it) 378 | { 379 | auto const cchMax = 32u; 380 | auto pch = m_renderBuffer.GetAppendPointer(cchMax); 381 | unsigned cch = 382 | static_cast(snprintf(pch, cchMax, "\"Custom#%u\"", it->Type())); 383 | pch += cch > cchMax ? cchMax : cch; 384 | m_renderBuffer.SetEndPointer(pch); 385 | } 386 | 387 | void JsonRenderer::RenderValue(iterator const& it) 388 | { 389 | assert(!it.IsRoot()); 390 | switch (it->Type()) 391 | { 392 | case JsonObject: 393 | RenderStructure(it, true); 394 | break; 395 | case JsonArray: 396 | RenderStructure(it, false); 397 | break; 398 | case JsonNull: 399 | WriteChars("null", 4); 400 | break; 401 | case JsonBool: 402 | if (it->GetUnchecked()) 403 | { 404 | WriteChars("true", 4); 405 | } 406 | else 407 | { 408 | WriteChars("false", 5); 409 | } 410 | break; 411 | case JsonUtf8: 412 | RenderString(it->GetUnchecked()); 413 | break; 414 | case JsonFloat: 415 | RenderFloat(it->GetUnchecked()); 416 | break; 417 | case JsonInt: 418 | RenderInt(it->GetUnchecked()); 419 | break; 420 | case JsonUInt: 421 | RenderUInt(it->GetUnchecked()); 422 | break; 423 | case JsonTime: 424 | RenderTime(it->GetUnchecked()); 425 | break; 426 | case JsonUuid: 427 | RenderUuid(it->GetUnchecked().Data); 428 | break; 429 | default: 430 | RenderCustom(m_renderBuffer, it); 431 | break; 432 | } 433 | } 434 | 435 | void JsonRenderer::RenderStructure(iterator const& itParent, bool showNames) 436 | { 437 | WriteChar(showNames ? '{' : '['); 438 | 439 | auto it = itParent.begin(); 440 | auto itEnd = itParent.end(); 441 | if (it != itEnd) 442 | { 443 | m_indent += m_indentSpaces; 444 | 445 | for (;;) 446 | { 447 | if (m_pretty) 448 | { 449 | RenderNewline(); 450 | } 451 | 452 | if (showNames) 453 | { 454 | RenderString(it->Name()); 455 | WriteChar(':'); 456 | 457 | if (m_pretty) 458 | { 459 | WriteChar(' '); 460 | } 461 | } 462 | 463 | RenderValue(it); 464 | 465 | ++it; 466 | if (it == itEnd) 467 | { 468 | break; 469 | } 470 | 471 | WriteChar(','); 472 | } 473 | 474 | m_indent -= m_indentSpaces; 475 | 476 | if (m_pretty) 477 | { 478 | RenderNewline(); 479 | } 480 | } 481 | 482 | WriteChar(showNames ? '}' : ']'); 483 | } 484 | 485 | void JsonRenderer::RenderFloat(double const value) 486 | { 487 | auto pch = m_renderBuffer.GetAppendPointer(32); 488 | pch += JsonRenderFloat(value, pch); 489 | m_renderBuffer.SetEndPointer(pch); 490 | } 491 | 492 | void JsonRenderer::RenderInt(long long signed const value) 493 | { 494 | auto const cchMax = 21u; 495 | auto pch = m_renderBuffer.GetAppendPointer(cchMax); 496 | auto result = std::to_chars(pch, pch + cchMax, value); 497 | assert(result.ec == std::errc()); 498 | m_renderBuffer.SetEndPointer(result.ptr); 499 | } 500 | 501 | void JsonRenderer::RenderUInt(long long unsigned const value) 502 | { 503 | auto const cchMax = 21u; 504 | auto pch = m_renderBuffer.GetAppendPointer(cchMax); 505 | auto result = std::to_chars(pch, pch + cchMax, value); 506 | assert(result.ec == std::errc()); 507 | m_renderBuffer.SetEndPointer(result.ptr); 508 | } 509 | 510 | void JsonRenderer::RenderTime(TimeStruct value) 511 | { 512 | auto pch = m_renderBuffer.GetAppendPointer(32); 513 | *pch++ = '"'; 514 | pch += RenderFileTime(value.Value(), pch); 515 | *pch++ = '"'; 516 | m_renderBuffer.SetEndPointer(pch); 517 | } 518 | 519 | void JsonRenderer::RenderUuid(_In_reads_(16) char unsigned const* value) 520 | { 521 | auto pch = m_renderBuffer.GetAppendPointer(38); 522 | *pch++ = '"'; 523 | pch += JsonRenderUuid(value, pch); 524 | *pch++ = '"'; 525 | m_renderBuffer.SetEndPointer(pch); 526 | } 527 | 528 | void JsonRenderer::RenderString(std::string_view const value) 529 | { 530 | WriteChar('"'); 531 | for (auto ch : value) 532 | { 533 | if (static_cast(ch) < 0x20) 534 | { 535 | // Control character - must be escaped. 536 | switch (ch) 537 | { 538 | case 8: 539 | WriteChar('\\'); 540 | WriteChar('b'); 541 | break; 542 | case 9: 543 | WriteChar('\\'); 544 | WriteChar('t'); 545 | break; 546 | case 10: 547 | WriteChar('\\'); 548 | WriteChar('n'); 549 | break; 550 | case 12: 551 | WriteChar('\\'); 552 | WriteChar('f'); 553 | break; 554 | case 13: 555 | WriteChar('\\'); 556 | WriteChar('r'); 557 | break; 558 | default: 559 | auto p = m_renderBuffer.GetAppendPointer(6); 560 | *p++ = '\\'; 561 | *p++ = 'u'; 562 | *p++ = '0'; 563 | *p++ = '0'; 564 | p += u8_to_hex_upper(ch, p); 565 | m_renderBuffer.SetEndPointer(p); 566 | break; 567 | } 568 | } 569 | else if (ch == '"' || ch == '\\') 570 | { 571 | // ASCII character - pass through (escape quote and backslash) 572 | WriteChar('\\'); 573 | WriteChar(ch); 574 | } 575 | else 576 | { 577 | WriteChar(ch); 578 | } 579 | } 580 | WriteChar('"'); 581 | } 582 | 583 | void JsonRenderer::RenderNewline() 584 | { 585 | WriteChars(m_newLine.data(), static_cast(m_newLine.size())); 586 | m_renderBuffer.append(m_indent, ' '); 587 | } 588 | } // namespace jsonbuilder 589 | -------------------------------------------------------------------------------- /src/PodVector.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. All rights reserved. 2 | // Licensed under the MIT License. 3 | 4 | #include 5 | 6 | #include 7 | #include 8 | 9 | namespace jsonbuilder { namespace JsonInternal { 10 | 11 | void PodVectorBase::CheckOffset(size_type index, size_type currentSize) noexcept 12 | { 13 | (void) index; // Unreferenced parameter 14 | (void) currentSize; // Unreferenced parameter 15 | assert(index < currentSize); 16 | } 17 | 18 | void PodVectorBase::CheckRange(void const* p1, void const* p2, void const* p3) noexcept 19 | { 20 | (void) p1; // Unreferenced parameter 21 | (void) p2; // Unreferenced parameter 22 | (void) p3; // Unreferenced parameter 23 | assert(p1 <= p2 && p2 <= p3); 24 | } 25 | 26 | PodVectorBase::size_type PodVectorBase::CheckedAdd(size_type a, size_type b) 27 | { 28 | size_type c = a + b; 29 | if (c < a) 30 | { 31 | JsonThrowLengthError("JsonVector - exceeded maximum capacity"); 32 | } 33 | return c; 34 | } 35 | 36 | void PodVectorBase::InitData( 37 | _Out_writes_bytes_(cb) void* pDest, 38 | _In_reads_bytes_(cb) void const* pSource, 39 | std::size_t cb) noexcept 40 | { 41 | memcpy(pDest, pSource, cb); 42 | } 43 | 44 | PodVectorBase::size_type 45 | PodVectorBase::GetNewCapacity(size_type minCapacity, size_type maxCapacity) 46 | { 47 | size_type cap; 48 | 49 | if (minCapacity <= 15) 50 | { 51 | cap = 15; 52 | } 53 | else 54 | { 55 | #ifdef __has_builtin 56 | #if __has_builtin(__builtin_clz) 57 | #define HAS_BUILTIN_CLZ 1 58 | #endif 59 | #endif 60 | #if defined(HAS_BUILTIN_CLZ) 61 | cap = 0xFFFFFFFF >> __builtin_clz(minCapacity); 62 | #elif defined(_MSC_VER) 63 | unsigned long index; 64 | _BitScanReverse(&index, minCapacity); 65 | cap = (2u << index) - 1; 66 | #else 67 | cap = 31; 68 | while (cap < minCapacity) 69 | { 70 | cap += cap + 1; // Always one less than a power of 2. Cannot overflow. 71 | } 72 | #endif 73 | } 74 | 75 | if (maxCapacity < cap) 76 | { 77 | if (maxCapacity < minCapacity) 78 | { 79 | JsonThrowLengthError("JsonVector - exceeded maximum capacity"); 80 | } 81 | 82 | cap = maxCapacity; 83 | } 84 | 85 | assert(15 <= cap); 86 | assert(minCapacity <= cap); 87 | assert(cap <= maxCapacity); 88 | return cap; 89 | } 90 | 91 | void* PodVectorBase::Allocate(size_t cb, bool zeroInitializeMemory) 92 | { 93 | void* const pbNew = malloc(cb); 94 | 95 | if (pbNew == nullptr) 96 | { 97 | JsonThrowBadAlloc(); 98 | } 99 | 100 | if (zeroInitializeMemory) 101 | { 102 | memset(pbNew, 0, cb); 103 | } 104 | 105 | return pbNew; 106 | } 107 | 108 | void PodVectorBase::Deallocate(void* pb) noexcept 109 | { 110 | if (pb) 111 | { 112 | free(pb); 113 | } 114 | } 115 | 116 | }} 117 | -------------------------------------------------------------------------------- /test/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.15) 2 | 3 | set(LIB_TARGET_UUID) 4 | if(NOT WIN32) 5 | find_package(uuid REQUIRED) 6 | set(LIB_TARGET_UUID uuid) 7 | endif() 8 | 9 | add_executable(jsonbuilderTest CatchMain.cpp TestBuilder.cpp TestRenderer.cpp) 10 | target_compile_features(jsonbuilderTest PRIVATE cxx_std_20) 11 | target_link_libraries(jsonbuilderTest PRIVATE jsonbuilder Catch2::Catch2 ${LIB_TARGET_UUID}) 12 | 13 | include(CTest) 14 | include(Catch) 15 | catch_discover_tests(jsonbuilderTest) 16 | -------------------------------------------------------------------------------- /test/CatchMain.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. All rights reserved. 2 | // Licensed under the MIT License. 3 | 4 | #define CATCH_CONFIG_MAIN 5 | #include 6 | -------------------------------------------------------------------------------- /test/TestBuilder.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. All rights reserved. 2 | // Licensed under the MIT License. 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | #define USTRING(prefix) prefix ## "\u0024\u00A3\u0418\u0939\u20AC\uD55C\U00010348" 9 | #define CHAR_USTRING() reinterpret_cast(USTRING(u8)) 10 | 11 | #ifdef _WIN32 12 | using uuid_t = char unsigned[16]; 13 | static void uuid_generate(uuid_t uuid) 14 | { 15 | for (unsigned i = 0; i != sizeof(uuid_t); i++) 16 | { 17 | uuid[i] = static_cast(rand()); 18 | } 19 | } 20 | #else 21 | #include 22 | #endif 23 | 24 | using namespace jsonbuilder; 25 | 26 | auto constexpr TicksPerSecond = 10'000'000u; 27 | auto constexpr FileTime1970 = 116444736000000000; 28 | using ticks = std::chrono::duration>; 29 | 30 | TEST_CASE("JsonBuilder buffer reserve", "[builder]") 31 | { 32 | constexpr auto c_maxSize = JsonBuilder::buffer_max_size(); 33 | 34 | JsonBuilder b; 35 | 36 | SECTION("Buffer begins empty") 37 | { 38 | REQUIRE(b.buffer_size() == 0); 39 | REQUIRE(b.buffer_capacity() == 0); 40 | } 41 | 42 | SECTION("Buffer reserving 0 remains empty") 43 | { 44 | b.buffer_reserve(0); 45 | REQUIRE(b.buffer_size() == 0); 46 | REQUIRE(b.buffer_capacity() == 0); 47 | } 48 | 49 | SECTION("Buffer reserving 1 keeps size 0 and makes a capacity of at least " 50 | "the size of one element") 51 | { 52 | b.buffer_reserve(1); 53 | REQUIRE(b.buffer_size() == 0); 54 | REQUIRE(b.buffer_capacity() >= 4); 55 | } 56 | 57 | SECTION("Buffer reserving 2 keeps size 0 and makes a capacity of at least " 58 | "the size of one element") 59 | { 60 | b.buffer_reserve(2); 61 | REQUIRE(b.buffer_size() == 0); 62 | REQUIRE(b.buffer_capacity() >= 4); 63 | } 64 | 65 | SECTION("Buffer reserving 5 keeps size 0 and makes a capacity of at least " 66 | "the size of two elements") 67 | { 68 | b.buffer_reserve(5); 69 | REQUIRE(b.buffer_size() == 0); 70 | REQUIRE(b.buffer_capacity() >= 8); 71 | } 72 | 73 | SECTION("Buffer reserving max succeeds or throws std::bad_alloc") 74 | { 75 | auto reserveMax = [&]() { 76 | try 77 | { 78 | b.buffer_reserve(c_maxSize); 79 | } 80 | catch (const std::bad_alloc&) 81 | {} 82 | }; 83 | 84 | REQUIRE_NOTHROW(reserveMax()); 85 | } 86 | 87 | SECTION("Buffer reserving one more than max throws std::length_error") 88 | { 89 | REQUIRE_THROWS_AS(b.buffer_reserve(c_maxSize + 1), std::length_error); 90 | } 91 | 92 | SECTION("Buffer reserving maximum of size_type throws std::length_error") 93 | { 94 | auto maxSizeType = ~JsonBuilder::size_type(0); 95 | REQUIRE_THROWS_AS(b.buffer_reserve(maxSizeType), std::length_error); 96 | } 97 | } 98 | 99 | template 100 | static void TestInputOutputScalar() 101 | { 102 | using InputLimits = std::numeric_limits; 103 | using OutputLimits = std::numeric_limits; 104 | 105 | JsonBuilder b; 106 | 107 | b.push_back(b.root(), USTRING(u), InputLimits::lowest()); 108 | b.push_back(b.root(), USTRING(U), InputLimits::min()); 109 | b.push_back(b.root(), USTRING(L), InputLimits::max()); 110 | b.push_back(b.root(), USTRING(u8), OutputLimits::lowest()); 111 | b.push_back(b.root(), "", OutputLimits::min()); 112 | b.push_back(b.root(), "", OutputLimits::max()); 113 | 114 | REQUIRE_NOTHROW(b.ValidateData()); 115 | 116 | InputType i; 117 | 118 | auto it = b.begin(); 119 | REQUIRE(it->Name() == CHAR_USTRING()); 120 | REQUIRE(it->GetUnchecked() == InputLimits::lowest()); 121 | REQUIRE(it->ConvertTo(i)); 122 | REQUIRE(i == InputLimits::lowest()); 123 | 124 | ++it; 125 | REQUIRE(it->Name() == CHAR_USTRING()); 126 | REQUIRE(it->GetUnchecked() == InputLimits::min()); 127 | REQUIRE(it->ConvertTo(i)); 128 | REQUIRE(i == InputLimits::min()); 129 | 130 | ++it; 131 | REQUIRE(it->Name() == CHAR_USTRING()); 132 | REQUIRE(it->GetUnchecked() == InputLimits::max()); 133 | REQUIRE(it->ConvertTo(i)); 134 | REQUIRE(i == InputLimits::max()); 135 | 136 | ++it; 137 | REQUIRE(it->Name() == CHAR_USTRING()); 138 | REQUIRE( 139 | it->GetUnchecked() == 140 | static_cast(OutputLimits::lowest())); 141 | if (it->ConvertTo(i)) 142 | { 143 | REQUIRE(i == static_cast(OutputLimits::lowest())); 144 | } 145 | else 146 | { 147 | REQUIRE(i == 0); 148 | REQUIRE(it->GetUnchecked() != OutputLimits::lowest()); 149 | } 150 | 151 | ++it; 152 | REQUIRE( 153 | it->GetUnchecked() == 154 | static_cast(OutputLimits::min())); 155 | if (it->ConvertTo(i)) 156 | { 157 | REQUIRE(i == static_cast(OutputLimits::min())); 158 | } 159 | else 160 | { 161 | REQUIRE(i == 0); 162 | REQUIRE(it->GetUnchecked() != OutputLimits::min()); 163 | } 164 | 165 | ++it; 166 | REQUIRE( 167 | it->GetUnchecked() == 168 | static_cast(OutputLimits::max())); 169 | if (it->ConvertTo(i)) 170 | { 171 | REQUIRE(i == static_cast(OutputLimits::max())); 172 | } 173 | else 174 | { 175 | REQUIRE(i == 0); 176 | REQUIRE(it->GetUnchecked() != OutputLimits::max()); 177 | } 178 | } 179 | 180 | TEST_CASE("JsonBuilder numeric limits", "[builder]") 181 | { 182 | SECTION("signed char") { TestInputOutputScalar(); } 183 | SECTION("signed short") { TestInputOutputScalar(); } 184 | SECTION("signed int") { TestInputOutputScalar(); } 185 | SECTION("signed long") { TestInputOutputScalar(); } 186 | SECTION("signed long long") 187 | { 188 | TestInputOutputScalar(); 189 | } 190 | 191 | SECTION("unsigned char") 192 | { 193 | TestInputOutputScalar(); 194 | } 195 | SECTION("unsigned short") 196 | { 197 | TestInputOutputScalar(); 198 | } 199 | SECTION("unsigned int") { TestInputOutputScalar(); } 200 | SECTION("unsigned long") 201 | { 202 | TestInputOutputScalar(); 203 | } 204 | SECTION("unsigned long long") 205 | { 206 | TestInputOutputScalar(); 207 | } 208 | 209 | SECTION("float") { TestInputOutputScalar(); } 210 | SECTION("double") { TestInputOutputScalar(); } 211 | } 212 | 213 | TEST_CASE("JsonBuilder internal name realloc") 214 | { 215 | JsonBuilder b; 216 | std::string_view constexpr expectedName = "expectedName"; 217 | std::string_view actualName = expectedName; 218 | std::string_view constexpr expectedVal = "expectedVal"; 219 | std::string_view actualVal = expectedVal; 220 | 221 | auto it = b.push_back(b.root(), actualName, actualVal); 222 | actualName = it->Name(); 223 | actualVal = it->GetUnchecked(); 224 | 225 | // Verify that the correct name and value get added even if they point into 226 | // the buffer and the buffer is reallocated. 227 | auto const cap = b.buffer_capacity(); 228 | while (cap == b.buffer_capacity()) 229 | { 230 | it = b.push_back(b.root(), actualName, actualVal); 231 | 232 | actualName = it->Name(); 233 | REQUIRE(actualName.data() != expectedName.data()); 234 | REQUIRE(actualName == expectedName); 235 | 236 | actualVal = it->GetUnchecked(); 237 | REQUIRE(actualVal.data() != expectedVal.data()); 238 | REQUIRE(actualVal == expectedVal); 239 | } 240 | } 241 | 242 | TEST_CASE("JsonBuilder string push_back") 243 | { 244 | JsonBuilder b; 245 | 246 | // char 247 | 248 | SECTION("push_back std::string_view") 249 | { 250 | auto itr = b.push_back(b.root(), "", std::string_view{ "ABCDE" }); 251 | REQUIRE(itr->GetUnchecked() == "ABCDE"); 252 | } 253 | 254 | SECTION("push_back std::string") 255 | { 256 | auto itr = b.push_back(b.root(), "", std::string{ "ABCDE" }); 257 | REQUIRE(itr->GetUnchecked() == "ABCDE"); 258 | } 259 | 260 | SECTION("push_back char*") 261 | { 262 | auto itr = b.push_back(b.root(), "", const_cast("ABC")); 263 | REQUIRE(itr->GetUnchecked() == "ABC"); 264 | } 265 | 266 | SECTION("push_back const char*") 267 | { 268 | auto itr = b.push_back(b.root(), "", static_cast("DEF")); 269 | REQUIRE(itr->GetUnchecked() == "DEF"); 270 | } 271 | 272 | SECTION("push_back const char[]") 273 | { 274 | auto itr = b.push_back(b.root(), "", CHAR_USTRING()); 275 | REQUIRE(itr->GetUnchecked() == CHAR_USTRING()); 276 | } 277 | 278 | SECTION("push_back latin1_view") 279 | { 280 | auto itr = b.push_back(b.root(), "", latin1_view{ "ABCDE\x80\x90\x9F\xA0\xB0\xF0\xFF" }); 281 | REQUIRE(itr->GetUnchecked() == reinterpret_cast(u8"ABCDE\u0080\u0090\u009F\u00A0\u00B0\u00F0\u00FF")); 282 | } 283 | 284 | SECTION("push_back cp1252_view") 285 | { 286 | auto itr = b.push_back(b.root(), "", cp1252_view{ "ABCDE\x80\x90\x9F\xA0\xB0\xF0\xFF" }); 287 | REQUIRE(itr->GetUnchecked() == reinterpret_cast(u8"ABCDE\u20AC\u0090\u0178\u00A0\u00B0\u00F0\u00FF")); 288 | } 289 | 290 | // wchar_t 291 | 292 | SECTION("push_back std::wstring_view") 293 | { 294 | auto itr = b.push_back(b.root(), "", std::wstring_view{ L"ABCDE" }); 295 | REQUIRE(itr->GetUnchecked() == "ABCDE"); 296 | } 297 | 298 | SECTION("push_back std::wstring") 299 | { 300 | auto itr = b.push_back(b.root(), "", std::wstring{ L"ABCDE" }); 301 | REQUIRE(itr->GetUnchecked() == "ABCDE"); 302 | } 303 | 304 | SECTION("push_back wchar_t*") 305 | { 306 | auto itr = b.push_back(b.root(), "", const_cast(L"ABC")); 307 | REQUIRE(itr->GetUnchecked() == "ABC"); 308 | } 309 | 310 | SECTION("push_back const wchar_t*") 311 | { 312 | auto itr = b.push_back(b.root(), "", static_cast(L"DEF")); 313 | REQUIRE(itr->GetUnchecked() == "DEF"); 314 | } 315 | 316 | SECTION("push_back const wchar_t[]") 317 | { 318 | auto itr = b.push_back(b.root(), "", USTRING(L)); 319 | REQUIRE(itr->GetUnchecked() == CHAR_USTRING()); 320 | } 321 | 322 | // char8_t 323 | 324 | #ifdef __cpp_lib_char8_t // C++20 325 | 326 | SECTION("push_back std::u8string_view") 327 | { 328 | auto itr = b.push_back(b.root(), "", std::u8string_view{ u8"ABCDE" }); 329 | REQUIRE(itr->GetUnchecked() == "ABCDE"); 330 | REQUIRE(itr->GetUnchecked() == u8"ABCDE"); 331 | } 332 | 333 | SECTION("push_back std::u8string") 334 | { 335 | auto itr = b.push_back(b.root(), "", std::u8string{ u8"ABCDE" }); 336 | REQUIRE(itr->GetUnchecked() == "ABCDE"); 337 | REQUIRE(itr->GetUnchecked() == u8"ABCDE"); 338 | } 339 | 340 | SECTION("push_back char8_t*") 341 | { 342 | auto itr = b.push_back(b.root(), "", const_cast(u8"ABC")); 343 | REQUIRE(itr->GetUnchecked() == "ABC"); 344 | REQUIRE(itr->GetUnchecked() == u8"ABC"); 345 | } 346 | 347 | SECTION("push_back const char8_t*") 348 | { 349 | auto itr = b.push_back(b.root(), "", static_cast(u8"DEF")); 350 | REQUIRE(itr->GetUnchecked() == "DEF"); 351 | REQUIRE(itr->GetUnchecked() == u8"DEF"); 352 | } 353 | 354 | #endif // __cpp_lib_char8_t 355 | 356 | SECTION("push_back const char8_t[]") 357 | { 358 | auto itr = b.push_back(b.root(), "", USTRING(u8)); 359 | REQUIRE(itr->GetUnchecked() == CHAR_USTRING()); 360 | #ifdef __cpp_lib_char8_t // C++20 361 | REQUIRE(itr->GetUnchecked() == USTRING(u8)); 362 | #endif // __cpp_lib_char8_t 363 | } 364 | 365 | // char16_t 366 | 367 | SECTION("push_back std::u16string_view") 368 | { 369 | auto itr = b.push_back(b.root(), "", std::u16string_view{ u"ABCDE" }); 370 | REQUIRE(itr->GetUnchecked() == "ABCDE"); 371 | } 372 | 373 | SECTION("push_back std::u16string") 374 | { 375 | auto itr = b.push_back(b.root(), "", std::u16string{ u"ABCDE" }); 376 | REQUIRE(itr->GetUnchecked() == "ABCDE"); 377 | } 378 | 379 | SECTION("push_back char16_t*") 380 | { 381 | auto itr = b.push_back(b.root(), "", const_cast(u"ABC")); 382 | REQUIRE(itr->GetUnchecked() == "ABC"); 383 | } 384 | 385 | SECTION("push_back const char16_t*") 386 | { 387 | auto itr = b.push_back(b.root(), "", static_cast(u"DEF")); 388 | REQUIRE(itr->GetUnchecked() == "DEF"); 389 | } 390 | 391 | SECTION("push_back const char16_t[]") 392 | { 393 | auto itr = b.push_back(b.root(), "", USTRING(u)); 394 | REQUIRE(itr->GetUnchecked() == CHAR_USTRING()); 395 | } 396 | 397 | // char32_t 398 | 399 | SECTION("push_back std::u32string_view") 400 | { 401 | auto itr = b.push_back(b.root(), "", std::u32string_view{ U"ABCDE" }); 402 | REQUIRE(itr->GetUnchecked() == "ABCDE"); 403 | } 404 | 405 | SECTION("push_back std::u32string") 406 | { 407 | auto itr = b.push_back(b.root(), "", std::u32string{ U"ABCDE" }); 408 | REQUIRE(itr->GetUnchecked() == "ABCDE"); 409 | } 410 | 411 | SECTION("push_back char32_t*") 412 | { 413 | auto itr = b.push_back(b.root(), "", const_cast(U"ABC")); 414 | REQUIRE(itr->GetUnchecked() == "ABC"); 415 | } 416 | 417 | SECTION("push_back const char32_t*") 418 | { 419 | auto itr = b.push_back(b.root(), "", static_cast(U"DEF")); 420 | REQUIRE(itr->GetUnchecked() == "DEF"); 421 | } 422 | 423 | SECTION("push_back const char32_t[]") 424 | { 425 | auto itr = b.push_back(b.root(), "", USTRING(U)); 426 | REQUIRE(itr->GetUnchecked() == CHAR_USTRING()); 427 | } 428 | 429 | b.ValidateData(); 430 | } 431 | 432 | TEST_CASE("JsonBuilder chrono push_back", "[builder]") 433 | { 434 | auto now = std::chrono::system_clock::now(); 435 | auto nowTicks = std::chrono::time_point_cast(now); 436 | 437 | JsonBuilder b; 438 | 439 | // Make sure a system time can round-trip (with loss of precision) 440 | auto itr = b.push_back(b.root(), "CurrentTime", now); 441 | auto retrieved = itr->GetUnchecked(); 442 | REQUIRE(retrieved == nowTicks); 443 | } 444 | 445 | TEST_CASE("JsonBuilder chrono-to-TimeStruct push_back", "[builder]") 446 | { 447 | JsonBuilder b; 448 | 449 | // Make sure a system time converts to the correct file time 450 | auto itr = b.push_back(b.root(), "+2", std::chrono::system_clock::from_time_t(2)); 451 | auto retrieved = itr->GetUnchecked(); 452 | auto const expected = FileTime1970 + 2 * TicksPerSecond; 453 | REQUIRE(retrieved.Value() == expected); 454 | } 455 | 456 | TEST_CASE("JsonBuilder TimeStruct-to-chrono push_back", "[builder]") 457 | { 458 | JsonBuilder b; 459 | 460 | // Make sure a system time converts to the correct file time 461 | auto itr = b.push_back(b.root(), "+2", TimeStruct::FromValue(FileTime1970 + 2 * TicksPerSecond)); 462 | auto retrieved = itr->GetUnchecked(); 463 | REQUIRE(retrieved == std::chrono::system_clock::from_time_t(2)); 464 | } 465 | 466 | TEST_CASE("JsonBuilder uuid push_back", "[builder]") 467 | { 468 | UuidStruct uuid; 469 | uuid_generate(uuid.Data); 470 | 471 | JsonBuilder b; 472 | auto itr = b.push_back(b.root(), "Uuid", uuid); 473 | 474 | auto retrieved = itr->GetUnchecked(); 475 | REQUIRE(memcmp(retrieved.Data, uuid.Data, sizeof(uuid.Data)) == 0); 476 | } 477 | 478 | TEST_CASE("JsonBuilder find", "[builder]") 479 | { 480 | JsonBuilder b; 481 | 482 | // Empty object 483 | REQUIRE(b.find("a1") == b.end()); 484 | REQUIRE(b.find("a1", "a2") == b.end()); 485 | 486 | // Single object (a1) 487 | auto itA1 = b.push_back(b.root(), "a1", JsonObject); 488 | REQUIRE(b.find("a1") == itA1); 489 | REQUIRE(b.find(b.root(), "a1") == itA1); 490 | REQUIRE(b.find("b1") == b.end()); 491 | REQUIRE(b.find(b.root(), "b1") == b.end()); 492 | REQUIRE(b.find("a1", "a2") == b.end()); 493 | 494 | // Second object b2, sibling of a1 495 | auto itB1 = b.push_back(b.root(), "b1", JsonObject); 496 | REQUIRE(b.find("a1") == itA1); 497 | REQUIRE(b.find("a1", "a2") == b.end()); 498 | REQUIRE(b.find("b1") == itB1); 499 | REQUIRE(b.find("c1") == b.end()); 500 | 501 | // First child of a1, a2 502 | auto itA1A2 = b.push_back(itA1, "a2", JsonObject); 503 | REQUIRE(b.find("a1") == itA1); 504 | REQUIRE(b.find("a1", "a2") == itA1A2); 505 | REQUIRE(b.find(b.root(), "a1", "a2") == itA1A2); 506 | REQUIRE(b.find("a1", "a2", "a3") == b.end()); 507 | REQUIRE(b.find("b1") == itB1); 508 | REQUIRE(b.find("c1") == b.end()); 509 | 510 | // First child of a2, a3 511 | auto itA1A2A3 = b.push_back(itA1A2, "a3", 0); 512 | REQUIRE(b.find("a1", "a2", "a3") == itA1A2A3); 513 | REQUIRE(b.find(itA1, "a2") == itA1A2); 514 | REQUIRE(b.find(itB1, "a2") == b.end()); 515 | 516 | REQUIRE_NOTHROW(b.ValidateData()); 517 | } 518 | 519 | TEST_CASE("JsonBuilder constructors", "[builder]") 520 | { 521 | JsonBuilder b; 522 | b.push_back(b.root(), "aname", "ava"); 523 | b.push_back(b.root(), "bname", "bva"); 524 | REQUIRE_NOTHROW(b.ValidateData()); 525 | 526 | SECTION("Copy constructor") 527 | { 528 | JsonBuilder copy{ b }; 529 | REQUIRE_NOTHROW(copy.ValidateData()); 530 | 531 | auto it = copy.begin(); 532 | REQUIRE(it->Name() == "aname"); 533 | REQUIRE(it->GetUnchecked() == "ava"); 534 | 535 | ++it; 536 | REQUIRE(it->Name() == "bname"); 537 | REQUIRE(it->GetUnchecked() == "bva"); 538 | } 539 | 540 | SECTION("Move constructor") 541 | { 542 | JsonBuilder move{ std::move(b) }; 543 | REQUIRE_NOTHROW(move.ValidateData()); 544 | 545 | auto it = move.begin(); 546 | REQUIRE(it->Name() == "aname"); 547 | REQUIRE(it->GetUnchecked() == "ava"); 548 | 549 | ++it; 550 | REQUIRE(it->Name() == "bname"); 551 | REQUIRE(it->GetUnchecked() == "bva"); 552 | } 553 | } 554 | 555 | TEST_CASE("JsonBuilder erase", "[builder]") 556 | { 557 | JsonBuilder b; 558 | b.push_back(b.root(), "aname", "ava"); 559 | b.push_back(b.root(), "bname", "bva"); 560 | REQUIRE_NOTHROW(b.ValidateData()); 561 | 562 | SECTION("erase a single child element") 563 | { 564 | auto itr = b.erase(b.begin()); 565 | REQUIRE_NOTHROW(b.ValidateData()); 566 | REQUIRE(itr == b.begin()); 567 | REQUIRE(b.count(b.root()) == 1); 568 | } 569 | 570 | SECTION("erase all children") 571 | { 572 | auto itr = b.erase(b.begin(), b.end()); 573 | REQUIRE_NOTHROW(b.ValidateData()); 574 | REQUIRE(itr == b.end()); 575 | REQUIRE(b.begin() == b.end()); 576 | REQUIRE(b.count(itr) == 0); 577 | } 578 | } 579 | 580 | TEST_CASE("JsonBuilder conversions", "[builder]") 581 | { 582 | int64_t ival; 583 | uint64_t uval; 584 | double fval; 585 | std::string_view sval; 586 | bool bval; 587 | std::chrono::system_clock::time_point tval; 588 | UuidStruct uuidval; 589 | 590 | JsonBuilder b; 591 | 592 | SECTION("JsonNull") 593 | { 594 | auto itr = b.push_back(b.root(), "FirstItem", JsonNull); 595 | 596 | REQUIRE(itr->IsNull()); 597 | REQUIRE(!itr->ConvertTo(bval)); 598 | REQUIRE(!itr->ConvertTo(fval)); 599 | REQUIRE(!itr->ConvertTo(ival)); 600 | REQUIRE(!itr->ConvertTo(uval)); 601 | REQUIRE(!itr->ConvertTo(sval)); 602 | REQUIRE(!itr->ConvertTo(tval)); 603 | REQUIRE(!itr->ConvertTo(uuidval)); 604 | } 605 | 606 | SECTION("false") 607 | { 608 | auto itr = b.push_back(b.root(), "", false); 609 | 610 | REQUIRE(itr->GetUnchecked() == false); 611 | REQUIRE((itr->ConvertTo(bval) && !bval)); 612 | REQUIRE(!itr->ConvertTo(fval)); 613 | REQUIRE(!itr->ConvertTo(ival)); 614 | REQUIRE(!itr->ConvertTo(uval)); 615 | REQUIRE(!itr->ConvertTo(sval)); 616 | REQUIRE(!itr->ConvertTo(tval)); 617 | REQUIRE(!itr->ConvertTo(uuidval)); 618 | } 619 | 620 | SECTION("true") 621 | { 622 | auto itr = b.push_back(b.root(), "", true); 623 | 624 | REQUIRE(itr->GetUnchecked() == true); 625 | REQUIRE((itr->ConvertTo(bval) && bval)); 626 | REQUIRE(!itr->ConvertTo(fval)); 627 | REQUIRE(!itr->ConvertTo(ival)); 628 | REQUIRE(!itr->ConvertTo(uval)); 629 | REQUIRE(!itr->ConvertTo(sval)); 630 | REQUIRE(!itr->ConvertTo(tval)); 631 | REQUIRE(!itr->ConvertTo(uuidval)); 632 | } 633 | 634 | SECTION("int64_t") 635 | { 636 | auto itr = b.push_back(b.root(), "", 123); 637 | 638 | REQUIRE(itr->GetUnchecked() == 123); 639 | REQUIRE(!itr->ConvertTo(bval)); 640 | REQUIRE((itr->ConvertTo(fval) && fval == 123)); 641 | REQUIRE((itr->ConvertTo(ival) && ival == 123)); 642 | REQUIRE((itr->ConvertTo(uval) && uval == 123)); 643 | REQUIRE(!itr->ConvertTo(sval)); 644 | REQUIRE(!itr->ConvertTo(tval)); 645 | REQUIRE(!itr->ConvertTo(uuidval)); 646 | } 647 | 648 | SECTION("uint64_t") 649 | { 650 | auto itr = b.push_back(b.root(), "", 123u); 651 | 652 | REQUIRE(itr->GetUnchecked() == 123u); 653 | REQUIRE(!itr->ConvertTo(bval)); 654 | REQUIRE((itr->ConvertTo(fval) && fval == 123)); 655 | REQUIRE((itr->ConvertTo(ival) && ival == 123)); 656 | REQUIRE((itr->ConvertTo(uval) && uval == 123)); 657 | REQUIRE(!itr->ConvertTo(sval)); 658 | REQUIRE(!itr->ConvertTo(tval)); 659 | REQUIRE(!itr->ConvertTo(uuidval)); 660 | } 661 | 662 | SECTION("double") 663 | { 664 | auto itr = b.push_back(b.root(), "", 123.0); 665 | 666 | REQUIRE(itr->GetUnchecked() == 123.0); 667 | REQUIRE(!itr->ConvertTo(bval)); 668 | REQUIRE((itr->ConvertTo(fval) && fval == 123)); 669 | REQUIRE((itr->ConvertTo(ival) && ival == 123)); 670 | REQUIRE((itr->ConvertTo(uval) && uval == 123)); 671 | REQUIRE(!itr->ConvertTo(sval)); 672 | REQUIRE(!itr->ConvertTo(tval)); 673 | REQUIRE(!itr->ConvertTo(uuidval)); 674 | } 675 | 676 | SECTION("string") 677 | { 678 | auto itr = b.push_back(b.root(), "", "ABC"); 679 | 680 | REQUIRE(itr->GetUnchecked() == "ABC"); 681 | REQUIRE(!itr->ConvertTo(bval)); 682 | REQUIRE(!itr->ConvertTo(fval)); 683 | REQUIRE(!itr->ConvertTo(ival)); 684 | REQUIRE(!itr->ConvertTo(uval)); 685 | REQUIRE((itr->ConvertTo(sval) && sval == "ABC")); 686 | REQUIRE(!itr->ConvertTo(tval)); 687 | REQUIRE(!itr->ConvertTo(uuidval)); 688 | } 689 | 690 | SECTION("less than int64_t min as double") 691 | { 692 | auto itr = b.push_back(b.root(), "", -9223372036854777856.0); 693 | 694 | REQUIRE(!itr->ConvertTo(bval)); 695 | REQUIRE((itr->ConvertTo(fval) && fval == -9223372036854777856.0)); 696 | REQUIRE(!itr->ConvertTo(ival)); 697 | REQUIRE(!itr->ConvertTo(uval)); 698 | REQUIRE(!itr->ConvertTo(sval)); 699 | REQUIRE(!itr->ConvertTo(tval)); 700 | REQUIRE(!itr->ConvertTo(uuidval)); 701 | } 702 | 703 | SECTION("int64_t min") 704 | { 705 | // This value of -9223372036854775808 cannot be described as a literal 706 | // due to negative literals being positive literals with a unary minus 707 | // applied. 708 | constexpr int64_t c_int64min = (-9223372036854775807ll - 1); 709 | 710 | auto itr = b.push_back(b.root(), "", c_int64min); 711 | 712 | REQUIRE(!itr->ConvertTo(bval)); 713 | REQUIRE((itr->ConvertTo(fval) && fval == -9223372036854775808.0)); 714 | REQUIRE((itr->ConvertTo(ival) && ival == c_int64min)); 715 | REQUIRE(!itr->ConvertTo(uval)); 716 | REQUIRE(!itr->ConvertTo(sval)); 717 | REQUIRE(!itr->ConvertTo(tval)); 718 | REQUIRE(!itr->ConvertTo(uuidval)); 719 | } 720 | 721 | SECTION("-1") 722 | { 723 | auto itr = b.push_back(b.root(), "", -1); 724 | 725 | REQUIRE(!itr->ConvertTo(bval)); 726 | REQUIRE((itr->ConvertTo(fval) && fval == -1.0)); 727 | REQUIRE((itr->ConvertTo(ival) && ival == -1)); 728 | REQUIRE(!itr->ConvertTo(uval)); 729 | REQUIRE(!itr->ConvertTo(sval)); 730 | REQUIRE(!itr->ConvertTo(tval)); 731 | REQUIRE(!itr->ConvertTo(uuidval)); 732 | } 733 | 734 | SECTION("0") 735 | { 736 | auto itr = b.push_back(b.root(), "", 0); 737 | 738 | REQUIRE(!itr->ConvertTo(bval)); 739 | REQUIRE((itr->ConvertTo(fval) && fval == 0.0)); 740 | REQUIRE((itr->ConvertTo(ival) && ival == 0)); 741 | REQUIRE((itr->ConvertTo(uval) && uval == 0)); 742 | REQUIRE(!itr->ConvertTo(sval)); 743 | REQUIRE(!itr->ConvertTo(tval)); 744 | REQUIRE(!itr->ConvertTo(uuidval)); 745 | } 746 | 747 | SECTION("int64_t max") 748 | { 749 | auto itr = b.push_back(b.root(), "", 9223372036854775807); 750 | 751 | REQUIRE(!itr->ConvertTo(bval)); 752 | REQUIRE((itr->ConvertTo(fval) && fval == 9223372036854775807.0)); 753 | REQUIRE((itr->ConvertTo(ival) && ival == 9223372036854775807)); 754 | REQUIRE((itr->ConvertTo(uval) && uval == 9223372036854775807)); 755 | REQUIRE(!itr->ConvertTo(sval)); 756 | REQUIRE(!itr->ConvertTo(tval)); 757 | REQUIRE(!itr->ConvertTo(uuidval)); 758 | } 759 | 760 | SECTION("greater than int64_t max and less than uint64_t max") 761 | { 762 | auto itr = b.push_back(b.root(), "", 9223372036854775808ull); 763 | 764 | REQUIRE(!itr->ConvertTo(bval)); 765 | REQUIRE((itr->ConvertTo(fval) && fval == 9223372036854775808.0)); 766 | REQUIRE(!itr->ConvertTo(ival)); 767 | REQUIRE((itr->ConvertTo(uval) && uval == 9223372036854775808ull)); 768 | REQUIRE(!itr->ConvertTo(sval)); 769 | REQUIRE(!itr->ConvertTo(tval)); 770 | REQUIRE(!itr->ConvertTo(uuidval)); 771 | } 772 | 773 | SECTION("greater than int64_t max and less than uint64_t max as double") 774 | { 775 | auto itr = b.push_back(b.root(), "", 9223372036854777856.0); 776 | 777 | REQUIRE(!itr->ConvertTo(bval)); 778 | REQUIRE((itr->ConvertTo(fval) && fval == 9223372036854777856.0)); 779 | REQUIRE(!itr->ConvertTo(ival)); 780 | REQUIRE((itr->ConvertTo(uval) && uval == 9223372036854777856ull)); 781 | REQUIRE(!itr->ConvertTo(sval)); 782 | REQUIRE(!itr->ConvertTo(tval)); 783 | REQUIRE(!itr->ConvertTo(uuidval)); 784 | } 785 | 786 | SECTION("uint64_t max") 787 | { 788 | auto itr = b.push_back(b.root(), "", 18446744073709551615ull); 789 | 790 | REQUIRE(!itr->ConvertTo(bval)); 791 | REQUIRE((itr->ConvertTo(fval) && fval == 18446744073709551615.0)); 792 | REQUIRE(!itr->ConvertTo(ival)); 793 | REQUIRE((itr->ConvertTo(uval) && uval == 18446744073709551615ull)); 794 | REQUIRE(!itr->ConvertTo(sval)); 795 | REQUIRE(!itr->ConvertTo(tval)); 796 | REQUIRE(!itr->ConvertTo(uuidval)); 797 | } 798 | 799 | SECTION("greater than uint64_t max") 800 | { 801 | auto itr = b.push_back(b.root(), "", 18446744073709551616.0); 802 | 803 | REQUIRE(!itr->ConvertTo(bval)); 804 | REQUIRE((itr->ConvertTo(fval) && fval == 18446744073709551615.0)); 805 | REQUIRE(!itr->ConvertTo(ival)); 806 | REQUIRE(!itr->ConvertTo(uval)); 807 | REQUIRE(!itr->ConvertTo(sval)); 808 | REQUIRE(!itr->ConvertTo(tval)); 809 | REQUIRE(!itr->ConvertTo(uuidval)); 810 | } 811 | 812 | SECTION("time") 813 | { 814 | auto now = std::chrono::system_clock::now(); 815 | auto nowTicks = std::chrono::time_point_cast(now); 816 | auto itr = b.push_back(b.root(), "", now); 817 | 818 | REQUIRE(!itr->ConvertTo(bval)); 819 | REQUIRE(!itr->ConvertTo(fval)); 820 | REQUIRE(!itr->ConvertTo(ival)); 821 | REQUIRE(!itr->ConvertTo(uval)); 822 | REQUIRE(!itr->ConvertTo(sval)); 823 | REQUIRE((itr->ConvertTo(tval) && tval == nowTicks)); 824 | REQUIRE(!itr->ConvertTo(uuidval)); 825 | } 826 | 827 | SECTION("uuid") 828 | { 829 | UuidStruct uuid; 830 | uuid_generate(uuid.Data); 831 | 832 | auto itr = b.push_back(b.root(), "", uuid); 833 | 834 | REQUIRE(!itr->ConvertTo(bval)); 835 | REQUIRE(!itr->ConvertTo(fval)); 836 | REQUIRE(!itr->ConvertTo(ival)); 837 | REQUIRE(!itr->ConvertTo(uval)); 838 | REQUIRE(!itr->ConvertTo(sval)); 839 | REQUIRE(!itr->ConvertTo(tval)); 840 | REQUIRE( 841 | (itr->ConvertTo(uuidval) && 842 | 0 == memcmp(uuidval.Data, uuid.Data, sizeof(uuidval.Data)))); 843 | } 844 | } 845 | -------------------------------------------------------------------------------- /test/TestRenderer.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. All rights reserved. 2 | // Licensed under the MIT License. 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #include 10 | #include 11 | 12 | #ifdef _WIN32 13 | #undef uuid_t 14 | using uuid_t = char unsigned[16]; 15 | #else 16 | #include 17 | #endif 18 | 19 | using namespace jsonbuilder; 20 | 21 | template 22 | static void TestUInt(N n) 23 | { 24 | unsigned const cchBuf = 24; 25 | char buf1[cchBuf]; 26 | char buf2[cchBuf]; 27 | unsigned cch; 28 | 29 | memset(buf1, 1, sizeof(buf1)); 30 | cch = JsonRenderUInt(n, buf1); 31 | REQUIRE(cch < cchBuf); 32 | for (unsigned i = cch + 1; i != cchBuf; i++) 33 | { 34 | REQUIRE(buf1[i] == 1); 35 | } 36 | std::snprintf( 37 | buf2, 38 | std::extent::value, 39 | "%llu", 40 | static_cast(n)); 41 | for (unsigned i = 0; i <= cch; i++) 42 | { 43 | REQUIRE(buf1[i] == buf2[i]); 44 | } 45 | } 46 | 47 | template 48 | static void TestInt(N n) 49 | { 50 | unsigned const cchBuf = 24; 51 | char buf1[cchBuf]; 52 | char buf2[cchBuf]; 53 | unsigned cch; 54 | 55 | memset(buf1, 1, sizeof(buf1)); 56 | cch = JsonRenderInt(n, buf1); 57 | REQUIRE(cch < cchBuf); 58 | for (unsigned i = cch + 1; i != cchBuf; i++) 59 | { 60 | REQUIRE(buf1[i] == 1); 61 | } 62 | std::snprintf( 63 | buf2, 64 | std::extent::value, 65 | "%lld", 66 | static_cast(n)); 67 | for (unsigned i = 0; i <= cch; i++) 68 | { 69 | REQUIRE(buf1[i] == buf2[i]); 70 | } 71 | } 72 | 73 | template 74 | static void TestFloat(N n) 75 | { 76 | unsigned const cchBuf = 32; 77 | char buf1[cchBuf]; 78 | char buf2[cchBuf]; 79 | unsigned cch; 80 | 81 | memset(buf1, 1, sizeof(buf1)); 82 | cch = JsonRenderFloat(n, buf1); 83 | REQUIRE(cch < cchBuf); 84 | for (unsigned i = cch + 1; i != cchBuf; i++) 85 | { 86 | REQUIRE(buf1[i] == 1); 87 | } 88 | std::snprintf( 89 | buf2, std::extent::value, "%.17g", static_cast(n)); 90 | for (unsigned i = 0; i <= cch; i++) 91 | { 92 | REQUIRE(buf1[i] == buf2[i]); 93 | } 94 | } 95 | 96 | static void TestBool(bool n) 97 | { 98 | unsigned const cchBuf = 6; 99 | char buf1[cchBuf]; 100 | char const* buf2 = n ? "true" : "false"; 101 | memset(buf1, 1, sizeof(buf1)); 102 | unsigned cch = JsonRenderBool(n, buf1); 103 | REQUIRE(cch < cchBuf); 104 | for (unsigned i = cch + 1; i != cchBuf; i++) 105 | { 106 | REQUIRE(buf1[i] == 1); 107 | } 108 | for (unsigned i = 0; i <= cch; i++) 109 | { 110 | REQUIRE(buf1[i] == buf2[i]); 111 | } 112 | } 113 | 114 | // TODO: Unused 115 | // static void TestTime() {} 116 | 117 | template 118 | static void TestUInts() 119 | { 120 | SECTION("0") { TestUInt(0); } 121 | SECTION("min") { TestUInt(std::numeric_limits::min()); } 122 | SECTION("max") { TestUInt(std::numeric_limits::max()); } 123 | } 124 | 125 | template 126 | static void TestInts() 127 | { 128 | SECTION("0") { TestInt(0); } 129 | SECTION("min") { TestInt(std::numeric_limits::min()); } 130 | SECTION("max") { TestInt(std::numeric_limits::max()); } 131 | } 132 | 133 | template 134 | static void TestFloats() 135 | { 136 | SECTION("0") { TestFloat(0); } 137 | SECTION("min") { TestFloat(std::numeric_limits::min()); } 138 | SECTION("max") { TestFloat(std::numeric_limits::max()); } 139 | } 140 | 141 | TEST_CASE("JsonRenderer values match printf", "[renderer]") 142 | { 143 | SECTION("signed char") { TestInts(); } 144 | SECTION("signed short") { TestInts(); } 145 | SECTION("signed int") { TestInts(); } 146 | SECTION("signed long") { TestInts(); } 147 | SECTION("signed long long") { TestInts(); } 148 | 149 | SECTION("unsigned char") { TestUInts(); } 150 | SECTION("unsigned short") { TestUInts(); } 151 | SECTION("unsigned int") { TestUInts(); } 152 | SECTION("unsigned long") { TestUInts(); } 153 | SECTION("unsigned long long") { TestUInts(); } 154 | 155 | SECTION("float") { TestFloats(); } 156 | SECTION("double") { TestFloats(); } 157 | 158 | SECTION("bool-false") { TestBool(false); } 159 | SECTION("bool-true") { TestBool(true); } 160 | } 161 | 162 | TEST_CASE("JsonRenderer JsonNull") 163 | { 164 | unsigned const cchBuf = 24; 165 | char buf1[cchBuf]; 166 | char const* buf2 = "null"; 167 | memset(buf1, 1, sizeof(buf1)); 168 | unsigned cch = JsonRenderNull(buf1); 169 | REQUIRE(cch < cchBuf); 170 | for (unsigned i = cch + 1; i != cchBuf; i++) 171 | { 172 | REQUIRE(buf1[i] == 1); 173 | } 174 | for (unsigned i = 0; i <= cch; i++) 175 | { 176 | REQUIRE(buf1[i] == buf2[i]); 177 | } 178 | } 179 | 180 | using namespace std::string_view_literals; 181 | 182 | TEST_CASE("JsonRenderer JsonTime", "[renderer]") 183 | { 184 | auto epoch = std::chrono::system_clock::from_time_t(0); 185 | using namespace std::chrono_literals; 186 | 187 | char chars[39]; 188 | unsigned cch; 189 | 190 | memset(chars, 1, sizeof(chars)); 191 | cch = JsonRenderTime(epoch, chars); 192 | REQUIRE(cch == strlen(chars)); 193 | REQUIRE(chars == "1970-01-01T00:00:00.0000000Z"sv); 194 | 195 | memset(chars, 1, sizeof(chars)); 196 | cch = JsonRenderTime(epoch + 2s, chars); 197 | REQUIRE(cch == strlen(chars)); 198 | REQUIRE(chars == "1970-01-01T00:00:02.0000000Z"sv); 199 | 200 | memset(chars, 1, sizeof(chars)); 201 | cch = JsonRenderTime(epoch - 2s, chars); 202 | REQUIRE(cch == strlen(chars)); 203 | REQUIRE(chars == "1969-12-31T23:59:58.0000000Z"sv); 204 | 205 | memset(chars, 1, sizeof(chars)); 206 | cch = JsonRenderTime(epoch + 2ms, chars); 207 | REQUIRE(cch == strlen(chars)); 208 | REQUIRE(chars == "1970-01-01T00:00:00.0020000Z"sv); 209 | 210 | memset(chars, 1, sizeof(chars)); 211 | cch = JsonRenderTime(epoch - 2ms, chars); 212 | REQUIRE(cch == strlen(chars)); 213 | REQUIRE(chars == "1969-12-31T23:59:59.9980000Z"sv); 214 | 215 | memset(chars, 1, sizeof(chars)); 216 | cch = JsonRenderTime(TimeStruct::FromValue(0xFEDCBA9876543210), chars); 217 | REQUIRE(cch == strlen(chars)); 218 | REQUIRE(chars == "FILETIME(0xFEDCBA9876543210)"sv); 219 | } 220 | 221 | TEST_CASE("JsonRenderer JsonUuid", "[renderer]") 222 | { 223 | uuid_t uuid; 224 | for (char unsigned i = 0; i < 16; i++) 225 | { 226 | uuid[i] = i; 227 | } 228 | 229 | char chars[39]; 230 | memset(chars, 1, sizeof(chars)); 231 | 232 | SECTION("Without braces") 233 | { 234 | unsigned cch = JsonRenderUuid(uuid, chars); 235 | REQUIRE(cch == strlen(chars)); 236 | REQUIRE(chars == "00010203-0405-0607-0809-0A0B0C0D0E0F"sv); 237 | } 238 | 239 | SECTION("With braces") 240 | { 241 | unsigned cch = JsonRenderUuidWithBraces(uuid, chars); 242 | REQUIRE(cch == strlen(chars)); 243 | REQUIRE(chars == "{00010203-0405-0607-0809-0A0B0C0D0E0F}"sv); 244 | } 245 | } 246 | 247 | TEST_CASE("JsonRenderer full object", "[renderer]") 248 | { 249 | JsonBuilder b; 250 | 251 | auto objItr = b.push_back(b.root(), "obj", JsonObject); 252 | b.push_back(objItr, "str", "strval"); 253 | b.push_back(objItr, "str2", "str2val"); 254 | b.push_back(objItr, "hugeUintVal", std::numeric_limits::max()); 255 | b.push_back(objItr, "mostNegativeIntVal", std::numeric_limits::min()); 256 | 257 | auto arrItr = b.push_back(b.root(), "arr", JsonArray); 258 | b.push_back(arrItr, "useless", 1); 259 | b.push_back(arrItr, "useless2", 2); 260 | 261 | SECTION("Default renderer") 262 | { 263 | JsonRenderer renderer; 264 | auto renderString = renderer.Render(b); 265 | 266 | const char* expectedString = 267 | R"({"obj":{"str":"strval","str2":"str2val","hugeUintVal":18446744073709551615,"mostNegativeIntVal":-9223372036854775808},"arr":[1,2]})"; 268 | 269 | REQUIRE(renderString == expectedString); 270 | } 271 | 272 | SECTION("Pretty renderer") 273 | { 274 | JsonRenderer renderer; 275 | renderer.Pretty(true); 276 | auto renderString = renderer.Render(b); 277 | 278 | const char* expectedString = 279 | R"({ 280 | "obj": { 281 | "str": "strval", 282 | "str2": "str2val", 283 | "hugeUintVal": 18446744073709551615, 284 | "mostNegativeIntVal": -9223372036854775808 285 | }, 286 | "arr": [ 287 | 1, 288 | 2 289 | ] 290 | })"; 291 | 292 | REQUIRE(renderString == expectedString); 293 | } 294 | } 295 | --------------------------------------------------------------------------------