From 8dba9a6f83e3dda89572c72b2d3f7cc7e512390e Mon Sep 17 00:00:00 2001 From: Rich Daniel Date: Fri, 18 Nov 2016 16:07:53 -0800 Subject: [PATCH] Initial check-in. --- .gitattributes | 63 + .gitignore | 193 +++ CONTRIBUTING.md | 30 + LICENSE.txt | 9 + Microsoft.Shared.Dna.Dictionary.xml | 9 + Microsoft.Shared.Dna.Json.Profile/App.config | 6 + .../Constants.cs | 115 ++ Microsoft.Shared.Dna.Json.Profile/IProfile.cs | 24 + Microsoft.Shared.Dna.Json.Profile/JsonDna.cs | 426 +++++ .../JsonDnaPerformanceTests.cs | 179 ++ .../Microsoft.Shared.Dna.Json.Profile.csproj | 103 ++ Microsoft.Shared.Dna.Json.Profile/Profiler.cs | 163 ++ Microsoft.Shared.Dna.Json.Profile/Program.cs | 73 + .../packages.config | 4 + .../profile_all.cmd | 7 + Microsoft.Shared.Dna.Json.Test/AssertToken.cs | 146 ++ Microsoft.Shared.Dna.Json.Test/Constants.cs | 106 ++ .../JsonBuilderTests.cs | 511 ++++++ .../JsonParserTests.cs | 461 +++++ .../Microsoft.Shared.Dna.Json.Test.csproj | 118 ++ .../packages.config | 5 + Microsoft.Shared.Dna.Json.sln | 46 + Microsoft.Shared.Dna.Json/JsonBuilder.cs | 1187 +++++++++++++ Microsoft.Shared.Dna.Json/JsonConstants.cs | 233 +++ Microsoft.Shared.Dna.Json/JsonParser.cs | 1542 +++++++++++++++++ .../JsonTokenTypeExtensions.cs | 221 +++ .../Microsoft.Shared.Dna.Json.csproj | 66 + .../Microsoft.Shared.Dna.Json.nuspec | 30 + .../FixedStringBuilder.cs | 442 +++++ .../Microsoft.Shared.Dna/StringSegment.cs | 123 ++ Microsoft.Shared.Dna.Json/packages.config | 5 + Microsoft.Shared.Dna.Test.ruleset | 23 + Microsoft.Shared.Dna.ruleset | 8 + Microsoft.Shared.Dna.targets | 33 + NuGet.config | 15 + README.md | 47 +- Settings.StyleCop | 20 + build.cmd | 14 + build.proj | 22 + init.cmd | 3 + init.ps1 | 1 + scripts/init/.version | 1 + scripts/init/Initialize-DownloadLatest.ps1 | 86 + scripts/init/Initialize-Environment.ps1 | 32 + scripts/init/Initialize-InstallFromNuget.ps1 | 33 + scripts/init/Initialize-NuGet.ps1 | 26 + scripts/init/Restore-ToolPackages.ps1 | 24 + scripts/init/Update-Environment.ps1 | 59 + 48 files changed, 7092 insertions(+), 1 deletion(-) create mode 100644 .gitattributes create mode 100644 .gitignore create mode 100644 CONTRIBUTING.md create mode 100644 LICENSE.txt create mode 100644 Microsoft.Shared.Dna.Dictionary.xml create mode 100644 Microsoft.Shared.Dna.Json.Profile/App.config create mode 100644 Microsoft.Shared.Dna.Json.Profile/Constants.cs create mode 100644 Microsoft.Shared.Dna.Json.Profile/IProfile.cs create mode 100644 Microsoft.Shared.Dna.Json.Profile/JsonDna.cs create mode 100644 Microsoft.Shared.Dna.Json.Profile/JsonDnaPerformanceTests.cs create mode 100644 Microsoft.Shared.Dna.Json.Profile/Microsoft.Shared.Dna.Json.Profile.csproj create mode 100644 Microsoft.Shared.Dna.Json.Profile/Profiler.cs create mode 100644 Microsoft.Shared.Dna.Json.Profile/Program.cs create mode 100644 Microsoft.Shared.Dna.Json.Profile/packages.config create mode 100644 Microsoft.Shared.Dna.Json.Profile/profile_all.cmd create mode 100644 Microsoft.Shared.Dna.Json.Test/AssertToken.cs create mode 100644 Microsoft.Shared.Dna.Json.Test/Constants.cs create mode 100644 Microsoft.Shared.Dna.Json.Test/JsonBuilderTests.cs create mode 100644 Microsoft.Shared.Dna.Json.Test/JsonParserTests.cs create mode 100644 Microsoft.Shared.Dna.Json.Test/Microsoft.Shared.Dna.Json.Test.csproj create mode 100644 Microsoft.Shared.Dna.Json.Test/packages.config create mode 100644 Microsoft.Shared.Dna.Json.sln create mode 100644 Microsoft.Shared.Dna.Json/JsonBuilder.cs create mode 100644 Microsoft.Shared.Dna.Json/JsonConstants.cs create mode 100644 Microsoft.Shared.Dna.Json/JsonParser.cs create mode 100644 Microsoft.Shared.Dna.Json/JsonTokenTypeExtensions.cs create mode 100644 Microsoft.Shared.Dna.Json/Microsoft.Shared.Dna.Json.csproj create mode 100644 Microsoft.Shared.Dna.Json/Microsoft.Shared.Dna.Json.nuspec create mode 100644 Microsoft.Shared.Dna.Json/Microsoft.Shared.Dna/FixedStringBuilder.cs create mode 100644 Microsoft.Shared.Dna.Json/Microsoft.Shared.Dna/StringSegment.cs create mode 100644 Microsoft.Shared.Dna.Json/packages.config create mode 100644 Microsoft.Shared.Dna.Test.ruleset create mode 100644 Microsoft.Shared.Dna.ruleset create mode 100644 Microsoft.Shared.Dna.targets create mode 100644 NuGet.config create mode 100644 Settings.StyleCop create mode 100644 build.cmd create mode 100644 build.proj create mode 100644 init.cmd create mode 100644 init.ps1 create mode 100644 scripts/init/.version create mode 100644 scripts/init/Initialize-DownloadLatest.ps1 create mode 100644 scripts/init/Initialize-Environment.ps1 create mode 100644 scripts/init/Initialize-InstallFromNuget.ps1 create mode 100644 scripts/init/Initialize-NuGet.ps1 create mode 100644 scripts/init/Restore-ToolPackages.ps1 create mode 100644 scripts/init/Update-Environment.ps1 diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..1ff0c42 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,63 @@ +############################################################################### +# Set default behavior to automatically normalize line endings. +############################################################################### +* text=auto + +############################################################################### +# Set default behavior for command prompt diff. +# +# This is need for earlier builds of msysgit that does not have it on by +# default for csharp files. +# Note: This is only used by command line +############################################################################### +#*.cs diff=csharp + +############################################################################### +# Set the merge driver for project and solution files +# +# Merging from the command prompt will add diff markers to the files if there +# are conflicts (Merging from VS is not affected by the settings below, in VS +# the diff markers are never inserted). Diff markers may cause the following +# file extensions to fail to load in VS. An alternative would be to treat +# these files as binary and thus will always conflict and require user +# intervention with every merge. To do so, just uncomment the entries below +############################################################################### +#*.sln merge=binary +#*.csproj merge=binary +#*.vbproj merge=binary +#*.vcxproj merge=binary +#*.vcproj merge=binary +#*.dbproj merge=binary +#*.fsproj merge=binary +#*.lsproj merge=binary +#*.wixproj merge=binary +#*.modelproj merge=binary +#*.sqlproj merge=binary +#*.wwaproj merge=binary + +############################################################################### +# behavior for image files +# +# image files are treated as binary by default. +############################################################################### +#*.jpg binary +#*.png binary +#*.gif binary + +############################################################################### +# diff behavior for common document formats +# +# Convert binary document formats to text before diffing them. This feature +# is only available from the command line. Turn it on by uncommenting the +# entries below. +############################################################################### +#*.doc diff=astextplain +#*.DOC diff=astextplain +#*.docx diff=astextplain +#*.DOCX diff=astextplain +#*.dot diff=astextplain +#*.DOT diff=astextplain +#*.pdf diff=astextplain +#*.PDF diff=astextplain +#*.rtf diff=astextplain +#*.RTF diff=astextplain diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..d38e4dc --- /dev/null +++ b/.gitignore @@ -0,0 +1,193 @@ +## Ignore Visual Studio temporary files, build results, and +## files generated by popular Visual Studio add-ons. + +# User-specific files +*.suo +*.user +*.sln.docstates + +# Build results +[Dd]rop/ +[Dd]ebug/ +[Dd]ebugPublic/ +[Rr]elease/ +x64/ +build/ +bld/ +[Bb]in/ +[Oo]bj/ + +# Roslyn cache directories +*.ide/ + +# MSTest test Results +[Tt]est[Rr]esult*/ +[Bb]uild[Ll]og.* + +#NUNIT +*.VisualState.xml +TestResult.xml + +# Build Results of an ATL Project +[Dd]ebugPS/ +[Rr]eleasePS/ +dlldata.c + +*_i.c +*_p.c +*_i.h +*.ilk +*.meta +*.obj +*.pch +*.pdb +*.pgc +*.pgd +*.rsp +*.sbr +*.tlb +*.tli +*.tlh +*.tmp +*.tmp_proj +*.log +*.vspscc +*.vssscc +.builds +*.pidb +*.svclog +*.scc + +# Chutzpah Test files +_Chutzpah* + +# Visual C++ cache files +ipch/ +*.aps +*.ncb +*.opensdf +*.sdf +*.cachefile + +# Visual Studio profiler +*.psess +*.vsp +*.vspx + +# TFS 2012 Local Workspace +$tf/ + +# Guidance Automation Toolkit +*.gpState + +# ReSharper is a .NET coding add-in +_ReSharper*/ +*.[Rr]e[Ss]harper +*.DotSettings.user + +# JustCode is a .NET coding addin-in +.JustCode + +# TeamCity is a build add-in +_TeamCity* + +# DotCover is a Code Coverage Tool +*.dotCover + +# NCrunch +_NCrunch_* +.*crunch*.local.xml + +# MightyMoose +*.mm.* +AutoTest.Net/ + +# Web workbench (sass) +.sass-cache/ + +# Installshield output folder +[Ee]xpress/ + +# DocProject is a documentation generator add-in +DocProject/buildhelp/ +DocProject/Help/*.HxT +DocProject/Help/*.HxC +DocProject/Help/*.hhc +DocProject/Help/*.hhk +DocProject/Help/*.hhp +DocProject/Help/Html2 +DocProject/Help/html + +# Click-Once directory +publish/ + +# Publish Web Output +*.[Pp]ublish.xml +*.azurePubxml +## TODO: Comment the next line if you want to checkin your +## web deploy settings but do note that will include unencrypted +## passwords +#*.pubxml + +# NuGet Bootstrap Tools +.tools/ + +# NuGet Packages Directory +packages/* +## TODO: If the tool you use requires repositories.config +## uncomment the next line +#!packages/repositories.config + +# Enable "build/" folder in the NuGet Packages folder since +# NuGet packages use it for MSBuild targets. +# This line needs to be after the ignore of the build folder +# (and the packages folder if the line above has been uncommented) +!packages/build/ + +# Windows Azure Build Output +csx/ +*.build.csdef + +# Windows Store app package directory +AppPackages/ + +# Others +sql/ +*.Cache +ClientBin/ +[Ss]tyle[Cc]op.* +~$* +*~ +*.dbmdl +*.dbproj.schemaview +*.pfx +*.publishsettings +node_modules/ + +# RIA/Silverlight projects +Generated_Code/ + +# Backup & report files from converting an old project file +# to a newer Visual Studio version. Backup files are not needed, +# because we have git ;-) +_UpgradeReport_Files/ +Backup*/ +UpgradeLog*.XML +UpgradeLog*.htm + +# SQL Server files +*.mdf +*.ldf + +# Business Intelligence projects +*.rdl.data +*.bim.layout +*.bim_*.settings + +# Microsoft Fakes +FakesAssemblies/ + +# LightSwitch generated files +GeneratedArtifacts/ +_Pvt_Extensions/ +ModelManifest.xml diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..97da636 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,30 @@ +Contributing +============ +There are many ways to contribute. + +1. [Report defects and provide suggestions](https://github.com/Microsoft/Microsoft.Shared.Dna.Json/issues). + If you believe you have found a security vulnerability in this project, please follow [these steps](https://technet.microsoft.com/en-us/security/ff852094.aspx) to report it. For more information on how vulnerabilities are disclosed, see [Coordinated Vulnerability Disclosure](https://technet.microsoft.com/en-us/security/dn467923). +2. Review [pull requests](https://github.com/Microsoft/Microsoft.Shared.Dna.Json/pulls), even if it's just to verify that the defect was fixed or that the feature was implemented as intended. +3. Contribute fixes and features: + 1. Fork this repository (https://github.com/Microsoft/Microsoft.Shared.Dna.Json/fork) and clone it. + 2. Create a feature branch (`git checkout -b my-new-feature`). + 3. Make small commits as you go along (`git commit -am 'Add helper method with test'`). + 4. Push your feature branch to your fork (`git push origin my-new-feature`). + 5. Create a new Pull Request (https://github.com/Microsoft/Microsoft.Shared.Dna.Json/pulls). + +For source code contributions, you will need to complete a **Contributor License Agreement (CLA)**. Briefly, this agreement testifies that you grant us permission to use the submitted change according to the terms of the project's license, and that the work being submitted is under the appropriate copyright. + +The Contributor License Agreement (CLA) process +----------------------------------------------- +The CLA process is automated on this GitHub repository. Here is what happens when you open a pull request: + +1. The Microsoft Pull Request BOT (MSBOT) checks whether the change requires a CLA. For example, trivial typo fixes usually do not require a CLA. If no CLA is required, the pull request is labeled as **cla-not-required** and you are done. +2. If the change requires a CLA, the system checks whether you have already signed a CLA. If you have, the pull request is labeled as **cla-signed** and you are done. +3. If you need to sign a CLA, MSBOT will label the request as **cla-required** and post a comment to the pull request asking you to sign in to a website to sign the CLA (it is fully digital and no faxing is involved). +4. Once you have signed a CLA, the pull request is labeled as **cla-signed** and you are done. + +Only pull requests that are labeled as **cla-not-required**, **cla-signed**, or **cla-already-signed** can be accepted. + +Code of Conduct +--------------- +This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments. diff --git a/LICENSE.txt b/LICENSE.txt new file mode 100644 index 0000000..892a968 --- /dev/null +++ b/LICENSE.txt @@ -0,0 +1,9 @@ +The MIT License (MIT) + +Copyright (c) 2016 Microsoft Corporation + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/Microsoft.Shared.Dna.Dictionary.xml b/Microsoft.Shared.Dna.Dictionary.xml new file mode 100644 index 0000000..1910876 --- /dev/null +++ b/Microsoft.Shared.Dna.Dictionary.xml @@ -0,0 +1,9 @@ + + + + + dna + json + + + \ No newline at end of file diff --git a/Microsoft.Shared.Dna.Json.Profile/App.config b/Microsoft.Shared.Dna.Json.Profile/App.config new file mode 100644 index 0000000..8e15646 --- /dev/null +++ b/Microsoft.Shared.Dna.Json.Profile/App.config @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/Microsoft.Shared.Dna.Json.Profile/Constants.cs b/Microsoft.Shared.Dna.Json.Profile/Constants.cs new file mode 100644 index 0000000..a915874 --- /dev/null +++ b/Microsoft.Shared.Dna.Json.Profile/Constants.cs @@ -0,0 +1,115 @@ +//----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. See license file in the project root for full license information. +// +//----------------------------------------------------------------------------- + +namespace Microsoft.Shared.Dna.Json.Profile +{ + using System; + using System.Collections.Generic; + + /// + /// Performance test constants. + /// + internal static class Constants + { + /// + /// Test JSON array. + /// + public const string ArrayJson = "[false,9223372036854775807,-1.7976931348623157E+308,\"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.\"]"; + + /// + /// Test array property. + /// + public const string ArrayProperty = "array"; + + /// + /// Test JSON complex object. + /// + public const string ComplexJson = "{\"logical\":false,\"integral\":9223372036854775807,\"floating point\":-1.7976931348623157E+308,\"text\":\"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.\",\"array\":[{\"logical\":false,\"integral\":9223372036854775807,\"floating point\":-1.7976931348623157E+308,\"text\":\"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.\",\"array\":[{\"logical\":false,\"integral\":9223372036854775807,\"floating point\":-1.7976931348623157E+308,\"text\":\"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.\",\"array\":[{\"logical\":false,\"integral\":9223372036854775807,\"floating point\":-1.7976931348623157E+308,\"text\":\"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.\"},{\"logical\":false,\"integral\":9223372036854775807,\"floating point\":-1.7976931348623157E+308,\"text\":\"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.\"},{\"logical\":false,\"integral\":9223372036854775807,\"floating point\":-1.7976931348623157E+308,\"text\":\"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.\"}]},{\"logical\":false,\"integral\":9223372036854775807,\"floating point\":-1.7976931348623157E+308,\"text\":\"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.\",\"array\":[{\"logical\":false,\"integral\":9223372036854775807,\"floating point\":-1.7976931348623157E+308,\"text\":\"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.\"},{\"logical\":false,\"integral\":9223372036854775807,\"floating point\":-1.7976931348623157E+308,\"text\":\"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.\"},{\"logical\":false,\"integral\":9223372036854775807,\"floating point\":-1.7976931348623157E+308,\"text\":\"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.\"}]},{\"logical\":false,\"integral\":9223372036854775807,\"floating point\":-1.7976931348623157E+308,\"text\":\"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.\",\"array\":[{\"logical\":false,\"integral\":9223372036854775807,\"floating point\":-1.7976931348623157E+308,\"text\":\"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.\"},{\"logical\":false,\"integral\":9223372036854775807,\"floating point\":-1.7976931348623157E+308,\"text\":\"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.\"},{\"logical\":false,\"integral\":9223372036854775807,\"floating point\":-1.7976931348623157E+308,\"text\":\"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.\"}]}]},{\"logical\":false,\"integral\":9223372036854775807,\"floating point\":-1.7976931348623157E+308,\"text\":\"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.\",\"array\":[{\"logical\":false,\"integral\":9223372036854775807,\"floating point\":-1.7976931348623157E+308,\"text\":\"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.\",\"array\":[{\"logical\":false,\"integral\":9223372036854775807,\"floating point\":-1.7976931348623157E+308,\"text\":\"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.\"},{\"logical\":false,\"integral\":9223372036854775807,\"floating point\":-1.7976931348623157E+308,\"text\":\"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.\"},{\"logical\":false,\"integral\":9223372036854775807,\"floating point\":-1.7976931348623157E+308,\"text\":\"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.\"}]},{\"logical\":false,\"integral\":9223372036854775807,\"floating point\":-1.7976931348623157E+308,\"text\":\"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.\",\"array\":[{\"logical\":false,\"integral\":9223372036854775807,\"floating point\":-1.7976931348623157E+308,\"text\":\"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.\"},{\"logical\":false,\"integral\":9223372036854775807,\"floating point\":-1.7976931348623157E+308,\"text\":\"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.\"},{\"logical\":false,\"integral\":9223372036854775807,\"floating point\":-1.7976931348623157E+308,\"text\":\"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.\"}]},{\"logical\":false,\"integral\":9223372036854775807,\"floating point\":-1.7976931348623157E+308,\"text\":\"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.\",\"array\":[{\"logical\":false,\"integral\":9223372036854775807,\"floating point\":-1.7976931348623157E+308,\"text\":\"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.\"},{\"logical\":false,\"integral\":9223372036854775807,\"floating point\":-1.7976931348623157E+308,\"text\":\"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.\"},{\"logical\":false,\"integral\":9223372036854775807,\"floating point\":-1.7976931348623157E+308,\"text\":\"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.\"}]}]},{\"logical\":false,\"integral\":9223372036854775807,\"floating point\":-1.7976931348623157E+308,\"text\":\"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.\",\"array\":[{\"logical\":false,\"integral\":9223372036854775807,\"floating point\":-1.7976931348623157E+308,\"text\":\"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.\",\"array\":[{\"logical\":false,\"integral\":9223372036854775807,\"floating point\":-1.7976931348623157E+308,\"text\":\"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.\"},{\"logical\":false,\"integral\":9223372036854775807,\"floating point\":-1.7976931348623157E+308,\"text\":\"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.\"},{\"logical\":false,\"integral\":9223372036854775807,\"floating point\":-1.7976931348623157E+308,\"text\":\"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.\"}]},{\"logical\":false,\"integral\":9223372036854775807,\"floating point\":-1.7976931348623157E+308,\"text\":\"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.\",\"array\":[{\"logical\":false,\"integral\":9223372036854775807,\"floating point\":-1.7976931348623157E+308,\"text\":\"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.\"},{\"logical\":false,\"integral\":9223372036854775807,\"floating point\":-1.7976931348623157E+308,\"text\":\"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.\"},{\"logical\":false,\"integral\":9223372036854775807,\"floating point\":-1.7976931348623157E+308,\"text\":\"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.\"}]},{\"logical\":false,\"integral\":9223372036854775807,\"floating point\":-1.7976931348623157E+308,\"text\":\"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.\",\"array\":[{\"logical\":false,\"integral\":9223372036854775807,\"floating point\":-1.7976931348623157E+308,\"text\":\"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.\"},{\"logical\":false,\"integral\":9223372036854775807,\"floating point\":-1.7976931348623157E+308,\"text\":\"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.\"},{\"logical\":false,\"integral\":9223372036854775807,\"floating point\":-1.7976931348623157E+308,\"text\":\"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.\"}]}]}]}"; + + /// + /// Test JSON floating point value. + /// + public const string FloatJson = "-1.7976931348623157E+308"; + + /// + /// Test floating point property. + /// + public const string FloatProperty = "floating point"; + + /// + /// Test floating point value. + /// + public const double FloatValue = double.MinValue; + + /// + /// Test JSON integral value. + /// + public const string IntegralJson = "9223372036854775807"; + + /// + /// Test integral property. + /// + public const string IntegralProperty = "integral"; + + /// + /// Test integral value. + /// + public const long IntegralValue = long.MaxValue; + + /// + /// Test JSON logical value. + /// + public const string LogicalJson = "false"; + + /// + /// Test logical property. + /// + public const string LogicalProperty = "logical"; + + /// + /// Test logical value. + /// + public const bool LogicalValue = false; + + /// + /// Test JSON object. + /// + public const string ObjectJson = "{\"logical\":false,\"integral\":9223372036854775807,\"floating point\":-1.7976931348623157E+308,\"text\":\"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.\"}"; + + /// + /// Performance test iteration count. + /// + public const int PerformanceIterations = 10000; + + /// + /// Test JSON text. + /// + public const string TextJson = "\"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.\""; + + /// + /// Test text property. + /// + public const string TextProperty = "text"; + + /// + /// Test text value. + /// + public const string TextValue = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum."; + + /// + /// Any property name. + /// + public static readonly Dictionary AnyProperty = new Dictionary(StringComparer.Ordinal) + { + { Constants.ArrayProperty, 0 }, + { Constants.FloatProperty, 0 }, + { Constants.IntegralProperty, 0 }, + { Constants.LogicalProperty, 0 }, + { Constants.TextProperty, 0 } + }; + } +} diff --git a/Microsoft.Shared.Dna.Json.Profile/IProfile.cs b/Microsoft.Shared.Dna.Json.Profile/IProfile.cs new file mode 100644 index 0000000..13c0222 --- /dev/null +++ b/Microsoft.Shared.Dna.Json.Profile/IProfile.cs @@ -0,0 +1,24 @@ +//----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. See license file in the project root for full license information. +// +//----------------------------------------------------------------------------- + +namespace Microsoft.Shared.Dna.Json.Profile +{ + using System.Diagnostics; + + /// + /// Profiler interface. + /// + public interface IProfile + { + /// + /// Execute a single test iteration. + /// + /// The stopwatch timing the iteration. + /// A value indicating whether or not the test is valid. + bool Execute(Stopwatch watch); + } +} diff --git a/Microsoft.Shared.Dna.Json.Profile/JsonDna.cs b/Microsoft.Shared.Dna.Json.Profile/JsonDna.cs new file mode 100644 index 0000000..468104e --- /dev/null +++ b/Microsoft.Shared.Dna.Json.Profile/JsonDna.cs @@ -0,0 +1,426 @@ +//----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. See license file in the project root for full license information. +// +//----------------------------------------------------------------------------- + +namespace Microsoft.Shared.Dna.Json.Profile +{ + using System; + using System.Diagnostics; + + /// + /// OSG Data and Analytics JSON profilers. + /// + public static class JsonDna + { + /// + /// Reusable reader. + /// + [ThreadStatic] + private static JsonParser reader = null; + + /// + /// Reusable writer. + /// + [ThreadStatic] + private static JsonBuilder writer = null; + + /// + /// Prepare the reader for use in a single iteration. + /// + /// The JSON payload to use. + /// A prepared reader. + private static JsonParser PrepareReader(string payload) + { + JsonParser result = JsonDna.reader; + if (result == null) + { + result = new JsonParser(); + JsonDna.reader = result; + } + + result.Reset(payload); + return result; + } + + /// + /// Prepare the writer for use in a single iteration. + /// + /// A prepared writer. + private static JsonBuilder PerpareWriter() + { + JsonBuilder result = JsonDna.writer; + if (result == null) + { + result = new JsonBuilder(); + JsonDna.writer = result; + } + + result.Clear(); + return result; + } + + /// + /// Confirm the reader successfully parses the payload. + /// + /// The reader to check. + /// The stopwatch timing the iteration. + /// A value indicating whether or not the iteration is valid. + private static bool ConfirmReader(JsonParser reader, Stopwatch watch) + { + bool valid = true; + bool parsed = false; + bool outcome = false; + bool asLogical = false; + long asIntegral = 0L; + double asFloating = 0D; + string asText = null; + while (valid && reader.Next()) + { + parsed = true; + switch (reader.TokenType) + { + case JsonTokenType.Boolean: + outcome = reader.TryParseToken(out asLogical); + watch.Stop(); + valid = valid && outcome && asLogical == Constants.LogicalValue; + watch.Start(); + break; + case JsonTokenType.Integer: + outcome = reader.TryParseToken(out asIntegral); + watch.Stop(); + valid = valid && outcome && asIntegral == Constants.IntegralValue; + watch.Start(); + break; + case JsonTokenType.Float: + outcome = reader.TryParseToken(out asFloating); + watch.Stop(); + valid = valid && outcome && asFloating == Constants.FloatValue; + watch.Start(); + break; + case JsonTokenType.BeginProperty: + outcome = reader.TryParseToken(out asText); + watch.Stop(); + valid = valid && outcome && Constants.AnyProperty.ContainsKey(asText); + watch.Start(); + break; + case JsonTokenType.String: + outcome = reader.TryParseToken(out asText); + watch.Stop(); + valid = valid && outcome && string.CompareOrdinal(asText, Constants.TextValue) == 0; + watch.Start(); + break; + } + } + + return parsed && valid; + } + + /// + /// Confirm the writer successfully builds the payload. + /// + /// The writer to check. + /// The expected payload. + /// The stopwatch timing the iteration. + /// A value indicating whether or not the iteration is valid. + private static bool ConfirmWriter(JsonBuilder writer, string expected, Stopwatch watch) + { + bool result = false; + string actual = writer.Finish(); + watch.Stop(); + result = string.CompareOrdinal(expected, actual) == 0; + watch.Start(); + return result; + } + + /// + /// Recursively build a complex object. + /// + /// The writer to use. + /// The length of the recursive array. + /// The depth of the recursive object. + private static void RecursiveObject(JsonBuilder writer, int width, int depth) + { + writer.OpenObject(); + writer.OpenProperty(Constants.LogicalProperty); + writer.WriteValue(Constants.LogicalValue); + writer.CloseToken(); + writer.OpenProperty(Constants.IntegralProperty); + writer.WriteValue(Constants.IntegralValue); + writer.CloseToken(); + writer.OpenProperty(Constants.FloatProperty); + writer.WriteValue(Constants.FloatValue); + writer.CloseToken(); + writer.OpenProperty(Constants.TextProperty); + writer.WriteValue(Constants.TextValue); + writer.CloseToken(); + if (depth > 0) + { + writer.OpenProperty(Constants.ArrayProperty); + writer.OpenArray(); + for (int i = 0; i < width; i++) + { + int shallow = depth - 1; + JsonDna.RecursiveObject(writer, width, shallow); + } + + writer.CloseToken(); + writer.CloseToken(); + } + + writer.CloseToken(); + } + + /// + /// Read logical value. + /// + public sealed class ReadLogical : IProfile + { + /// + /// Execute a single test iteration. + /// + /// The stopwatch timing the iteration. + /// A value indicating whether or not the test is valid. + public bool Execute(Stopwatch watch) + { + JsonParser reader = JsonDna.PrepareReader(Constants.LogicalJson); + return JsonDna.ConfirmReader(reader, watch); + } + } + + /// + /// Read integral value. + /// + public sealed class ReadIntegral : IProfile + { + /// + /// Execute a single test iteration. + /// + /// The stopwatch timing the iteration. + /// A value indicating whether or not the test is valid. + public bool Execute(Stopwatch watch) + { + JsonParser reader = JsonDna.PrepareReader(Constants.IntegralJson); + return JsonDna.ConfirmReader(reader, watch); + } + } + + /// + /// Read floating point value. + /// + public sealed class ReadFloat : IProfile + { + /// + /// Execute a single test iteration. + /// + /// The stopwatch timing the iteration. + /// A value indicating whether or not the test is valid. + public bool Execute(Stopwatch watch) + { + JsonParser reader = JsonDna.PrepareReader(Constants.FloatJson); + return JsonDna.ConfirmReader(reader, watch); + } + } + + /// + /// Read text value. + /// + public sealed class ReadText : IProfile + { + /// + /// Execute a single test iteration. + /// + /// The stopwatch timing the iteration. + /// A value indicating whether or not the test is valid. + public bool Execute(Stopwatch watch) + { + JsonParser reader = JsonDna.PrepareReader(Constants.TextJson); + return JsonDna.ConfirmReader(reader, watch); + } + } + + /// + /// Read array. + /// + public sealed class ReadArray : IProfile + { + /// + /// Execute a single test iteration. + /// + /// The stopwatch timing the iteration. + /// A value indicating whether or not the test is valid. + public bool Execute(Stopwatch watch) + { + JsonParser reader = JsonDna.PrepareReader(Constants.ArrayJson); + return JsonDna.ConfirmReader(reader, watch); + } + } + + /// + /// Read object. + /// + public sealed class ReadObject : IProfile + { + /// + /// Execute a single test iteration. + /// + /// The stopwatch timing the iteration. + /// A value indicating whether or not the test is valid. + public bool Execute(Stopwatch watch) + { + JsonParser reader = JsonDna.PrepareReader(Constants.ObjectJson); + return JsonDna.ConfirmReader(reader, watch); + } + } + + /// + /// Read complex object. + /// + public sealed class ReadComplex : IProfile + { + /// + /// Execute a single test iteration. + /// + /// The stopwatch timing the iteration. + /// A value indicating whether or not the test is valid. + public bool Execute(Stopwatch watch) + { + JsonParser reader = JsonDna.PrepareReader(Constants.ComplexJson); + return JsonDna.ConfirmReader(reader, watch); + } + } + + /// + /// Write logical value. + /// + public sealed class WriteLogical : IProfile + { + /// + /// Execute a single test iteration. + /// + /// The stopwatch timing the iteration. + /// A value indicating whether or not the test is valid. + public bool Execute(Stopwatch watch) + { + JsonBuilder writer = JsonDna.PerpareWriter(); + writer.WriteValue(Constants.LogicalValue); + return JsonDna.ConfirmWriter(writer, Constants.LogicalJson, watch); + } + } + + /// + /// Write integral value. + /// + public sealed class WriteIntegral : IProfile + { + /// + /// Execute a single test iteration. + /// + /// The stopwatch timing the iteration. + /// A value indicating whether or not the test is valid. + public bool Execute(Stopwatch watch) + { + JsonBuilder writer = JsonDna.PerpareWriter(); + writer.WriteValue(Constants.IntegralValue); + return JsonDna.ConfirmWriter(writer, Constants.IntegralJson, watch); + } + } + + /// + /// Write floating-point value. + /// + public sealed class WriteFloat : IProfile + { + /// + /// Execute a single test iteration. + /// + /// The stopwatch timing the iteration. + /// A value indicating whether or not the test is valid. + public bool Execute(Stopwatch watch) + { + JsonBuilder writer = JsonDna.PerpareWriter(); + writer.WriteValue(Constants.FloatValue); + return JsonDna.ConfirmWriter(writer, Constants.FloatJson, watch); + } + } + + /// + /// Write text value. + /// + public sealed class WriteText : IProfile + { + /// + /// Execute a single test iteration. + /// + /// The stopwatch timing the iteration. + /// A value indicating whether or not the test is valid. + public bool Execute(Stopwatch watch) + { + JsonBuilder writer = JsonDna.PerpareWriter(); + writer.WriteValue(Constants.TextValue); + return JsonDna.ConfirmWriter(writer, Constants.TextJson, watch); + } + } + + /// + /// Write array. + /// + public sealed class WriteArray : IProfile + { + /// + /// Execute a single test iteration. + /// + /// The stopwatch timing the iteration. + /// A value indicating whether or not the test is valid. + public bool Execute(Stopwatch watch) + { + JsonBuilder writer = JsonDna.PerpareWriter(); + writer.OpenArray(); + writer.WriteValue(Constants.LogicalValue); + writer.WriteValue(Constants.IntegralValue); + writer.WriteValue(Constants.FloatValue); + writer.WriteValue(Constants.TextValue); + writer.CloseToken(); + return JsonDna.ConfirmWriter(writer, Constants.ArrayJson, watch); + } + } + + /// + /// Write object. + /// + public sealed class WriteObject : IProfile + { + /// + /// Execute a single test iteration. + /// + /// The stopwatch timing the iteration. + /// A value indicating whether or not the test is valid. + public bool Execute(Stopwatch watch) + { + JsonBuilder writer = JsonDna.PerpareWriter(); + JsonDna.RecursiveObject(writer, 0, 0); + return JsonDna.ConfirmWriter(writer, Constants.ObjectJson, watch); + } + } + + /// + /// Write complex object. + /// + public sealed class WriteComplex : IProfile + { + /// + /// Execute a single test iteration. + /// + /// The stopwatch timing the iteration. + /// A value indicating whether or not the test is valid. + public bool Execute(Stopwatch watch) + { + JsonBuilder writer = JsonDna.PerpareWriter(); + JsonDna.RecursiveObject(writer, 3, 3); + return JsonDna.ConfirmWriter(writer, Constants.ComplexJson, watch); + } + } + } +} diff --git a/Microsoft.Shared.Dna.Json.Profile/JsonDnaPerformanceTests.cs b/Microsoft.Shared.Dna.Json.Profile/JsonDnaPerformanceTests.cs new file mode 100644 index 0000000..78e6c85 --- /dev/null +++ b/Microsoft.Shared.Dna.Json.Profile/JsonDnaPerformanceTests.cs @@ -0,0 +1,179 @@ +//----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. See license file in the project root for full license information. +// +//----------------------------------------------------------------------------- + +namespace Microsoft.Shared.Dna.Json.Profile +{ + using System.Diagnostics.CodeAnalysis; + using Microsoft.VisualStudio.TestTools.UnitTesting; + + /// + /// Performance tests for OSG Data and Analytics JSON code. + /// + [TestClass] + [ExcludeFromCodeCoverage] + public class JsonDnaPerformanceTests + { + /// + /// Gets or sets the test context. + /// + public TestContext TestContext { get; set; } + + /// + /// Read logical. + /// + [TestMethod] + public void JsonDna_Read_Logical() + { + double meanExecutionMilliseconds = 0D; + Assert.IsTrue(Profiler.Execute(Constants.PerformanceIterations, out meanExecutionMilliseconds)); + this.TestContext.WriteLine("Average execution time (ms): {0}", meanExecutionMilliseconds); + } + + /// + /// Read integral. + /// + [TestMethod] + public void JsonDna_Read_Integral() + { + double meanExecutionMilliseconds = 0D; + Assert.IsTrue(Profiler.Execute(Constants.PerformanceIterations, out meanExecutionMilliseconds)); + this.TestContext.WriteLine("Average execution time (ms): {0}", meanExecutionMilliseconds); + } + + /// + /// Read float. + /// + [TestMethod] + public void JsonDna_Read_Float() + { + double meanExecutionMilliseconds = 0D; + Assert.IsTrue(Profiler.Execute(Constants.PerformanceIterations, out meanExecutionMilliseconds)); + this.TestContext.WriteLine("Average execution time (ms): {0}", meanExecutionMilliseconds); + } + + /// + /// Read text. + /// + [TestMethod] + public void JsonDna_Read_Text() + { + double meanExecutionMilliseconds = 0D; + Assert.IsTrue(Profiler.Execute(Constants.PerformanceIterations, out meanExecutionMilliseconds)); + this.TestContext.WriteLine("Average execution time (ms): {0}", meanExecutionMilliseconds); + } + + /// + /// Read array. + /// + [TestMethod] + public void JsonDna_Read_Array() + { + double meanExecutionMilliseconds = 0D; + Assert.IsTrue(Profiler.Execute(Constants.PerformanceIterations, out meanExecutionMilliseconds)); + this.TestContext.WriteLine("Average execution time (ms): {0}", meanExecutionMilliseconds); + } + + /// + /// Read object. + /// + [TestMethod] + public void JsonDna_Read_Object() + { + double meanExecutionMilliseconds = 0D; + Assert.IsTrue(Profiler.Execute(Constants.PerformanceIterations, out meanExecutionMilliseconds)); + this.TestContext.WriteLine("Average execution time (ms): {0}", meanExecutionMilliseconds); + } + + /// + /// Read complex. + /// + [TestMethod] + public void JsonDna_Read_Complex() + { + double meanExecutionMilliseconds = 0D; + Assert.IsTrue(Profiler.Execute(Constants.PerformanceIterations, out meanExecutionMilliseconds)); + this.TestContext.WriteLine("Average execution time (ms): {0}", meanExecutionMilliseconds); + } + + /// + /// Write logical. + /// + [TestMethod] + public void JsonDna_Write_Logical() + { + double meanExecutionMilliseconds = 0D; + Assert.IsTrue(Profiler.Execute(Constants.PerformanceIterations, out meanExecutionMilliseconds)); + this.TestContext.WriteLine("Average execution time (ms): {0}", meanExecutionMilliseconds); + } + + /// + /// Write integral. + /// + [TestMethod] + public void JsonDna_Write_Integral() + { + double meanExecutionMilliseconds = 0D; + Assert.IsTrue(Profiler.Execute(Constants.PerformanceIterations, out meanExecutionMilliseconds)); + this.TestContext.WriteLine("Average execution time (ms): {0}", meanExecutionMilliseconds); + } + + /// + /// Write float. + /// + [TestMethod] + public void JsonDna_Write_Float() + { + double meanExecutionMilliseconds = 0D; + Assert.IsTrue(Profiler.Execute(Constants.PerformanceIterations, out meanExecutionMilliseconds)); + this.TestContext.WriteLine("Average execution time (ms): {0}", meanExecutionMilliseconds); + } + + /// + /// Write text. + /// + [TestMethod] + public void JsonDna_Write_Text() + { + double meanExecutionMilliseconds = 0D; + Assert.IsTrue(Profiler.Execute(Constants.PerformanceIterations, out meanExecutionMilliseconds)); + this.TestContext.WriteLine("Average execution time (ms): {0}", meanExecutionMilliseconds); + } + + /// + /// Write array. + /// + [TestMethod] + public void JsonDna_Write_Array() + { + double meanExecutionMilliseconds = 0D; + Assert.IsTrue(Profiler.Execute(Constants.PerformanceIterations, out meanExecutionMilliseconds)); + this.TestContext.WriteLine("Average execution time (ms): {0}", meanExecutionMilliseconds); + } + + /// + /// Write object. + /// + [TestMethod] + public void JsonDna_Write_Object() + { + double meanExecutionMilliseconds = 0D; + Assert.IsTrue(Profiler.Execute(Constants.PerformanceIterations, out meanExecutionMilliseconds)); + this.TestContext.WriteLine("Average execution time (ms): {0}", meanExecutionMilliseconds); + } + + /// + /// Write complex. + /// + [TestMethod] + public void JsonDna_Write_Complex() + { + double meanExecutionMilliseconds = 0D; + Assert.IsTrue(Profiler.Execute(Constants.PerformanceIterations, out meanExecutionMilliseconds)); + this.TestContext.WriteLine("Average execution time (ms): {0}", meanExecutionMilliseconds); + } + } +} diff --git a/Microsoft.Shared.Dna.Json.Profile/Microsoft.Shared.Dna.Json.Profile.csproj b/Microsoft.Shared.Dna.Json.Profile/Microsoft.Shared.Dna.Json.Profile.csproj new file mode 100644 index 0000000..50e8bb2 --- /dev/null +++ b/Microsoft.Shared.Dna.Json.Profile/Microsoft.Shared.Dna.Json.Profile.csproj @@ -0,0 +1,103 @@ + + + + + Debug + AnyCPU + {C338ACCE-9521-435A-9011-2D6DF981920A} + Exe + Properties + Microsoft.Shared.Dna.Json.Profile + Microsoft.Shared.Dna.Json.Profile + v4.5 + 512 + ..\ + prompt + 4 + false + false + true + true + true + ..\Microsoft.Shared.Dna.Test.ruleset + + + true + + + AnyCPU + true + full + false + bin\Debug\ + DEBUG;TRACE + + + AnyCPU + pdbonly + true + bin\Release\ + TRACE + + + + + + + + + + + + + + + + + + + + Microsoft.Shared.Dna\JsonBuilder.cs + + + Microsoft.Shared.Dna\JsonConstants.cs + + + Microsoft.Shared.Dna\JsonParser.cs + + + Microsoft.Shared.Dna\JsonTokenTypeExtensions.cs + + + Microsoft.Shared.Dna\FixedStringBuilder.cs + + + Microsoft.Shared.Dna\StringSegment.cs + + + + + + + + + + + + PreserveNewest + + + Designer + + + + + + + + + + + + + \ No newline at end of file diff --git a/Microsoft.Shared.Dna.Json.Profile/Profiler.cs b/Microsoft.Shared.Dna.Json.Profile/Profiler.cs new file mode 100644 index 0000000..a5f0f47 --- /dev/null +++ b/Microsoft.Shared.Dna.Json.Profile/Profiler.cs @@ -0,0 +1,163 @@ +//----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. See license file in the project root for full license information. +// +//----------------------------------------------------------------------------- + +namespace Microsoft.Shared.Dna.Json.Profile +{ + using System; + using System.Diagnostics; + using System.Reflection; + using System.Threading; + + /// + /// Performance profiler. + /// + public static class Profiler + { + /// + /// Execute a profile. + /// + /// The profile type. + /// The number of iterations. + /// + /// The average elapsed time per iteration. + /// + /// A value indicating whether or not the execution is valid. + public static bool Execute(int iterations, out double meanElapsedMilliseconds) where T : IProfile, new() + { + return Profiler.ExecuteGeneric(iterations, out meanElapsedMilliseconds); + } + + /// + /// Execute a profile. + /// + /// The profile type. + /// The number of iterations. + /// + /// The average elapsed time per iteration. + /// + /// A value indicating whether or not the execution is valid. + public static bool Execute(Type type, int iterations, out double meanElapsedMilliseconds) + { + object[] parameters = new object[] { iterations, 0D }; + bool result = (bool)typeof(Profiler) + .GetMethod("ExecuteGeneric", BindingFlags.NonPublic | BindingFlags.Static) + .MakeGenericMethod(type) + .Invoke(null, parameters); + meanElapsedMilliseconds = (double)parameters[1]; + return result; + } + + /// + /// Execute a profile. + /// + /// The profile type. + /// The number of iterations. + /// + /// The average elapsed time per iteration. + /// + /// A value indicating whether or not the execution is valid. + private static bool ExecuteGeneric(int iterations, out double meanElapsedMilliseconds) where T : IProfile, new() + { + bool result = true; + int dop = Environment.ProcessorCount; + Thread[] executors = new Thread[dop]; + ExecuteState[] states = new ExecuteState[dop]; + int perExecutor = iterations / dop; + int remainder = iterations % dop; + for (int i = 0; i < dop; i++) + { + states[i] = new ExecuteState + { + Iterations = perExecutor + (--remainder > 0 ? 1 : 0), + Count = 0L, + Total = 0L, + Valid = true + }; + executors[i] = new Thread(Profiler.ExecutePartial); + executors[i].Start(states[i]); + } + + long total = 0L; + long count = 0L; + for (int i = 0; i < dop; i++) + { + executors[i].Join(); + total += states[i].Total; + count += states[i].Count; + result = result && states[i].Valid; + } + + meanElapsedMilliseconds = (double)total / TimeSpan.TicksPerMillisecond / count; + return result; + } + + /// + /// Execute a fraction of the iterations for a profile. + /// + /// The profile type. + /// The execution state. + private static void ExecutePartial(object state) where T : IProfile, new() + { + ExecuteState asExecute = state as ExecuteState; + T profile = new T(); + Stopwatch watch = new Stopwatch(); + for (int warmup = asExecute.Iterations / 10; warmup > 0; warmup--) + { + profile.Execute(watch); + } + + bool valid = false; + for (int i = 0; i < asExecute.Iterations; i++) + { + try + { + watch.Restart(); + valid = profile.Execute(watch); + watch.Stop(); + } + catch + { + valid = false; + } + + if (valid) + { + asExecute.Total += watch.ElapsedTicks; + asExecute.Count++; + } + + asExecute.Valid = asExecute.Valid && valid; + } + } + + /// + /// Profiler execution state. + /// + private sealed class ExecuteState + { + /// + /// Gets or sets the number of valid iterations. + /// + public long Count { get; set; } + + /// + /// Gets or sets the number of iterations to attempt. + /// + public int Iterations { get; set; } + + /// + /// Gets or sets the total execution time of all iterations in ticks. + /// + public long Total { get; set; } + + /// + /// Gets or sets a value indicating whether or not all iterations were valid. + /// + public bool Valid { get; set; } + } + } +} diff --git a/Microsoft.Shared.Dna.Json.Profile/Program.cs b/Microsoft.Shared.Dna.Json.Profile/Program.cs new file mode 100644 index 0000000..d4315dd --- /dev/null +++ b/Microsoft.Shared.Dna.Json.Profile/Program.cs @@ -0,0 +1,73 @@ +//----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. See license file in the project root for full license information. +// +//----------------------------------------------------------------------------- + +namespace Microsoft.Shared.Dna.Json.Profile +{ + using System; + using System.Globalization; + + /// + /// Entry point class. + /// + public static class Program + { + /// + /// Entry point method. + /// + /// Command line arguments. + public static void Main(string[] args) + { + try + { + string group = args[0]; + string profile = args[1]; + int iterations = int.Parse(args[2], CultureInfo.InvariantCulture); + string typeName = string.Concat("Microsoft.Shared.Dna.Json.Profile." + group + "+" + profile); + Type type = null; + try + { + type = typeof(Program).Assembly.GetType(typeName); + } + catch + { + } + + if (type == null) + { + Console.WriteLine("Couldn't find profile \"{0}\" in group \"{1}\".", profile, group); + return; + } + + double meanExecutionMilliseconds = 0D; + bool valid = Profiler.Execute(type, iterations, out meanExecutionMilliseconds); + Console.WriteLine("{0},{1},{2},{3}", group, profile, meanExecutionMilliseconds, valid); + } + catch + { + Console.WriteLine("Usage: Microsoft.Shared.Dna.Json.Profile.exe (group) (profile) (iterations)"); + Console.WriteLine(" Groups:"); + Console.WriteLine(" JsonDna"); + Console.WriteLine(" Reading Profiles:"); + Console.WriteLine(" ReadLogical"); + Console.WriteLine(" ReadIntegral"); + Console.WriteLine(" ReadFloat"); + Console.WriteLine(" ReadText"); + Console.WriteLine(" ReadArray"); + Console.WriteLine(" ReadObject"); + Console.WriteLine(" ReadComplex"); + Console.WriteLine(" Writing Profiles:"); + Console.WriteLine(" WriteLogical"); + Console.WriteLine(" WriteIntegral"); + Console.WriteLine(" WriteFloat"); + Console.WriteLine(" WriteText"); + Console.WriteLine(" WriteArray"); + Console.WriteLine(" WriteObject"); + Console.WriteLine(" WriteComplex"); + } + } + } +} diff --git a/Microsoft.Shared.Dna.Json.Profile/packages.config b/Microsoft.Shared.Dna.Json.Profile/packages.config new file mode 100644 index 0000000..798cfe1 --- /dev/null +++ b/Microsoft.Shared.Dna.Json.Profile/packages.config @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/Microsoft.Shared.Dna.Json.Profile/profile_all.cmd b/Microsoft.Shared.Dna.Json.Profile/profile_all.cmd new file mode 100644 index 0000000..1d38ac5 --- /dev/null +++ b/Microsoft.Shared.Dna.Json.Profile/profile_all.cmd @@ -0,0 +1,7 @@ +@ECHO OFF +ECHO Group,Profile,MeanElapsedMilliseconds,Valid +FOR %%I IN (JsonDna) DO ( + FOR %%J IN (ReadLogical ReadIntegral ReadFloat ReadText ReadArray ReadObject ReadComplex WriteLogical WriteIntegral WriteFloat WriteText WriteArray WriteObject WriteComplex) DO ( + Microsoft.Shared.Dna.Json.Profile.exe %%I %%J 100000 + ) +) diff --git a/Microsoft.Shared.Dna.Json.Test/AssertToken.cs b/Microsoft.Shared.Dna.Json.Test/AssertToken.cs new file mode 100644 index 0000000..e37b55b --- /dev/null +++ b/Microsoft.Shared.Dna.Json.Test/AssertToken.cs @@ -0,0 +1,146 @@ +//----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. See license file in the project root for full license information. +// +//----------------------------------------------------------------------------- + +namespace Microsoft.Shared.Dna.Json.Test +{ + using Microsoft.VisualStudio.TestTools.UnitTesting; + + /// + /// Parser token validation. + /// + internal static class AssertToken + { + /// + /// Validates that the parser is complete. + /// + /// The expected payload. + /// The actual parser. + public static void IsComplete(string expectedPayload, JsonParser actualParser) + { + Assert.IsFalse(actualParser.Next()); + AssertToken.Matches(JsonTokenType.Complete, expectedPayload, 0, expectedPayload.Length, actualParser); + Assert.IsFalse(actualParser.Next()); + AssertToken.Matches(JsonTokenType.Complete, expectedPayload, 0, expectedPayload.Length, actualParser); + } + + /// + /// Validates that the parser is on a property. + /// + /// The expected property. + /// The expected payload. + /// The expected offset. + /// The expected count. + /// The actual parser. + public static void IsProperty(string expectedProperty, string expectedPayload, int expectedOffset, int expectedCount, JsonParser actualParser) + { + AssertToken.Matches(JsonTokenType.BeginProperty, expectedPayload, expectedOffset, expectedCount, actualParser); + string actualProperty = null; + Assert.IsTrue(actualParser.TryParseToken(out actualProperty)); + Assert.AreEqual(expectedProperty, actualProperty); + } + + /// + /// Validates that the parser is on a value. + /// + /// The expected value. + /// The expected payload. + /// The expected offset. + /// The expected count. + /// The actual parser. + public static void IsValue(bool expectedValue, string expectedPayload, int expectedOffset, int expectedCount, JsonParser actualParser) + { + AssertToken.Matches(JsonTokenType.Boolean, expectedPayload, expectedOffset, expectedCount, actualParser); + bool actualValue = false; + Assert.IsTrue(actualParser.TryParseToken(out actualValue)); + Assert.AreEqual(expectedValue, actualValue); + } + + /// + /// Validates that the parser is on a value. + /// + /// The expected value. + /// The expected payload. + /// The expected offset. + /// The expected count. + /// The actual parser. + public static void IsValue(long expectedValue, string expectedPayload, int expectedOffset, int expectedCount, JsonParser actualParser) + { + AssertToken.Matches(JsonTokenType.Integer, expectedPayload, expectedOffset, expectedCount, actualParser); + long actualValue = 0L; + Assert.IsTrue(actualParser.TryParseToken(out actualValue)); + Assert.AreEqual(expectedValue, actualValue); + } + + /// + /// Validates that the parser is on a value. + /// + /// The expected value. + /// The expected payload. + /// The expected offset. + /// The expected count. + /// The actual parser. + public static void IsValue(ulong expectedValue, string expectedPayload, int expectedOffset, int expectedCount, JsonParser actualParser) + { + AssertToken.Matches(JsonTokenType.Integer, expectedPayload, expectedOffset, expectedCount, actualParser); + ulong actualValue = 0UL; + Assert.IsTrue(actualParser.TryParseToken(out actualValue)); + Assert.AreEqual(expectedValue, actualValue); + } + + /// + /// Validates that the parser is on a value. + /// + /// The expected value. + /// The expected payload. + /// The expected offset. + /// The expected count. + /// The actual parser. + public static void IsValue(double expectedValue, string expectedPayload, int expectedOffset, int expectedCount, JsonParser actualParser) + { + AssertToken.Matches(JsonTokenType.Float, expectedPayload, expectedOffset, expectedCount, actualParser); + double actualValue = 0D; + Assert.IsTrue(actualParser.TryParseToken(out actualValue)); + Assert.AreEqual(expectedValue, actualValue); + } + + /// + /// Validates that the parser is on a value. + /// + /// The expected value. + /// The expected payload. + /// The expected offset. + /// The expected count. + /// The actual parser. + public static void IsValue(string expectedValue, string expectedPayload, int expectedOffset, int expectedCount, JsonParser actualParser) + { + AssertToken.Matches(JsonTokenType.String, expectedPayload, expectedOffset, expectedCount, actualParser); + string actualValue = null; + Assert.IsTrue(actualParser.TryParseToken(out actualValue)); + Assert.AreEqual(expectedValue.Length, actualValue.Length); + for (int i = 0; i < expectedValue.Length; i++) + { + Assert.AreEqual(expectedValue[i], actualValue[i], "index:{0}", i); + } + } + + /// + /// Validates that the parser is on a given token. + /// + /// The expected token type. + /// The expected payload. + /// The expected offset. + /// The expected count. + /// The actual parser. + public static void Matches(JsonTokenType expectedType, string expectedPayload, int expectedOffset, int expectedCount, JsonParser actualParser) + { + Assert.AreEqual(expectedType, actualParser.TokenType); + Assert.AreSame(expectedPayload, actualParser.TokenSegment.String); + Assert.AreEqual(expectedOffset, actualParser.TokenSegment.Offset); + Assert.AreEqual(expectedCount, actualParser.TokenSegment.Count); + } + } +} diff --git a/Microsoft.Shared.Dna.Json.Test/Constants.cs b/Microsoft.Shared.Dna.Json.Test/Constants.cs new file mode 100644 index 0000000..28f6846 --- /dev/null +++ b/Microsoft.Shared.Dna.Json.Test/Constants.cs @@ -0,0 +1,106 @@ +//----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. See license file in the project root for full license information. +// +//----------------------------------------------------------------------------- + +namespace Microsoft.Shared.Dna.Json.Test +{ + using System; + using System.Text; + + /// + /// Test constants. + /// + public static class Constants + { + /// + /// A string containing every unicode character. + /// + public static readonly string UnicodeRainbowDecoded = Constants.CreateUnicodeRainbowDecoded(); + + /// + /// A string containing every unicode character encoded as a JSON escape sequence. + /// + public static readonly string UnicodeRainbowEncoded = Constants.CreateUnicodeRainbowEncoded(); + + /// + /// Create the unicode rainbow. + /// + /// A string containing every unicode character. + private static string CreateUnicodeRainbowDecoded() + { + char[] result = new char[char.MaxValue + 1]; + for (char c = char.MinValue; c < char.MaxValue; c++) + { + result[c] = c; + } + + result[char.MaxValue] = char.MaxValue; + return new string(result); + } + + /// + /// Create a JSON-encoded version of the unicode rainbow. + /// + /// A string containing every unicode character encoded as a JSON escape sequence. + private static string CreateUnicodeRainbowEncoded() + { + StringBuilder result = new StringBuilder(70000); + result.Append("\""); + for (char c = char.MinValue; c < char.MaxValue; c++) + { + switch (c) + { + case '"': + result.Append("\\\""); + break; + case '\\': + result.Append("\\\\"); + break; + case '\b': + result.Append("\\b"); + break; + case '\f': + result.Append("\\f"); + break; + case '\n': + result.Append("\\n"); + break; + case '\r': + result.Append("\\r"); + break; + case '\t': + result.Append("\\t"); + break; + default: + if (char.IsControl(c)) + { + result.Append("\\u"); + byte[] bytes = BitConverter.GetBytes(c); + if (BitConverter.IsLittleEndian) + { + Array.Reverse(bytes); + } + + for (int i = 0; i < bytes.Length; i++) + { + result.Append(BitConverter.ToString(bytes, i, 1)); + } + } + else + { + result.Append(c); + } + + break; + } + } + + result.Append(char.MaxValue); + result.Append("\""); + return result.ToString(); + } + } +} diff --git a/Microsoft.Shared.Dna.Json.Test/JsonBuilderTests.cs b/Microsoft.Shared.Dna.Json.Test/JsonBuilderTests.cs new file mode 100644 index 0000000..ca5cef6 --- /dev/null +++ b/Microsoft.Shared.Dna.Json.Test/JsonBuilderTests.cs @@ -0,0 +1,511 @@ +//----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. See license file in the project root for full license information. +// +//----------------------------------------------------------------------------- + +namespace Microsoft.Shared.Dna.Json.Test +{ + using System; + using System.Diagnostics.CodeAnalysis; + using System.Globalization; + using Microsoft.VisualStudio.TestTools.UnitTesting; + + /// + /// Tests for the class. + /// + [TestClass] + [ExcludeFromCodeCoverage] + public class JsonBuilderTests + { + /// + /// Constructor assigns minimum capacity. + /// + [TestMethod] + public void JsonBuilder_Constructor_Assigns_Minimum_Capacity() + { + JsonBuilder target = new JsonBuilder(0, 1); + Assert.IsFalse(target.WriteValue(null)); + Assert.AreEqual("{\"(truncated)\":true}", target.Finish()); + } + + /// + /// Constructor rejects non-positive depth. + /// + [TestMethod] + public void JsonBuilder_Constructor_Rejects_Non_Positive_Depth() + { + try + { + JsonBuilder target = new JsonBuilder(0, 0); + Assert.IsNull(target); + } + catch (ArgumentOutOfRangeException ex) + { + Assert.AreEqual(typeof(ArgumentOutOfRangeException), ex.GetType()); + } + } + + /// + /// Clear causes Finish to return empty string. + /// + [TestMethod] + public void JsonBuilder_Clear_Causes_Finish_To_Return_Empty_String() + { + JsonBuilder target = new JsonBuilder(); + Assert.IsTrue(target.WriteValue("Should not be returned")); + target.Clear(); + Assert.AreEqual(string.Empty, target.Finish()); + } + + /// + /// OpenArray creates empty array. + /// + [TestMethod] + public void JsonBuilder_OpenArray_Creates_Empty_Array() + { + JsonBuilder target = new JsonBuilder(); + Assert.IsTrue(target.OpenArray()); + Assert.AreEqual("[]", target.Finish()); + } + + /// + /// OpenArray can be nested in OpenArray. + /// + [TestMethod] + public void JsonBuilder_OpenArray_Can_Be_Nested_In_OpenArray() + { + JsonBuilder target = new JsonBuilder(); + Assert.IsTrue(target.OpenArray()); + Assert.IsTrue(target.OpenArray()); + Assert.IsTrue(target.WriteValue(1)); + target.CloseToken(); + Assert.IsTrue(target.OpenArray()); + Assert.IsTrue(target.WriteValue(2)); + Assert.AreEqual("[[1],[2]]", target.Finish()); + } + + /// + /// OpenArray can be nested in OpenProperty. + /// + [TestMethod] + public void JsonBuilder_OpenArray_Can_Be_Nested_In_OpenProperty() + { + JsonBuilder target = new JsonBuilder(); + Assert.IsTrue(target.OpenObject()); + Assert.IsTrue(target.OpenProperty("first")); + Assert.IsTrue(target.OpenArray()); + Assert.IsTrue(target.WriteValue(1)); + target.CloseToken(); + target.CloseToken(); + Assert.IsTrue(target.OpenProperty("second")); + Assert.IsTrue(target.OpenArray()); + Assert.IsTrue(target.WriteValue(2)); + Assert.AreEqual("{\"first\":[1],\"second\":[2]}", target.Finish()); + } + + /// + /// OpenObject creates empty object. + /// + [TestMethod] + public void JsonBuilder_OpenObject_Creates_Empty_Object() + { + JsonBuilder target = new JsonBuilder(); + Assert.IsTrue(target.OpenObject()); + Assert.AreEqual("{}", target.Finish()); + } + + /// + /// OpenObject can be nested in OpenArray. + /// + [TestMethod] + public void JsonBuilder_OpenObject_Can_Be_Nested_In_OpenArray() + { + JsonBuilder target = new JsonBuilder(); + Assert.IsTrue(target.OpenArray()); + Assert.IsTrue(target.OpenObject()); + Assert.IsTrue(target.OpenProperty("value")); + Assert.IsTrue(target.WriteValue(1)); + target.CloseToken(); + target.CloseToken(); + Assert.IsTrue(target.OpenObject()); + Assert.IsTrue(target.OpenProperty("value")); + Assert.IsTrue(target.WriteValue(2)); + Assert.AreEqual("[{\"value\":1},{\"value\":2}]", target.Finish()); + } + + /// + /// OpenObject can be nested in OpenProperty. + /// + [TestMethod] + public void JsonBuilder_OpenObject_Can_Be_Nested_In_OpenProperty() + { + JsonBuilder target = new JsonBuilder(); + Assert.IsTrue(target.OpenObject()); + Assert.IsTrue(target.OpenProperty("first")); + Assert.IsTrue(target.OpenObject()); + Assert.IsTrue(target.OpenProperty("value")); + Assert.IsTrue(target.WriteValue(1)); + target.CloseToken(); + target.CloseToken(); + target.CloseToken(); + Assert.IsTrue(target.OpenProperty("second")); + Assert.IsTrue(target.OpenObject()); + Assert.IsTrue(target.OpenProperty("value")); + Assert.IsTrue(target.WriteValue(2)); + Assert.AreEqual("{\"first\":{\"value\":1},\"second\":{\"value\":2}}", target.Finish()); + } + + /// + /// OpenProperty creates null property. + /// + [TestMethod] + public void JsonBuilder_OpenProperty_Creates_Null_Property() + { + JsonBuilder target = new JsonBuilder(); + Assert.IsTrue(target.OpenObject()); + Assert.IsTrue(target.OpenProperty("isNull")); + Assert.AreEqual("{\"isNull\":null}", target.Finish()); + } + + /// + /// OpenProperty creates null property. + /// + [TestMethod] + public void JsonBuilder_OpenProperty_Rejects_Null_Property_Name() + { + JsonBuilder target = new JsonBuilder(); + Assert.IsTrue(target.OpenObject()); + try + { + target.OpenProperty(null); + Assert.Fail(); + } + catch (ArgumentNullException ex) + { + Assert.AreEqual(typeof(ArgumentNullException), ex.GetType()); + } + } + + /// + /// TryResize increases capacity and preserves current state. + /// + [TestMethod] + public void JsonBuilder_TryResize_Increases_Capacity_And_Preserves_Current_State() + { + JsonBuilder target = new JsonBuilder(50, 2); + Assert.IsTrue(target.OpenArray()); + for (int i = 0; i < 10; i++) + { + Assert.IsTrue(target.WriteValue(i), "index:{0}", i); + } + + Assert.IsTrue(target.TryResize(100)); + for (int i = 10; i < 20; i++) + { + Assert.IsTrue(target.WriteValue(i), "index:{0}", i); + } + + Assert.AreEqual("[0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19]", target.Finish()); + } + + /// + /// TryResize decreases capacity if state can be preserved. + /// + [TestMethod] + public void JsonBuilder_TryResize_Decreases_Capacity_If_State_Can_Be_Preserved() + { + JsonBuilder target = new JsonBuilder(100, 2); + Assert.IsTrue(target.OpenArray()); + for (int i = 0; i < 10; i++) + { + Assert.IsTrue(target.WriteValue(i), "index:{0}", i); + } + + Assert.IsTrue(target.TryResize(50)); + Assert.AreEqual("[0,1,2,3,4,5,6,7,8,9]", target.Finish()); + } + + /// + /// TryResize does nothing if capacity is below minimum. + /// + [TestMethod] + public void JsonBuilder_TryResize_Does_Nothing_If_Capacity_Is_Below_Minimum() + { + JsonBuilder target = new JsonBuilder(100, 2); + Assert.IsFalse(target.TryResize(0)); + Assert.IsTrue(target.OpenArray()); + for (int i = 0; i < 20; i++) + { + Assert.IsTrue(target.WriteValue(i), "index:{0}", i); + } + + Assert.AreEqual("[0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19]", target.Finish()); + } + + /// + /// TryResize does nothing if state cannot be preserved. + /// + [TestMethod] + public void JsonBuilder_TryResize_Does_Nothing_If_State_Cannot_Be_Preserved() + { + JsonBuilder target = new JsonBuilder(100, 2); + Assert.IsTrue(target.OpenArray()); + for (int i = 0; i < 20; i++) + { + Assert.IsTrue(target.WriteValue(i), "index:{0}", i); + } + + Assert.IsFalse(target.TryResize(50)); + Assert.AreEqual("[0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19]", target.Finish()); + } + + /// + /// WriteValue writes Boolean. + /// + [TestMethod] + public void JsonBuilder_WriteValue_Writes_Boolean() + { + JsonBuilder target = new JsonBuilder(); + Assert.IsTrue(target.WriteValue(true)); + Assert.AreEqual("true", target.Finish()); + target.Clear(); + Assert.IsTrue(target.WriteValue(false)); + Assert.AreEqual("false", target.Finish()); + } + + /// + /// WriteValue writes Byte. + /// + [TestMethod] + public void JsonBuilder_WriteValue_Writes_Byte() + { + JsonBuilder target = new JsonBuilder(); + Assert.IsTrue(target.WriteValue(byte.MinValue)); + Assert.AreEqual("0", target.Finish()); + target.Clear(); + Assert.IsTrue(target.WriteValue(byte.MaxValue)); + Assert.AreEqual("255", target.Finish()); + } + + /// + /// WriteValue writes SByte. + /// + [TestMethod] + public void JsonBuilder_WriteValue_Writes_SByte() + { + JsonBuilder target = new JsonBuilder(); + Assert.IsTrue(target.WriteValue(sbyte.MinValue)); + Assert.AreEqual("-128", target.Finish()); + target.Clear(); + Assert.IsTrue(target.WriteValue(sbyte.MaxValue)); + Assert.AreEqual("127", target.Finish()); + } + + /// + /// WriteValue writes Int16. + /// + [TestMethod] + public void JsonBuilder_WriteValue_Writes_Int16() + { + JsonBuilder target = new JsonBuilder(); + Assert.IsTrue(target.WriteValue(short.MinValue)); + Assert.AreEqual("-32768", target.Finish()); + target.Clear(); + Assert.IsTrue(target.WriteValue(short.MaxValue)); + Assert.AreEqual("32767", target.Finish()); + } + + /// + /// WriteValue writes UInt16. + /// + [TestMethod] + public void JsonBuilder_WriteValue_Writes_UInt16() + { + JsonBuilder target = new JsonBuilder(); + Assert.IsTrue(target.WriteValue(ushort.MinValue)); + Assert.AreEqual("0", target.Finish()); + target.Clear(); + Assert.IsTrue(target.WriteValue(ushort.MaxValue)); + Assert.AreEqual("65535", target.Finish()); + } + + /// + /// WriteValue writes Int32. + /// + [TestMethod] + public void JsonBuilder_WriteValue_Writes_Int32() + { + JsonBuilder target = new JsonBuilder(); + Assert.IsTrue(target.WriteValue(int.MinValue)); + Assert.AreEqual("-2147483648", target.Finish()); + target.Clear(); + Assert.IsTrue(target.WriteValue(int.MaxValue)); + Assert.AreEqual("2147483647", target.Finish()); + } + + /// + /// WriteValue writes UInt32. + /// + [TestMethod] + public void JsonBuilder_WriteValue_Writes_UInt32() + { + JsonBuilder target = new JsonBuilder(); + Assert.IsTrue(target.WriteValue(uint.MinValue)); + Assert.AreEqual("0", target.Finish()); + target.Clear(); + Assert.IsTrue(target.WriteValue(uint.MaxValue)); + Assert.AreEqual("4294967295", target.Finish()); + } + + /// + /// WriteValue writes Int64. + /// + [TestMethod] + public void JsonBuilder_WriteValue_Writes_Int64() + { + JsonBuilder target = new JsonBuilder(); + Assert.IsTrue(target.WriteValue(long.MinValue)); + Assert.AreEqual("-9223372036854775808", target.Finish()); + target.Clear(); + Assert.IsTrue(target.WriteValue(long.MaxValue)); + Assert.AreEqual("9223372036854775807", target.Finish()); + } + + /// + /// WriteValue writes UInt64. + /// + [TestMethod] + public void JsonBuilder_WriteValue_Writes_UInt64() + { + JsonBuilder target = new JsonBuilder(); + Assert.IsTrue(target.WriteValue(ulong.MinValue)); + Assert.AreEqual("0", target.Finish()); + target.Clear(); + Assert.IsTrue(target.WriteValue(ulong.MaxValue)); + Assert.AreEqual("18446744073709551615", target.Finish()); + } + + /// + /// WriteValue writes Single. + /// + [TestMethod] + public void JsonBuilder_WriteValue_Writes_Single() + { + JsonBuilder target = new JsonBuilder(); + Assert.IsTrue(target.WriteValue(float.MinValue)); + Assert.AreEqual("-3.40282347E+38", target.Finish()); + target.Clear(); + Assert.IsTrue(target.WriteValue(float.Epsilon)); + Assert.AreEqual("1.401298E-45", target.Finish()); + target.Clear(); + Assert.IsTrue(target.WriteValue(float.MaxValue)); + Assert.AreEqual("3.40282347E+38", target.Finish()); + } + + /// + /// WriteValue writes Double. + /// + [TestMethod] + public void JsonBuilder_WriteValue_Writes_Double() + { + JsonBuilder target = new JsonBuilder(); + Assert.IsTrue(target.WriteValue(double.MinValue)); + Assert.AreEqual("-1.7976931348623157E+308", target.Finish()); + target.Clear(); + Assert.IsTrue(target.WriteValue(double.Epsilon)); + Assert.AreEqual("4.94065645841247E-324", target.Finish()); + target.Clear(); + Assert.IsTrue(target.WriteValue(double.MaxValue)); + Assert.AreEqual("1.7976931348623157E+308", target.Finish()); + } + + /// + /// WriteValue writes decimal. + /// + [TestMethod] + public void JsonBuilder_WriteValue_Writes_Decimal() + { + JsonBuilder target = new JsonBuilder(); + Assert.IsTrue(target.WriteValue(decimal.MinValue)); + Assert.AreEqual("-79228162514264337593543950335", target.Finish()); + target.Clear(); + Assert.IsTrue(target.WriteValue(decimal.MaxValue)); + Assert.AreEqual("79228162514264337593543950335", target.Finish()); + } + + /// + /// WriteValue writes String. + /// + [TestMethod] + public void JsonBuilder_WriteValue_Writes_String() + { + JsonBuilder target = new JsonBuilder(70000, 1); + Assert.IsTrue(target.WriteValue(null)); + Assert.AreEqual("null", target.Finish()); + target.Clear(); + Assert.IsTrue(target.WriteValue(string.Empty)); + Assert.AreEqual("\"\"", target.Finish()); + target.Clear(); + Assert.IsTrue(target.WriteValue(Constants.UnicodeRainbowDecoded)); + string encoded = target.Finish(); + for (int i = 0; i < Constants.UnicodeRainbowEncoded.Length; i++) + { + Assert.AreEqual(Constants.UnicodeRainbowEncoded[i], encoded[i], "index:{0}", i); + } + } + + /// + /// Writing truncates array when capacity is exceeded. + /// + [TestMethod] + public void JsonBuilder_Writing_Truncates_Array_When_Capacity_Is_Exceeded() + { + JsonBuilder target = new JsonBuilder(50, 2); + Assert.IsTrue(target.OpenArray()); + bool succeeded = true; + for (int i = 0; succeeded; i++) + { + succeeded = target.WriteValue(i); + } + + Assert.AreEqual("[0,1,2,3,4,5,6,7,8,9,10,11,{\"(truncated)\":true}]", target.Finish()); + } + + /// + /// Writing truncates object when capacity is exceeded. + /// + [TestMethod] + public void JsonBuilder_Writing_Truncates_Object_When_Capacity_Is_Exceeded() + { + JsonBuilder target = new JsonBuilder(50, 4); + Assert.IsTrue(target.OpenObject()); + bool succeeded = true; + for (int i = 0; succeeded; i++) + { + if (target.OpenProperty(i.ToString(CultureInfo.InvariantCulture))) + { + succeeded = target.WriteValue(i); + target.CloseToken(); + continue; + } + + break; + } + + Assert.AreEqual("{\"0\":0,\"1\":1,\"2\":2,\"3\":3,\"(truncated)\":true}", target.Finish()); + } + + /// + /// Writing truncates value when capacity is exceeded. + /// + [TestMethod] + public void JsonBuilder_Writing_Truncates_Value_When_Capacity_Is_Exceeded() + { + JsonBuilder target = new JsonBuilder(20, 1); + target.WriteValue("ABCDEFGHIJKLMNOPQRSTUVWXYZ"); + Assert.AreEqual("{\"(truncated)\":true}", target.Finish()); + } + } +} diff --git a/Microsoft.Shared.Dna.Json.Test/JsonParserTests.cs b/Microsoft.Shared.Dna.Json.Test/JsonParserTests.cs new file mode 100644 index 0000000..da88b8e --- /dev/null +++ b/Microsoft.Shared.Dna.Json.Test/JsonParserTests.cs @@ -0,0 +1,461 @@ +//----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. See license file in the project root for full license information. +// +//----------------------------------------------------------------------------- + +namespace Microsoft.Shared.Dna.Json.Test +{ + using System; + using System.Globalization; + using System.Text; + using Microsoft.VisualStudio.TestTools.UnitTesting; + + /// + /// Tests for the class. + /// + [TestClass] + public class JsonParserTests + { + /// + /// Constructor allows null payload. + /// + [TestMethod] + public void JsonParser_Constructor_Allows_Null_Payload() + { + string payload = null; + JsonParser target = new JsonParser(payload); + Assert.IsFalse(target.Next()); + Assert.AreEqual(JsonTokenType.Invalid, target.TokenType); + } + + /// + /// Constructor rejects non-positive depth. + /// + [TestMethod] + public void JsonParser_Constructor_Rejects_Non_Positive_Depth() + { + try + { + JsonParser target = new JsonParser("null", 4, 0); + Assert.IsNull(target); + } + catch (ArgumentOutOfRangeException ex) + { + Assert.AreEqual(typeof(ArgumentOutOfRangeException), ex.GetType()); + } + } + + /// + /// Next parses empty array. + /// + [TestMethod] + public void JsonParser_Next_Parses_Empty_Array() + { + string payload = "[]"; + JsonParser target = new JsonParser(payload); + Assert.IsTrue(target.Next()); + AssertToken.Matches(JsonTokenType.BeginArray, payload, 0, 1, target); + Assert.IsTrue(target.Next()); + AssertToken.Matches(JsonTokenType.EndArray, payload, 0, 2, target); + AssertToken.IsComplete(payload, target); + } + + /// + /// Next parses array nested in array. + /// + [TestMethod] + public void JsonParser_Next_Parses_Array_Nested_In_Array() + { + string payload = "[[1,2]]"; + JsonParser target = new JsonParser(payload); + Assert.IsTrue(target.Next()); + AssertToken.Matches(JsonTokenType.BeginArray, payload, 0, 1, target); + Assert.IsTrue(target.Next()); + AssertToken.Matches(JsonTokenType.BeginArray, payload, 1, 1, target); + Assert.IsTrue(target.Next()); + AssertToken.IsValue(1L, payload, 2, 1, target); + Assert.IsTrue(target.Next()); + AssertToken.IsValue(2L, payload, 4, 1, target); + Assert.IsTrue(target.Next()); + AssertToken.Matches(JsonTokenType.EndArray, payload, 1, 5, target); + Assert.IsTrue(target.Next()); + AssertToken.Matches(JsonTokenType.EndArray, payload, 0, 7, target); + AssertToken.IsComplete(payload, target); + } + + /// + /// Next parses array nested in property. + /// + [TestMethod] + public void JsonParser_Next_Parses_Array_Nested_In_Property() + { + string payload = "{\"array\":[1,2]}"; + JsonParser target = new JsonParser(payload); + Assert.IsTrue(target.Next()); + AssertToken.Matches(JsonTokenType.BeginObject, payload, 0, 1, target); + Assert.IsTrue(target.Next()); + AssertToken.IsProperty("array", payload, 1, 8, target); + Assert.IsTrue(target.Next()); + AssertToken.Matches(JsonTokenType.BeginArray, payload, 9, 1, target); + Assert.IsTrue(target.Next()); + AssertToken.IsValue(1L, payload, 10, 1, target); + Assert.IsTrue(target.Next()); + AssertToken.IsValue(2L, payload, 12, 1, target); + Assert.IsTrue(target.Next()); + AssertToken.Matches(JsonTokenType.EndArray, payload, 9, 5, target); + Assert.IsTrue(target.Next()); + AssertToken.Matches(JsonTokenType.EndProperty, payload, 1, 13, target); + Assert.IsTrue(target.Next()); + AssertToken.Matches(JsonTokenType.EndObject, payload, 0, 15, target); + AssertToken.IsComplete(payload, target); + } + + /// + /// Next parses empty object. + /// + [TestMethod] + public void JsonParser_Next_Parses_Empty_Object() + { + string payload = "{}"; + JsonParser target = new JsonParser(payload); + Assert.IsTrue(target.Next()); + AssertToken.Matches(JsonTokenType.BeginObject, payload, 0, 1, target); + Assert.IsTrue(target.Next()); + AssertToken.Matches(JsonTokenType.EndObject, payload, 0, 2, target); + AssertToken.IsComplete(payload, target); + } + + /// + /// Next parses object nested in array. + /// + [TestMethod] + public void JsonParser_Next_Parses_Object_Nested_In_Array() + { + string payload = "[{\"value\":1},{\"value\":2}]"; + JsonParser target = new JsonParser(payload); + Assert.IsTrue(target.Next()); + AssertToken.Matches(JsonTokenType.BeginArray, payload, 0, 1, target); + Assert.IsTrue(target.Next()); + AssertToken.Matches(JsonTokenType.BeginObject, payload, 1, 1, target); + Assert.IsTrue(target.Next()); + AssertToken.IsProperty("value", payload, 2, 8, target); + Assert.IsTrue(target.Next()); + AssertToken.IsValue(1L, payload, 10, 1, target); + Assert.IsTrue(target.Next()); + AssertToken.Matches(JsonTokenType.EndProperty, payload, 2, 9, target); + Assert.IsTrue(target.Next()); + AssertToken.Matches(JsonTokenType.EndObject, payload, 1, 11, target); + Assert.IsTrue(target.Next()); + AssertToken.Matches(JsonTokenType.BeginObject, payload, 13, 1, target); + Assert.IsTrue(target.Next()); + AssertToken.IsProperty("value", payload, 14, 8, target); + Assert.IsTrue(target.Next()); + AssertToken.IsValue(2L, payload, 22, 1, target); + Assert.IsTrue(target.Next()); + AssertToken.Matches(JsonTokenType.EndProperty, payload, 14, 9, target); + Assert.IsTrue(target.Next()); + AssertToken.Matches(JsonTokenType.EndObject, payload, 13, 11, target); + Assert.IsTrue(target.Next()); + AssertToken.Matches(JsonTokenType.EndArray, payload, 0, 25, target); + AssertToken.IsComplete(payload, target); + } + + /// + /// Next parses object nested in property. + /// + [TestMethod] + public void JsonParser_Next_Parses_Object_Nested_In_Property() + { + string payload = "{\"first\":{\"value\":1},\"second\":{\"value\":2}}"; + JsonParser target = new JsonParser(payload); + Assert.IsTrue(target.Next()); + AssertToken.Matches(JsonTokenType.BeginObject, payload, 0, 1, target); + Assert.IsTrue(target.Next()); + AssertToken.IsProperty("first", payload, 1, 8, target); + Assert.IsTrue(target.Next()); + AssertToken.Matches(JsonTokenType.BeginObject, payload, 9, 1, target); + Assert.IsTrue(target.Next()); + AssertToken.IsProperty("value", payload, 10, 8, target); + Assert.IsTrue(target.Next()); + AssertToken.IsValue(1L, payload, 18, 1, target); + Assert.IsTrue(target.Next()); + AssertToken.Matches(JsonTokenType.EndProperty, payload, 10, 9, target); + Assert.IsTrue(target.Next()); + AssertToken.Matches(JsonTokenType.EndObject, payload, 9, 11, target); + Assert.IsTrue(target.Next()); + AssertToken.Matches(JsonTokenType.EndProperty, payload, 1, 19, target); + Assert.IsTrue(target.Next()); + AssertToken.IsProperty("second", payload, 21, 9, target); + Assert.IsTrue(target.Next()); + AssertToken.Matches(JsonTokenType.BeginObject, payload, 30, 1, target); + Assert.IsTrue(target.Next()); + AssertToken.IsProperty("value", payload, 31, 8, target); + Assert.IsTrue(target.Next()); + AssertToken.IsValue(2L, payload, 39, 1, target); + Assert.IsTrue(target.Next()); + AssertToken.Matches(JsonTokenType.EndProperty, payload, 31, 9, target); + Assert.IsTrue(target.Next()); + AssertToken.Matches(JsonTokenType.EndObject, payload, 30, 11, target); + Assert.IsTrue(target.Next()); + AssertToken.Matches(JsonTokenType.EndProperty, payload, 21, 20, target); + Assert.IsTrue(target.Next()); + AssertToken.Matches(JsonTokenType.EndObject, payload, 0, 42, target); + AssertToken.IsComplete(payload, target); + } + + /// + /// Next halts on empty element. + /// + [TestMethod] + public void JsonParser_Next_Halts_On_Empty_Element() + { + string payload = "{\"array\":[0z0]}"; + JsonParser target = new JsonParser(payload); + Assert.IsTrue(target.Next()); + AssertToken.Matches(JsonTokenType.BeginObject, payload, 0, 1, target); + Assert.IsTrue(target.Next()); + AssertToken.IsProperty("array", payload, 1, 8, target); + Assert.IsTrue(target.Next()); + AssertToken.Matches(JsonTokenType.BeginArray, payload, 9, 1, target); + Assert.IsTrue(target.Next()); + AssertToken.IsValue(0L, payload, 10, 1, target); + Assert.IsFalse(target.Next()); + AssertToken.Matches(JsonTokenType.Invalid, payload, 11, 0, target); + } + + /// + /// Skip moves over containers. + /// + [TestMethod] + public void JsonParser_Skip_Moves_Over_Containers() + { + string payload = "{\"first\":{\"value\":1},\"second\":[1,2],\"third\":123.45}"; + JsonParser target = new JsonParser(payload); + Assert.IsTrue(target.Next()); + AssertToken.Matches(JsonTokenType.BeginObject, payload, 0, 1, target); + Assert.IsTrue(target.Next()); + AssertToken.IsProperty("first", payload, 1, 8, target); + Assert.IsTrue(target.Next()); + AssertToken.Matches(JsonTokenType.BeginObject, payload, 9, 1, target); + Assert.IsTrue(target.Skip()); + AssertToken.Matches(JsonTokenType.EndObject, payload, 9, 11, target); + Assert.IsTrue(target.Next()); + AssertToken.Matches(JsonTokenType.EndProperty, payload, 1, 19, target); + Assert.IsTrue(target.Next()); + AssertToken.IsProperty("second", payload, 21, 9, target); + Assert.IsTrue(target.Next()); + AssertToken.Matches(JsonTokenType.BeginArray, payload, 30, 1, target); + Assert.IsTrue(target.Next()); + AssertToken.IsValue(1, payload, 31, 1, target); + Assert.IsTrue(target.Skip()); + AssertToken.Matches(JsonTokenType.EndArray, payload, 30, 5, target); + Assert.IsTrue(target.Next()); + AssertToken.Matches(JsonTokenType.EndProperty, payload, 21, 14, target); + Assert.IsTrue(target.Next()); + AssertToken.IsProperty("third", payload, 36, 8, target); + Assert.IsTrue(target.Skip()); + AssertToken.Matches(JsonTokenType.EndProperty, payload, 36, 14, target); + Assert.IsTrue(target.Next()); + AssertToken.Matches(JsonTokenType.EndObject, payload, 0, 51, target); + AssertToken.IsComplete(payload, target); + } + + /// + /// Next parses null property. + /// + [TestMethod] + public void JsonParser_Next_Parses_Null_Property() + { + string payload = "{\"isNull\":null}"; + JsonParser target = new JsonParser(payload); + Assert.IsTrue(target.Next()); + AssertToken.Matches(JsonTokenType.BeginObject, payload, 0, 1, target); + Assert.IsTrue(target.Next()); + AssertToken.IsProperty("isNull", payload, 1, 9, target); + Assert.IsTrue(target.Next()); + AssertToken.Matches(JsonTokenType.Null, payload, 10, 4, target); + Assert.IsTrue(target.Next()); + AssertToken.Matches(JsonTokenType.EndProperty, payload, 1, 13, target); + Assert.IsTrue(target.Next()); + AssertToken.Matches(JsonTokenType.EndObject, payload, 0, 15, target); + AssertToken.IsComplete(payload, target); + } + + /// + /// Reset expands buffer. + /// + [TestMethod] + public void JsonParser_Reset_Expands_Buffer() + { + string payload = "{\"text\":\"\\n\"}"; + JsonParser target = new JsonParser(payload); + Assert.IsTrue(target.Next()); + AssertToken.Matches(JsonTokenType.BeginObject, payload, 0, 1, target); + Assert.IsTrue(target.Next()); + AssertToken.IsProperty("text", payload, 1, 7, target); + Assert.IsTrue(target.Next()); + AssertToken.IsValue("\n", payload, 8, 4, target); + Assert.IsTrue(target.Next()); + AssertToken.Matches(JsonTokenType.EndProperty, payload, 1, 11, target); + Assert.IsTrue(target.Next()); + AssertToken.Matches(JsonTokenType.EndObject, payload, 0, 13, target); + AssertToken.IsComplete(payload, target); + StringBuilder builder = new StringBuilder(); + builder.Append("{\"text\":\""); + for (int i = 0; i < 100000; i++) + { + builder.Append("\\n"); + } + + builder.Append("\"}"); + payload = builder.ToString(); + target.Reset(payload); + Assert.IsTrue(target.Next()); + AssertToken.Matches(JsonTokenType.BeginObject, payload, 0, 1, target); + Assert.IsTrue(target.Next()); + AssertToken.IsProperty("text", payload, 1, 7, target); + Assert.IsTrue(target.Next()); + AssertToken.IsValue(new string('\n', 100000), payload, 8, 200002, target); + Assert.IsTrue(target.Next()); + AssertToken.Matches(JsonTokenType.EndProperty, payload, 1, 200009, target); + Assert.IsTrue(target.Next()); + AssertToken.Matches(JsonTokenType.EndObject, payload, 0, 200011, target); + AssertToken.IsComplete(payload, target); + } + + /// + /// TryParseToken parses Boolean. + /// + [TestMethod] + public void JsonParser_TryParseToken_Parses_Boolean() + { + string payload = "true"; + JsonParser target = new JsonParser(payload); + Assert.IsTrue(target.Next()); + AssertToken.IsValue(true, payload, 0, payload.Length, target); + AssertToken.IsComplete(payload, target); + payload = "false"; + target.Reset(payload); + Assert.IsTrue(target.Next()); + AssertToken.IsValue(false, payload, 0, payload.Length, target); + AssertToken.IsComplete(payload, target); + } + + /// + /// TryParseToken parses Int64. + /// + [TestMethod] + public void JsonParser_TryParseToken_Parses_Int64() + { + string payload = long.MaxValue.ToString(CultureInfo.InvariantCulture); + JsonParser target = new JsonParser(payload); + Assert.IsTrue(target.Next()); + AssertToken.IsValue(long.MaxValue, payload, 0, payload.Length, target); + AssertToken.IsComplete(payload, target); + } + + /// + /// TryParseToken parses UInt64. + /// + [TestMethod] + public void JsonParser_TryParseToken_Parses_UInt64() + { + string payload = ulong.MaxValue.ToString(CultureInfo.InvariantCulture); + JsonParser target = new JsonParser(payload); + Assert.IsTrue(target.Next()); + AssertToken.IsValue(ulong.MaxValue, payload, 0, payload.Length, target); + AssertToken.IsComplete(payload, target); + } + + /// + /// TryParseToken parses UInt64. + /// + [TestMethod] + public void JsonParser_TryParseToken_Parses_Hexadecimal_UInt64() + { + string payload = "0x0123456789ABCDEF"; + JsonParser target = new JsonParser(payload); + Assert.IsTrue(target.Next()); + AssertToken.IsValue(81985529216486895UL, payload, 0, payload.Length, target); + AssertToken.IsComplete(payload, target); + } + + /// + /// TryParseToken parses Double. + /// + [TestMethod] + public void JsonParser_TryParseToken_Parses_Double() + { + string payload = double.MinValue.ToString("R", CultureInfo.InvariantCulture); + JsonParser target = new JsonParser(payload); + Assert.IsTrue(target.Next()); + AssertToken.IsValue(double.MinValue, payload, 0, payload.Length, target); + AssertToken.IsComplete(payload, target); + payload = double.Epsilon.ToString("R", CultureInfo.InvariantCulture); + target.Reset(payload); + Assert.IsTrue(target.Next()); + AssertToken.IsValue(double.Epsilon, payload, 0, payload.Length, target); + AssertToken.IsComplete(payload, target); + payload = double.MaxValue.ToString("R", CultureInfo.InvariantCulture); + target.Reset(payload); + Assert.IsTrue(target.Next()); + AssertToken.IsValue(double.MaxValue, payload, 0, payload.Length, target); + AssertToken.IsComplete(payload, target); + } + + /// + /// TryParseToken parses String. + /// + [TestMethod] + public void JsonParser_TryParseToken_Parses_String() + { + string payload = Constants.UnicodeRainbowEncoded; + JsonParser target = new JsonParser(payload); + Assert.IsTrue(target.Next()); + AssertToken.IsValue(Constants.UnicodeRainbowDecoded, payload, 0, payload.Length, target); + AssertToken.IsComplete(payload, target); + } + + /// + /// TryParseToken ignores array white space. + /// + [TestMethod] + public void JsonParser_TryParseToken_Ignores_Array_White_Space() + { + string payload = " [ 1 , 2 ] "; + JsonParser target = new JsonParser(payload); + Assert.IsTrue(target.Next()); + AssertToken.Matches(JsonTokenType.BeginArray, payload, 1, 1, target); + Assert.IsTrue(target.Next()); + AssertToken.IsValue(1, payload, 3, 1, target); + Assert.IsTrue(target.Next()); + AssertToken.IsValue(2, payload, 7, 1, target); + Assert.IsTrue(target.Next()); + AssertToken.Matches(JsonTokenType.EndArray, payload, 1, 9, target); + AssertToken.IsComplete(payload, target); + } + + /// + /// TryParseToken ignores object white space. + /// + [TestMethod] + public void JsonParser_TryParseToken_Ignores_Object_White_Space() + { + string payload = " { \"key\" : \"value\" } "; + JsonParser target = new JsonParser(payload); + Assert.IsTrue(target.Next()); + AssertToken.Matches(JsonTokenType.BeginObject, payload, 1, 1, target); + Assert.IsTrue(target.Next()); + AssertToken.IsProperty("key", payload, 3, 7, target); + Assert.IsTrue(target.Next()); + AssertToken.IsValue("value", payload, 11, 7, target); + Assert.IsTrue(target.Next()); + AssertToken.Matches(JsonTokenType.EndProperty, payload, 3, 15, target); + Assert.IsTrue(target.Next()); + AssertToken.Matches(JsonTokenType.EndObject, payload, 1, 19, target); + AssertToken.IsComplete(payload, target); + } + } +} diff --git a/Microsoft.Shared.Dna.Json.Test/Microsoft.Shared.Dna.Json.Test.csproj b/Microsoft.Shared.Dna.Json.Test/Microsoft.Shared.Dna.Json.Test.csproj new file mode 100644 index 0000000..6dc8d36 --- /dev/null +++ b/Microsoft.Shared.Dna.Json.Test/Microsoft.Shared.Dna.Json.Test.csproj @@ -0,0 +1,118 @@ + + + + Debug + AnyCPU + {453C48C4-AA86-45A1-AEAE-A404EA3E0B94} + Library + Properties + Microsoft.Shared.Dna.Json.Test + Microsoft.Shared.Dna.Json.Test + v4.5 + 512 + {3AC096D0-A1C2-E12C-1390-A8335801FDAB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} + 10.0 + $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) + $(ProgramFiles)\Common Files\microsoft shared\VSTT\$(VisualStudioVersion)\UITestExtensionPackages + False + UnitTest + ..\ + prompt + 4 + true + true + true + ..\Microsoft.Shared.Dna.Test.ruleset + + + true + $(MSBuildProjectDirectory)\..\packages\DotNet.Contracts.1.10.20606.1\ + 1 + true + + + true + full + false + bin\Debug\ + DEBUG;TRACE + + + pdbonly + true + bin\Release\ + TRACE + + + + + + + + + + + + + + + + + + + + Microsoft.Shared.Dna\JsonBuilder.cs + + + Microsoft.Shared.Dna\JsonConstants.cs + + + Microsoft.Shared.Dna\JsonParser.cs + + + Microsoft.Shared.Dna\JsonTokenTypeExtensions.cs + + + Microsoft.Shared.Dna\FixedStringBuilder.cs + + + Microsoft.Shared.Dna\StringSegment.cs + + + + + + + + + Designer + + + + + + + False + + + False + + + False + + + False + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Microsoft.Shared.Dna.Json.Test/packages.config b/Microsoft.Shared.Dna.Json.Test/packages.config new file mode 100644 index 0000000..d16b31d --- /dev/null +++ b/Microsoft.Shared.Dna.Json.Test/packages.config @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/Microsoft.Shared.Dna.Json.sln b/Microsoft.Shared.Dna.Json.sln new file mode 100644 index 0000000..c27cc00 --- /dev/null +++ b/Microsoft.Shared.Dna.Json.sln @@ -0,0 +1,46 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 14 +VisualStudioVersion = 14.0.25420.1 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.Shared.Dna.Json", "Microsoft.Shared.Dna.Json\Microsoft.Shared.Dna.Json.csproj", "{90D59DA0-084B-4722-8F45-D742818F3D9A}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.Shared.Dna.Json.Test", "Microsoft.Shared.Dna.Json.Test\Microsoft.Shared.Dna.Json.Test.csproj", "{453C48C4-AA86-45A1-AEAE-A404EA3E0B94}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.Shared.Dna.Json.Profile", "Microsoft.Shared.Dna.Json.Profile\Microsoft.Shared.Dna.Json.Profile.csproj", "{C338ACCE-9521-435A-9011-2D6DF981920A}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{D5A0AB76-DA28-4961-A2BD-8E0EC7F30138}" + ProjectSection(SolutionItems) = preProject + CONTRIBUTING.md = CONTRIBUTING.md + LICENSE.txt = LICENSE.txt + Microsoft.Shared.Dna.Dictionary.xml = Microsoft.Shared.Dna.Dictionary.xml + Microsoft.Shared.Dna.ruleset = Microsoft.Shared.Dna.ruleset + Microsoft.Shared.Dna.targets = Microsoft.Shared.Dna.targets + Microsoft.Shared.Dna.Test.ruleset = Microsoft.Shared.Dna.Test.ruleset + README.md = README.md + Settings.StyleCop = Settings.StyleCop + EndProjectSection +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {90D59DA0-084B-4722-8F45-D742818F3D9A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {90D59DA0-084B-4722-8F45-D742818F3D9A}.Debug|Any CPU.Build.0 = Debug|Any CPU + {90D59DA0-084B-4722-8F45-D742818F3D9A}.Release|Any CPU.ActiveCfg = Release|Any CPU + {90D59DA0-084B-4722-8F45-D742818F3D9A}.Release|Any CPU.Build.0 = Release|Any CPU + {453C48C4-AA86-45A1-AEAE-A404EA3E0B94}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {453C48C4-AA86-45A1-AEAE-A404EA3E0B94}.Debug|Any CPU.Build.0 = Debug|Any CPU + {453C48C4-AA86-45A1-AEAE-A404EA3E0B94}.Release|Any CPU.ActiveCfg = Release|Any CPU + {453C48C4-AA86-45A1-AEAE-A404EA3E0B94}.Release|Any CPU.Build.0 = Release|Any CPU + {C338ACCE-9521-435A-9011-2D6DF981920A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {C338ACCE-9521-435A-9011-2D6DF981920A}.Debug|Any CPU.Build.0 = Debug|Any CPU + {C338ACCE-9521-435A-9011-2D6DF981920A}.Release|Any CPU.ActiveCfg = Release|Any CPU + {C338ACCE-9521-435A-9011-2D6DF981920A}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/Microsoft.Shared.Dna.Json/JsonBuilder.cs b/Microsoft.Shared.Dna.Json/JsonBuilder.cs new file mode 100644 index 0000000..1dbcf26 --- /dev/null +++ b/Microsoft.Shared.Dna.Json/JsonBuilder.cs @@ -0,0 +1,1187 @@ +//----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. See license file in the project root for full license information. +// +//----------------------------------------------------------------------------- + +namespace Microsoft.Shared.Dna.Json +{ + using System; + using System.Collections.Generic; + using System.Diagnostics.Contracts; + using System.Globalization; + using Microsoft.Shared.Dna.Text; + + /// + /// Builds fixed-capacity JSON strings, truncating the payload when necessary. + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage( + "Microsoft.Performance", + "CA1812:AvoidUninstantiatedInternalClasses", + Justification = "You don't have to use everything in an in-line code share.")] + internal sealed class JsonBuilder + { + /// + /// JSON string buffer. + /// + private FixedStringBuilder builder = null; + + /// + /// JSON token scope. + /// + private Stack scope = null; + + /// + /// Whether or not the output has been truncated. + /// + private bool truncated = false; + + /// + /// Initializes a new instance of the class. + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage( + "Microsoft.Performance", + "CA1811:AvoidUncalledPrivateCode", + Justification = "You don't have to use everything in an in-line code share.")] + public JsonBuilder() + : this(JsonConstants.DefaultCapacity, JsonConstants.DefaultDepth) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// The maximum number of characters the JSON string can be. + /// + /// + /// The initial depth of the token scope stack. The stack will grow depending on how + /// deeply nested the object is. + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage( + "Microsoft.Performance", + "CA1811:AvoidUncalledPrivateCode", + Justification = "You don't have to use everything in an in-line code share.")] + public JsonBuilder(int capacity, int depth) + { +#if CONTRACTS_FULL // Work around the implicit rewrite requirements of Contract.Requires + Contract.Requires(depth > 0); +#endif + if (capacity < JsonConstants.TruncatedObjectLength) + { + capacity = JsonConstants.TruncatedObjectLength; + } + + this.builder = new FixedStringBuilder(capacity); + this.scope = new Stack(depth); + this.scope.Push(JsonTokenType.None); + } + + /// + /// Gets the current token scope. + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage( + "Microsoft.Performance", + "CA1811:AvoidUncalledPrivateCode", + Justification = "You don't have to use everything in an in-line code share.")] + private JsonTokenType Current + { + get + { + return this.scope.Peek(); + } + } + + /// + /// Gets the number of reserve characters required to write the truncate flag and + /// close any open tokens. + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage( + "Microsoft.Performance", + "CA1811:AvoidUncalledPrivateCode", + Justification = "You don't have to use everything in an in-line code share.")] + private int Reserve + { + get + { + return this.scope.Count + JsonConstants.TruncatedObjectLength; + } + } + + /// + /// Clears the content of the builder. The instance can be reused to build a new + /// string after this method returns. + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage( + "Microsoft.Performance", + "CA1811:AvoidUncalledPrivateCode", + Justification = "You don't have to use everything in an in-line code share.")] + public void Clear() + { + this.builder.Clear(); + this.scope.Clear(); + this.scope.Push(JsonTokenType.None); + this.truncated = false; + } + + /// + /// Closes the current token, writing null to an unassigned property and doing + /// nothing if there are no tokens to close. + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage( + "Microsoft.Performance", + "CA1811:AvoidUncalledPrivateCode", + Justification = "You don't have to use everything in an in-line code share.")] + public void CloseToken() + { + if (this.Current == JsonTokenType.None) + { + return; + } + + JsonTokenType container = this.scope.Pop(); + switch (container) + { + case JsonTokenType.BeginArray: + this.builder.TryAppend(JsonConstants.ArrayFooter, 0); + break; + case JsonTokenType.BeginObject: + this.builder.TryAppend(JsonConstants.ObjectFooter, 0); + break; + case JsonTokenType.BeginProperty: + if (this.builder.Last == JsonConstants.NameValueSeparator) + { + this.builder.TryAppend(JsonConstants.NullValue, 0); + } + + break; + } + } + + /// + /// Closes any open tokens and builds the final JSON string. + /// + /// The completed JSON string. + [System.Diagnostics.CodeAnalysis.SuppressMessage( + "Microsoft.Performance", + "CA1811:AvoidUncalledPrivateCode", + Justification = "You don't have to use everything in an in-line code share.")] + public string Finish() + { + while (this.Current != JsonTokenType.None) + { + this.CloseToken(); + } + + return this.builder.ToString(); + } + + /// + /// Opens an array token. + /// + /// + /// A value indicating whether the array was opened or the payload truncated. + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage( + "Microsoft.Performance", + "CA1811:AvoidUncalledPrivateCode", + Justification = "You don't have to use everything in an in-line code share.")] + public bool OpenArray() + { + int reserve = this.Reserve; + int rollback = 0; + if (this.PrepareContainer(reserve, out rollback)) + { + if (this.builder.TryAppend(JsonConstants.ArrayHeader, reserve, rollback)) + { + this.scope.Push(JsonTokenType.BeginArray); + return true; + } + } + + this.Truncate(); + return false; + } + + /// + /// Opens an object token. + /// + /// + /// A value indicating whether the object was opened or the payload truncated. + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage( + "Microsoft.Performance", + "CA1811:AvoidUncalledPrivateCode", + Justification = "You don't have to use everything in an in-line code share.")] + public bool OpenObject() + { + int reserve = this.Reserve; + int rollback = 0; + if (this.PrepareContainer(reserve, out rollback)) + { + if (this.builder.TryAppend(JsonConstants.ObjectHeader, reserve, rollback)) + { + this.scope.Push(JsonTokenType.BeginObject); + return true; + } + } + + this.Truncate(); + return false; + } + + /// + /// Opens a property token. + /// + /// The name of the property. + /// + /// A value indicating whether the property was opened or the payload truncated. + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage( + "Microsoft.Performance", + "CA1811:AvoidUncalledPrivateCode", + Justification = "You don't have to use everything in an in-line code share.")] + public bool OpenProperty(string name) + { +#if CONTRACTS_FULL // Work around the implicit rewrite requirements of Contract.Requires + Contract.Requires(name != null); +#endif + int reserve = this.Reserve; + int rollback = 0; + if (this.PrepareProperty(reserve, out rollback)) + { + if (this.TryEncode(name, reserve, rollback)) + { + if (this.builder.TryAppend(JsonConstants.NameValueSeparator, reserve, rollback)) + { + this.scope.Push(JsonTokenType.BeginProperty); + return true; + } + } + } + + this.Truncate(); + return false; + } + + /// + /// Tries to change the capacity of the JSON builder. + /// + /// + /// The new maximum number of characters that the builder can create a string for. + /// + /// + /// A value indicating whether the builder was resized or the current content and + /// reserve exceeded the new capacity. + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage( + "Microsoft.Performance", + "CA1811:AvoidUncalledPrivateCode", + Justification = "You don't have to use everything in an in-line code share.")] + public bool TryResize(int capacity) + { + if (capacity < this.Reserve) + { + return false; + } + + return this.builder.TryResize(capacity, this.Reserve); + } + + /// + /// Writes a value to the payload. + /// + /// The value to write. + /// + /// A value indicating whether the value was written or the payload truncated. + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage( + "Microsoft.Performance", + "CA1811:AvoidUncalledPrivateCode", + Justification = "You don't have to use everything in an in-line code share.")] + public bool WriteValue(bool value) + { + int reserve = this.Reserve; + int rollback = 0; + if (this.PrepareValue(reserve, out rollback)) + { + if (this.TryEncode(value, reserve, rollback)) + { + return true; + } + } + + this.Truncate(); + return false; + } + + /// + /// Writes a value to the payload. + /// + /// The value to write. + /// + /// A value indicating whether the value was written or the payload truncated. + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage( + "Microsoft.Performance", + "CA1811:AvoidUncalledPrivateCode", + Justification = "You don't have to use everything in an in-line code share.")] + public bool WriteValue(byte value) + { + int reserve = this.Reserve; + int rollback = 0; + if (this.PrepareValue(reserve, out rollback)) + { + if (this.TryEncode(value, reserve, rollback)) + { + return true; + } + } + + this.Truncate(); + return false; + } + + /// + /// Writes a value to the payload. + /// + /// The value to write. + /// + /// A value indicating whether the value was written or the payload truncated. + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage( + "Microsoft.Performance", + "CA1811:AvoidUncalledPrivateCode", + Justification = "You don't have to use everything in an in-line code share.")] + public bool WriteValue(sbyte value) + { + int reserve = this.Reserve; + int rollback = 0; + if (this.PrepareValue(reserve, out rollback)) + { + if (this.TryEncode(value, reserve, rollback)) + { + return true; + } + } + + this.Truncate(); + return false; + } + + /// + /// Writes a value to the payload. + /// + /// The value to write. + /// + /// A value indicating whether the value was written or the payload truncated. + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage( + "Microsoft.Performance", + "CA1811:AvoidUncalledPrivateCode", + Justification = "You don't have to use everything in an in-line code share.")] + public bool WriteValue(short value) + { + int reserve = this.Reserve; + int rollback = 0; + if (this.PrepareValue(reserve, out rollback)) + { + if (this.TryEncode(value, reserve, rollback)) + { + return true; + } + } + + this.Truncate(); + return false; + } + + /// + /// Writes a value to the payload. + /// + /// The value to write. + /// + /// A value indicating whether the value was written or the payload truncated. + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage( + "Microsoft.Performance", + "CA1811:AvoidUncalledPrivateCode", + Justification = "You don't have to use everything in an in-line code share.")] + public bool WriteValue(ushort value) + { + int reserve = this.Reserve; + int rollback = 0; + if (this.PrepareValue(reserve, out rollback)) + { + if (this.TryEncode(value, reserve, rollback)) + { + return true; + } + } + + this.Truncate(); + return false; + } + + /// + /// Writes a value to the payload. + /// + /// The value to write. + /// + /// A value indicating whether the value was written or the payload truncated. + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage( + "Microsoft.Performance", + "CA1811:AvoidUncalledPrivateCode", + Justification = "You don't have to use everything in an in-line code share.")] + public bool WriteValue(int value) + { + int reserve = this.Reserve; + int rollback = 0; + if (this.PrepareValue(reserve, out rollback)) + { + if (this.TryEncode(value, reserve, rollback)) + { + return true; + } + } + + this.Truncate(); + return false; + } + + /// + /// Writes a value to the payload. + /// + /// The value to write. + /// + /// A value indicating whether the value was written or the payload truncated. + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage( + "Microsoft.Performance", + "CA1811:AvoidUncalledPrivateCode", + Justification = "You don't have to use everything in an in-line code share.")] + public bool WriteValue(uint value) + { + int reserve = this.Reserve; + int rollback = 0; + if (this.PrepareValue(reserve, out rollback)) + { + if (this.TryEncode(value, reserve, rollback)) + { + return true; + } + } + + this.Truncate(); + return false; + } + + /// + /// Writes a value to the payload. + /// + /// The value to write. + /// + /// A value indicating whether the value was written or the payload truncated. + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage( + "Microsoft.Performance", + "CA1811:AvoidUncalledPrivateCode", + Justification = "You don't have to use everything in an in-line code share.")] + public bool WriteValue(long value) + { + int reserve = this.Reserve; + int rollback = 0; + if (this.PrepareValue(reserve, out rollback)) + { + if (this.TryEncode(value, reserve, rollback)) + { + return true; + } + } + + this.Truncate(); + return false; + } + + /// + /// Writes a value to the payload. + /// + /// The value to write. + /// + /// A value indicating whether the value was written or the payload truncated. + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage( + "Microsoft.Performance", + "CA1811:AvoidUncalledPrivateCode", + Justification = "You don't have to use everything in an in-line code share.")] + public bool WriteValue(ulong value) + { + int reserve = this.Reserve; + int rollback = 0; + if (this.PrepareValue(reserve, out rollback)) + { + if (this.TryEncode(value, reserve, rollback)) + { + return true; + } + } + + this.Truncate(); + return false; + } + + /// + /// Writes a value to the payload. + /// + /// The value to write. + /// + /// A value indicating whether the value was written or the payload truncated. + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage( + "Microsoft.Performance", + "CA1811:AvoidUncalledPrivateCode", + Justification = "You don't have to use everything in an in-line code share.")] + public bool WriteValue(float value) + { + int reserve = this.Reserve; + int rollback = 0; + if (this.PrepareValue(reserve, out rollback)) + { + if (this.TryEncode(value, reserve, rollback)) + { + return true; + } + } + + this.Truncate(); + return false; + } + + /// + /// Writes a value to the payload. + /// + /// The value to write. + /// + /// A value indicating whether the value was written or the payload truncated. + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage( + "Microsoft.Performance", + "CA1811:AvoidUncalledPrivateCode", + Justification = "You don't have to use everything in an in-line code share.")] + public bool WriteValue(double value) + { + int reserve = this.Reserve; + int rollback = 0; + if (this.PrepareValue(reserve, out rollback)) + { + if (this.TryEncode(value, reserve, rollback)) + { + return true; + } + } + + this.Truncate(); + return false; + } + + /// + /// Writes a value to the payload. + /// + /// The value to write. + /// + /// A value indicating whether the value was written or the payload truncated. + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage( + "Microsoft.Performance", + "CA1811:AvoidUncalledPrivateCode", + Justification = "You don't have to use everything in an in-line code share.")] + public bool WriteValue(decimal value) + { + int reserve = this.Reserve; + int rollback = 0; + if (this.PrepareValue(reserve, out rollback)) + { + if (this.TryEncode(value, reserve, rollback)) + { + return true; + } + } + + this.Truncate(); + return false; + } + + /// + /// Writes a value to the payload. + /// + /// The value to write. + /// + /// A value indicating whether the value was written or the payload truncated. + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage( + "Microsoft.Performance", + "CA1811:AvoidUncalledPrivateCode", + Justification = "You don't have to use everything in an in-line code share.")] + public bool WriteValue(string value) + { + int reserve = this.Reserve; + int rollback = 0; + if (this.PrepareValue(reserve, out rollback)) + { + if (this.TryEncode(value, reserve, rollback)) + { + return true; + } + } + + this.Truncate(); + return false; + } + + /// + /// Prepares the current token for a container write. + /// + /// + /// The amount of capacity that must remain in the builder after the token has been + /// prepared. + /// + /// + /// The length of the builder before the token was prepared. + /// + /// + /// A value indicating whether the token was prepared or the payload should be + /// truncated. + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage( + "Microsoft.Performance", + "CA1811:AvoidUncalledPrivateCode", + Justification = "You don't have to use everything in an in-line code share.")] + private bool PrepareContainer(int reserve, out int rollback) + { + rollback = this.builder.Length; + if (this.truncated) + { + return false; + } + + switch (this.Current) + { + case JsonTokenType.None: + return this.builder.Length == 0; + case JsonTokenType.BeginArray: + if (this.builder.Last != JsonConstants.ArrayHeader) + { + return this.builder.TryAppend(JsonConstants.ElementSeparator, reserve, out rollback); + } + + return true; + case JsonTokenType.BeginObject: + return false; + case JsonTokenType.BeginProperty: + return true; + } + + return false; + } + + /// + /// Prepares the current token for a property write. + /// + /// + /// The amount of capacity that must remain in the builder after the token has been + /// prepared. + /// + /// + /// The length of the builder before the token was prepared. + /// + /// + /// A value indicating whether the token was prepared or the payload should be + /// truncated. + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage( + "Microsoft.Performance", + "CA1811:AvoidUncalledPrivateCode", + Justification = "You don't have to use everything in an in-line code share.")] + private bool PrepareProperty(int reserve, out int rollback) + { + rollback = this.builder.Length; + if (this.truncated) + { + return false; + } + + if (this.Current == JsonTokenType.BeginObject) + { + if (this.builder.Last != JsonConstants.ObjectHeader) + { + return this.builder.TryAppend(JsonConstants.ElementSeparator, reserve, out rollback); + } + + return true; + } + + return false; + } + + /// + /// Prepares the current token for a value write. + /// + /// + /// The amount of capacity that must remain in the builder after the token has been + /// prepared. + /// + /// + /// The length of the builder before the token was prepared. + /// + /// + /// A value indicating whether the token was prepared or the payload should be + /// truncated. + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage( + "Microsoft.Performance", + "CA1811:AvoidUncalledPrivateCode", + Justification = "You don't have to use everything in an in-line code share.")] + private bool PrepareValue(int reserve, out int rollback) + { + rollback = this.builder.Length; + if (this.truncated) + { + return false; + } + + switch (this.Current) + { + case JsonTokenType.None: + return this.builder.Length == 0; + case JsonTokenType.BeginArray: + if (this.builder.Last != JsonConstants.ArrayHeader) + { + return this.builder.TryAppend(JsonConstants.ElementSeparator, reserve, out rollback); + } + + return true; + case JsonTokenType.BeginObject: + return false; + case JsonTokenType.BeginProperty: + return this.builder.Last == JsonConstants.NameValueSeparator; + } + + return false; + } + + /// + /// Writes the truncate flag to the current token and blocks any further updates + /// apart from closing and finishing. + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage( + "Microsoft.Performance", + "CA1811:AvoidUncalledPrivateCode", + Justification = "You don't have to use everything in an in-line code share.")] + private void Truncate() + { + if (this.truncated) + { + return; + } + + switch (this.Current) + { + case JsonTokenType.None: + this.builder.TryAppend(JsonConstants.TruncatedObject, 0); + break; + case JsonTokenType.BeginArray: + if (this.builder.Last != JsonConstants.ArrayHeader) + { + this.builder.TryAppend(JsonConstants.ElementSeparator, 0); + } + + this.builder.TryAppend(JsonConstants.TruncatedObject, 0); + break; + case JsonTokenType.BeginObject: + if (this.builder.Last != JsonConstants.ObjectHeader) + { + this.builder.TryAppend(JsonConstants.ElementSeparator, 0); + } + + this.builder.TryAppend(JsonConstants.TruncatedProperty, 0); + break; + case JsonTokenType.BeginProperty: + if (this.builder.Last == JsonConstants.NameValueSeparator) + { + this.builder.TryAppend(JsonConstants.TruncatedObject, 0); + } + else + { + this.builder.TryAppend(JsonConstants.ElementSeparator, 0); + this.builder.TryAppend(JsonConstants.TruncatedProperty, 0); + } + + break; + } + + this.truncated = true; + } + + /// + /// Tries to encode and write the value into the payload. + /// + /// The value to encode. + /// + /// The amount of capacity that must remain in the builder after the value has been + /// added. + /// + /// + /// The length to set the builder to if the encoding failed. + /// + /// + /// A value indicating whether the value was added or the payload should be + /// truncated. + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage( + "Microsoft.Performance", + "CA1811:AvoidUncalledPrivateCode", + Justification = "You don't have to use everything in an in-line code share.")] + private bool TryEncode(bool value, int reserve, int rollback) + { + if (value) + { + return this.builder.TryAppend(JsonConstants.TrueValue, reserve, rollback); + } + + return this.builder.TryAppend(JsonConstants.FalseValue, reserve, rollback); + } + + /// + /// Tries to encode and write the value into the payload. + /// + /// The value to encode. + /// + /// The amount of capacity that must remain in the builder after the value has been + /// added. + /// + /// + /// The length to set the builder to if the encoding failed. + /// + /// + /// A value indicating whether the value was added or the payload should be + /// truncated. + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage( + "Microsoft.Performance", + "CA1811:AvoidUncalledPrivateCode", + Justification = "You don't have to use everything in an in-line code share.")] + private bool TryEncode(byte value, int reserve, int rollback) + { + return this.builder.TryAppend(value.ToString(CultureInfo.InvariantCulture), reserve, rollback); + } + + /// + /// Tries to encode and write the value into the payload. + /// + /// The value to encode. + /// + /// The amount of capacity that must remain in the builder after the value has been + /// added. + /// + /// + /// The length to set the builder to if the encoding failed. + /// + /// + /// A value indicating whether the value was added or the payload should be + /// truncated. + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage( + "Microsoft.Performance", + "CA1811:AvoidUncalledPrivateCode", + Justification = "You don't have to use everything in an in-line code share.")] + private bool TryEncode(sbyte value, int reserve, int rollback) + { + return this.builder.TryAppend(value.ToString(CultureInfo.InvariantCulture), reserve, rollback); + } + + /// + /// Tries to encode and write the value into the payload. + /// + /// The value to encode. + /// + /// The amount of capacity that must remain in the builder after the value has been + /// added. + /// + /// + /// The length to set the builder to if the encoding failed. + /// + /// + /// A value indicating whether the value was added or the payload should be + /// truncated. + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage( + "Microsoft.Performance", + "CA1811:AvoidUncalledPrivateCode", + Justification = "You don't have to use everything in an in-line code share.")] + private bool TryEncode(short value, int reserve, int rollback) + { + return this.builder.TryAppend(value.ToString(CultureInfo.InvariantCulture), reserve, rollback); + } + + /// + /// Tries to encode and write the value into the payload. + /// + /// The value to encode. + /// + /// The amount of capacity that must remain in the builder after the value has been + /// added. + /// + /// + /// The length to set the builder to if the encoding failed. + /// + /// + /// A value indicating whether the value was added or the payload should be + /// truncated. + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage( + "Microsoft.Performance", + "CA1811:AvoidUncalledPrivateCode", + Justification = "You don't have to use everything in an in-line code share.")] + private bool TryEncode(ushort value, int reserve, int rollback) + { + return this.builder.TryAppend(value.ToString(CultureInfo.InvariantCulture), reserve, rollback); + } + + /// + /// Tries to encode and write the value into the payload. + /// + /// The value to encode. + /// + /// The amount of capacity that must remain in the builder after the value has been + /// added. + /// + /// + /// The length to set the builder to if the encoding failed. + /// + /// + /// A value indicating whether the value was added or the payload should be + /// truncated. + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage( + "Microsoft.Performance", + "CA1811:AvoidUncalledPrivateCode", + Justification = "You don't have to use everything in an in-line code share.")] + private bool TryEncode(int value, int reserve, int rollback) + { + return this.builder.TryAppend(value.ToString(CultureInfo.InvariantCulture), reserve, rollback); + } + + /// + /// Tries to encode and write the value into the payload. + /// + /// The value to encode. + /// + /// The amount of capacity that must remain in the builder after the value has been + /// added. + /// + /// + /// The length to set the builder to if the encoding failed. + /// + /// + /// A value indicating whether the value was added or the payload should be + /// truncated. + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage( + "Microsoft.Performance", + "CA1811:AvoidUncalledPrivateCode", + Justification = "You don't have to use everything in an in-line code share.")] + private bool TryEncode(uint value, int reserve, int rollback) + { + return this.builder.TryAppend(value.ToString(CultureInfo.InvariantCulture), reserve, rollback); + } + + /// + /// Tries to encode and write the value into the payload. + /// + /// The value to encode. + /// + /// The amount of capacity that must remain in the builder after the value has been + /// added. + /// + /// + /// The length to set the builder to if the encoding failed. + /// + /// + /// A value indicating whether the value was added or the payload should be + /// truncated. + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage( + "Microsoft.Performance", + "CA1811:AvoidUncalledPrivateCode", + Justification = "You don't have to use everything in an in-line code share.")] + private bool TryEncode(long value, int reserve, int rollback) + { + return this.builder.TryAppend(value.ToString(CultureInfo.InvariantCulture), reserve, rollback); + } + + /// + /// Tries to encode and write the value into the payload. + /// + /// The value to encode. + /// + /// The amount of capacity that must remain in the builder after the value has been + /// added. + /// + /// + /// The length to set the builder to if the encoding failed. + /// + /// + /// A value indicating whether the value was added or the payload should be + /// truncated. + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage( + "Microsoft.Performance", + "CA1811:AvoidUncalledPrivateCode", + Justification = "You don't have to use everything in an in-line code share.")] + private bool TryEncode(ulong value, int reserve, int rollback) + { + return this.builder.TryAppend(value.ToString(CultureInfo.InvariantCulture), reserve, rollback); + } + + /// + /// Tries to encode and write the value into the payload. + /// + /// The value to encode. + /// + /// The amount of capacity that must remain in the builder after the value has been + /// added. + /// + /// + /// The length to set the builder to if the encoding failed. + /// + /// + /// A value indicating whether the value was added or the payload should be + /// truncated. + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage( + "Microsoft.Performance", + "CA1811:AvoidUncalledPrivateCode", + Justification = "You don't have to use everything in an in-line code share.")] + private bool TryEncode(float value, int reserve, int rollback) + { + return this.builder.TryAppend(value.ToString("R", CultureInfo.InvariantCulture), reserve, rollback); + } + + /// + /// Tries to encode and write the value into the payload. + /// + /// The value to encode. + /// + /// The amount of capacity that must remain in the builder after the value has been + /// added. + /// + /// + /// The length to set the builder to if the encoding failed. + /// + /// + /// A value indicating whether the value was added or the payload should be + /// truncated. + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage( + "Microsoft.Performance", + "CA1811:AvoidUncalledPrivateCode", + Justification = "You don't have to use everything in an in-line code share.")] + private bool TryEncode(double value, int reserve, int rollback) + { + return this.builder.TryAppend(value.ToString("R", CultureInfo.InvariantCulture), reserve, rollback); + } + + /// + /// Tries to encode and write the value into the payload. + /// + /// The value to encode. + /// + /// The amount of capacity that must remain in the builder after the value has been + /// added. + /// + /// + /// The length to set the builder to if the encoding failed. + /// + /// + /// A value indicating whether the value was added or the payload should be + /// truncated. + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage( + "Microsoft.Performance", + "CA1811:AvoidUncalledPrivateCode", + Justification = "You don't have to use everything in an in-line code share.")] + private bool TryEncode(decimal value, int reserve, int rollback) + { + return this.builder.TryAppend(value.ToString(CultureInfo.InvariantCulture), reserve, rollback); + } + + /// + /// Tries to encode and write the value into the payload. + /// + /// The value to encode. + /// + /// The amount of capacity that must remain in the builder after the value has been + /// added. + /// + /// + /// The length to set the builder to if the encoding failed. + /// + /// + /// A value indicating whether the value was added or the payload should be + /// truncated. + /// + /// + /// This method uses character pointer arithmetic to iterate over the string because + /// it is significantly faster than using the string indexer. + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage( + "Microsoft.Performance", + "CA1811:AvoidUncalledPrivateCode", + Justification = "You don't have to use everything in an in-line code share.")] + private unsafe bool TryEncode(string value, int reserve, int rollback) + { + if (value == null) + { + return this.builder.TryAppend(JsonConstants.NullValue, reserve, rollback); + } + + if (!this.builder.TryAppend(JsonConstants.StringEnclosure, reserve, rollback)) + { + return false; + } + + fixed (char* valuePointer = value) + { + int valueLength = value.Length; + for (int i = 0; i < valueLength; i++) + { + char* c = valuePointer + i; + string escaped = null; + int asIndex = *c; + if (asIndex < JsonConstants.EscapeSequencesLength) + { + escaped = JsonConstants.EscapeSequences[asIndex]; + } + + if (escaped == null) + { + if (this.builder.TryAppend(*c, reserve, rollback)) + { + continue; + } + } + else if (this.builder.TryAppend(escaped, reserve, rollback)) + { + continue; + } + + return false; + } + } + + return this.builder.TryAppend(JsonConstants.StringEnclosure, reserve, rollback); + } + } +} diff --git a/Microsoft.Shared.Dna.Json/JsonConstants.cs b/Microsoft.Shared.Dna.Json/JsonConstants.cs new file mode 100644 index 0000000..f9dbcfc --- /dev/null +++ b/Microsoft.Shared.Dna.Json/JsonConstants.cs @@ -0,0 +1,233 @@ +//----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. See license file in the project root for full license information. +// +//----------------------------------------------------------------------------- + +namespace Microsoft.Shared.Dna.Json +{ + /// + /// Constant JSON values used by both and + /// . + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage( + "Microsoft.Performance", + "CA1812:AvoidUninstantiatedInternalClasses", + Justification = "You don't have to use everything in an in-line code share.")] + internal static class JsonConstants + { + /// + /// The array header character. + /// + public const char ArrayHeader = '['; + + /// + /// The array footer character. + /// + public const char ArrayFooter = ']'; + + /// + /// Maximum character length of a boolean value. + /// + public const int BooleanValueLength = 5; + + /// + /// The escape character. + /// + public const char CharacterEscape = '\\'; + + /// + /// The decimal point character. + /// + public const char DecimalPoint = '.'; + + /// + /// The base of a decimal number. + /// + public const byte DecimalRadix = 10; + + /// + /// The default capacity for all internal character buffers. + /// + /// + /// Keep this under the large object limit. + /// + public const int DefaultCapacity = 40000; + + /// + /// The default depth for all internal context stacks. + /// + public const int DefaultDepth = 20; + + /// + /// The element separator character. + /// + public const char ElementSeparator = ','; + + /// + /// The lower-case exponent character. + /// + public const char ExponentLowercase = 'e'; + + /// + /// The upper-case exponent character. + /// + public const char ExponentUppercase = 'E'; + + /// + /// The first character of the false literal. + /// + public const char FalseLeadCharacter = 'f'; + + /// + /// The false literal string. + /// + public const string FalseValue = "false"; + + /// + /// The character length of the false literal string. + /// + public const int FalseValueLength = 5; + + /// + /// The lower-case hexadecimal indicator. + /// + public const char HexLowercase = 'x'; + + /// + /// The base of a hexadecimal number. + /// + public const byte HexRadix = 16; + + /// + /// The upper-case hexadecimal indicator. + /// + public const char HexUppercase = 'X'; + + /// + /// The name-value separator character. + /// + public const char NameValueSeparator = ':'; + + /// + /// The negative sign character. + /// + public const char NegativeSign = '-'; + + /// + /// The first character of the null literal. + /// + public const char NullLeadCharacter = 'n'; + + /// + /// The null literal string. + /// + public const string NullValue = "null"; + + /// + /// The character length of the null literal string. + /// + public const int NullValueLength = 4; + + /// + /// The object header character. + /// + public const char ObjectHeader = '{'; + + /// + /// The object footer character. + /// + public const char ObjectFooter = '}'; + + /// + /// The negative sign character. + /// + public const char PositiveSign = '+'; + + /// + /// The string enclosure character. + /// + public const char StringEnclosure = '"'; + + /// + /// The first character of the true literal. + /// + public const char TrueLeadCharacter = 't'; + + /// + /// The true literal string. + /// + public const string TrueValue = "true"; + + /// + /// The character length of the true literal string. + /// + public const int TrueValueLength = 4; + + /// + /// The truncated flag as an object. + /// + public const string TruncatedObject = "{\"(truncated)\":true}"; + + /// + /// The character length of the truncated flag as an object. + /// + public const int TruncatedObjectLength = 20; + + /// + /// The truncated flag as a property. + /// + public const string TruncatedProperty = "\"(truncated)\":true"; + + /// + /// The character length of the truncated flag as a property. + /// + public const int TruncatedPropertyLength = 18; + + /// + /// The zero character. + /// + public const char Zero = '0'; + + /// + /// The decimal digit lookup table. + /// + public static readonly sbyte[] DecimalDigits = { -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 }; + + /// + /// The element length of the decimal digit lookup table. + /// + public static readonly int DecimalDigitsLength = JsonConstants.DecimalDigits.Length; + + /// + /// The character escape sequences lookup table. + /// + public static readonly string[] EscapeSequences = { "\\u0000", "\\u0001", "\\u0002", "\\u0003", "\\u0004", "\\u0005", "\\u0006", "\\u0007", "\\b", "\\t", "\\n", "\\u000B", "\\f", "\\r", "\\u000E", "\\u000F", "\\u0010", "\\u0011", "\\u0012", "\\u0013", "\\u0014", "\\u0015", "\\u0016", "\\u0017", "\\u0018", "\\u0019", "\\u001A", "\\u001B", "\\u001C", "\\u001D", "\\u001E", "\\u001F", null, null, "\\\"", null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, "\\\\", null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, "\\u007F", "\\u0080", "\\u0081", "\\u0082", "\\u0083", "\\u0084", "\\u0085", "\\u0086", "\\u0087", "\\u0088", "\\u0089", "\\u008A", "\\u008B", "\\u008C", "\\u008D", "\\u008E", "\\u008F", "\\u0090", "\\u0091", "\\u0092", "\\u0093", "\\u0094", "\\u0095", "\\u0096", "\\u0097", "\\u0098", "\\u0099", "\\u009A", "\\u009B", "\\u009C", "\\u009D", "\\u009E", "\\u009F" }; + + /// + /// The element length of the character escape sequences lookup table. + /// + public static readonly int EscapeSequencesLength = JsonConstants.EscapeSequences.Length; + + /// + /// The hexadecimal digit lookup table. + /// + public static readonly sbyte[] HexDigits = { -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, -1, -1, -1, -1, -1, -1, -1, 10, 11, 12, 13, 14, 15, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 10, 11, 12, 13, 14, 15 }; + + /// + /// The element length of the hexadecimal digit lookup table. + /// + public static readonly int HexDigitsLength = JsonConstants.HexDigits.Length; + + /// + /// The character un-escape sequences lookup table. + /// + public static readonly char[] UnescapeSequences = { '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\"', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '/', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\\', '\0', '\0', '\0', '\0', '\0', '\b', '\0', '\0', '\0', '\f', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\n', '\0', '\0', '\0', '\r', '\0', '\t' }; + + /// + /// The element length of the character un-escape sequences lookup table. + /// + public static readonly int UnescapeSequencesLength = JsonConstants.UnescapeSequences.Length; + } +} diff --git a/Microsoft.Shared.Dna.Json/JsonParser.cs b/Microsoft.Shared.Dna.Json/JsonParser.cs new file mode 100644 index 0000000..b3c991d --- /dev/null +++ b/Microsoft.Shared.Dna.Json/JsonParser.cs @@ -0,0 +1,1542 @@ +//----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. See license file in the project root for full license information. +// +//----------------------------------------------------------------------------- + +namespace Microsoft.Shared.Dna.Json +{ + using System; + using System.Collections.Generic; + using System.Diagnostics.Contracts; + using System.Globalization; + using Microsoft.Shared.Dna.Text; + + /// + /// Parses JSON strings in a fast, forward-only manner. + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage( + "Microsoft.Performance", + "CA1812:AvoidUninstantiatedInternalClasses", + Justification = "You don't have to use everything in an in-line code share.")] + internal sealed class JsonParser + { + /// + /// Whether or not to close the token in the next iteration. + /// + private bool close = false; + + /// + /// Whether or not the token needs string escape sequence decoding. + /// + private bool decode = false; + + /// + /// The buffer used for decoding strings. + /// + private FixedStringBuilder decodeBuffer = null; + + /// + /// The payload being parsed. + /// + private string payload = null; + + /// + /// The character length of the payload being parsed. + /// + private int payloadLength = 0; + + /// + /// The current index inside the payload of the parser. + /// + private int position = 0; + + /// + /// JSON token scope. + /// + private Stack scope = null; + + /// + /// Initializes a new instance of the class. + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage( + "Microsoft.Performance", + "CA1811:AvoidUncalledPrivateCode", + Justification = "You don't have to use everything in an in-line code share.")] + public JsonParser() + : this(string.Empty, JsonConstants.DefaultCapacity, JsonConstants.DefaultDepth) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The JSON payload to parse. + [System.Diagnostics.CodeAnalysis.SuppressMessage( + "Microsoft.Performance", + "CA1811:AvoidUncalledPrivateCode", + Justification = "You don't have to use everything in an in-line code share.")] + public JsonParser(string json) + : this(json, JsonConstants.DefaultCapacity, JsonConstants.DefaultDepth) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The JSON payload to parse. + /// + /// The initial capacity for the string decoding buffer. It will always at least + /// cover the size of the payload. + /// + /// + /// The initial depth of the token scope stack. The stack will grow depending on how + /// deeply nested the object is. + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage( + "Microsoft.Performance", + "CA1811:AvoidUncalledPrivateCode", + Justification = "You don't have to use everything in an in-line code share.")] + public JsonParser(string json, int capacity, int depth) + { +#if CONTRACTS_FULL // Work around the implicit rewrite requirements of Contract.Requires + Contract.Requires(depth > 0); +#endif + this.decodeBuffer = new FixedStringBuilder(Math.Max(capacity, json == null ? 0 : json.Length)); + this.scope = new Stack(depth); + this.Reset(json); + } + + /// + /// Gets the delimited string segment of the current token. + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage( + "Microsoft.Performance", + "CA1811:AvoidUncalledPrivateCode", + Justification = "You don't have to use everything in an in-line code share.")] + public StringSegment TokenSegment { get; private set; } + + /// + /// Gets the type of the current token. + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage( + "Microsoft.Performance", + "CA1811:AvoidUncalledPrivateCode", + Justification = "You don't have to use everything in an in-line code share.")] + public JsonTokenType TokenType { get; private set; } + + /// + /// Gets the current token scope. + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage( + "Microsoft.Performance", + "CA1811:AvoidUncalledPrivateCode", + Justification = "You don't have to use everything in an in-line code share.")] + private Container Current + { + get + { + return this.scope.Peek(); + } + } + + /// + /// Advances the parser to the next token. + /// + /// + /// A value indicating whether or not parsing may continue. + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage( + "Microsoft.Performance", + "CA1811:AvoidUncalledPrivateCode", + Justification = "You don't have to use everything in an in-line code share.")] + public bool Next() + { + if (this.TokenType.IsEndOfPayload()) + { + return false; + } + + this.decode = false; + this.NextUnsafe(); + return !this.TokenType.IsEndOfPayload(); + } + + /// + /// Advances the parser straight to the end of the container that it is currently + /// inside of. + /// + /// + /// A value indicating whether or not parsing may continue. + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage( + "Microsoft.Performance", + "CA1811:AvoidUncalledPrivateCode", + Justification = "You don't have to use everything in an in-line code share.")] + public bool Skip() + { + int depth = this.scope.Count; + while (depth <= this.scope.Count) + { + if (!this.Next()) + { + return false; + } + } + + return true; + } + + /// + /// Resets the parser with a new string. The instance can be reused after this method returns. + /// + /// The new JSON payload to parse. + [System.Diagnostics.CodeAnalysis.SuppressMessage( + "Microsoft.Performance", + "CA1811:AvoidUncalledPrivateCode", + Justification = "You don't have to use everything in an in-line code share.")] + public void Reset(string json) + { + this.close = false; + this.decode = false; + this.decodeBuffer.Clear(); + this.payload = json ?? string.Empty; + this.payloadLength = this.payload.Length; + this.decodeBuffer.TryExpand(this.payloadLength); + this.position = 0; + this.scope.Clear(); + this.scope.Push(Container.Root); + this.TokenType = JsonTokenType.None; + this.TokenSegment = new StringSegment(this.payload, 0, 0); + } + + /// + /// Tries to parse the current token. + /// + /// The managed value of the token. + /// + /// A value indicating whether or not the token could be parsed. Failure to parse + /// indicates either malformed JSON or that the token cannot be converted to the + /// value type. + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage( + "Microsoft.Performance", + "CA1811:AvoidUncalledPrivateCode", + Justification = "You don't have to use everything in an in-line code share.")] + public bool TryParseToken(out bool value) + { + value = default(bool); + if ((this.TokenType & JsonTokenType.Boolean) == JsonTokenType.None) + { + return false; + } + + if (this.TokenSegment.Count == JsonConstants.TrueValueLength) + { + if (string.CompareOrdinal(this.TokenSegment.String, this.TokenSegment.Offset, JsonConstants.TrueValue, 0, JsonConstants.TrueValueLength) != 0) + { + return false; + } + + value = true; + } + else if (this.TokenSegment.Count == JsonConstants.FalseValueLength) + { + if (string.CompareOrdinal(this.TokenSegment.String, this.TokenSegment.Offset, JsonConstants.FalseValue, 0, JsonConstants.FalseValueLength) != 0) + { + return false; + } + + value = false; + } + else + { + return false; + } + + return true; + } + + /// + /// Tries to parse the current token. + /// + /// The managed value of the token. + /// + /// A value indicating whether or not the token could be parsed. Failure to parse + /// indicates either malformed JSON or that the token cannot be converted to the + /// value type. + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage( + "Microsoft.Performance", + "CA1811:AvoidUncalledPrivateCode", + Justification = "You don't have to use everything in an in-line code share.")] + public bool TryParseToken(out bool? value) + { + value = null; + if (this.TokenType.IsNull()) + { + return true; + } + + bool actual = default(bool); + if (this.TryParseToken(out actual)) + { + value = actual; + return true; + } + + return false; + } + + /// + /// Tries to parse the current token. + /// + /// The managed value of the token. + /// + /// A value indicating whether or not the token could be parsed. Failure to parse + /// indicates either malformed JSON or that the token cannot be converted to the + /// value type. + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage( + "Microsoft.Performance", + "CA1811:AvoidUncalledPrivateCode", + Justification = "You don't have to use everything in an in-line code share.")] + public bool TryParseToken(out long value) + { + value = default(long); + if (this.TokenType != JsonTokenType.Integer) + { + return false; + } + + return this.TryParseTokenUnsafe(out value); + } + + /// + /// Tries to parse the current token. + /// + /// The managed value of the token. + /// + /// A value indicating whether or not the token could be parsed. Failure to parse + /// indicates either malformed JSON or that the token cannot be converted to the + /// value type. + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage( + "Microsoft.Performance", + "CA1811:AvoidUncalledPrivateCode", + Justification = "You don't have to use everything in an in-line code share.")] + public bool TryParseToken(out long? value) + { + value = null; + if (this.TokenType.IsNull()) + { + return true; + } + + long actual = default(long); + if (this.TryParseToken(out actual)) + { + value = actual; + return true; + } + + return false; + } + + /// + /// Tries to parse the current token. + /// + /// The managed value of the token. + /// + /// A value indicating whether or not the token could be parsed. Failure to parse + /// indicates either malformed JSON or that the token cannot be converted to the + /// value type. + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage( + "Microsoft.Performance", + "CA1811:AvoidUncalledPrivateCode", + Justification = "You don't have to use everything in an in-line code share.")] + public bool TryParseToken(out ulong value) + { + value = default(ulong); + if (this.TokenType != JsonTokenType.Integer) + { + return false; + } + + return this.TryParseTokenUnsafe(out value); + } + + /// + /// Tries to parse the current token. + /// + /// The managed value of the token. + /// + /// A value indicating whether or not the token could be parsed. Failure to parse + /// indicates either malformed JSON or that the token cannot be converted to the + /// value type. + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage( + "Microsoft.Performance", + "CA1811:AvoidUncalledPrivateCode", + Justification = "You don't have to use everything in an in-line code share.")] + public bool TryParseToken(out ulong? value) + { + value = null; + if (this.TokenType.IsNull()) + { + return true; + } + + ulong actual = default(ulong); + if (this.TryParseToken(out actual)) + { + value = actual; + return true; + } + + return false; + } + + /// + /// Tries to parse the current token. + /// + /// The managed value of the token. + /// + /// A value indicating whether or not the token could be parsed. Failure to parse + /// indicates either malformed JSON or that the token cannot be converted to the + /// value type. + /// + /// + /// Punting on this one by allowing it to allocate an ephemeral string to parse. + /// Dealing with floating point rounding errors is just too fragile. + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage( + "Microsoft.Performance", + "CA1811:AvoidUncalledPrivateCode", + Justification = "You don't have to use everything in an in-line code share.")] + public bool TryParseToken(out double value) + { + value = default(double); + if (this.TokenType != JsonTokenType.Float && this.TokenType != JsonTokenType.Integer) + { + return false; + } + + return double.TryParse(this.TokenSegment.ToString(), NumberStyles.Float, CultureInfo.InvariantCulture, out value); + } + + /// + /// Tries to parse the current token. + /// + /// The managed value of the token. + /// + /// A value indicating whether or not the token could be parsed. Failure to parse + /// indicates either malformed JSON or that the token cannot be converted to the + /// value type. + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage( + "Microsoft.Performance", + "CA1811:AvoidUncalledPrivateCode", + Justification = "You don't have to use everything in an in-line code share.")] + public bool TryParseToken(out double? value) + { + value = null; + if (this.TokenType.IsNull()) + { + return true; + } + + double actual = default(double); + if (this.TryParseToken(out actual)) + { + value = actual; + return true; + } + + return false; + } + + /// + /// Tries to parse the current token. + /// + /// The managed value of the token. + /// + /// A value indicating whether or not the token could be parsed. Failure to parse + /// indicates either malformed JSON or that the token cannot be converted to the + /// value type. + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage( + "Microsoft.Performance", + "CA1811:AvoidUncalledPrivateCode", + Justification = "You don't have to use everything in an in-line code share.")] + public bool TryParseToken(out string value) + { + value = null; + if (this.TokenType.IsNull()) + { + return true; + } + + int trim = 0; + if (this.TokenType == JsonTokenType.String) + { + trim = 2; + } + else if (this.TokenType == JsonTokenType.BeginProperty) + { + int cursor = this.TokenSegment.Offset + this.TokenSegment.Count - 2; + trim = cursor - this.EatWhitespaceReverse(cursor) + 3; + } + + if (trim > 0) + { + StringSegment subsegment = new StringSegment(this.TokenSegment.String, this.TokenSegment.Offset + 1, this.TokenSegment.Count - trim); + /* Skip the enclosing quotation marks. */ + if (!this.decode) + { + /* If there are no character escapes, then skip the more complicated decoding logic. */ + value = subsegment.ToString(); + return true; + } + + return this.TryParseTokenUnsafe(subsegment, out value); + } + else + { + value = this.TokenSegment.ToString(); + } + + return true; + } + + /// + /// Tries to convert a character to its decimal equivalent. + /// + /// The character to convert. + /// The decimal value. + /// + /// A value indicating whether or not the character is a decimal digit. + /// + /// + /// Don't use the value of digit if this returns false. + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage( + "Microsoft.Performance", + "CA1811:AvoidUncalledPrivateCode", + Justification = "You don't have to use everything in an in-line code share.")] + private static bool TryConvertDecimal(char character, out byte digit) + { + digit = default(byte); + int asIndex = character; + if (asIndex >= JsonConstants.DecimalDigitsLength) + { + return false; + } + + sbyte quarantine = JsonConstants.DecimalDigits[asIndex]; + if (quarantine < 0) + { + return false; + } + + digit = (byte)quarantine; + return true; + } + + /// + /// Tries to convert a character to its hexadecimal equivalent. + /// + /// The character to convert. + /// The hexadecimal value. + /// + /// A value indicating whether or not the character is a hexadecimal digit. + /// + /// + /// Don't use the value of digit if this returns false. + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage( + "Microsoft.Performance", + "CA1811:AvoidUncalledPrivateCode", + Justification = "You don't have to use everything in an in-line code share.")] + private static bool TryConvertHex(char character, out byte digit) + { + digit = default(byte); + int asIndex = character; + if (asIndex >= JsonConstants.HexDigitsLength) + { + return false; + } + + sbyte quarantine = JsonConstants.HexDigits[asIndex]; + if (quarantine < 0) + { + return false; + } + + digit = (byte)quarantine; + return true; + } + + /// + /// Tries to parse a hexadecimal character escape sequence. + /// + /// A pointer to the payload. + /// + /// The character offset of the beginning of the sequence. + /// + /// The character offset of the end of the sequence. + /// The character code of the escape sequence. + /// + /// A value indicating whether or not the sequence represents a valid unicode + /// character code. + /// + /// + /// This method uses character pointer arithmetic to iterate over the string because + /// it is significantly faster than using the string indexer. + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage( + "Microsoft.Performance", + "CA1811:AvoidUncalledPrivateCode", + Justification = "You don't have to use everything in an in-line code share.")] + private static unsafe bool TryParseHex(char* payloadPointer, int first, int last, out ushort value) + { + value = default(ushort); + ushort accumulator = 0; + byte digit = default(byte); + for (int i = first; i < last; i++) + { + if (JsonParser.TryConvertHex(*(payloadPointer + i), out digit)) + { + try + { + accumulator = checked((ushort)((accumulator * JsonConstants.HexRadix) + digit)); + } + catch (OverflowException) + { + return false; + } + } + else + { + return false; + } + } + + value = accumulator; + return true; + } + + /// + /// Creates the completion token for the parser. + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage( + "Microsoft.Performance", + "CA1811:AvoidUncalledPrivateCode", + Justification = "You don't have to use everything in an in-line code share.")] + private void CreateCompleteToken() + { + this.TokenSegment = new StringSegment(this.payload, 0, this.payloadLength); + this.TokenType = JsonTokenType.Complete; + this.position = this.payloadLength; + this.close = false; + } + + /// + /// Creates an invalid token for the parser. + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage( + "Microsoft.Performance", + "CA1811:AvoidUncalledPrivateCode", + Justification = "You don't have to use everything in an in-line code share.")] + private void CreateInvalidToken() + { + this.TokenSegment = new StringSegment(this.payload, this.position, 0); + this.TokenType = JsonTokenType.Invalid; + this.position = this.payloadLength; + } + + /// + /// Creates a token for the parser. + /// + /// + /// The number of characters in the token from the current position. + /// + /// The type of token being represented. + [System.Diagnostics.CodeAnalysis.SuppressMessage( + "Microsoft.Performance", + "CA1811:AvoidUncalledPrivateCode", + Justification = "You don't have to use everything in an in-line code share.")] + private void CreateToken(int count, JsonTokenType type) + { + if (this.position + count > this.payloadLength) + { + this.CreateInvalidToken(); + } + else + { + this.TokenSegment = new StringSegment(this.payload, this.position, count); + this.TokenType = type; + this.position += count; + } + } + + /// + /// Skips past an element separator in an array or object. + /// + /// A pointer to the payload. + /// + /// This method uses character pointer arithmetic to iterate over the string because + /// it is significantly faster than using the string indexer. + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage( + "Microsoft.Performance", + "CA1811:AvoidUncalledPrivateCode", + Justification = "You don't have to use everything in an in-line code share.")] + private unsafe void EatElementSeparator(char* payloadPointer) + { + if (*(payloadPointer + this.position) == JsonConstants.ElementSeparator) + { + this.position++; + } + } + + /// + /// Skips past any insignificant whitespace in the payload. + /// + /// A pointer to the payload. + /// + /// This method uses character pointer arithmetic to iterate over the string because + /// it is significantly faster than using the string indexer. + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage( + "Microsoft.Performance", + "CA1811:AvoidUncalledPrivateCode", + Justification = "You don't have to use everything in an in-line code share.")] + private unsafe void EatWhitespace(char* payloadPointer) + { + this.EatWhitespace(payloadPointer, ref this.position); + } + + /// + /// Skips past any insignificant whitespace in the payload. + /// + /// A pointer to the payload. + /// + /// The position in the payload before and after the skip. + /// + /// + /// This method uses character pointer arithmetic to iterate over the string because + /// it is significantly faster than using the string indexer. + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage( + "Microsoft.Performance", + "CA1811:AvoidUncalledPrivateCode", + Justification = "You don't have to use everything in an in-line code share.")] + private unsafe void EatWhitespace(char* payloadPointer, ref int cursor) + { + while (cursor < this.payloadLength && char.IsWhiteSpace(*(payloadPointer + cursor))) + { + cursor++; + } + } + + /// + /// Skips past any insignificant whitespace in the payload in reverse. + /// + /// + /// The position in the payload before the skip. + /// + /// + /// The position in the payload after the skip. + /// + /// + /// This method uses character pointer arithmetic to iterate over the string because + /// it is significantly faster than using the string indexer. + /// + private unsafe int EatWhitespaceReverse(int cursor) + { + int result = cursor; + fixed (char* payloadPointer = this.payload) + { + while (result >= 0 && char.IsWhiteSpace(*(payloadPointer + result))) + { + result--; + } + } + + return result; + } + + /// + /// Finds the end of a string token. + /// + /// A pointer to the payload. + /// The position of the end of the string. + /// + /// This method uses character pointer arithmetic to iterate over the string because + /// it is significantly faster than using the string indexer. + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage( + "Microsoft.Performance", + "CA1811:AvoidUncalledPrivateCode", + Justification = "You don't have to use everything in an in-line code share.")] + private unsafe int EndOfString(char* payloadPointer) + { + int result = this.position + 1; + bool escaped = false; + while (result < this.payloadLength) + { + char* c = payloadPointer + result; + result++; + if (escaped) + { + escaped = false; + } + else + { + if (*c == JsonConstants.StringEnclosure) + { + return result; + } + + if (*c == JsonConstants.CharacterEscape) + { + escaped = true; + this.decode = true; + } + } + } + + return -1; + } + + /// + /// Advances the parser to the next token. + /// + /// + /// This method uses character pointer arithmetic to iterate over the string because + /// it is significantly faster than using the string indexer. + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage( + "Microsoft.Performance", + "CA1811:AvoidUncalledPrivateCode", + Justification = "You don't have to use everything in an in-line code share.")] + private unsafe void NextUnsafe() + { + fixed (char* payloadPointer = this.payload) + { + this.EatWhitespace(payloadPointer); + Container current = this.Current; + switch (current.TokenType) + { + case JsonTokenType.BeginArray: + if (this.close) + { + this.ReadEndArray(payloadPointer); + return; + } + + break; + case JsonTokenType.BeginObject: + if (this.close) + { + this.ReadEndObject(payloadPointer); + } + else + { + this.ReadBeginProperty(payloadPointer); + } + + return; + case JsonTokenType.BeginProperty: + if (this.close) + { + this.ReadEndProperty(payloadPointer); + return; + } + + break; + } + + this.ReadToken(payloadPointer); + } + } + + /// + /// Prepares the parser to close out an open token in the next iteration, the + /// content being parsed in the current iteration. + /// + /// A pointer to the payload. + /// + /// This method uses character pointer arithmetic to iterate over the string because + /// it is significantly faster than using the string indexer. + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage( + "Microsoft.Performance", + "CA1811:AvoidUncalledPrivateCode", + Justification = "You don't have to use everything in an in-line code share.")] + private unsafe void PrepareForClose(char* payloadPointer) + { + Container current = this.Current; + this.EatWhitespace(payloadPointer); + if (this.position >= this.payloadLength) + { + if (this.Current.TokenType == JsonTokenType.None) + { + this.close = true; + } + else + { + this.CreateInvalidToken(); + } + + return; + } + + char* c = payloadPointer + this.position; + switch (current.TokenType) + { + case JsonTokenType.BeginArray: + if (*c == JsonConstants.ArrayFooter) + { + this.close = true; + } + + this.EatElementSeparator(payloadPointer); + break; + case JsonTokenType.BeginObject: + if (*c == JsonConstants.ObjectFooter) + { + this.close = true; + } + + this.EatElementSeparator(payloadPointer); + break; + case JsonTokenType.BeginProperty: + this.close = true; + break; + } + } + + /// + /// Reads the beginning of an array. + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage( + "Microsoft.Performance", + "CA1811:AvoidUncalledPrivateCode", + Justification = "You don't have to use everything in an in-line code share.")] + private void ReadBeginArray() + { + this.scope.Push(new Container(JsonTokenType.BeginArray, this.position)); + this.CreateToken(1, JsonTokenType.BeginArray); + } + + /// + /// Reads the beginning of an object. + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage( + "Microsoft.Performance", + "CA1811:AvoidUncalledPrivateCode", + Justification = "You don't have to use everything in an in-line code share.")] + private void ReadBeginObject() + { + this.scope.Push(new Container(JsonTokenType.BeginObject, this.position)); + this.CreateToken(1, JsonTokenType.BeginObject); + } + + /// + /// Reads the beginning of a property. + /// + /// A pointer to the payload. + [System.Diagnostics.CodeAnalysis.SuppressMessage( + "Microsoft.Performance", + "CA1811:AvoidUncalledPrivateCode", + Justification = "You don't have to use everything in an in-line code share.")] + private unsafe void ReadBeginProperty(char* payloadPointer) + { + this.scope.Push(new Container(JsonTokenType.BeginProperty, this.position)); + int cursor = this.EndOfString(payloadPointer); + if (cursor > 0) + { + this.EatWhitespace(payloadPointer, ref cursor); + char* c = payloadPointer + cursor; + if (*c == JsonConstants.NameValueSeparator) + { + cursor++; + this.CreateToken(cursor - this.position, JsonTokenType.BeginProperty); + return; + } + } + + this.CreateInvalidToken(); + } + + /// + /// Reads the end of an array. + /// + /// A pointer to the payload. + /// + /// This method uses character pointer arithmetic to iterate over the string because + /// it is significantly faster than using the string indexer. + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage( + "Microsoft.Performance", + "CA1811:AvoidUncalledPrivateCode", + Justification = "You don't have to use everything in an in-line code share.")] + private unsafe void ReadEndArray(char* payloadPointer) + { + Container current = this.scope.Pop(); + int count = this.position - current.Offset + 1; + this.position = current.Offset; + this.CreateToken(count, JsonTokenType.EndArray); + this.close = false; + this.PrepareForClose(payloadPointer); + } + + /// + /// Reads the end of an object. + /// + /// A pointer to the payload. + /// + /// This method uses character pointer arithmetic to iterate over the string because + /// it is significantly faster than using the string indexer. + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage( + "Microsoft.Performance", + "CA1811:AvoidUncalledPrivateCode", + Justification = "You don't have to use everything in an in-line code share.")] + private unsafe void ReadEndObject(char* payloadPointer) + { + Container current = this.scope.Pop(); + int count = this.position - current.Offset + 1; + this.position = current.Offset; + this.CreateToken(count, JsonTokenType.EndObject); + this.close = false; + this.PrepareForClose(payloadPointer); + } + + /// + /// Reads the end of a property. + /// + /// A pointer to the payload. + /// + /// This method uses character pointer arithmetic to iterate over the string because + /// it is significantly faster than using the string indexer. + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage( + "Microsoft.Performance", + "CA1811:AvoidUncalledPrivateCode", + Justification = "You don't have to use everything in an in-line code share.")] + private unsafe void ReadEndProperty(char* payloadPointer) + { + Container current = this.scope.Pop(); + int count = this.TokenSegment.Offset + this.TokenSegment.Count - current.Offset; + this.position = current.Offset; + this.CreateToken(count, JsonTokenType.EndProperty); + this.close = false; + this.PrepareForClose(payloadPointer); + } + + /// + /// Reads a false literal. + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage( + "Microsoft.Performance", + "CA1811:AvoidUncalledPrivateCode", + Justification = "You don't have to use everything in an in-line code share.")] + private void ReadFalse() + { + this.CreateToken(JsonConstants.FalseValueLength, JsonTokenType.Boolean); + } + + /// + /// Reads a null literal. + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage( + "Microsoft.Performance", + "CA1811:AvoidUncalledPrivateCode", + Justification = "You don't have to use everything in an in-line code share.")] + private void ReadNull() + { + if (string.CompareOrdinal(this.payload, this.position, JsonConstants.NullValue, 0, JsonConstants.NullValueLength) == 0) + { + this.CreateToken(JsonConstants.NullValueLength, JsonTokenType.Null); + } + else + { + this.CreateInvalidToken(); + } + } + + /// + /// Reads a number. + /// + /// A pointer to the payload. + /// + /// This method uses character pointer arithmetic to iterate over the string because + /// it is significantly faster than using the string indexer. + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage( + "Microsoft.Performance", + "CA1811:AvoidUncalledPrivateCode", + Justification = "You don't have to use everything in an in-line code share.")] + private unsafe void ReadNumber(char* payloadPointer) + { + int cursor = this.position; + char* c = payloadPointer + cursor; + if (*c == JsonConstants.NegativeSign) + { + cursor++; + } + + JsonTokenType type = JsonTokenType.Integer; + bool hasDigits = false; + bool isHex = false; + sbyte[] digits = JsonConstants.DecimalDigits; + int digitsLength = JsonConstants.DecimalDigitsLength; + while (cursor < this.payloadLength) + { + c = payloadPointer + cursor; + switch (*c) + { + case JsonConstants.HexLowercase: + case JsonConstants.HexUppercase: + isHex = true; + hasDigits = false; + digits = JsonConstants.HexDigits; + digitsLength = JsonConstants.HexDigitsLength; + break; + case JsonConstants.NegativeSign: + case JsonConstants.PositiveSign: + if (isHex) + { + this.CreateInvalidToken(); + return; + } + + break; + case JsonConstants.DecimalPoint: + if (isHex) + { + this.CreateInvalidToken(); + return; + } + + type = JsonTokenType.Float; + break; + case JsonConstants.ExponentLowercase: + case JsonConstants.ExponentUppercase: + if (!isHex) + { + type = JsonTokenType.Float; + } + + break; + default: + int asIndex = *c; + if (asIndex < digitsLength) + { + sbyte digit = digits[asIndex]; + if (digit >= 0) + { + hasDigits = true; + break; + } + } + + if (hasDigits) + { + this.CreateToken(cursor - this.position, type); + } + else + { + this.CreateInvalidToken(); + } + + return; + } + + cursor++; + } + + if (hasDigits && this.Current.TokenType == JsonTokenType.None) + { + this.CreateToken(this.payloadLength - this.position, type); + } + else + { + this.CreateInvalidToken(); + } + } + + /// + /// Reads a string. + /// + /// A pointer to the payload. + /// + /// This method uses character pointer arithmetic to iterate over the string because + /// it is significantly faster than using the string indexer. + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage( + "Microsoft.Performance", + "CA1811:AvoidUncalledPrivateCode", + Justification = "You don't have to use everything in an in-line code share.")] + private unsafe void ReadString(char* payloadPointer) + { + int cursor = this.EndOfString(payloadPointer); + if (cursor > 0) + { + this.CreateToken(cursor - this.position, JsonTokenType.String); + } + else + { + this.CreateInvalidToken(); + } + } + + /// + /// Reads any token. + /// + /// A pointer to the payload. + /// + /// This method uses character pointer arithmetic to iterate over the string because + /// it is significantly faster than using the string indexer. + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage( + "Microsoft.Performance", + "CA1811:AvoidUncalledPrivateCode", + Justification = "You don't have to use everything in an in-line code share.")] + private unsafe void ReadToken(char* payloadPointer) + { + if (this.close) + { + this.CreateCompleteToken(); + return; + } + + char* c = payloadPointer + this.position; + switch (*c) + { + case JsonConstants.NullLeadCharacter: + this.ReadNull(); + break; + case JsonConstants.FalseLeadCharacter: + this.ReadFalse(); + break; + case JsonConstants.TrueLeadCharacter: + this.ReadTrue(); + break; + case JsonConstants.ArrayHeader: + this.ReadBeginArray(); + break; + case JsonConstants.ObjectHeader: + this.ReadBeginObject(); + break; + case JsonConstants.StringEnclosure: + this.ReadString(payloadPointer); + break; + default: + this.ReadNumber(payloadPointer); + break; + } + + if (this.TokenType != JsonTokenType.Invalid) + { + this.PrepareForClose(payloadPointer); + } + } + + /// + /// Reads a true literal. + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage( + "Microsoft.Performance", + "CA1811:AvoidUncalledPrivateCode", + Justification = "You don't have to use everything in an in-line code share.")] + private void ReadTrue() + { + this.CreateToken(JsonConstants.TrueValueLength, JsonTokenType.Boolean); + } + + /// + /// Tries to parse the current token. + /// + /// The managed value of the token. + /// + /// A value indicating whether or not the token could be parsed. Failure to parse + /// indicates either malformed JSON or that the token cannot be converted to the + /// value type. + /// + private unsafe bool TryParseTokenUnsafe(out long value) + { + value = default(long); + long accumulator = 0L; + int first = this.TokenSegment.Offset; + int last = first + this.TokenSegment.Count; + byte digit = default(byte); + long sign = 1; + fixed (char* payloadPointer = this.TokenSegment.String) + { + char* c = payloadPointer + first; + if (*c == JsonConstants.NegativeSign) + { + sign = -1; + first++; + } + + for (int i = first; i < last; i++) + { + c = payloadPointer + i; + if (JsonParser.TryConvertDecimal(*c, out digit)) + { + try + { + accumulator = checked((accumulator * JsonConstants.DecimalRadix) + digit); + } + catch (OverflowException) + { + return false; + } + } + else + { + return false; + } + } + } + + try + { + accumulator = sign * accumulator; + } + catch (OverflowException) + { + return false; + } + + value = accumulator; + return true; + } + + /// + /// Tries to parse the current token. + /// + /// The managed value of the token. + /// + /// A value indicating whether or not the token could be parsed. Failure to parse + /// indicates either malformed JSON or that the token cannot be converted to the + /// value type. + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage( + "Microsoft.Performance", + "CA1811:AvoidUncalledPrivateCode", + Justification = "You don't have to use everything in an in-line code share.")] + private unsafe bool TryParseTokenUnsafe(out ulong value) + { + value = default(ulong); + ulong accumulator = 0UL; + int first = this.TokenSegment.Offset; + int last = first + this.TokenSegment.Count; + byte digit = default(byte); + bool isHex = false; + fixed (char* payloadPointer = this.TokenSegment.String) + { + char* c = payloadPointer + first; + if (first + 2 < last && *c == JsonConstants.Zero) + { + first++; + c = payloadPointer + first; + isHex = *c == JsonConstants.HexLowercase || *c == JsonConstants.HexUppercase; + } + + if (isHex) + { + first++; + for (int i = first; i < last; i++) + { + c = payloadPointer + i; + if (JsonParser.TryConvertHex(*c, out digit)) + { + try + { + accumulator = checked((accumulator * JsonConstants.HexRadix) + digit); + } + catch (OverflowException) + { + return false; + } + } + else + { + return false; + } + } + } + else + { + for (int i = first; i < last; i++) + { + c = payloadPointer + i; + if (JsonParser.TryConvertDecimal(*c, out digit)) + { + try + { + accumulator = checked((accumulator * JsonConstants.DecimalRadix) + digit); + } + catch (OverflowException) + { + return false; + } + } + else + { + return false; + } + } + } + } + + value = accumulator; + return true; + } + + /// + /// Tries to parse the current token. + /// + /// + /// The string body without the enclosing quotation marks. + /// + /// The managed value of the token. + /// + /// A value indicating whether or not the token could be parsed. Failure to parse + /// indicates either malformed JSON or that the token cannot be converted to the + /// value type. + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage( + "Microsoft.Performance", + "CA1811:AvoidUncalledPrivateCode", + Justification = "You don't have to use everything in an in-line code share.")] + private unsafe bool TryParseTokenUnsafe(StringSegment subsegment, out string value) + { + value = null; + int endOfSegment = subsegment.Offset + subsegment.Count; + int cursor = subsegment.Offset; + this.decodeBuffer.Clear(); + fixed (char* payloadPointer = subsegment.String) + { + while (cursor < endOfSegment) + { + char* c = payloadPointer + cursor; + if (*c == JsonConstants.StringEnclosure) + { + break; + } + + if (*c != JsonConstants.CharacterEscape) + { + if (this.decodeBuffer.TryAppend(*c, 0)) + { + cursor++; + continue; + } + + return false; + } + + if (++cursor >= endOfSegment) + { + return false; + } + + c = payloadPointer + cursor; + int asIndex = *c; + if (asIndex > JsonConstants.UnescapeSequencesLength) + { + return false; + } + + if (asIndex == JsonConstants.UnescapeSequencesLength) + { + int start = cursor + 1; + int jump = start + 4; + if (jump > endOfSegment) + { + return false; + } + + ushort code = 0; + if (JsonParser.TryParseHex(payloadPointer, start, jump, out code)) + { + if (this.decodeBuffer.TryAppend((char)code, 0)) + { + cursor = jump; + continue; + } + + return false; + } + + return false; + } + + char u = JsonConstants.UnescapeSequences[asIndex]; + if (this.decodeBuffer.TryAppend(u, 0)) + { + cursor++; + continue; + } + + return false; + } + + value = this.decodeBuffer.ToString(); + } + + return true; + } + + /// + /// Parsing container scope state. + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage( + "Microsoft.Performance", + "CA1812:AvoidUninstantiatedInternalClasses", + Justification = "You don't have to use everything in an in-line code share.")] + private struct Container + { + /// + /// The root container. + /// + public static readonly Container Root = new Container(JsonTokenType.None, 0); + + /// + /// The offset of the beginning of the container in the payload. + /// + public readonly int Offset; + + /// + /// The container token type. + /// + public readonly JsonTokenType TokenType; + + /// + /// Initializes a new instance of the struct. + /// + /// The container token type. + /// + /// The offset of the beginning of the container in the payload. + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage( + "Microsoft.Performance", + "CA1811:AvoidUncalledPrivateCode", + Justification = "You don't have to use everything in an in-line code share.")] + public Container(JsonTokenType type, int offset) + { + this.TokenType = type; + this.Offset = offset; + } + } + } +} diff --git a/Microsoft.Shared.Dna.Json/JsonTokenTypeExtensions.cs b/Microsoft.Shared.Dna.Json/JsonTokenTypeExtensions.cs new file mode 100644 index 0000000..865aaaa --- /dev/null +++ b/Microsoft.Shared.Dna.Json/JsonTokenTypeExtensions.cs @@ -0,0 +1,221 @@ +//----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. See license file in the project root for full license information. +// +//----------------------------------------------------------------------------- + +namespace Microsoft.Shared.Dna.Json +{ + using System; + + /// + /// Represents the various JSON token types that the parser can encounter. + /// + [Flags] + internal enum JsonTokenType : short + { + /// + /// No token encountered yet. + /// + None = 0, + + /// + /// The beginning of an array. + /// + BeginArray = 1, + + /// + /// The end of an array. + /// + EndArray = 2, + + /// + /// The beginning of an object. + /// + BeginObject = 4, + + /// + /// The end of an object. + /// + EndObject = 8, + + /// + /// The beginning of a property. + /// + BeginProperty = 16, + + /// + /// The end of a property. + /// + EndProperty = 32, + + /// + /// A null literal. + /// + Null = 64, + + /// + /// A boolean literal - true or false. + /// + Boolean = 128, + + /// + /// An integer value. + /// + Integer = 256, + + /// + /// A floating-point value. + /// + Float = 512, + + /// + /// A string value. + /// + String = 1024, + + /// + /// Parsing reached the end of the string with no errors. + /// + Complete = 2048, + + /// + /// Parsing encountered a malformed token in the string. + /// + Invalid = 4096, + + /// + /// Any open container type - arrays, objects or properties. + /// + OpenContainer = BeginArray | BeginObject | BeginProperty, + + /// + /// Any closed container type - arrays, objects or properties. + /// + ClosedContainer = EndArray | EndObject | EndProperty, + + /// + /// Any container type - arrays, objects or properties. + /// + Container = BeginArray | EndArray | BeginObject | EndObject | BeginProperty | EndProperty, + + /// + /// Any numeric type - integers or floating-point values. + /// + Number = Integer | Float, + + /// + /// Any value type - nulls, booleans, numbers, or strings. + /// + Value = Null | Boolean | Integer | Float | String, + + /// + /// Parsing is complete - either because the end of the string was reached or an + /// invalid token was encountered. + /// + EndOfPayload = Complete | Invalid + } + + /// + /// Extension methods for . + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage( + "Microsoft.Performance", + "CA1812:AvoidUninstantiatedInternalClasses", + Justification = "You don't have to use everything in an in-line code share.")] + internal static class JsonTokenTypeExtensions + { + /// + /// Determines if a token type is a closed container. + /// + /// The token to check. + /// + /// A value indicating whether or not the token is a closed container. + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage( + "Microsoft.Performance", + "CA1811:AvoidUncalledPrivateCode", + Justification = "You don't have to use everything in an in-line code share.")] + public static bool IsClosedContainer(this JsonTokenType target) + { + return (target & JsonTokenType.ClosedContainer) != JsonTokenType.None; + } + + /// + /// Determines if a token type is a container or not. + /// + /// The token to check. + /// A value indicating whether or not the token is a container. + [System.Diagnostics.CodeAnalysis.SuppressMessage( + "Microsoft.Performance", + "CA1811:AvoidUncalledPrivateCode", + Justification = "You don't have to use everything in an in-line code share.")] + public static bool IsContainer(this JsonTokenType target) + { + return (target & JsonTokenType.Container) != JsonTokenType.None; + } + + /// + /// Determines if a token is at the end of the payload. + /// + /// The token to check. + /// + /// A value indicating whether or not the token is at the end of the payload. + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage( + "Microsoft.Performance", + "CA1811:AvoidUncalledPrivateCode", + Justification = "You don't have to use everything in an in-line code share.")] + public static bool IsEndOfPayload(this JsonTokenType target) + { + return (target & JsonTokenType.EndOfPayload) != JsonTokenType.None; + } + + /// + /// Determines if a token type is a null literal. + /// + /// The token to check. + /// + /// A value indicating whether or not the token is a null literal. + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage( + "Microsoft.Performance", + "CA1811:AvoidUncalledPrivateCode", + Justification = "You don't have to use everything in an in-line code share.")] + public static bool IsNull(this JsonTokenType target) + { + return target == JsonTokenType.Null; + } + + /// + /// Determines if a token type is an open container. + /// + /// The token to check. + /// + /// A value indicating whether or not the token is an open container. + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage( + "Microsoft.Performance", + "CA1811:AvoidUncalledPrivateCode", + Justification = "You don't have to use everything in an in-line code share.")] + public static bool IsOpenContainer(this JsonTokenType target) + { + return (target & JsonTokenType.OpenContainer) != JsonTokenType.None; + } + + /// + /// Determines if a token type is a value or not. + /// + /// The token to check. + /// A value indicating whether or not the token is a value. + [System.Diagnostics.CodeAnalysis.SuppressMessage( + "Microsoft.Performance", + "CA1811:AvoidUncalledPrivateCode", + Justification = "You don't have to use everything in an in-line code share.")] + public static bool IsValue(this JsonTokenType target) + { + return (target & JsonTokenType.Value) != JsonTokenType.None; + } + } +} diff --git a/Microsoft.Shared.Dna.Json/Microsoft.Shared.Dna.Json.csproj b/Microsoft.Shared.Dna.Json/Microsoft.Shared.Dna.Json.csproj new file mode 100644 index 0000000..13a8d12 --- /dev/null +++ b/Microsoft.Shared.Dna.Json/Microsoft.Shared.Dna.Json.csproj @@ -0,0 +1,66 @@ + + + + + Debug + AnyCPU + {90D59DA0-084B-4722-8F45-D742818F3D9A} + Library + Properties + Microsoft.Shared.Dna.Json + Microsoft.Shared.Dna.Json + v4.0 + 512 + ..\ + true + true + ..\Microsoft.Shared.Dna.ruleset + + + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + true + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + true + + + + + + + + + + + + + + + + + + Designer + + + + + + + + + + \ No newline at end of file diff --git a/Microsoft.Shared.Dna.Json/Microsoft.Shared.Dna.Json.nuspec b/Microsoft.Shared.Dna.Json/Microsoft.Shared.Dna.Json.nuspec new file mode 100644 index 0000000..bb36c29 --- /dev/null +++ b/Microsoft.Shared.Dna.Json/Microsoft.Shared.Dna.Json.nuspec @@ -0,0 +1,30 @@ + + + + Microsoft.Shared.Dna.Json + 2.0.0 + Windows Data and Analytics Shared Code - JSON Processing + Microsoft,microsoft-shared-dna + Microsoft,microsoft-shared-dna + Are you doing simple read and write tasks with JSON.NET (or even System.Runtime.Serialization.Json)? Do you want to partially process your JSON payload? Are you running it in a tight loop? Are you wondering why there's so much pressure on the garbage collector? Do you want something even faster? If so, then this package might be right for you. + Windows Data and Analytics shared code for reading and writing JSON in big data applications. + https://github.com/Microsoft/Microsoft.Shared.Dna.Json + https://github.com/Microsoft/Microsoft.Shared.Dna.Json/blob/master/LICENSE.txt + Microsoft Corporation + true + microsoft telemetry map-reduce big data json + true + + + + + + + + + + + + + + diff --git a/Microsoft.Shared.Dna.Json/Microsoft.Shared.Dna/FixedStringBuilder.cs b/Microsoft.Shared.Dna.Json/Microsoft.Shared.Dna/FixedStringBuilder.cs new file mode 100644 index 0000000..17d5acd --- /dev/null +++ b/Microsoft.Shared.Dna.Json/Microsoft.Shared.Dna/FixedStringBuilder.cs @@ -0,0 +1,442 @@ +//----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. See license file in the project root for full license information. +// +//----------------------------------------------------------------------------- + +namespace Microsoft.Shared.Dna.Text +{ + using System; + using System.Diagnostics.Contracts; + + /// + /// Fixed-length string builder. + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage( + "Microsoft.Performance", + "CA1812:AvoidUninstantiatedInternalClasses", + Justification = "You don't have to use everything in an in-line code share.")] + internal sealed class FixedStringBuilder + { + /// + /// Character buffer array. + /// + private char[] buffer = null; + + /// + /// Cached buffer capacity. Always equal to this.buffer.Length. + /// + private int bufferCapacity = 0; + + /// + /// Current length of the characters in the buffer. + /// + private int length = 0; + + /// + /// Initializes a new instance of the class. + /// + /// + /// The maximum number of characters that the builder can create a string for. + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage( + "Microsoft.Performance", + "CA1811:AvoidUncalledPrivateCode", + Justification = "You don't have to use everything in an in-line code share.")] + public FixedStringBuilder(int capacity) + { +#if CONTRACTS_FULL // Work around the implicit rewrite requirements of Contract.Requires + Contract.Requires(capacity >= 0); +#endif + this.buffer = new char[capacity]; + this.bufferCapacity = capacity; + } + + /// + /// Gets the last character written to the builder, or the null character if nothing + /// has been added. + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage( + "Microsoft.Performance", + "CA1811:AvoidUncalledPrivateCode", + Justification = "You don't have to use everything in an in-line code share.")] + public char Last + { + get + { + if (this.length == 0) + { + return char.MinValue; + } + + return this.buffer[this.length - 1]; + } + } + + /// + /// Gets the current number of characters that have been written to the builder. + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage( + "Microsoft.Performance", + "CA1811:AvoidUncalledPrivateCode", + Justification = "You don't have to use everything in an in-line code share.")] + public int Length + { + get { return this.length; } + } + + /// + /// Clears all the characters that have been written to the builder. + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage( + "Microsoft.Performance", + "CA1811:AvoidUncalledPrivateCode", + Justification = "You don't have to use everything in an in-line code share.")] + public void Clear() + { + this.length = 0; + } + + /// + /// Converts all the characters in the builder to a single string. + /// + /// + /// A string containing all the characters currently in the builder. + /// + public override string ToString() + { + return new string(this.buffer, 0, this.length); + } + + /// + /// Tries to append a single character to the builder. + /// + /// The character to append. + /// + /// The amount of capacity that must remain in the builder after the character has + /// been added. + /// + /// A value indicating whether or not the character was added. + /// + /// You'll see a lot of repeated code in the TryAppend methods. This is deliberate. + /// Since these methods are are usually on the hot path, you get a significant + /// performance boost by manually in-lining the code. + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage( + "Microsoft.Performance", + "CA1811:AvoidUncalledPrivateCode", + Justification = "You don't have to use everything in an in-line code share.")] + public bool TryAppend(char value, int reserve) + { +#if CONTRACTS_FULL // Work around the implicit rewrite requirements of Contract.Requires + Contract.Requires(reserve >= 0); +#endif + if (this.length + reserve < this.bufferCapacity) + { + this.buffer[this.length++] = value; + return true; + } + + return false; + } + + /// + /// Tries to append a single character to the builder. + /// + /// The character to append. + /// + /// The amount of capacity that must remain in the builder after the character has + /// been added. + /// + /// + /// The length to set the builder to if the append failed. + /// + /// A value indicating whether or not the character was added. + /// + /// You'll see a lot of repeated code in the TryAppend methods. This is deliberate. + /// Since these methods are are usually on the hot path, you get a significant + /// performance boost by manually in-lining the code. + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage( + "Microsoft.Performance", + "CA1811:AvoidUncalledPrivateCode", + Justification = "You don't have to use everything in an in-line code share.")] + public bool TryAppend(char value, int reserve, int rollback) + { +#if CONTRACTS_FULL // Work around the implicit rewrite requirements of Contract.Requires + Contract.Requires(reserve >= 0); + Contract.Requires(rollback >= 0); +#endif + if (this.length + reserve < this.bufferCapacity) + { + this.buffer[this.length++] = value; + return true; + } + + this.length = rollback; + return false; + } + + /// + /// Tries to append a single character to the builder. + /// + /// The character to append. + /// + /// The amount of capacity that must remain in the builder after the character has + /// been added. + /// + /// + /// The length of the builder before the character was added. + /// + /// A value indicating whether or not the character was added. + /// + /// You'll see a lot of repeated code in the TryAppend methods. This is deliberate. + /// Since these methods are are usually on the hot path, you get a significant + /// performance boost by manually in-lining the code. + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage( + "Microsoft.Performance", + "CA1811:AvoidUncalledPrivateCode", + Justification = "You don't have to use everything in an in-line code share.")] + public bool TryAppend(char value, int reserve, out int rollback) + { +#if CONTRACTS_FULL // Work around the implicit rewrite requirements of Contract.Requires + Contract.Requires(reserve >= 0); +#endif + rollback = this.length; + if (this.length + reserve < this.bufferCapacity) + { + this.buffer[this.length++] = value; + return true; + } + + return false; + } + + /// + /// Tries to append a string to the builder. + /// + /// The string to append. + /// + /// The amount of capacity that must remain in the builder after the string has been + /// added. + /// + /// A value indicating whether or not the string was added. + /// + /// You'll see a lot of repeated code in the TryAppend methods. This is deliberate. + /// Since these methods are are usually on the hot path, you get a significant + /// performance boost by manually in-lining the code. + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage( + "Microsoft.Performance", + "CA1811:AvoidUncalledPrivateCode", + Justification = "You don't have to use everything in an in-line code share.")] + public bool TryAppend(string value, int reserve) + { +#if CONTRACTS_FULL // Work around the implicit rewrite requirements of Contract.Requires + Contract.Requires(reserve >= 0); +#endif + return this.TryAppendUnsafe(value, reserve); + } + + /// + /// Tries to append a string to the builder. + /// + /// The string to append. + /// + /// The amount of capacity that must remain in the builder after the string has been + /// added. + /// + /// + /// The length to set the builder to if the append failed. + /// + /// A value indicating whether or not the string was added. + /// + /// You'll see a lot of repeated code in the TryAppend methods. This is deliberate. + /// Since these methods are are usually on the hot path, you get a significant + /// performance boost by manually in-lining the code. + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage( + "Microsoft.Performance", + "CA1811:AvoidUncalledPrivateCode", + Justification = "You don't have to use everything in an in-line code share.")] + public bool TryAppend(string value, int reserve, int rollback) + { +#if CONTRACTS_FULL // Work around the implicit rewrite requirements of Contract.Requires + Contract.Requires(reserve >= 0); + Contract.Requires(rollback >= 0); +#endif + return this.TryAppendUnsafe(value, reserve, rollback); + } + + /// + /// Tries to expand the capacity of the string builder. + /// + /// + /// The new maximum number of characters that the builder can create a string for. + /// + /// + /// A value indicating whether the builder was expanded or the current capacity was + /// sufficient. + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage( + "Microsoft.Performance", + "CA1811:AvoidUncalledPrivateCode", + Justification = "You don't have to use everything in an in-line code share.")] + public bool TryExpand(int capacity) + { +#if CONTRACTS_FULL // Work around the implicit rewrite requirements of Contract.Requires + Contract.Requires(capacity >= 0); +#endif + if (capacity > this.bufferCapacity) + { + this.Resize(capacity); + return true; + } + + return false; + } + + /// + /// Tries to change the capacity of the string builder. + /// + /// + /// The new maximum number of characters that the builder can create a string for. + /// + /// + /// The amount of capacity that must remain in the builder after the builder has + /// been resized. + /// + /// + /// A value indicating whether the builder was resized or the current content and + /// reserve exceeded the new capacity. + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage( + "Microsoft.Performance", + "CA1811:AvoidUncalledPrivateCode", + Justification = "You don't have to use everything in an in-line code share.")] + public bool TryResize(int capacity, int reserve) + { +#if CONTRACTS_FULL // Work around the implicit rewrite requirements of Contract.Requires + Contract.Requires(capacity >= reserve); + Contract.Requires(reserve >= 0); +#endif + if (capacity - reserve >= this.length) + { + this.Resize(capacity); + return true; + } + + return false; + } + + /// + /// Changes the capacity of the string builder. + /// + /// + /// The new maximum number of characters that the builder can create a string for. + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage( + "Microsoft.Performance", + "CA1811:AvoidUncalledPrivateCode", + Justification = "You don't have to use everything in an in-line code share.")] + private void Resize(int capacity) + { + char[] resized = new char[capacity]; + for (int i = 0; i < this.length; i++) + { + resized[i] = this.buffer[i]; + } + + this.buffer = resized; + this.bufferCapacity = capacity; + } + + /// + /// Tries to append a string to the builder. + /// + /// The string to append. + /// + /// The amount of capacity that must remain in the builder after the string has been + /// added. + /// + /// A value indicating whether or not the string was added. + /// + /// This method uses character pointer arithmetic to iterate over the string because + /// it is significantly faster than using the string indexer. + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage( + "Microsoft.Performance", + "CA1811:AvoidUncalledPrivateCode", + Justification = "You don't have to use everything in an in-line code share.")] + private unsafe bool TryAppendUnsafe(string value, int reserve) + { + if (value == null) + { + return true; + } + + int valueLength = value.Length; + if (this.length + valueLength + reserve <= this.bufferCapacity) + { + fixed (char* valuePointer = value) + { + for (int i = 0; i < valueLength; i++) + { + this.buffer[this.length++] = *(valuePointer + i); + } + } + + return true; + } + + return false; + } + + /// + /// Tries to append a string to the builder. + /// + /// The string to append. + /// + /// The amount of capacity that must remain in the builder after the string has been + /// added. + /// + /// + /// The length to set the builder to if the append failed. + /// + /// A value indicating whether or not the string was added. + /// + /// This method uses character pointer arithmetic to iterate over the string because + /// it is significantly faster than using the string indexer. + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage( + "Microsoft.Performance", + "CA1811:AvoidUncalledPrivateCode", + Justification = "You don't have to use everything in an in-line code share.")] + private unsafe bool TryAppendUnsafe(string value, int reserve, int rollback) + { + if (value == null) + { + return true; + } + + int valueLength = value.Length; + if (this.length + valueLength + reserve <= this.bufferCapacity) + { + fixed (char* valuePointer = value) + { + for (int i = 0; i < valueLength; i++) + { + this.buffer[this.length++] = *(valuePointer + i); + } + } + + return true; + } + + this.length = rollback; + return false; + } + } +} diff --git a/Microsoft.Shared.Dna.Json/Microsoft.Shared.Dna/StringSegment.cs b/Microsoft.Shared.Dna.Json/Microsoft.Shared.Dna/StringSegment.cs new file mode 100644 index 0000000..3e11b2d --- /dev/null +++ b/Microsoft.Shared.Dna.Json/Microsoft.Shared.Dna/StringSegment.cs @@ -0,0 +1,123 @@ +//----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. See license file in the project root for full license information. +// +//----------------------------------------------------------------------------- + +namespace Microsoft.Shared.Dna.Text +{ + using System; + using System.Diagnostics.Contracts; + + /// + /// Delimits a section of a string. + /// + /// + /// It's surprising that this isn't in the .NET framework itself given the existence + /// of . + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage( + "Microsoft.Performance", + "CA1812:AvoidUninstantiatedInternalClasses", + Justification = "You don't have to use everything in an in-line code share.")] + internal struct StringSegment + { + /// + /// The original string containing the substring. + /// + public readonly string String; + + /// + /// The position of the first character in the substring. + /// + public readonly int Offset; + + /// + /// The number of characters in the substring. + /// + public readonly int Count; + + /// + /// Initializes a new instance of the struct. + /// + /// The string to delimit + /// + /// This creates a substring containing the entire string. + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage( + "Microsoft.Performance", + "CA1811:AvoidUncalledPrivateCode", + Justification = "You don't have to use everything in an in-line code share.")] + public StringSegment(string str) + { +#if CONTRACTS_FULL // Work around the implicit rewrite requirements of Contract.Requires + Contract.Requires(str != null); +#endif + this.String = str; + this.Offset = 0; + this.Count = str.Length; + } + + /// + /// Initializes a new instance of the struct. + /// + /// The original string containing the substring. + /// + /// The position of the first character in the substring. + /// + /// + /// The count is set to the remainder of the string. + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage( + "Microsoft.Performance", + "CA1811:AvoidUncalledPrivateCode", + Justification = "You don't have to use everything in an in-line code share.")] + public StringSegment(string str, int offset) + { +#if CONTRACTS_FULL // Work around the implicit rewrite requirements of Contract.Requires + Contract.Requires(str != null); + Contract.Requires(offset >= 0 && (offset == 0 || offset < str.Length)); +#endif + this.String = str; + this.Offset = offset; + this.Count = str.Length - offset; + } + + /// + /// Initializes a new instance of the struct. + /// + /// The original string containing the substring. + /// + /// The position of the first character in the substring. + /// + /// The number of characters in the substring. + [System.Diagnostics.CodeAnalysis.SuppressMessage( + "Microsoft.Performance", + "CA1811:AvoidUncalledPrivateCode", + Justification = "You don't have to use everything in an in-line code share.")] + public StringSegment(string str, int offset, int count) + { +#if CONTRACTS_FULL // Work around the implicit rewrite requirements of Contract.Requires + Contract.Requires(str != null); + Contract.Requires(offset >= 0 && (offset == 0 || offset < str.Length)); + Contract.Requires(count >= 0 && count <= str.Length - offset); +#endif + this.String = str; + this.Offset = offset; + this.Count = count; + } + + /// + /// Creates a substring from the original string. + /// + /// + /// A substring starting at the offset position and having a length equal to the + /// count. + /// + public override string ToString() + { + return this.String.Substring(this.Offset, this.Count); + } + } +} diff --git a/Microsoft.Shared.Dna.Json/packages.config b/Microsoft.Shared.Dna.Json/packages.config new file mode 100644 index 0000000..94f0a3e --- /dev/null +++ b/Microsoft.Shared.Dna.Json/packages.config @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/Microsoft.Shared.Dna.Test.ruleset b/Microsoft.Shared.Dna.Test.ruleset new file mode 100644 index 0000000..edc5aa2 --- /dev/null +++ b/Microsoft.Shared.Dna.Test.ruleset @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Microsoft.Shared.Dna.ruleset b/Microsoft.Shared.Dna.ruleset new file mode 100644 index 0000000..3f48bf0 --- /dev/null +++ b/Microsoft.Shared.Dna.ruleset @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/Microsoft.Shared.Dna.targets b/Microsoft.Shared.Dna.targets new file mode 100644 index 0000000..0f007b7 --- /dev/null +++ b/Microsoft.Shared.Dna.targets @@ -0,0 +1,33 @@ + + + + + false + + + + + + + + + + $(BuildDependsOn);BuildNuSpec; + + + + -NonInteractive + $(MSBuildThisFileDirectory).tools\NuGet.exe + $(MSBuildThisFileDirectory)Drop + $(TargetDir.Trim('\\')) + + + + + $(MSBuildThisFileDirectory).tools\NuGet.exe pack "%(Package.FullPath)" -Properties "Configuration=$(Configuration);Platform=$(Platform)" $(NonInteractiveSwitch) -OutputDirectory "$(PackageOutputDir)" + + + + + + diff --git a/NuGet.config b/NuGet.config new file mode 100644 index 0000000..c88f0fc --- /dev/null +++ b/NuGet.config @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/README.md b/README.md index 0b04138..0c00dfc 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,47 @@ -# Microsoft.Shared.Dna.Json Windows Data and Analytics Shared Code - JSON Processing +======================================================== +Are you doing simple read and write tasks with JSON strings? +Do you want to partially process your JSON payload? +Are you running it in a tight loop? +Are you wondering why there's so much pressure on the garbage collector? +Do you want something even faster? + +If so, then this package might be right for you. + +The Windows team is building it's own high-performance, low-allocation JSON API for processing data in various "big data" systems. We've managed to squeeze some pretty astonishing performance out of our implementation. Since it wasn't tightly coupled to anything either, we thought we'd share the goodness with everyone. + +We're sharing our code as NuGet "recipe" packages. That means that instead of adding a DLL reference to your code, we're adding the source files themselves. Everything we add is internal and therefore scoped only to that assembly. So, you can freely add this to as many projects as you like without fear of conflicts. We do this for a few reasons: + +1. In this day and age, the JIT compiler is usually smart enough to optimize away annything you don't use and codegen overhead isn't typically concerning on beefy server machines. +2. It helps avoid assembly versioning issues when several projects have a dependency on this and each other. +3. It also -- to a lesser degree -- insulates us from framework versioning issues. We try to keep the syntax simple and therefore, in many cases, we're natually compatible all the way back to .NET 3.5. +4. It gives the compiler a chance to inline our code in your project, thus giving us a boost in performance in some cases. +5. Most importantly, it gives you the freedom to tweak and tinker without even necessarily having to wait for a pull request to be approved. Although, you can still do that (and we encourage it). You just don't have to necessarily wait for all the paperwork to go through if you're in a hurry. + +License +------- +This source code and artifacts are released under the terms of the [MIT License](LICENSE.txt). + +How do I install it? +-------------------- +The package is available on [nuget.org](http://www.nuget.org/packages/Microsoft.Shared.Dna.Json). + +How do I build it? +------------------ +If you have Visual Studio 2015 installed, open an MSBuild command prompt and run: + + build.cmd + +This will download the dependencies, compile the code, run unit tests, and package everything. You should end up with a file named something like Microsoft.Shared.Dna.Json.{major}.{minor}.{patch}.nupkg under the Drop folder. + +How can I contribute? +--------------------- +Please refer to [CONTRIBUTING.md](CONTRIBUTING.md). + +Reporting Security Vulnerabilities +---------------------------------- +If you believe you have found a security vulnerability in this project, please follow [these steps](https://technet.microsoft.com/en-us/security/ff852094.aspx) to report it. For more information on how vulnerabilities are disclosed, see [Coordinated Vulnerability Disclosure](https://technet.microsoft.com/en-us/security/dn467923). + +Code of Conduct +--------------- +This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments. diff --git a/Settings.StyleCop b/Settings.StyleCop new file mode 100644 index 0000000..a2506e3 --- /dev/null +++ b/Settings.StyleCop @@ -0,0 +1,20 @@ + + + + Int + + + + + + False + + \.g\.cs$ + \.generated\.cs$ + \.g\.i\.cs$ + TemporaryGeneratedFile_.*\.cs$ + + + + + \ No newline at end of file diff --git a/build.cmd b/build.cmd new file mode 100644 index 0000000..6b3f41f --- /dev/null +++ b/build.cmd @@ -0,0 +1,14 @@ +@ECHO OFF +PUSHD "%~dp0" +IF NOT EXIST .tools CALL init.cmd +.tools\nuget restore +msbuild build.proj /verbosity:n /clp:ShowCommandLine /m:%NUMBER_OF_PROCESSORS% /nr:false /fl /flp:LogFile=MSBuild.log;Verbosity=diag;ShowTimestamp +FOR %%I IN (Debug Release) DO ( + FOR /R %%J IN (bin\%%~I\*.Test.dll) DO ( + vstest.console "%%~J" + ) + FOR /R %%J IN (bin\%%~I\*.Profile.exe) DO ( + vstest.console "%%~J" + ) +) +POPD diff --git a/build.proj b/build.proj new file mode 100644 index 0000000..a03b4ce --- /dev/null +++ b/build.proj @@ -0,0 +1,22 @@ + + + + + Debug + Any CPU + + + Release + Any CPU + + + + + + + + + + + + \ No newline at end of file diff --git a/init.cmd b/init.cmd new file mode 100644 index 0000000..f627df0 --- /dev/null +++ b/init.cmd @@ -0,0 +1,3 @@ +@echo off +powershell -ExecutionPolicy Bypass -Command .\init.ps1 +set PATH=%~dp0.tools;%~dp0.tools\VSS.NuGet.AuthHelper;%PATH% \ No newline at end of file diff --git a/init.ps1 b/init.ps1 new file mode 100644 index 0000000..75940fd --- /dev/null +++ b/init.ps1 @@ -0,0 +1 @@ +. "$PSScriptRoot\scripts\init\Initialize-Environment.ps1" -RepoRoot $PSScriptRoot \ No newline at end of file diff --git a/scripts/init/.version b/scripts/init/.version new file mode 100644 index 0000000..b1e80bb --- /dev/null +++ b/scripts/init/.version @@ -0,0 +1 @@ +0.1.3 diff --git a/scripts/init/Initialize-DownloadLatest.ps1 b/scripts/init/Initialize-DownloadLatest.ps1 new file mode 100644 index 0000000..97cfd61 --- /dev/null +++ b/scripts/init/Initialize-DownloadLatest.ps1 @@ -0,0 +1,86 @@ +Param( + [Parameter(Mandatory=$true)] [string] $outDir, + [Parameter(Mandatory=$true)] [string] $downloadUrl, + [Parameter(Mandatory=$true)] [string] $downloadName, + [Parameter(Mandatory=$false)] [boolean] $unzip = $false +) + +$ErrorActionPreference = "Stop" + +# Grab the VSS.NuGet toolset from VSO +Add-Type -AssemblyName System.IO +Add-Type -AssemblyName System.IO.Compression.FileSystem + +$etagFile = Join-Path $outDir "$downloadName.ETag" +$downloadPath = Join-Path $outDir "$downloadName.download" +$downloadDest = Join-Path $outDir $downloadName +$downloadDestTemp = Join-Path $outDir "$downloadName.tmp" +$headers = @{} + +Write-Host -NoNewLine "Ensuring that $downloadName is up to date..." + +# If the destination folder doesn't exist, delete the ETag file if it exists +if (!(Test-Path -PathType Container $downloadDest) -and (Test-Path -PathType Container $etagFile)) { + Remove-Item -Force $etagFile +} + +if (Test-Path $etagFile) +{ + $headers.Add("If-None-Match", [System.IO.File]::ReadAllText($etagFile)) +} + +try +{ + $response = Invoke-WebRequest -Headers $headers -Uri $downloadUrl -PassThru -OutFile $downloadPath -UseBasicParsing +} +catch [System.Net.WebException] +{ + $response = $_.Exception.Response +} + +if ($response.StatusCode -eq 200) +{ + Unblock-File $downloadPath + [System.IO.File]::WriteAllText($etagFile, $response.Headers["ETag"]) + + if($unzip) + { + # Extract to a temp folder + if (Test-Path -PathType Container $downloadDestTemp) + { + [System.IO.Directory]::Delete($downloadDestTemp, $true) + } + + [System.IO.Compression.ZipFile]::ExtractToDirectory($downloadPath, $downloadDestTemp) + Remove-Item $downloadPath + } + else + { + $downloadDestTemp = $downloadPath; + } + + # Delete and rename to final dest + if (Test-Path -PathType Container $downloadDest) + { + [System.IO.Directory]::Delete($downloadDest, $true) + } + + Move-Item -Force $downloadDestTemp $downloadDest + Write-Host "Updated $downloadName" +} +elseif ($response.StatusCode -eq 304) +{ + Write-Host "Done" +} +else +{ + Write-Host + Write-Warning "Failed to fetch updated NuGet tools from $downloadUrl" + if (!(Test-Path $downloadDest)) { + throw "$downloadName was not found at $downloadDest" + } else { + Write-Warning "$downloadName may be out of date" + } +} + +return $downloadDest \ No newline at end of file diff --git a/scripts/init/Initialize-Environment.ps1 b/scripts/init/Initialize-Environment.ps1 new file mode 100644 index 0000000..3cbb522 --- /dev/null +++ b/scripts/init/Initialize-Environment.ps1 @@ -0,0 +1,32 @@ +param( + [Parameter(Mandatory=$true)] [string] $repoRoot +) + +$ErrorActionPreference = "Stop" + +$scriptsDir = $PSScriptRoot +$customScriptsDir = "$repoRoot\scripts\" +$preInitScript = "$customScriptsDir\PreInit.ps1" +$preRestoreToolsScript = "$customScriptsDir\PreRestoreTools.ps1" +$postInitScript = "$customScriptsDir\PostInit.ps1" + +# Run PreInit.ps1 if it exists +if (Test-Path $preInitScript) { + . $preInitScript -RepoRoot $repoRoot +} + +# Download the NuGet tools if necessary +. "$scriptsDir\Initialize-NuGet.ps1" -RepoRoot $repoRoot + +# Run PreRestoreTools.ps1 if it exists +if (Test-Path $preRestoreToolsScript) { + . $preRestoreToolsScript -RepoRoot $repoRoot +} + +# Restores tool packages (if .nuget\tools\packages.config exists) +. "$scriptsDir\Restore-ToolPackages.ps1" -RepoRoot $repoRoot + +# Run PostInit.ps1 if it exists +if (Test-Path $postInitScript) { + . $postInitScript -RepoRoot $repoRoot +} \ No newline at end of file diff --git a/scripts/init/Initialize-InstallFromNuget.ps1 b/scripts/init/Initialize-InstallFromNuget.ps1 new file mode 100644 index 0000000..17709b4 --- /dev/null +++ b/scripts/init/Initialize-InstallFromNuget.ps1 @@ -0,0 +1,33 @@ +Param( + [Parameter(Mandatory=$true)] [string] $outDir, + [Parameter(Mandatory=$true)] [string] $downloadFeed, + [Parameter(Mandatory=$true)] [string] $packageName, + [Parameter(Mandatory=$true)] [string] $targetFileName +) + +$ErrorActionPreference = "Stop" + +$downloadDest = Join-Path $outDir $targetFileName + +$tempFolder = "$env:temp\Package-$(Get-Date -format 'yyyy-MM-dd_hh-mm-ss')" +New-Item $tempFolder -ItemType Directory + +& "$outDir\nuget.exe" install $packageName -source $downloadFeed -OutputDirectory `"$tempFolder`" -Prerelease -NonInteractive + +$authHelper = Get-ChildItem -Filter $targetFileName -Path $tempFolder -Recurse + +if(-not $authHelper) +{ + Write-Warning "Failed to fetch updated $targetFileName from $downloadFeed" + if (!(Test-Path $downloadDest)) { + throw "$packageName was not found at $downloadFeed" + } else { + Write-Warning "$targetFileName may be out of date" + } +} +else +{ + Copy-Item -Path $authHelper.FullName -Destination $outDir +} + +Remove-Item $tempFolder -Force -Recurse \ No newline at end of file diff --git a/scripts/init/Initialize-NuGet.ps1 b/scripts/init/Initialize-NuGet.ps1 new file mode 100644 index 0000000..2535751 --- /dev/null +++ b/scripts/init/Initialize-NuGet.ps1 @@ -0,0 +1,26 @@ +Param( + [Parameter(Mandatory=$true)] [string] $repoRoot +) + +$ErrorActionPreference = "Stop" + +# Create the .tools directory +New-Item -ItemType Directory -Force -Path "$repoRoot\.tools" | Out-Null +$toolsDir = Join-Path -Resolve $repoRoot ".tools" + +# Ensure nuget.exe is up-to-date +$nugetDownloadName = "nuget.exe" +. "$PSScriptRoot\Initialize-DownloadLatest.ps1" -OutDir $toolsDir -DownloadUrl "https://dist.nuget.org/win-x86-commandline/latest/nuget.exe" -DownloadName $nugetDownloadName -Unzip $false + +# Ensure VSS.NuGet.AuthHelper is up-to-date +$credProviderDownloadFeed = "https://nuget.org/api/v2/" +$credProviderPackageName = "Microsoft.VisualStudio.Services.NuGet.CredentialProvider" +$credProviderExeName = "CredentialProvider.VSS.exe" + +. "$PSScriptRoot\Initialize-InstallFromNuget.ps1" -OutDir $toolsDir -DownloadFeed $credProviderDownloadFeed -PackageName $credProviderPackageName -targetFileName $credProviderExeName + +# Add the tools dir to the path which directly contains NuGet.exe and VSS.NuGet.AuthHelper.exe +if (!($env:Path -like "*$toolsDir;*")) +{ + $env:Path = "$toolsDir;" + $env:Path +} diff --git a/scripts/init/Restore-ToolPackages.ps1 b/scripts/init/Restore-ToolPackages.ps1 new file mode 100644 index 0000000..988d99e --- /dev/null +++ b/scripts/init/Restore-ToolPackages.ps1 @@ -0,0 +1,24 @@ +Param( + [Parameter(Mandatory=$true)] [string] $repoRoot +) + +$ErrorActionPreference = "Stop" + +$toolsPackagesConfigPath = "$repoRoot\.nuget\tools\packages.config" +$packagesDirectory = "$repoRoot\packages" + +# Restore NuGet tools packages, unless we're on a build machine +if((Test-Path env:\BUILD_BUILDNUMBER) -eq $true) +{ + return; +} + +if (Test-Path $toolsPackagesConfigPath) { + Write-Host "Restoring tool packages..." + nuget restore -PackagesDirectory $packagesDirectory $toolsPackagesConfigPath + if ($LASTEXITCODE -ne 0) { + Write-Error "Failed to restore tool packages." + } else { + Write-Host "Restored tool packages" + } +} \ No newline at end of file diff --git a/scripts/init/Update-Environment.ps1 b/scripts/init/Update-Environment.ps1 new file mode 100644 index 0000000..1e0cdb3 --- /dev/null +++ b/scripts/init/Update-Environment.ps1 @@ -0,0 +1,59 @@ +param( + [string] $repoRoot +) + +$ErrorActionPreference = "Stop" + +if (!$repoRoot -or !(Test-Path $repoRoot)) { + Write-Host "The option -repoRoot was not specified or was not found, assuming current working directory" + $repoRoot = (Resolve-Path .\).Path +} + +$packagesDir = "$repoRoot\packages" + +# Prefer the absolute path of the environment's nuget.exe if it exists, otherwise use nuget.exe on the path +$nugetExe = "$repoRoot\.tools\VSS.NuGet\nuget.exe" +if(!(Test-Path $nugetExe)) { + # Check if there's one on the path\ + $nugetExe = (Get-Command -ErrorAction SilentlyContinue -Name nuget).Path + if (! $nugetExe) { + Write-Error "Failed to find nuget.exe. Please ensure a nuget.exe is on the path." + } +} + +# Get current version +$packageName = "Microsoft.VisualStudio.Services.NuGet.Bootstrap" +$versionMarkFilePath = "$repoRoot\scripts\init\.version" +$currentVersion = (Get-Content $versionMarkFilePath) +$nugetSource = "https://nuget.org/api/v2/" +Write-Host "Current version is $currentVersion" + +# Get newest available version +$listOutput = & $nugetExe list -source $nugetSource -NonInteractive $packageName + +if ($LastExitCode -ne 0 -or (! $listOutput)) { + Write-Error "Unable to find $packageName" +} + +$latestLine = $listOutput | Select-String -Pattern $packageName | Select-Object -First 1 +if ((! $latestLine) ) { + Write-Error "Error parsing available versions of $packageName" +} + +$newestVersion = ($latestLine -split ' ')[1] +Write-Host "Newest version is $newestVersion" + +# Note: Doesn't actually check that it's newer. +# Instead of implementing a semver check here, just leave it up to the user to confirm they actually want to "update" +if ($currentVersion -ne $newestVersion) { + $confirmation = Read-Host "Update from $currentVersion to $($newestVersion)? [y/n]" + if ($confirmation -eq "y") { + + # Download the newest version + & $nugetExe install $packageName -Version $newestVersion -OutputDirectory $packagesDir -source $nugetSource + + # Bootstrap + $bootstrapPath = Resolve-Path "$repoRoot\packages\$packageName.$newestVersion\tools\Bootstrap.ps1" + & $bootstrapPath -RepoRoot $repoRoot + } +} \ No newline at end of file