├── .gitignore ├── license.txt ├── readme.md └── src ├── Jsonite.Benchmarks ├── App.config ├── BenchGenericDeserialize.cs ├── Jsonite.Benchmarks.csproj ├── Program.cs ├── Properties │ └── AssemblyInfo.cs ├── packages.config ├── results │ └── JsonResults.xlsx └── test.json ├── Jsonite.Tests ├── App.config ├── Jsonite.Tests.csproj ├── Program.cs ├── Properties │ └── AssemblyInfo.cs ├── TestFiles │ ├── 000-test-error-false1.out.txt │ ├── 000-test-error-false1.txt │ ├── 000-test-error-null1.out.txt │ ├── 000-test-error-null1.txt │ ├── 000-test-error-true1.out.txt │ ├── 000-test-error-true1.txt │ ├── 000-test-error-true2.out.txt │ ├── 000-test-error-true2.txt │ ├── 001-test-error-string1.out.txt │ ├── 001-test-error-string1.txt │ ├── 001-test-error-string2.out.txt │ ├── 001-test-error-string2.txt │ ├── 001-test-error-string3.out.txt │ ├── 001-test-error-string3.txt │ ├── 001-test-error-string4.out.txt │ ├── 001-test-error-string4.txt │ ├── 002-test-error-number1.out.txt │ ├── 002-test-error-number1.txt │ ├── 002-test-error-number2.out.txt │ ├── 002-test-error-number2.txt │ ├── 002-test-error-number3.out.txt │ ├── 002-test-error-number3.txt │ ├── 002-test-error-number4.out.txt │ ├── 002-test-error-number4.txt │ ├── 020-test-error-object1.out.txt │ ├── 020-test-error-object1.txt │ ├── 020-test-error-object2.out.txt │ ├── 020-test-error-object2.txt │ ├── 020-test-error-object3.out.txt │ ├── 020-test-error-object3.txt │ ├── 020-test-error-object4.out.txt │ ├── 020-test-error-object4.txt │ ├── 020-test-error-object5.out.txt │ ├── 020-test-error-object5.txt │ ├── 030-test-error-array1.out.txt │ ├── 030-test-error-array1.txt │ ├── 030-test-error-array2.out.txt │ ├── 030-test-error-array2.txt │ ├── 030-test-error-array3.out.txt │ └── 030-test-error-array3.txt ├── TestParser.cs ├── TextAssert.cs └── packages.config ├── Jsonite ├── Jsonite.cs └── Jsonite.csproj ├── Textamina.Jsonite.sln.DotSettings └── jsonite.sln /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | 4 | # User-specific files 5 | *.suo 6 | *.user 7 | *.userosscache 8 | *.sln.docstates 9 | 10 | # User-specific files (MonoDevelop/Xamarin Studio) 11 | *.userprefs 12 | 13 | # Build results 14 | [Dd]ebug/ 15 | [Dd]ebugPublic/ 16 | [Rr]elease/ 17 | [Rr]eleases/ 18 | x64/ 19 | x86/ 20 | build/ 21 | bld/ 22 | [Bb]in/ 23 | [Oo]bj/ 24 | 25 | # Visual Studio 2015 cache/options directory 26 | .vs/ 27 | # Uncomment if you have tasks that create the project's static files in wwwroot 28 | #wwwroot/ 29 | 30 | # MSTest test Results 31 | [Tt]est[Rr]esult*/ 32 | [Bb]uild[Ll]og.* 33 | 34 | # NUNIT 35 | *.VisualState.xml 36 | TestResult.xml 37 | 38 | # Build Results of an ATL Project 39 | [Dd]ebugPS/ 40 | [Rr]eleasePS/ 41 | dlldata.c 42 | 43 | # DNX 44 | project.lock.json 45 | artifacts/ 46 | 47 | *_i.c 48 | *_p.c 49 | *_i.h 50 | *.ilk 51 | *.meta 52 | *.obj 53 | *.pch 54 | *.pdb 55 | *.pgc 56 | *.pgd 57 | *.rsp 58 | *.sbr 59 | *.tlb 60 | *.tli 61 | *.tlh 62 | *.tmp 63 | *.tmp_proj 64 | *.log 65 | *.vspscc 66 | *.vssscc 67 | .builds 68 | *.pidb 69 | *.svclog 70 | *.scc 71 | 72 | # Chutzpah Test files 73 | _Chutzpah* 74 | 75 | # Visual C++ cache files 76 | ipch/ 77 | *.aps 78 | *.ncb 79 | *.opensdf 80 | *.sdf 81 | *.cachefile 82 | 83 | # Visual Studio profiler 84 | *.psess 85 | *.vsp 86 | *.vspx 87 | 88 | # TFS 2012 Local Workspace 89 | $tf/ 90 | 91 | # Guidance Automation Toolkit 92 | *.gpState 93 | 94 | # ReSharper is a .NET coding add-in 95 | _ReSharper*/ 96 | *.[Rr]e[Ss]harper 97 | *.DotSettings.user 98 | 99 | # JustCode is a .NET coding add-in 100 | .JustCode 101 | 102 | # TeamCity is a build add-in 103 | _TeamCity* 104 | 105 | # DotCover is a Code Coverage Tool 106 | *.dotCover 107 | 108 | # NCrunch 109 | _NCrunch_* 110 | .*crunch*.local.xml 111 | nCrunchTemp_* 112 | 113 | # MightyMoose 114 | *.mm.* 115 | AutoTest.Net/ 116 | 117 | # Web workbench (sass) 118 | .sass-cache/ 119 | 120 | # Installshield output folder 121 | [Ee]xpress/ 122 | 123 | # DocProject is a documentation generator add-in 124 | DocProject/buildhelp/ 125 | DocProject/Help/*.HxT 126 | DocProject/Help/*.HxC 127 | DocProject/Help/*.hhc 128 | DocProject/Help/*.hhk 129 | DocProject/Help/*.hhp 130 | DocProject/Help/Html2 131 | DocProject/Help/html 132 | 133 | # Click-Once directory 134 | publish/ 135 | 136 | # Publish Web Output 137 | *.[Pp]ublish.xml 138 | *.azurePubxml 139 | # TODO: Comment the next line if you want to checkin your web deploy settings 140 | # but database connection strings (with potential passwords) will be unencrypted 141 | *.pubxml 142 | *.publishproj 143 | 144 | # NuGet Packages 145 | *.nupkg 146 | # The packages folder can be ignored because of Package Restore 147 | **/packages/* 148 | # except build/, which is used as an MSBuild target. 149 | !**/packages/build/ 150 | # Uncomment if necessary however generally it will be regenerated when needed 151 | #!**/packages/repositories.config 152 | 153 | # Windows Azure Build Output 154 | csx/ 155 | *.build.csdef 156 | 157 | # Windows Store app package directory 158 | AppPackages/ 159 | 160 | # Visual Studio cache files 161 | # files ending in .cache can be ignored 162 | *.[Cc]ache 163 | # but keep track of directories ending in .cache 164 | !*.[Cc]ache/ 165 | 166 | # Others 167 | ClientBin/ 168 | [Ss]tyle[Cc]op.* 169 | ~$* 170 | *~ 171 | *.dbmdl 172 | *.dbproj.schemaview 173 | *.pfx 174 | *.publishsettings 175 | node_modules/ 176 | orleans.codegen.cs 177 | 178 | # RIA/Silverlight projects 179 | Generated_Code/ 180 | 181 | # Backup & report files from converting an old project file 182 | # to a newer Visual Studio version. Backup files are not needed, 183 | # because we have git ;-) 184 | _UpgradeReport_Files/ 185 | Backup*/ 186 | UpgradeLog*.XML 187 | UpgradeLog*.htm 188 | 189 | # SQL Server files 190 | *.mdf 191 | *.ldf 192 | 193 | # Business Intelligence projects 194 | *.rdl.data 195 | *.bim.layout 196 | *.bim_*.settings 197 | 198 | # Microsoft Fakes 199 | FakesAssemblies/ 200 | 201 | # Node.js Tools for Visual Studio 202 | .ntvs_analysis.dat 203 | 204 | # Visual Studio 6 build log 205 | *.plg 206 | 207 | # Visual Studio 6 workspace options file 208 | *.opt 209 | 210 | # Visual Studio LightSwitch build output 211 | **/*.HTMLClient/GeneratedArtifacts 212 | **/*.DesktopClient/GeneratedArtifacts 213 | **/*.DesktopClient/ModelManifest.xml 214 | **/*.Server/GeneratedArtifacts 215 | **/*.Server/ModelManifest.xml 216 | _Pvt_Extensions 217 | -------------------------------------------------------------------------------- /license.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2016, Alexandre Mutel 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without modification 5 | , are permitted provided that the following conditions are met: 6 | 7 | 1. Redistributions of source code must retain the above copyright notice, this 8 | list of conditions and the following disclaimer. 9 | 10 | 2. Redistributions in binary form must reproduce the above copyright notice, 11 | this list of conditions and the following disclaimer in the documentation 12 | and/or other materials provided with the distribution. 13 | 14 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 15 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 16 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 17 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 18 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 19 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 20 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 21 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 22 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 23 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # jsonite 2 | 3 | Jsonite is a lightweight JSON serializer and deserializer for .NET 4 | 5 | ```C# 6 | var obj = (JsonObject)Json.Deserialize(@"{""name"": ""John"", ""age"": 26}") 7 | ``` 8 | 9 | Jsonite provides the following features: 10 | 11 | - The implementation *should be* [ECMA-404](http://www.ecma-international.org/publications/files/ECMA-ST/ECMA-404.pdf) and [RFC 4627](https://tools.ietf.org/html/rfc4627) compliant. If you find any issues please log an issue! 12 | - Single file serializer/deserializer that can be embedded directly into a project. 13 | - Default implementation serializing/deserializing from/to `JsonObject` / `JsonArray` 14 | - Method `Json.Validate` to validate a json object 15 | - Precise error with line/column when deserializing an invalid json text. 16 | - Very fast and very low GC memory pressure when deserializing/serializing compare to other JSON libraries. 17 | - Simple pluggable API to allow to deserialize/serialize from/to other kinds of .NET objects (through the `JsonReflector` class) 18 | - Default implementation does not use Reflection or Expression to serialize/deserialize to .NET `JsonObject`/`JsonArray`. 19 | 20 | Jsonite is easily embeddable for quickly decoding/encoding JSON without relying on an external Json library. 21 | 22 | ## Usage and Compilation 23 | 24 | As this library is intended to be embedded and compiled directly from your project, we don't provide a nuget package. 25 | 26 | Instead, you can for example use this repository as a git sub-module of your project and reference directly the file [`Jsonite.cs`](https://github.com/xoofx/jsonite/blob/master/src/Jsonite/Jsonite.cs) 27 | 28 | The code is compatible with `PCL .NET 4.5+`, `CoreCLR`, `CoreRT` and `UWP10`. 29 | 30 | ## Limitations 31 | 32 | Jsonite does not provide a deserializer/serializer from/to an arbitrary object graph. Prefers using a more complete solution like Json.NET. 33 | 34 | ## License 35 | This software is released under the [BSD-Clause 2 license](http://opensource.org/licenses/BSD-2-Clause). 36 | 37 | ## Author 38 | 39 | Alexandre Mutel aka [xoofx](http://xoofx.com). 40 | -------------------------------------------------------------------------------- /src/Jsonite.Benchmarks/App.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /src/Jsonite.Benchmarks/BenchGenericDeserialize.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.IO; 3 | using BenchmarkDotNet; 4 | using BenchmarkDotNet.Attributes; 5 | 6 | namespace Jsonite.Benchmarks 7 | { 8 | public class BenchGenericDeserialize 9 | { 10 | private readonly string testJson; 11 | 12 | public BenchGenericDeserialize() 13 | { 14 | testJson = File.ReadAllText("test.json"); 15 | } 16 | 17 | [Benchmark(Description = "Textamina.Jsonite")] 18 | public void TestJsonite() 19 | { 20 | var result = Json.Deserialize(testJson); 21 | } 22 | 23 | [Benchmark(Description = "Newtonsoft.Json")] 24 | public void TestNewtonsoftJson() 25 | { 26 | var result = Newtonsoft.Json.JsonConvert.DeserializeObject>(testJson); 27 | } 28 | 29 | [Benchmark(Description = "System.Text.Json (FastJsonParser)")] 30 | public void TestSystemTextJson() 31 | { 32 | var parser = new System.Text.Json.JsonParser(); 33 | var result = parser.Parse>(testJson); 34 | } 35 | 36 | [Benchmark(Description = "ServiceStack.Text")] 37 | public void TestServiceStackText() 38 | { 39 | // Force ServiceStack.Text to deserialize completely the object (otherwise it is deserializing only the first object level, which is not what we want to test here) 40 | ServiceStack.Text.JsConfig.ConvertObjectTypesIntoStringDictionary = true; 41 | var result = (Dictionary)ServiceStack.StringExtensions.FromJson(testJson); 42 | } 43 | 44 | [Benchmark(Description = "fastJSON")] 45 | public void TestFastJson() 46 | { 47 | var result = fastJSON.JSON.Parse(testJson); 48 | } 49 | 50 | [Benchmark(Description = "Jil")] 51 | public void TestJil() 52 | { 53 | var result = Jil.JSON.Deserialize>(testJson); 54 | } 55 | 56 | [Benchmark(Description = "JavaScriptSerializer")] 57 | public void TestJavaScriptSerializer() 58 | { 59 | var serializer = new System.Web.Script.Serialization.JavaScriptSerializer(); 60 | serializer.DeserializeObject(testJson); 61 | } 62 | } 63 | } -------------------------------------------------------------------------------- /src/Jsonite.Benchmarks/Jsonite.Benchmarks.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | {39E21907-05A1-4C5C-B83E-EB225F88F34D} 8 | Exe 9 | Properties 10 | Jsonite.Benchmarks 11 | Jsonite.Benchmarks 12 | v4.6.1 13 | 512 14 | true 15 | 16 | 17 | AnyCPU 18 | true 19 | full 20 | false 21 | bin\Debug\ 22 | DEBUG;TRACE 23 | prompt 24 | 4 25 | 26 | 27 | AnyCPU 28 | pdbonly 29 | true 30 | bin\Release\ 31 | TRACE 32 | prompt 33 | 4 34 | 35 | 36 | 37 | ..\packages\BenchmarkDotNet.0.10.14\lib\net46\BenchmarkDotNet.dll 38 | 39 | 40 | ..\packages\BenchmarkDotNet.Core.0.10.14\lib\net46\BenchmarkDotNet.Core.dll 41 | 42 | 43 | ..\packages\BenchmarkDotNet.Toolchains.Roslyn.0.10.14\lib\net46\BenchmarkDotNet.Toolchains.Roslyn.dll 44 | 45 | 46 | ..\packages\fastJSON.2.1.34\lib\net40\fastjson.dll 47 | 48 | 49 | ..\packages\Jil.2.16.0\lib\net45\Jil.dll 50 | 51 | 52 | False 53 | ..\Jsonite\bin\$(Configuration)\netstandard1.1\Jsonite.dll 54 | 55 | 56 | ..\packages\Microsoft.CodeAnalysis.Common.2.6.1\lib\netstandard1.3\Microsoft.CodeAnalysis.dll 57 | 58 | 59 | ..\packages\Microsoft.CodeAnalysis.CSharp.2.6.1\lib\netstandard1.3\Microsoft.CodeAnalysis.CSharp.dll 60 | 61 | 62 | ..\packages\Microsoft.DotNet.InternalAbstractions.1.0.0\lib\net451\Microsoft.DotNet.InternalAbstractions.dll 63 | 64 | 65 | ..\packages\Microsoft.DotNet.PlatformAbstractions.1.1.1\lib\net451\Microsoft.DotNet.PlatformAbstractions.dll 66 | 67 | 68 | ..\packages\Microsoft.Win32.Registry.4.3.0\lib\net46\Microsoft.Win32.Registry.dll 69 | 70 | 71 | ..\packages\Newtonsoft.Json.11.0.2\lib\net45\Newtonsoft.Json.dll 72 | 73 | 74 | ..\packages\ServiceStack.Text.5.1.0\lib\net45\ServiceStack.Text.dll 75 | 76 | 77 | ..\packages\Sigil.4.7.0\lib\net45\Sigil.dll 78 | 79 | 80 | 81 | ..\packages\System.AppContext.4.3.0\lib\net46\System.AppContext.dll 82 | 83 | 84 | ..\packages\System.Collections.Immutable.1.3.1\lib\portable-net45+win8+wp8+wpa81\System.Collections.Immutable.dll 85 | 86 | 87 | 88 | ..\packages\System.Console.4.3.0\lib\net46\System.Console.dll 89 | 90 | 91 | 92 | ..\packages\System.Diagnostics.FileVersionInfo.4.3.0\lib\net46\System.Diagnostics.FileVersionInfo.dll 93 | 94 | 95 | ..\packages\System.Diagnostics.StackTrace.4.3.0\lib\net46\System.Diagnostics.StackTrace.dll 96 | 97 | 98 | ..\packages\System.IO.Compression.4.3.0\lib\net46\System.IO.Compression.dll 99 | 100 | 101 | ..\packages\System.IO.FileSystem.4.3.0\lib\net46\System.IO.FileSystem.dll 102 | 103 | 104 | ..\packages\System.IO.FileSystem.Primitives.4.3.0\lib\net46\System.IO.FileSystem.Primitives.dll 105 | 106 | 107 | 108 | 109 | ..\packages\System.Reflection.Metadata.1.4.2\lib\portable-net45+win8\System.Reflection.Metadata.dll 110 | 111 | 112 | 113 | ..\packages\System.Security.Cryptography.Algorithms.4.3.0\lib\net461\System.Security.Cryptography.Algorithms.dll 114 | 115 | 116 | ..\packages\System.Security.Cryptography.Encoding.4.3.0\lib\net46\System.Security.Cryptography.Encoding.dll 117 | 118 | 119 | ..\packages\System.Security.Cryptography.Primitives.4.3.0\lib\net46\System.Security.Cryptography.Primitives.dll 120 | 121 | 122 | ..\packages\System.Security.Cryptography.X509Certificates.4.3.0\lib\net461\System.Security.Cryptography.X509Certificates.dll 123 | 124 | 125 | ..\packages\System.Text.Encoding.CodePages.4.3.0\lib\net46\System.Text.Encoding.CodePages.dll 126 | 127 | 128 | ..\packages\System.Text.Json.2.0.0.11\lib\net40\System.Text.Json.dll 129 | True 130 | 131 | 132 | ..\packages\System.Threading.Tasks.Extensions.4.3.0\lib\portable-net45+win8+wp8+wpa81\System.Threading.Tasks.Extensions.dll 133 | 134 | 135 | ..\packages\System.Threading.Thread.4.3.0\lib\net46\System.Threading.Thread.dll 136 | 137 | 138 | ..\packages\System.ValueTuple.4.3.0\lib\netstandard1.0\System.ValueTuple.dll 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | ..\packages\System.Xml.ReaderWriter.4.3.0\lib\net46\System.Xml.ReaderWriter.dll 149 | 150 | 151 | ..\packages\System.Xml.XmlDocument.4.3.0\lib\net46\System.Xml.XmlDocument.dll 152 | 153 | 154 | ..\packages\System.Xml.XPath.4.3.0\lib\net46\System.Xml.XPath.dll 155 | 156 | 157 | ..\packages\System.Xml.XPath.XDocument.4.3.0\lib\net46\System.Xml.XPath.XDocument.dll 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | PreserveNewest 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 184 | -------------------------------------------------------------------------------- /src/Jsonite.Benchmarks/Program.cs: -------------------------------------------------------------------------------- 1 | using BenchmarkDotNet.Running; 2 | 3 | namespace Jsonite.Benchmarks 4 | { 5 | class Program 6 | { 7 | static void Main(string[] args) 8 | { 9 | BenchmarkRunner.Run(); 10 | } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/Jsonite.Benchmarks/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyTitle("Textamina.Jsonite.Benchmarks")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("")] 12 | [assembly: AssemblyProduct("Textamina.Jsonite.Benchmarks")] 13 | [assembly: AssemblyCopyright("Copyright © 2016")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // Setting ComVisible to false makes the types in this assembly not visible 18 | // to COM components. If you need to access a type in this assembly from 19 | // COM, set the ComVisible attribute to true on that type. 20 | [assembly: ComVisible(false)] 21 | 22 | // The following GUID is for the ID of the typelib if this project is exposed to COM 23 | [assembly: Guid("39e21907-05a1-4c5c-b83e-eb225f88f34d")] 24 | 25 | // Version information for an assembly consists of the following four values: 26 | // 27 | // Major Version 28 | // Minor Version 29 | // Build Number 30 | // Revision 31 | // 32 | // You can specify all the values or you can default the Build and Revision Numbers 33 | // by using the '*' as shown below: 34 | // [assembly: AssemblyVersion("1.0.*")] 35 | [assembly: AssemblyVersion("1.0.0.0")] 36 | [assembly: AssemblyFileVersion("1.0.0.0")] 37 | -------------------------------------------------------------------------------- /src/Jsonite.Benchmarks/packages.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | -------------------------------------------------------------------------------- /src/Jsonite.Benchmarks/results/JsonResults.xlsx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xoofx/jsonite/35ecf451b18bfe1ec4bb278654f61a8b3dfec2a2/src/Jsonite.Benchmarks/results/JsonResults.xlsx -------------------------------------------------------------------------------- /src/Jsonite.Tests/App.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /src/Jsonite.Tests/Jsonite.Tests.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | {A0C5CB5F-5568-40AB-B945-D6D2664D51B0} 8 | Exe 9 | Properties 10 | Jsonite.Tests 11 | Jsonite.Tests 12 | v4.6.1 13 | 512 14 | true 15 | 16 | 17 | AnyCPU 18 | true 19 | full 20 | false 21 | bin\Debug\ 22 | DEBUG;TRACE 23 | prompt 24 | 4 25 | 26 | 27 | AnyCPU 28 | pdbonly 29 | true 30 | bin\Release\ 31 | TRACE 32 | prompt 33 | 4 34 | 35 | 36 | 37 | 38 | 39 | 40 | False 41 | ..\Jsonite\bin\$(Configuration)\netstandard1.1\Jsonite.dll 42 | 43 | 44 | ..\packages\Newtonsoft.Json.8.0.2\lib\net45\Newtonsoft.Json.dll 45 | True 46 | 47 | 48 | ..\packages\NUnit.3.0.1\lib\net45\nunit.framework.dll 49 | True 50 | 51 | 52 | 53 | 54 | ..\packages\System.Text.Json.2.0.0.11\lib\net40\System.Text.Json.dll 55 | True 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 125 | -------------------------------------------------------------------------------- /src/Jsonite.Tests/Program.cs: -------------------------------------------------------------------------------- 1 | namespace Textamina.Jsonite.Tests 2 | { 3 | public class Program 4 | { 5 | public static void Main() 6 | { 7 | } 8 | } 9 | } -------------------------------------------------------------------------------- /src/Jsonite.Tests/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyTitle("Textamin.Tests")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("")] 12 | [assembly: AssemblyProduct("Textamin.Tests")] 13 | [assembly: AssemblyCopyright("Copyright © 2016")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // Setting ComVisible to false makes the types in this assembly not visible 18 | // to COM components. If you need to access a type in this assembly from 19 | // COM, set the ComVisible attribute to true on that type. 20 | [assembly: ComVisible(false)] 21 | 22 | // The following GUID is for the ID of the typelib if this project is exposed to COM 23 | [assembly: Guid("a0c5cb5f-5568-40ab-b945-d6d2664d51b0")] 24 | 25 | // Version information for an assembly consists of the following four values: 26 | // 27 | // Major Version 28 | // Minor Version 29 | // Build Number 30 | // Revision 31 | // 32 | // You can specify all the values or you can default the Build and Revision Numbers 33 | // by using the '*' as shown below: 34 | // [assembly: AssemblyVersion("1.0.*")] 35 | [assembly: AssemblyVersion("1.0.0.0")] 36 | [assembly: AssemblyFileVersion("1.0.0.0")] 37 | -------------------------------------------------------------------------------- /src/Jsonite.Tests/TestFiles/000-test-error-false1.out.txt: -------------------------------------------------------------------------------- 1 | (1,5) : error : Unexpected character 'x' while trying to parse a BOOL 'false' value -------------------------------------------------------------------------------- /src/Jsonite.Tests/TestFiles/000-test-error-false1.txt: -------------------------------------------------------------------------------- 1 | falsx -------------------------------------------------------------------------------- /src/Jsonite.Tests/TestFiles/000-test-error-null1.out.txt: -------------------------------------------------------------------------------- 1 | (1,2) : error : Unexpected character 'x' while trying to parse the NULL 'null' value -------------------------------------------------------------------------------- /src/Jsonite.Tests/TestFiles/000-test-error-null1.txt: -------------------------------------------------------------------------------- 1 | nxll -------------------------------------------------------------------------------- /src/Jsonite.Tests/TestFiles/000-test-error-true1.out.txt: -------------------------------------------------------------------------------- 1 | (1,4) : error : Unexpected character 'x' while trying to parse a BOOL 'true' value -------------------------------------------------------------------------------- /src/Jsonite.Tests/TestFiles/000-test-error-true1.txt: -------------------------------------------------------------------------------- 1 | trux -------------------------------------------------------------------------------- /src/Jsonite.Tests/TestFiles/000-test-error-true2.out.txt: -------------------------------------------------------------------------------- 1 | (1,2) : error : Unexpected character 'x' while trying to parse a BOOL 'true' value -------------------------------------------------------------------------------- /src/Jsonite.Tests/TestFiles/000-test-error-true2.txt: -------------------------------------------------------------------------------- 1 | txue -------------------------------------------------------------------------------- /src/Jsonite.Tests/TestFiles/001-test-error-string1.out.txt: -------------------------------------------------------------------------------- 1 | (1,3) : error : Unexpected character 'X' while parsing a string. Only \ " b f n r t v u0000-uFFFF are allowed -------------------------------------------------------------------------------- /src/Jsonite.Tests/TestFiles/001-test-error-string1.txt: -------------------------------------------------------------------------------- 1 | "\X" -------------------------------------------------------------------------------- /src/Jsonite.Tests/TestFiles/001-test-error-string2.out.txt: -------------------------------------------------------------------------------- 1 | (1,5) : error : Unexpected EOF while parsing a string -------------------------------------------------------------------------------- /src/Jsonite.Tests/TestFiles/001-test-error-string2.txt: -------------------------------------------------------------------------------- 1 | "abc -------------------------------------------------------------------------------- /src/Jsonite.Tests/TestFiles/001-test-error-string3.out.txt: -------------------------------------------------------------------------------- 1 | (1,2) : error : Unexpected character '\t' while parsing a string. Use escape \ instead -------------------------------------------------------------------------------- /src/Jsonite.Tests/TestFiles/001-test-error-string3.txt: -------------------------------------------------------------------------------- 1 | " <- invalid tab character before -------------------------------------------------------------------------------- /src/Jsonite.Tests/TestFiles/001-test-error-string4.out.txt: -------------------------------------------------------------------------------- 1 | (1,7) : error : Unexpected character 'X' while parsing a string. Expecting only hexadecimals [0-9a-fA-F] after escape \u -------------------------------------------------------------------------------- /src/Jsonite.Tests/TestFiles/001-test-error-string4.txt: -------------------------------------------------------------------------------- 1 | "\u000X -------------------------------------------------------------------------------- /src/Jsonite.Tests/TestFiles/002-test-error-number1.out.txt: -------------------------------------------------------------------------------- 1 | (1,2) : error : Unexpected character 'X' while parsing a number after a '-'. Expecting a digit 0-9 -------------------------------------------------------------------------------- /src/Jsonite.Tests/TestFiles/002-test-error-number1.txt: -------------------------------------------------------------------------------- 1 | -X -------------------------------------------------------------------------------- /src/Jsonite.Tests/TestFiles/002-test-error-number2.out.txt: -------------------------------------------------------------------------------- 1 | (1,2) : error : Unexpected character '1' while parsing a number. The number '0' must followed by '.' or by an exponent or nothing -------------------------------------------------------------------------------- /src/Jsonite.Tests/TestFiles/002-test-error-number2.txt: -------------------------------------------------------------------------------- 1 | 01 -------------------------------------------------------------------------------- /src/Jsonite.Tests/TestFiles/002-test-error-number3.out.txt: -------------------------------------------------------------------------------- 1 | (1,3) : error : Unexpected character 'X' while parsing the floating part of a number. Expecting a digit 0-9 after a period '.' -------------------------------------------------------------------------------- /src/Jsonite.Tests/TestFiles/002-test-error-number3.txt: -------------------------------------------------------------------------------- 1 | 0.X -------------------------------------------------------------------------------- /src/Jsonite.Tests/TestFiles/002-test-error-number4.out.txt: -------------------------------------------------------------------------------- 1 | (1,3) : error : Unexpected character 'X' while parsing the exponent of a number. Expecting a digit 0-9 after an exponent -------------------------------------------------------------------------------- /src/Jsonite.Tests/TestFiles/002-test-error-number4.txt: -------------------------------------------------------------------------------- 1 | 1eX -------------------------------------------------------------------------------- /src/Jsonite.Tests/TestFiles/020-test-error-object1.out.txt: -------------------------------------------------------------------------------- 1 | (1,3) : error : Unexpected character 'a' while parsing an object. Expecting a STRING or '}' -------------------------------------------------------------------------------- /src/Jsonite.Tests/TestFiles/020-test-error-object1.txt: -------------------------------------------------------------------------------- 1 | { a: 1 } -------------------------------------------------------------------------------- /src/Jsonite.Tests/TestFiles/020-test-error-object2.out.txt: -------------------------------------------------------------------------------- 1 | (1,7) : error : Unexpected character '@' while parsing an object. Expecting a colon ':' after a member -------------------------------------------------------------------------------- /src/Jsonite.Tests/TestFiles/020-test-error-object2.txt: -------------------------------------------------------------------------------- 1 | { "a" @ 1 } -------------------------------------------------------------------------------- /src/Jsonite.Tests/TestFiles/020-test-error-object3.out.txt: -------------------------------------------------------------------------------- 1 | (1,7) : error : Unexpected EOF while parsing a value. Expecting OBJECT, ARRAY, STRING, NUMBER, true, false or null -------------------------------------------------------------------------------- /src/Jsonite.Tests/TestFiles/020-test-error-object3.txt: -------------------------------------------------------------------------------- 1 | { "a": -------------------------------------------------------------------------------- /src/Jsonite.Tests/TestFiles/020-test-error-object4.out.txt: -------------------------------------------------------------------------------- 1 | (1,8) : error : Unexpected EOF while parsing an object -------------------------------------------------------------------------------- /src/Jsonite.Tests/TestFiles/020-test-error-object4.txt: -------------------------------------------------------------------------------- 1 | { -------------------------------------------------------------------------------- /src/Jsonite.Tests/TestFiles/020-test-error-object5.out.txt: -------------------------------------------------------------------------------- 1 | (1,8) : error : Unexpected character '}' while parsing an object. Expecting a STRING after a comma ',' -------------------------------------------------------------------------------- /src/Jsonite.Tests/TestFiles/020-test-error-object5.txt: -------------------------------------------------------------------------------- 1 | {"a":1,} -------------------------------------------------------------------------------- /src/Jsonite.Tests/TestFiles/030-test-error-array1.out.txt: -------------------------------------------------------------------------------- 1 | (1,2) : error : Unexpected EOF while parsing an array -------------------------------------------------------------------------------- /src/Jsonite.Tests/TestFiles/030-test-error-array1.txt: -------------------------------------------------------------------------------- 1 | [ -------------------------------------------------------------------------------- /src/Jsonite.Tests/TestFiles/030-test-error-array2.out.txt: -------------------------------------------------------------------------------- 1 | (1,5) : error : Unexpected character '@' while parsing an array -------------------------------------------------------------------------------- /src/Jsonite.Tests/TestFiles/030-test-error-array2.txt: -------------------------------------------------------------------------------- 1 | [ 1 @ -------------------------------------------------------------------------------- /src/Jsonite.Tests/TestFiles/030-test-error-array3.out.txt: -------------------------------------------------------------------------------- 1 | (1,6) : error : Unexpected character ']' while parsing an array. Expecting a STRING, NUMBER, OBJECT, ARRAY, true, false or null after a comma ',' -------------------------------------------------------------------------------- /src/Jsonite.Tests/TestFiles/030-test-error-array3.txt: -------------------------------------------------------------------------------- 1 | [ 1, ] -------------------------------------------------------------------------------- /src/Jsonite.Tests/TestParser.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Alexandre Mutel. All rights reserved. 2 | // Licensed under the BSD-Clause 2 license. See license.txt file in the project root for full license information. 3 | using System; 4 | using System.Collections.Generic; 5 | using System.IO; 6 | using System.Linq; 7 | using System.Reflection; 8 | using System.Text; 9 | using Jsonite; 10 | using NUnit.Framework; 11 | 12 | namespace Textamina.Jsonite.Tests 13 | { 14 | /// 15 | /// Tests for and . 16 | /// 17 | [TestFixture] 18 | public class TestParser 19 | { 20 | private const string RelativeBasePath = @"..\..\TestFiles"; 21 | private const string InputFilePattern = "*.txt"; 22 | private const string OutputEndFileExtension = ".out.txt"; 23 | 24 | [Test] 25 | public void ParseBoolAndNull() 26 | { 27 | // See TestErrors files for other cases 28 | Assert.AreEqual(true, Json.Deserialize("true")); 29 | Assert.AreEqual(false, Json.Deserialize("false")); 30 | Assert.AreEqual(null, Json.Deserialize("null")); 31 | Assert.Catch(() => Json.Deserialize("invalid")); 32 | } 33 | 34 | [Test] 35 | public void ParseNumbers() 36 | { 37 | // See TestErrors files for other cases 38 | Assert.AreEqual(0, Json.Deserialize("0")); 39 | Assert.AreEqual(1, Json.Deserialize("1")); 40 | Assert.AreEqual(-1, Json.Deserialize("-1")); 41 | Assert.AreEqual(1.0, Json.Deserialize("1.0")); 42 | Assert.AreEqual(-1.0, Json.Deserialize("-1.0")); 43 | Assert.AreEqual(0.001, Json.Deserialize("0.001")); 44 | Assert.AreEqual(-0.001, Json.Deserialize("-0.001")); 45 | Assert.AreEqual(1e10, Json.Deserialize("1e10")); 46 | Assert.AreEqual(1e-10, Json.Deserialize("1e-10")); 47 | Assert.AreEqual(0.1e-10, Json.Deserialize("0.1e-10")); 48 | Assert.AreEqual(1.1e-10, Json.Deserialize("1.1e-10")); 49 | Assert.AreEqual(1.1e+10, Json.Deserialize("1.1e+10")); 50 | Assert.AreEqual(-1.0, Json.Deserialize("-1.0")); 51 | Assert.AreEqual(int.MaxValue, Json.Deserialize(int.MaxValue.ToString())); 52 | Assert.AreEqual(int.MinValue, Json.Deserialize(int.MinValue.ToString())); 53 | Assert.AreEqual(long.MaxValue, Json.Deserialize(long.MaxValue.ToString())); 54 | Assert.AreEqual(long.MinValue, Json.Deserialize(long.MinValue.ToString())); 55 | Assert.AreEqual(ulong.MaxValue, Json.Deserialize(ulong.MaxValue.ToString())); 56 | Assert.AreEqual(ulong.MinValue, Json.Deserialize(ulong.MinValue.ToString())); 57 | Assert.AreEqual(decimal.MaxValue, Json.Deserialize(decimal.MaxValue.ToString())); 58 | Assert.AreEqual(decimal.MinValue, Json.Deserialize(decimal.MinValue.ToString())); 59 | Assert.AreEqual(double.MaxValue, Json.Deserialize(double.MaxValue.ToString("R"))); 60 | Assert.AreEqual(double.MinValue, Json.Deserialize(double.MinValue.ToString("R"))); 61 | } 62 | 63 | [Test] 64 | public void ParseString() 65 | { 66 | // See TestErrors files for other cases 67 | Assert.AreEqual("test", Json.Deserialize(@"""test""")); 68 | TextAssert.AreEqual("\"\\\b\f\r\n\t ", Json.Deserialize(@"""\""\\\b\f\r\n\t\u0020""") as string); 69 | } 70 | 71 | [Test] 72 | public void TestObject() 73 | { 74 | // See TestErrors files for other cases 75 | var src = "{\"member\":15,\"member2\":null,\"toto\":[1,2,3,4]}"; 76 | var obj = Json.Deserialize(src); 77 | Assert.NotNull(obj); 78 | Assert.AreEqual(typeof(JsonObject), obj.GetType()); 79 | var output = Json.Serialize(obj); 80 | TextAssert.AreEqual(src, output); 81 | } 82 | 83 | [Test] 84 | public void TestArray() 85 | { 86 | // See TestErrors files for other cases 87 | var src = "[1,2,null,true,false,\"YES\"]"; 88 | var obj = Json.Deserialize(src); 89 | Assert.NotNull(obj); 90 | Assert.AreEqual(typeof (JsonArray), obj.GetType()); 91 | Assert.AreEqual(new JsonArray() { 1, 2, null, true, false, "YES"}, obj); 92 | var output = Json.Serialize(obj); 93 | TextAssert.AreEqual(src, output); 94 | } 95 | 96 | [Test] 97 | public void TestLarge() 98 | { 99 | var rootPath = Path.GetDirectoryName(typeof (TestParser).Assembly.Location); 100 | var src = File.ReadAllText(Path.Combine(rootPath, @"..\..\..\Textamina.Jsonite.Benchmarks\test.json")); 101 | var obj = Json.Deserialize(src); 102 | var output1 = Json.Serialize(obj); 103 | var obj1 = Json.Deserialize(output1); 104 | var output2 = Json.Serialize(obj1); 105 | TextAssert.AreEqual(output1, output2); 106 | } 107 | 108 | [TestCaseSource("TestFiles")] 109 | public void TestErrors(TestFilePath testFilePath) 110 | { 111 | var inputName = testFilePath.FilePath; 112 | var baseDir = Path.GetFullPath(Path.Combine(BaseDirectory, RelativeBasePath)); 113 | 114 | var inputFile = Path.Combine(baseDir, inputName); 115 | var inputText = File.ReadAllText(inputFile); 116 | 117 | var expectedOutputFile = Path.ChangeExtension(inputFile, OutputEndFileExtension); 118 | Assert.True(File.Exists(expectedOutputFile), $"Expecting output result file [{expectedOutputFile}] for input file [{inputName}]"); 119 | var expectedOutputText = File.ReadAllText(expectedOutputFile, Encoding.UTF8); 120 | 121 | var result = string.Empty; 122 | 123 | try 124 | { 125 | Json.Validate(inputText); 126 | } 127 | catch (JsonException exception) 128 | { 129 | result = exception.ToString(); 130 | } 131 | 132 | Console.Write(result); 133 | 134 | TextAssert.AreEqual(expectedOutputText, result); 135 | } 136 | 137 | public static IEnumerable TestFiles 138 | { 139 | get 140 | { 141 | var baseDir = Path.GetFullPath(Path.Combine(BaseDirectory, RelativeBasePath)); 142 | return 143 | Directory.EnumerateFiles(baseDir, InputFilePattern, SearchOption.AllDirectories) 144 | .Where(f => !f.EndsWith(OutputEndFileExtension)) 145 | .Select(f => f.StartsWith(baseDir) ? f.Substring(baseDir.Length + 1) : f) 146 | .OrderBy(f => f) 147 | .Select(x => new object[] 148 | { 149 | new TestFilePath(x) 150 | }); 151 | } 152 | } 153 | 154 | /// 155 | /// Use an internal class to have a better display of the filename in Resharper Unit Tests runner. 156 | /// 157 | public struct TestFilePath 158 | { 159 | public TestFilePath(string filePath) 160 | { 161 | FilePath = filePath; 162 | } 163 | 164 | public string FilePath { get; } 165 | 166 | public override string ToString() 167 | { 168 | return FilePath; 169 | } 170 | } 171 | 172 | private static string BaseDirectory 173 | { 174 | get 175 | { 176 | var assembly = Assembly.GetExecutingAssembly(); 177 | var codebase = new Uri(assembly.CodeBase); 178 | var path = codebase.LocalPath; 179 | return Path.GetDirectoryName(path); 180 | } 181 | } 182 | } 183 | } -------------------------------------------------------------------------------- /src/Jsonite.Tests/TextAssert.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Alexandre Mutel. All rights reserved. 2 | // Licensed under the BSD-Clause 2 license. See license.txt file in the project root for full license information. 3 | using System; 4 | using System.Globalization; 5 | using System.IO; 6 | using NUnit.Framework; 7 | 8 | namespace Textamina.Jsonite.Tests 9 | { 10 | /// 11 | /// Pretty text assert from https://gist.github.com/Haacked/1610603 12 | /// Modified version to only print +-10 characters around the first diff 13 | /// 14 | public static class TextAssert 15 | { 16 | public enum DiffStyle 17 | { 18 | Full, 19 | Minimal 20 | } 21 | 22 | public static void AreEqual(string expectedValue, string actualValue) 23 | { 24 | AreEqual(actualValue, expectedValue, DiffStyle.Full, Console.Out); 25 | } 26 | 27 | public static void AreEqual(string expectedValue, string actualValue, DiffStyle diffStyle) 28 | { 29 | AreEqual(actualValue, expectedValue, diffStyle, Console.Out); 30 | } 31 | 32 | public static void AreEqual(string expectedValue, string actualValue, DiffStyle diffStyle, TextWriter output) 33 | { 34 | if (actualValue == null || expectedValue == null) 35 | { 36 | Assert.AreEqual(expectedValue, actualValue); 37 | return; 38 | } 39 | 40 | if (actualValue.Equals(expectedValue, StringComparison.Ordinal)) 41 | { 42 | return; 43 | } 44 | 45 | Console.WriteLine(); 46 | output.WriteLine("Index Expected Actual"); 47 | output.WriteLine("----------------------------"); 48 | int maxLen = Math.Max(actualValue.Length, expectedValue.Length); 49 | int minLen = Math.Min(actualValue.Length, expectedValue.Length); 50 | 51 | if (diffStyle != DiffStyle.Minimal) 52 | { 53 | int startDifferAt = 0; 54 | for (int i = 0; i < maxLen; i++) 55 | { 56 | if (i >= minLen || actualValue[i] != expectedValue[i]) 57 | { 58 | startDifferAt = i; 59 | break; 60 | } 61 | } 62 | 63 | var endDifferAt = Math.Min(startDifferAt + 10, maxLen); 64 | startDifferAt = Math.Max(startDifferAt - 10, 0); 65 | 66 | bool isFirstDiff = true; 67 | for (int i = startDifferAt; i < endDifferAt; i++) 68 | { 69 | if (i >= minLen || actualValue[i] != expectedValue[i]) 70 | { 71 | output.WriteLine("{0,-3} {1,-3} {2,-4} {3,-3} {4,-4} {5,-3}", 72 | i < minLen && actualValue[i] == expectedValue[i] ? " " : isFirstDiff ? ">>>": "***", 73 | // put a mark beside a differing row 74 | i, // the index 75 | i < expectedValue.Length ? ((int) expectedValue[i]).ToString() : "", 76 | // character decimal value 77 | i < expectedValue.Length ? expectedValue[i].ToSafeString() : "", // character safe string 78 | i < actualValue.Length ? ((int) actualValue[i]).ToString() : "", // character decimal value 79 | i < actualValue.Length ? actualValue[i].ToSafeString() : "" // character safe string 80 | ); 81 | 82 | isFirstDiff = false; 83 | } 84 | } 85 | //output.WriteLine(); 86 | } 87 | 88 | Assert.True(string.CompareOrdinal(expectedValue, actualValue) == 0, "strings are differing"); 89 | } 90 | 91 | private static string ToSafeString(this char c) 92 | { 93 | if (char.IsControl(c) || char.IsWhiteSpace(c)) 94 | { 95 | switch (c) 96 | { 97 | case '\b': 98 | return @"\b"; 99 | case '\r': 100 | return @"\r"; 101 | case '\n': 102 | return @"\n"; 103 | case '\t': 104 | return @"\t"; 105 | case '\a': 106 | return @"\a"; 107 | case '\v': 108 | return @"\v"; 109 | case '\f': 110 | return @"\f"; 111 | default: 112 | return $"\\u{(int) c:X};"; 113 | } 114 | } 115 | return c.ToString(CultureInfo.InvariantCulture); 116 | } 117 | } 118 | } -------------------------------------------------------------------------------- /src/Jsonite.Tests/packages.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /src/Jsonite/Jsonite.cs: -------------------------------------------------------------------------------- 1 | // Copyright(c) 2016, Alexandre Mutel 2 | // All rights reserved. 3 | // 4 | // Redistribution and use in source and binary forms, with or without modification 5 | // , are permitted provided that the following conditions are met: 6 | // 7 | // 1. Redistributions of source code must retain the above copyright notice, this 8 | // list of conditions and the following disclaimer. 9 | // 10 | // 2. Redistributions in binary form must reproduce the above copyright notice, 11 | // this list of conditions and the following disclaimer in the documentation 12 | // and/or other materials provided with the distribution. 13 | // 14 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 15 | // ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 16 | // WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 17 | // DISCLAIMED.IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 18 | // FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 19 | // DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 20 | // SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 21 | // CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 22 | // OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 23 | // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 24 | 25 | using System; 26 | using System.Collections; 27 | using System.Collections.Generic; 28 | using System.Globalization; 29 | using System.IO; 30 | using System.Reflection; 31 | using System.Runtime.CompilerServices; 32 | using System.Text; 33 | 34 | [assembly:InternalsVisibleTo("Jsonite.Benchmarks")] 35 | [assembly:InternalsVisibleTo("Jsonite.Tests")] 36 | 37 | // ---------------------------------------------------------------------------- 38 | // This is a single file version of a fast, simple and accurate JSON serializer 39 | // and deserializer. 40 | // The serializer should be compatible with the ECMA-404 http://json.org 41 | // And the RFC-4627: https://tools.ietf.org/html/rfc4627 42 | // 43 | // By default, all types are declared internal so that they don't show up in 44 | // the external dependencies of your project when this file is included directly 45 | // 46 | // Latest version of this code is at http://github.com/textamina/jsonite 47 | // ---------------------------------------------------------------------------- 48 | // Version history 49 | // ---------------------------------------------------------------------------- 50 | // Version 1.0 xoofx, 2016-02-07 51 | // - Initial version, serializer and deserializer to a simple object 52 | // graph. Method for validating a json text. 53 | // ---------------------------------------------------------------------------- 54 | namespace Jsonite 55 | { 56 | /// 57 | /// A JSON parser and reflector to Dictionary/List. 58 | /// 59 | #if JSONITE_PUBLIC 60 | public 61 | #else 62 | internal 63 | #endif 64 | static class Json 65 | { 66 | private static readonly JsonSettings DefaultSettings = new JsonSettings(); 67 | private static readonly JsonSettings DefaultSettingsForValidate = new JsonSettings(); 68 | 69 | /// 70 | /// Deserializes the specified json text into an object. 71 | /// 72 | /// A json text. 73 | /// The settings used to deserialize. 74 | /// An object representing the deserialized json text 75 | /// if text is null 76 | public static object Deserialize(string text, JsonSettings settings = null) 77 | { 78 | if (text == null) throw new ArgumentNullException(nameof(text)); 79 | return Deserialize(new StringReader(text), settings); 80 | } 81 | 82 | /// 83 | /// Deserializes the specified json text into an object. 84 | /// 85 | /// The reader providing a json text. 86 | /// The settings used to deserialize. 87 | /// An object representing the deserialized json text 88 | /// if reader is null 89 | public static object Deserialize(TextReader reader, JsonSettings settings = null) 90 | { 91 | if (reader == null) throw new ArgumentNullException(nameof(reader)); 92 | 93 | var parser = new JsonReader(reader, settings ?? DefaultSettings); 94 | return parser.Parse(null, typeof(object), false); 95 | } 96 | 97 | /// 98 | /// Validates the specified json text. 99 | /// 100 | /// A json text. 101 | /// The settings used to deserialize. 102 | /// if reader is null 103 | /// if the json text is not valid 104 | public static void Validate(string text, JsonSettings settings = null) 105 | { 106 | Validate(new StringReader(text), settings); 107 | } 108 | 109 | /// 110 | /// Validates the specified json text. 111 | /// 112 | /// The reader providing a json text. 113 | /// The settings used to deserialize. 114 | /// if reader is null 115 | /// if the json text is not valid 116 | public static void Validate(TextReader reader, JsonSettings settings = null) 117 | { 118 | settings = settings ?? DefaultSettingsForValidate; 119 | settings.Reflector = JsonReflectorForValidate.Default; 120 | Deserialize(reader, settings); 121 | } 122 | 123 | /// 124 | /// Serializes the specified value to a json text. 125 | /// 126 | /// The value to serialize. 127 | /// The settings used to serialize. 128 | /// A json string representation of the serialized value 129 | public static string Serialize(object value, JsonSettings settings = null) 130 | { 131 | var stringWriter = new StringWriter(CultureInfo.InvariantCulture); 132 | Serialize(value, stringWriter, settings); 133 | return stringWriter.ToString(); 134 | } 135 | 136 | /// 137 | /// Serializes the specified value to a json text. 138 | /// 139 | /// The value to serialize. 140 | /// The output writer that will contains the serialized json text. 141 | /// The settings used to serialize. 142 | public static void Serialize(object value, TextWriter writer, JsonSettings settings = null) 143 | { 144 | var jsonWriter = new JsonWriter(writer, settings ?? DefaultSettings); 145 | jsonWriter.Write(value); 146 | } 147 | 148 | /// 149 | /// The internal JsonReader used to deserialize a json text into an object graph. 150 | /// 151 | private struct JsonReader 152 | { 153 | private int offset; 154 | private int line; 155 | private int column; 156 | private char c; 157 | private readonly JsonSettings settings; 158 | private readonly IJsonReflector reflector; 159 | private readonly StringBuilder builder; 160 | private const char Eof = '\0'; 161 | private bool isEof; 162 | private readonly bool isValidate; 163 | private int level; 164 | 165 | public JsonReader(TextReader reader, JsonSettings settings) 166 | { 167 | Reader = reader; 168 | this.settings = settings; 169 | this.reflector = settings.Reflector ?? JsonReflectorDefault.Instance; 170 | reflector.Initialize(settings); 171 | isValidate = reflector is JsonReflectorForValidate; 172 | offset = 0; 173 | line = 0; 174 | column = 0; 175 | c = Eof; 176 | level = 0; 177 | isEof = false; 178 | builder = new StringBuilder(); 179 | NextCharSkipWhitespaces(); 180 | } 181 | 182 | private TextReader Reader { get; } 183 | 184 | public object Parse(object existingObject, Type expectedType, bool expectValue) 185 | { 186 | switch (c) 187 | { 188 | case '{': 189 | return ParseObject(existingObject, expectedType); 190 | case '[': 191 | return ParseArray(existingObject, expectedType); 192 | case '"': 193 | return ParseString(); 194 | case 't': 195 | return ParseTrue(); 196 | case 'f': 197 | return ParseFalse(); 198 | case 'n': 199 | return ParseNull(); 200 | default: 201 | if (c == '-' || IsDigit(c)) 202 | { 203 | return ParseNumber(); 204 | } 205 | 206 | if (c != Eof) 207 | { 208 | RaiseUnexpected(""); 209 | } 210 | break; 211 | 212 | } 213 | 214 | if (expectValue) 215 | { 216 | RaiseUnexpected("while parsing a value. Expecting OBJECT, ARRAY, STRING, NUMBER, true, false or null"); // unit-test: 020-test-error-object3.txt 217 | } 218 | 219 | return null; 220 | } 221 | 222 | private void IncrementLevel() 223 | { 224 | level++; 225 | if (settings.MaxDepth > 0 && level > settings.MaxDepth) 226 | { 227 | RaiseException("The maximum allowed depth [{settings.MaxDepth}] level has been reached. The object graph is too deep"); 228 | } 229 | } 230 | 231 | private void DecrementLevel() 232 | { 233 | level--; 234 | } 235 | 236 | private object ParseObject(object obj, Type expectedType) 237 | { 238 | IncrementLevel(); 239 | 240 | NextCharSkipWhitespaces(); // Skip starting { 241 | 242 | // If we are deserializing to a value that is the same as the target, we can reuse it 243 | 244 | object objectContext; 245 | obj = reflector.OnDeserializeEnterObject(obj, expectedType, out objectContext); 246 | 247 | bool expectMember = false; 248 | 249 | while (c != Eof) 250 | { 251 | if (c == '"') 252 | { 253 | // Deserialize the member 254 | var memberName = ParseString(); 255 | 256 | if (c != ':') 257 | { 258 | RaiseUnexpected($"while parsing an object. Expecting a colon ':' after a member"); // unit test: 020-test-error-object2.txt 259 | } 260 | 261 | NextCharSkipWhitespaces(); 262 | 263 | Type memberExpectedType; 264 | object memberContext; 265 | object memberExistingValue; 266 | reflector.OnDeserializePrepareMemberForObject(objectContext, obj, memberName, out memberExpectedType, out memberContext, out memberExistingValue); 267 | 268 | // Deserialize the value 269 | var value = Parse(memberExistingValue, memberExpectedType, true); 270 | 271 | // Sets the value on the object 272 | reflector.OnDeserializeSetObjectMember(objectContext, obj, memberContext, value); 273 | expectMember = false; 274 | 275 | if (c == ',') 276 | { 277 | NextCharSkipWhitespaces(); 278 | expectMember = true; 279 | continue; 280 | } 281 | } 282 | 283 | if (c == '}') 284 | { 285 | break; 286 | } 287 | 288 | RaiseUnexpected("while parsing an object. Expecting a STRING or '}'"); // unit test: 020-test-error-object1.txt 289 | } 290 | 291 | if (c == Eof) 292 | { 293 | RaiseUnexpected("while parsing an object"); // unit-test: 020-test-error-object4.txt 294 | } 295 | else if (expectMember && !settings.AllowTrailingCommas) 296 | { 297 | RaiseUnexpected("while parsing an object. Expecting a STRING after a comma ','"); // unit-test: 020-test-error-object5.txt 298 | } 299 | 300 | NextCharSkipWhitespaces(); // Skip closing } 301 | 302 | var result = reflector.OnDeserializeExitObject(objectContext, obj); 303 | DecrementLevel(); 304 | return result; 305 | } 306 | 307 | private object ParseArray(object array, Type expectedType) 308 | { 309 | IncrementLevel(); 310 | NextCharSkipWhitespaces(); // Skip starting [ 311 | 312 | Type expectedArrayItemType; 313 | object arrayContext; 314 | array = reflector.OnDeserializeEnterArray(array, expectedType, out expectedArrayItemType, out arrayContext); 315 | bool expectItem = false; 316 | 317 | int index = 0; 318 | while (c != Eof) 319 | { 320 | if (c == ']') 321 | { 322 | break; 323 | } 324 | 325 | var value = Parse(null, expectedArrayItemType, true); 326 | expectItem = false; 327 | 328 | // Add the item to the array 329 | reflector.OnDeserializeAddArrayItem(arrayContext, array, index++, value); 330 | 331 | if (c == ']') 332 | { 333 | break; 334 | } 335 | 336 | if (c == ',') 337 | { 338 | NextCharSkipWhitespaces(); 339 | expectItem = true; 340 | } 341 | else 342 | { 343 | RaiseUnexpected("while parsing an array"); // unit-test: 030-test-error-array2.txt 344 | } 345 | } 346 | 347 | if (c == Eof) 348 | { 349 | RaiseUnexpected("while parsing an array"); // unit-test: 030-test-error-array1.txt 350 | } 351 | else if (expectItem && !settings.AllowTrailingCommas) 352 | { 353 | RaiseUnexpected("while parsing an array. Expecting a STRING, NUMBER, OBJECT, ARRAY, true, false or null after a comma ','"); // unit-test: 030-test-error-array3.txt 354 | } 355 | 356 | NextCharSkipWhitespaces(); // Skip closing ] 357 | 358 | var result = reflector.OnDeserializeExitArray(arrayContext, array); 359 | DecrementLevel(); 360 | return result; 361 | } 362 | 363 | private string ParseString() 364 | { 365 | NextChar(); // Skip " but don't skip whitespaces 366 | builder.Length = 0; 367 | while (true) 368 | { 369 | // Handle escape 370 | switch (c) 371 | { 372 | case '\\': 373 | NextChar(); 374 | switch (c) 375 | { 376 | case '"': 377 | builder.Append('"'); 378 | NextChar(); 379 | continue; 380 | case '\\': 381 | builder.Append('\\'); 382 | NextChar(); 383 | continue; 384 | case '/': 385 | builder.Append('/'); 386 | NextChar(); 387 | continue; 388 | case 'b': 389 | builder.Append('\b'); 390 | NextChar(); 391 | continue; 392 | case 'f': 393 | builder.Append('\f'); 394 | NextChar(); 395 | continue; 396 | case 'n': 397 | builder.Append('\n'); 398 | NextChar(); 399 | continue; 400 | case 'r': 401 | builder.Append('\r'); 402 | NextChar(); 403 | continue; 404 | case 't': 405 | builder.Append('\t'); 406 | NextChar(); 407 | continue; 408 | case 'u': 409 | NextChar(); 410 | // Must be followed 4 hex numbers (0000-FFFF) 411 | if (IsHex(c)) // 1 412 | { 413 | var value = HexToInt(c); 414 | NextChar(); 415 | if (IsHex(c)) // 2 416 | { 417 | value = (value << 4) | HexToInt(c); 418 | NextChar(); 419 | if (IsHex(c)) // 3 420 | { 421 | value = (value << 4) | HexToInt(c); 422 | NextChar(); 423 | if (IsHex(c)) // 4 424 | { 425 | value = (value << 4) | HexToInt(c); 426 | builder.Append((char)value); 427 | NextChar(); 428 | continue; 429 | } 430 | } 431 | } 432 | } 433 | RaiseUnexpected("while parsing a string. Expecting only hexadecimals [0-9a-fA-F] after escape \\u"); // unit-test: 001-test-error-string4.txt 434 | goto end_of_parsing; 435 | } 436 | RaiseUnexpected("while parsing a string. Only \\ \" b f n r t v u0000-uFFFF are allowed"); // unit-test: 001-test-error-string1.txt 437 | goto end_of_parsing; 438 | case Eof: 439 | RaiseUnexpected("while parsing a string"); // unit-test: 001-test-error-string2.txt 440 | goto end_of_parsing; 441 | case '"': 442 | NextCharSkipWhitespaces(); 443 | goto end_of_parsing; 444 | default: 445 | if (c < ' ') 446 | { 447 | RaiseUnexpected("while parsing a string. Use escape \\ instead"); // unit-test: 001-test-error-string3.txt 448 | } 449 | builder.Append(c); 450 | NextChar(); 451 | break; 452 | } 453 | } 454 | end_of_parsing: 455 | 456 | // If we are validating, no need to create a string as we won't use it 457 | return isValidate ? null : builder.ToString(); 458 | } 459 | 460 | private object ParseNumber() 461 | { 462 | bool isFloat = false; 463 | bool hasExponent = false; 464 | bool isNegative = false; 465 | builder.Length = 0; 466 | if (c == '-') 467 | { 468 | isNegative = true; 469 | builder.Append(c); 470 | NextChar(); 471 | } 472 | 473 | if (!IsDigit(c)) 474 | { 475 | RaiseUnexpected("while parsing a number after a '-'. Expecting a digit 0-9"); // unit-test: 002-test-error-number1.txt 476 | } 477 | 478 | // If number starts by 0, we don't expect any digit after 479 | if (c == '0') 480 | { 481 | builder.Append(c); 482 | NextChar(); 483 | 484 | // Make sure that we don't have a digit after 485 | if (IsDigit(c)) 486 | { 487 | RaiseUnexpected("while parsing a number. The number '0' must followed by '.' or by an exponent or nothing"); // unit-test: 002-test-error-number2.txt 488 | } 489 | } 490 | else 491 | { 492 | // Else number starts by non-0, so we can advance as much digits as we have 493 | do 494 | { 495 | builder.Append(c); 496 | NextChar(); 497 | } while (IsDigit(c)); 498 | } 499 | 500 | if (c == '.') 501 | { 502 | isFloat = true; 503 | builder.Append('.'); 504 | NextChar(); 505 | 506 | if (!IsDigit(c)) 507 | { 508 | RaiseUnexpected("while parsing the floating part of a number. Expecting a digit 0-9 after a period '.'"); // unit-test: 002-test-error-number3.txt 509 | } 510 | 511 | do 512 | { 513 | builder.Append(c); 514 | NextChar(); 515 | } while (IsDigit(c)); 516 | } 517 | 518 | if (c == 'e' || c == 'E') 519 | { 520 | hasExponent = true; 521 | 522 | builder.Append(c); 523 | NextChar(); 524 | if (c == '+' || c == '-') 525 | { 526 | builder.Append(c); 527 | NextChar(); 528 | } 529 | 530 | if (!IsDigit(c)) 531 | { 532 | RaiseUnexpected("while parsing the exponent of a number. Expecting a digit 0-9 after an exponent"); // unit-test: 002-test-error-number4.txt 533 | } 534 | 535 | do 536 | { 537 | builder.Append(c); 538 | NextChar(); 539 | } while (IsDigit(c)); 540 | } 541 | 542 | // Skip any whitespaces after a value 543 | while (IsWhiteSpace(c)) 544 | { 545 | NextChar(); 546 | } 547 | 548 | // If we are expecting to parse only things into strings, early exit here 549 | if (settings.ParseValuesAsStrings) 550 | { 551 | return builder.ToString(); 552 | } 553 | 554 | if (isFloat || hasExponent) 555 | { 556 | var numberAsText = builder.ToString(); 557 | if (settings.ParseFloatAsDecimal) 558 | { 559 | decimal decimalNumber; 560 | if (decimal.TryParse(numberAsText, NumberStyles.Float, CultureInfo.InvariantCulture, out decimalNumber)) 561 | { 562 | return decimalNumber; 563 | } 564 | } 565 | else 566 | { 567 | double doubleNumber; 568 | if (double.TryParse(numberAsText, NumberStyles.Float, CultureInfo.InvariantCulture, out doubleNumber)) 569 | { 570 | return doubleNumber; 571 | } 572 | } 573 | } 574 | else 575 | { 576 | // Fast parse for all integers smaller than -999999999 <= value <= 999999999 577 | // 2147483647 578 | // 999999999 579 | const int maxIntStringEasyParse = 9; 580 | int intNumber = 0; 581 | if (builder.Length <= (isNegative ? maxIntStringEasyParse + 1 : maxIntStringEasyParse)) 582 | { 583 | for (int i = isNegative ? 1 : 0; i < builder.Length; i++) 584 | { 585 | intNumber = intNumber * 10 + (builder[i] - '0'); 586 | } 587 | return isNegative ? -intNumber : intNumber; 588 | } 589 | 590 | // Else go the long way 591 | 592 | // Try first to parse to an int 593 | var numberAsText = builder.ToString(); 594 | if (int.TryParse(numberAsText, NumberStyles.Integer, CultureInfo.InvariantCulture, out intNumber)) 595 | { 596 | return intNumber; 597 | } 598 | 599 | // Then a long 600 | long longNumber; 601 | if (long.TryParse(numberAsText, NumberStyles.Integer, CultureInfo.InvariantCulture, out longNumber)) 602 | { 603 | return longNumber; 604 | } 605 | 606 | // Or an ulong 607 | ulong ulongNumber; 608 | if (ulong.TryParse(numberAsText, NumberStyles.Integer, CultureInfo.InvariantCulture, out ulongNumber)) 609 | { 610 | return ulongNumber; 611 | } 612 | 613 | // Or a decimal 614 | decimal decimalNumber; 615 | if (decimal.TryParse(numberAsText, NumberStyles.Integer, CultureInfo.InvariantCulture, out decimalNumber)) 616 | { 617 | return decimalNumber; 618 | } 619 | } 620 | 621 | RaiseException($"Unable to parse number [{builder}] to a valid C# number "); 622 | return null; 623 | } 624 | 625 | private object ParseTrue() 626 | { 627 | NextChar(); 628 | if (c == 'r') 629 | { 630 | NextChar(); 631 | if (c == 'u') 632 | { 633 | NextChar(); 634 | if (c == 'e') 635 | { 636 | NextCharSkipWhitespaces(); 637 | return settings.ParseValuesAsStrings ? (object)"true" : true; 638 | } 639 | } 640 | } 641 | RaiseUnexpected("while trying to parse a BOOL 'true' value"); // unit-test: 000-test-error-true1.txt and 000-test-error-true2.txt 642 | return null; 643 | } 644 | 645 | private object ParseFalse() 646 | { 647 | NextChar(); 648 | if (c == 'a') 649 | { 650 | NextChar(); 651 | if (c == 'l') 652 | { 653 | NextChar(); 654 | if (c == 's') 655 | { 656 | NextChar(); 657 | if (c == 'e') 658 | { 659 | NextCharSkipWhitespaces(); 660 | return settings.ParseValuesAsStrings ? (object)"false" : false; 661 | } 662 | } 663 | } 664 | } 665 | RaiseUnexpected("while trying to parse a BOOL 'false' value"); // unit-test: 000-test-error-false1.txt 666 | return null; 667 | } 668 | 669 | private object ParseNull() 670 | { 671 | NextChar(); 672 | if (c == 'u') 673 | { 674 | NextChar(); 675 | if (c == 'l') 676 | { 677 | NextChar(); 678 | if (c == 'l') 679 | { 680 | NextCharSkipWhitespaces(); 681 | return null; 682 | } 683 | } 684 | } 685 | RaiseUnexpected("while trying to parse the NULL 'null' value"); // unit-test: 000-test-error-null1.txt 686 | return null; 687 | } 688 | 689 | private void RaiseException(string message) 690 | { 691 | reflector.OnDeserializeRaiseParsingError(offset, line, column, message, null); 692 | } 693 | 694 | private void RaiseUnexpected(string message) 695 | { 696 | RaiseException((isEof ? "Unexpected EOF " : $"Unexpected character '{EscapeChar(c)}' ") + message); 697 | } 698 | 699 | private void NextCharSkipWhitespaces() 700 | { 701 | do 702 | { 703 | NextChar(); 704 | } while (IsWhiteSpace(c)); 705 | } 706 | 707 | [MethodImpl((MethodImplOptions)256)] 708 | private void NextChar() 709 | { 710 | var nextChar = Reader.Read(); 711 | if (nextChar < 0) 712 | { 713 | if (c != Eof) 714 | { 715 | column++; 716 | offset++; 717 | } 718 | isEof = true; 719 | c = Eof; 720 | return; 721 | } 722 | 723 | if (c == '\n') 724 | { 725 | offset++; 726 | column = 0; 727 | line++; 728 | } 729 | else if (c != Eof) 730 | { 731 | offset++; 732 | column++; 733 | } 734 | c = (char)nextChar; 735 | } 736 | 737 | [MethodImpl((MethodImplOptions)256)] 738 | private static bool IsWhiteSpace(char c) 739 | { 740 | return c == ' ' || c == '\n' || c == '\t' || c == '\r'; 741 | } 742 | 743 | [MethodImpl((MethodImplOptions)256)] 744 | private static bool IsDigit(char c) 745 | { 746 | return c >= '0' && c <= '9'; 747 | } 748 | 749 | [MethodImpl((MethodImplOptions)256)] 750 | private static int HexToInt(char c) 751 | { 752 | if (c >= '0' && c <= '9') 753 | { 754 | return c - '0'; 755 | } 756 | if (c >= 'a' && c <= 'f') 757 | { 758 | return c - 'a' + 10; 759 | } 760 | return c - 'A' + 10; 761 | } 762 | 763 | [MethodImpl((MethodImplOptions)256)] 764 | private static bool IsHex(char c) 765 | { 766 | return (c >= '0' && c <= '9') || 767 | (c >= 'a' && c <= 'f') || 768 | (c >= 'A' && c <= 'F'); 769 | } 770 | } 771 | 772 | /// 773 | /// The internal class used to serialize an object graph to a json text. 774 | /// 775 | private class JsonWriter 776 | { 777 | private readonly TextWriter writer; 778 | private readonly JsonSettings settings; 779 | private readonly IJsonReflector reflector; 780 | private readonly bool indent; 781 | private readonly char indentChar; 782 | private readonly int indentCount; 783 | private int indentLevel; 784 | private readonly Dictionary> writers; 785 | 786 | public JsonWriter(TextWriter writer, JsonSettings settings) 787 | { 788 | this.settings = settings; 789 | this.reflector = settings.Reflector ?? JsonReflectorDefault.Instance; 790 | this.writer = writer; 791 | this.indent = settings.Indent; 792 | this.indentChar = settings.IndentChar; 793 | this.indentCount = settings.IndentCount; 794 | this.indentLevel = 0; 795 | writers = new Dictionary>() 796 | { 797 | // These converters have to match to the one declared in JsonReflectorDefault.Converters 798 | {typeof (string), value => WriteString((string) value)}, 799 | {typeof (bool), value => { writer.Write((bool) value ? "true" : "false"); }}, 800 | {typeof (char), value => { writer.Write(((char) value).ToString()); }}, 801 | {typeof (byte), value => { writer.Write(((byte) value).ToString(CultureInfo.InvariantCulture)); }}, 802 | {typeof (sbyte), value => { writer.Write(((sbyte) value).ToString(CultureInfo.InvariantCulture)); }}, 803 | {typeof (short), value => { writer.Write(((short) value).ToString(CultureInfo.InvariantCulture)); }}, 804 | {typeof (ushort), value => { writer.Write(((ushort) value).ToString(CultureInfo.InvariantCulture)); }}, 805 | {typeof (int), value => { writer.Write(((int) value).ToString(CultureInfo.InvariantCulture)); }}, 806 | {typeof (uint), value => { writer.Write(((uint) value).ToString(CultureInfo.InvariantCulture)); }}, 807 | {typeof (long), value => { writer.Write(((long) value).ToString(CultureInfo.InvariantCulture)); }}, 808 | {typeof (ulong), value => { writer.Write(((ulong) value).ToString(CultureInfo.InvariantCulture)); }}, 809 | {typeof (float), value => { writer.Write(((double)(float) value).ToString("R", CultureInfo.InvariantCulture)); }}, 810 | {typeof (double), value => { writer.Write(((double) value).ToString("R", CultureInfo.InvariantCulture)); }}, 811 | {typeof (decimal), value => { writer.Write(((decimal) value).ToString(CultureInfo.InvariantCulture)); }}, 812 | {typeof (Type), value => { WriteString(value.ToString()); }}, 813 | {typeof (Guid), value => { WriteString(((Guid)value).ToString("D")); }}, 814 | {typeof (StringBuilder), value => WriteString(((StringBuilder) value).ToString())}, // TODO: Could be optimized but it is a very uncommon case, so... 815 | {typeof (DateTime), value => { WriteString(((DateTime)value).ToString(CultureInfo.InvariantCulture)); }}, // TODO: handle correctly 816 | {typeof (TimeSpan), value => { WriteString(((TimeSpan)value).ToString()); }}, // TODO: handle correctly 817 | }; 818 | } 819 | 820 | public void Write(object value) 821 | { 822 | if (value == null) 823 | { 824 | writer.Write("null"); 825 | return; 826 | } 827 | 828 | var type = value.GetType(); 829 | Action valueWriter; 830 | 831 | if (writers.TryGetValue(type, out valueWriter)) 832 | { 833 | valueWriter(value); 834 | return; 835 | } 836 | 837 | object objectContext; 838 | var objectType = reflector.OnSerializeGetObjectType(value, type, out objectContext); 839 | switch (objectType) 840 | { 841 | case JsonObjectType.Object: 842 | WriteObject(reflector.OnSerializeGetObjectMembers(objectContext, value)); 843 | break; 844 | case JsonObjectType.Array: 845 | WriteArray(reflector.OnSerializeGetArrayItems(objectContext, value)); 846 | break; 847 | default: 848 | // Try to serialize as a string 849 | WriteString(Convert.ToString(value, CultureInfo.InvariantCulture)); 850 | break; 851 | } 852 | 853 | //throw new InvalidOperationException($"Unsupported object type [{value.GetType()}]"); 854 | } 855 | 856 | private void WriteObject(IEnumerable> members) 857 | { 858 | writer.Write('{'); 859 | indentLevel++; 860 | bool isFirst = true; 861 | foreach (var keyValue in members) 862 | { 863 | if (!isFirst) 864 | { 865 | writer.Write(','); 866 | } 867 | 868 | if (indent) 869 | { 870 | writer.Write('\n'); 871 | Indent(); 872 | } 873 | 874 | WriteString(keyValue.Key); 875 | writer.Write(':'); 876 | if (indent) 877 | { 878 | writer.Write(' '); 879 | } 880 | Write(keyValue.Value); 881 | isFirst = false; 882 | } 883 | indentLevel--; 884 | if (!isFirst && indent) 885 | { 886 | writer.Write('\n'); 887 | Indent(); 888 | } 889 | writer.Write('}'); 890 | } 891 | 892 | private void WriteArray(IEnumerable list) 893 | { 894 | // Serialize list 895 | writer.Write('['); 896 | indentLevel++; 897 | bool isFirst = true; 898 | foreach (var item in list) 899 | { 900 | if (!isFirst) 901 | { 902 | writer.Write(','); 903 | } 904 | if (indent) 905 | { 906 | writer.Write('\n'); 907 | Indent(); 908 | } 909 | Write(item); 910 | isFirst = false; 911 | } 912 | indentLevel--; 913 | if (!isFirst && indent) 914 | { 915 | writer.Write('\n'); 916 | Indent(); 917 | } 918 | writer.Write(']'); 919 | } 920 | 921 | [MethodImpl((MethodImplOptions)256)] 922 | private void Indent() 923 | { 924 | for (int i = 0; i < indentCount * indentLevel; i++) 925 | { 926 | writer.Write(indentChar); 927 | } 928 | } 929 | 930 | private void WriteString(string text) 931 | { 932 | writer.Write('"'); 933 | for (int i = 0; i < text.Length; i++) 934 | { 935 | var c = text[i]; 936 | 937 | switch (c) 938 | { 939 | case '"': 940 | writer.Write('\\'); 941 | writer.Write('\"'); 942 | break; 943 | case '\\': 944 | writer.Write('\\'); 945 | writer.Write('\\'); 946 | break; 947 | case '\b': 948 | writer.Write('\\'); 949 | writer.Write('b'); 950 | break; 951 | case '\f': 952 | writer.Write('\\'); 953 | writer.Write('f'); 954 | break; 955 | case '\n': 956 | writer.Write('\\'); 957 | writer.Write('n'); 958 | break; 959 | case '\r': 960 | writer.Write('\\'); 961 | writer.Write('r'); 962 | break; 963 | case '\t': 964 | writer.Write('\\'); 965 | writer.Write('t'); 966 | break; 967 | default: 968 | if (c < ' ') 969 | { 970 | throw new ArgumentException($"Invalid control character '{EscapeChar(c)}' found in string"); 971 | } 972 | 973 | if (IsHighSurrogate(c) || IsLowSurrogate(c)) 974 | { 975 | writer.Write('\\'); 976 | writer.Write('u'); 977 | writer.Write(((int)c).ToString("X4", CultureInfo.InvariantCulture)); 978 | } 979 | else 980 | { 981 | writer.Write(c); 982 | } 983 | break; 984 | } 985 | } 986 | writer.Write('"'); 987 | } 988 | } 989 | 990 | [MethodImpl((MethodImplOptions)256)] 991 | private static bool IsHighSurrogate(char c) 992 | { 993 | if ((int)c >= 55296) 994 | return (int)c <= 56319; 995 | return false; 996 | } 997 | 998 | [MethodImpl((MethodImplOptions)256)] 999 | private static bool IsLowSurrogate(char c) 1000 | { 1001 | if ((int)c >= 56320) 1002 | return (int)c <= 57343; 1003 | return false; 1004 | } 1005 | 1006 | 1007 | private static string EscapeChar(char chr) 1008 | { 1009 | // http://stackoverflow.com/questions/12309104/how-to-print-control-characters-in-console-window 1010 | switch (chr) 1011 | { 1012 | case '\'': 1013 | return @"\'"; 1014 | case '"': 1015 | return "\\\""; 1016 | case '\\': 1017 | return @"\\"; 1018 | case '\0': 1019 | return @"\0"; 1020 | case '\a': 1021 | return @"\a"; 1022 | case '\b': 1023 | return @"\b"; 1024 | case '\f': 1025 | return @"\f"; 1026 | case '\n': 1027 | return @"\n"; 1028 | case '\r': 1029 | return @"\r"; 1030 | case '\t': 1031 | return @"\t"; 1032 | case '\v': 1033 | return @"\v"; 1034 | default: 1035 | if (char.IsControl(chr) || IsHighSurrogate(chr) || IsLowSurrogate(chr)) 1036 | return @"\u" + ((int)chr).ToString("X4"); 1037 | else 1038 | return new string(chr, 1); 1039 | } 1040 | } 1041 | 1042 | private sealed class JsonReflectorForValidate : IJsonReflector 1043 | { 1044 | public static readonly JsonReflectorForValidate Default = new JsonReflectorForValidate(); 1045 | 1046 | public void Initialize(JsonSettings settings) 1047 | { 1048 | } 1049 | 1050 | public object OnDeserializeEnterObject(object obj, Type expectedType, out object objectContext) 1051 | { 1052 | objectContext = null; 1053 | return null; 1054 | } 1055 | 1056 | public void OnDeserializePrepareMemberForObject(object objectContext, object obj, string member, out Type memberType, 1057 | out object memberContext, out object existingMemberValue) 1058 | { 1059 | memberType = typeof (object); 1060 | memberContext = null; 1061 | existingMemberValue = null; 1062 | } 1063 | 1064 | public void OnDeserializeSetObjectMember(object objectContext, object obj, object memberContext, object value) 1065 | { 1066 | } 1067 | 1068 | public object OnDeserializeExitObject(object objectContext, object obj) 1069 | { 1070 | return null; 1071 | } 1072 | 1073 | public object OnDeserializeEnterArray(object obj, Type expectedType, out Type expectedArrayTypeItem, out object arrayContext) 1074 | { 1075 | expectedArrayTypeItem = null; 1076 | arrayContext = null; 1077 | return null; 1078 | } 1079 | 1080 | public void OnDeserializeAddArrayItem(object arrayContext, object array, int index, object value) 1081 | { 1082 | } 1083 | 1084 | public object OnDeserializeExitArray(object arrayContext, object obj) 1085 | { 1086 | return null; 1087 | } 1088 | 1089 | public void OnDeserializeRaiseParsingError(int offset, int line, int column, string message, Exception inner) 1090 | { 1091 | throw new JsonException(offset, line, column, message, inner); 1092 | } 1093 | 1094 | 1095 | public JsonObjectType OnSerializeGetObjectType(object obj, Type type, out object objectContext) 1096 | { 1097 | throw new NotImplementedException(); 1098 | } 1099 | 1100 | public bool IsObjectType(Type type) 1101 | { 1102 | throw new NotImplementedException(); 1103 | } 1104 | 1105 | public bool IsArrayType(Type type) 1106 | { 1107 | throw new NotImplementedException(); 1108 | } 1109 | 1110 | public IEnumerable> OnSerializeGetObjectMembers(object objectContext, object obj) 1111 | { 1112 | throw new NotImplementedException(); 1113 | } 1114 | 1115 | public IEnumerable OnSerializeGetArrayItems(object objectContext, object array) 1116 | { 1117 | throw new NotImplementedException(); 1118 | } 1119 | } 1120 | } 1121 | 1122 | /// 1123 | /// The default object used when a deserializing to an object type. 1124 | /// 1125 | #if JSONITE_PUBLIC 1126 | public 1127 | #else 1128 | internal 1129 | #endif 1130 | class JsonObject : Dictionary 1131 | { 1132 | public override string ToString() 1133 | { 1134 | return Json.Serialize(this); 1135 | } 1136 | } 1137 | 1138 | /// 1139 | /// The default array used when deserializing to an array type. 1140 | /// 1141 | #if JSONITE_PUBLIC 1142 | public 1143 | #else 1144 | internal 1145 | #endif 1146 | class JsonArray : List 1147 | { 1148 | public override string ToString() 1149 | { 1150 | return Json.Serialize(this); 1151 | } 1152 | } 1153 | 1154 | /// 1155 | /// Instance exception used when a parsing exception occured. 1156 | /// 1157 | /// 1158 | /// This exception can be overriden by overriding the method . 1159 | /// 1160 | #if JSONITE_PUBLIC 1161 | public 1162 | #else 1163 | internal 1164 | #endif 1165 | class JsonException : Exception 1166 | { 1167 | public JsonException(int offset, int line, int column, string message, Exception inner = null) : base(message, inner) 1168 | { 1169 | Offset = offset; 1170 | Line = line; 1171 | Column = column; 1172 | } 1173 | 1174 | /// 1175 | /// Character offset from the beginning of the text being parsed. 1176 | /// 1177 | public readonly int Offset; 1178 | 1179 | /// 1180 | /// Line position (zero-based) where the error occured from the beginning of the text being parsed. 1181 | /// 1182 | public readonly int Line; 1183 | 1184 | /// 1185 | /// Column position (zero-based) where the error occured. 1186 | /// 1187 | public readonly int Column; 1188 | 1189 | /// 1190 | /// Prints the line (1-based) and column (1-based). 1191 | /// 1192 | /// A string representation of this object 1193 | public override string ToString() 1194 | { 1195 | var innerMessage = InnerException != null ? " Check inner exception for more details" : string.Empty; 1196 | return $"({Line + 1},{Column + 1}) : error : {Message}{innerMessage}"; 1197 | } 1198 | } 1199 | 1200 | /// 1201 | /// Defines serialization and deserialization settings used by and 1202 | /// 1203 | #if JSONITE_PUBLIC 1204 | public 1205 | #else 1206 | internal 1207 | #endif 1208 | class JsonSettings 1209 | { 1210 | public JsonSettings() 1211 | { 1212 | IndentCount = 2; 1213 | IndentChar = ' '; 1214 | Reflector = JsonReflectorDefault.Instance; 1215 | } 1216 | 1217 | /// 1218 | /// Gets or sets the maximum depth used when serializing or deserializing. 1219 | /// 1220 | public int MaxDepth { get; set; } 1221 | 1222 | /// 1223 | /// Gets or sets a value indicating to indent the text when serializing. Default is false. 1224 | /// 1225 | public bool Indent { get; set; } 1226 | 1227 | /// 1228 | /// Gets or sets the number of used to indent a json output when is true. 1229 | /// 1230 | public int IndentCount { get; set; } 1231 | 1232 | /// 1233 | /// Gets or sets the indent character used when is true. 1234 | /// 1235 | public char IndentChar { get; set; } 1236 | 1237 | /// 1238 | /// Gets or sets a value indicating whether floats should be deserialized to decimal instead of double (default). 1239 | /// 1240 | public bool ParseFloatAsDecimal { get; set; } 1241 | 1242 | /// 1243 | /// Gets or sets a value indicating whether all values should be deserialized to strings instead of numbers. 1244 | /// 1245 | public bool ParseValuesAsStrings { get; set; } 1246 | 1247 | /// 1248 | /// Gets or sets a value indicating whether to allow trailing commas in object and array declaration. 1249 | /// 1250 | public bool AllowTrailingCommas { get; set; } 1251 | 1252 | /// 1253 | /// Gets or sets the reflector used for interfacing the json text to an object graph. 1254 | /// 1255 | public IJsonReflector Reflector { get; set; } 1256 | } 1257 | 1258 | /// 1259 | /// A callback interface used during the serialization and deserialization. 1260 | /// 1261 | #if JSONITE_PUBLIC 1262 | public 1263 | #else 1264 | internal 1265 | #endif 1266 | interface IJsonReflector 1267 | { 1268 | /// 1269 | /// Initializes this instance with the specified settings. 1270 | /// 1271 | /// The settings. 1272 | void Initialize(JsonSettings settings); 1273 | 1274 | /// 1275 | /// Called when starting to deserialize an object. 1276 | /// 1277 | /// An existing object instance (may be null). 1278 | /// The expected type (not null). 1279 | /// The object context that will be passed to other deserialize methods for objects. 1280 | /// The object instance to deserialize to. The return value must not be null. This instance can be the input if not null, or this method could choose to replace the instance by another during the deserialization. 1281 | object OnDeserializeEnterObject(object obj, Type expectedType, out object objectContext); 1282 | 1283 | /// 1284 | /// Called when deserializing a member, before deserializing its value. 1285 | /// 1286 | /// The object context that was returned by the 1287 | /// The object instance (not null). 1288 | /// The member name being deserialized. 1289 | /// Expected type of the member. 1290 | /// The member context that will be passed back to . 1291 | /// The existing member value if any (may be null). 1292 | void OnDeserializePrepareMemberForObject(object objectContext, object obj, string member, out Type memberType, out object memberContext, out object existingMemberValue); 1293 | 1294 | /// 1295 | /// Called when deserializing a member value to effectively set the value for the member on the specified object instance. 1296 | /// 1297 | /// The object context that was returned by the 1298 | /// The object instance (not null). 1299 | /// The member context that was generated by . 1300 | /// The value of the member to set on the object. 1301 | void OnDeserializeSetObjectMember(object objectContext, object obj, object memberContext, object value); 1302 | 1303 | /// 1304 | /// Called when deserializing an object is done. This method allows to transform the object to another value. 1305 | /// 1306 | /// The object context. 1307 | /// The object instance that has been deserialized. 1308 | /// The final object deserialized (may be different from ) 1309 | object OnDeserializeExitObject(object objectContext, object obj); 1310 | 1311 | /// 1312 | /// Called when starting to deserialize an array. 1313 | /// 1314 | /// An existing array instance (may be null). 1315 | /// The expected type of the array. 1316 | /// The expected type of an array item. 1317 | /// The array context that will be passed to other deserialize methods for arrays. 1318 | /// The array instance to deserialize to. The return value must not be null. 1319 | object OnDeserializeEnterArray(object obj, Type expectedType, out Type expectedArrayTypeItem, out object arrayContext); 1320 | 1321 | /// 1322 | /// Called when deserializing an array item to add to the specified array instance. 1323 | /// 1324 | /// The array context that was returned by the 1325 | /// The array being deserialized. 1326 | /// The index of the next element (may be used for plain arrays). 1327 | /// The value of the item to add to the array. 1328 | void OnDeserializeAddArrayItem(object arrayContext, object array, int index, object value); 1329 | 1330 | /// 1331 | /// Called when deserializing an array is done. This method allows to transform the array to another value (transform a list to a plain .NET array for example) 1332 | /// 1333 | /// The array context that was returned by the 1334 | /// The array instance that has been deserialized. 1335 | /// The final array instance deserialized (may be different from ) 1336 | object OnDeserializeExitArray(object arrayContext, object obj); 1337 | 1338 | /// 1339 | /// Called when an error occured when deserializing. A default implementation should throw a . 1340 | /// 1341 | /// The character position from the begining of the buffer being deserialized. 1342 | /// The line position (zero-based) 1343 | /// The column position (zero-based) 1344 | /// The error message. 1345 | /// An optional inner exception. 1346 | void OnDeserializeRaiseParsingError(int offset, int line, int column, string message, Exception inner); 1347 | 1348 | /// 1349 | /// Called when serializing an object, to determine whether the object is an array or a simple object (with members/properties). 1350 | /// This method is then used to correctly route to or . 1351 | /// 1352 | /// The object instance being serialized 1353 | /// The type of the object being serialized. 1354 | /// An object context that will be passed to other serialize methods. 1355 | /// The type of the specified object instance (array or object or unknown) 1356 | JsonObjectType OnSerializeGetObjectType(object obj, Type type, out object objectContext); 1357 | 1358 | /// 1359 | /// Called when serializing an object to the members value of this object. 1360 | /// 1361 | /// The object context that was returned by the 1362 | /// The object instance being serialized. 1363 | /// An enumeration of members [name, value]. 1364 | IEnumerable> OnSerializeGetObjectMembers(object objectContext, object obj); 1365 | 1366 | /// 1367 | /// Called when serializing an array to get the array items. 1368 | /// 1369 | /// The object context that was returned by the 1370 | /// The object instance being serialized. 1371 | /// An enumeration of the array items to serialize. 1372 | IEnumerable OnSerializeGetArrayItems(object objectContext, object array); 1373 | } 1374 | 1375 | /// 1376 | /// Defines the type of object when serializing (returned by method . 1377 | /// 1378 | #if JSONITE_PUBLIC 1379 | public 1380 | #else 1381 | internal 1382 | #endif 1383 | enum JsonObjectType 1384 | { 1385 | /// 1386 | /// The object type being serialized is unkwown. 1387 | /// 1388 | Unknown, 1389 | 1390 | /// 1391 | /// The object being serialized is an object with members. 1392 | /// 1393 | Object, 1394 | 1395 | /// 1396 | /// The object being serialized is an array (providing ) 1397 | /// 1398 | Array, 1399 | } 1400 | 1401 | /// 1402 | /// The default implementation of that allows to deserialize a JSON text to a generic or . 1403 | /// 1404 | /// 1405 | #if JSONITE_PUBLIC 1406 | public 1407 | #else 1408 | internal 1409 | #endif 1410 | sealed class JsonReflectorDefault : IJsonReflector 1411 | { 1412 | public static readonly JsonReflectorDefault Instance = new JsonReflectorDefault(); 1413 | 1414 | private JsonReflectorDefault() 1415 | { 1416 | } 1417 | 1418 | public void Initialize(JsonSettings settings) 1419 | { 1420 | } 1421 | 1422 | public object OnDeserializeEnterObject(object obj, Type expectedType, out object objectContext) 1423 | { 1424 | if (!typeof(IDictionary).GetTypeInfo().IsAssignableFrom(expectedType.GetTypeInfo()) && expectedType != typeof(object)) 1425 | { 1426 | throw new ArgumentException($"The default reflector only supports deserializing to a Dictionary or a JsonObject instead of [{expectedType}]"); 1427 | } 1428 | 1429 | objectContext = null; 1430 | return expectedType == typeof(object) || expectedType == typeof (JsonObject) || expectedType.GetTypeInfo().IsInterface 1431 | ? new JsonObject() 1432 | : Activator.CreateInstance(expectedType); 1433 | } 1434 | 1435 | public void OnDeserializePrepareMemberForObject(object objectContext, object obj, string member, out Type expectedMemberType, out object memberContext, out object existingMemberValue) 1436 | { 1437 | memberContext = member; 1438 | expectedMemberType = typeof(object); 1439 | existingMemberValue = null; 1440 | } 1441 | 1442 | public void OnDeserializeSetObjectMember(object objectContext, object target, object memberContext, object value) 1443 | { 1444 | ((IDictionary)target)[(string)memberContext] = value; 1445 | } 1446 | 1447 | public object OnDeserializeExitObject(object objectContext, object obj) 1448 | { 1449 | return obj; 1450 | } 1451 | 1452 | public object OnDeserializeEnterArray(object obj, Type expectedType, out Type expectedArrayItemType, out object arrayContext) 1453 | { 1454 | if (!typeof(IList).GetTypeInfo().IsAssignableFrom(expectedType.GetTypeInfo()) && expectedType != typeof(object)) 1455 | { 1456 | throw new ArgumentException($"The default reflector only supports deserializing to a IList or a JsonArray instead of [{expectedType}]"); 1457 | } 1458 | 1459 | arrayContext = null; 1460 | expectedArrayItemType = typeof(object); 1461 | return expectedType == typeof(object) || expectedType == typeof(JsonArray) || expectedType.GetTypeInfo().IsInterface 1462 | ? new JsonArray() 1463 | : Activator.CreateInstance(expectedType); 1464 | } 1465 | 1466 | public void OnDeserializeAddArrayItem(object arrayContext, object array, int index, object value) 1467 | { 1468 | ((IList)array).Add(value); 1469 | } 1470 | 1471 | public object OnDeserializeExitArray(object arrayContext, object obj) 1472 | { 1473 | return obj; 1474 | } 1475 | public void OnDeserializeRaiseParsingError(int offset, int line, int column, string message, Exception inner) 1476 | { 1477 | throw new JsonException(offset, line, column, message, inner); 1478 | } 1479 | 1480 | public JsonObjectType OnSerializeGetObjectType(object obj, Type type, out object objectContext) 1481 | { 1482 | objectContext = null; 1483 | var typeInfo = type.GetTypeInfo(); 1484 | if (typeof(IDictionary).GetTypeInfo().IsAssignableFrom(typeInfo)) 1485 | { 1486 | return JsonObjectType.Object; 1487 | } 1488 | 1489 | if (typeof(IList).GetTypeInfo().IsAssignableFrom(typeInfo) || 1490 | typeof(IList).GetTypeInfo().IsAssignableFrom(typeInfo)) 1491 | { 1492 | return JsonObjectType.Array; 1493 | } 1494 | return JsonObjectType.Unknown; 1495 | } 1496 | 1497 | public IEnumerable> OnSerializeGetObjectMembers(object objectContext, object obj) 1498 | { 1499 | return ((IDictionary)obj); 1500 | } 1501 | 1502 | public IEnumerable OnSerializeGetArrayItems(object objectContext, object array) 1503 | { 1504 | return (IEnumerable)array; 1505 | } 1506 | } 1507 | #if NETPRE45 1508 | static class ReflectionHelper 1509 | { 1510 | public static Type GetTypeInfo(this Type type) 1511 | { 1512 | return type; 1513 | } 1514 | } 1515 | #endif 1516 | } -------------------------------------------------------------------------------- /src/Jsonite/Jsonite.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | A single file JSON parser/serializer for .NET 5 | Alexandre Mutel 6 | Jsonite 7 | en-US 8 | Alexandre Mutel 9 | netstandard1.1 10 | Jsonite 11 | Jsonite 12 | https://raw.githubusercontent.com/xoofx/jsonite/master/jsonite.png 13 | https://github.com/xoofx/jsonite 14 | https://github.com/xoofx/jsonite/blob/master/license.txt 15 | $(DefineConstants);JSONITE_PUBLIC 16 | 17 | 18 | 21 | 22 | 27 | 28 | 33 | 34 | -------------------------------------------------------------------------------- /src/Textamina.Jsonite.sln.DotSettings: -------------------------------------------------------------------------------- 1 |  2 | Copyright (c) Alexandre Mutel. All rights reserved. 3 | Licensed under the BSD-Clause 2 license. See license.txt file in the project root for full license information. -------------------------------------------------------------------------------- /src/jsonite.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 15 4 | VisualStudioVersion = 15.0.27703.2026 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Build", "Build", "{061866E2-005C-4D13-A338-EA464BBEC60F}" 7 | ProjectSection(SolutionItems) = preProject 8 | ..\license.txt = ..\license.txt 9 | EndProjectSection 10 | EndProject 11 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Jsonite", "Jsonite\Jsonite.csproj", "{E7916EBE-D9C6-4FE6-B9C4-9A85E33707F7}" 12 | EndProject 13 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Jsonite.Tests", "Jsonite.Tests\Jsonite.Tests.csproj", "{A0C5CB5F-5568-40AB-B945-D6D2664D51B0}" 14 | ProjectSection(ProjectDependencies) = postProject 15 | {E7916EBE-D9C6-4FE6-B9C4-9A85E33707F7} = {E7916EBE-D9C6-4FE6-B9C4-9A85E33707F7} 16 | EndProjectSection 17 | EndProject 18 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Jsonite.Benchmarks", "Jsonite.Benchmarks\Jsonite.Benchmarks.csproj", "{39E21907-05A1-4C5C-B83E-EB225F88F34D}" 19 | ProjectSection(ProjectDependencies) = postProject 20 | {E7916EBE-D9C6-4FE6-B9C4-9A85E33707F7} = {E7916EBE-D9C6-4FE6-B9C4-9A85E33707F7} 21 | EndProjectSection 22 | EndProject 23 | Global 24 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 25 | Debug|Any CPU = Debug|Any CPU 26 | Release|Any CPU = Release|Any CPU 27 | EndGlobalSection 28 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 29 | {E7916EBE-D9C6-4FE6-B9C4-9A85E33707F7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 30 | {E7916EBE-D9C6-4FE6-B9C4-9A85E33707F7}.Debug|Any CPU.Build.0 = Debug|Any CPU 31 | {E7916EBE-D9C6-4FE6-B9C4-9A85E33707F7}.Release|Any CPU.ActiveCfg = Release|Any CPU 32 | {E7916EBE-D9C6-4FE6-B9C4-9A85E33707F7}.Release|Any CPU.Build.0 = Release|Any CPU 33 | {A0C5CB5F-5568-40AB-B945-D6D2664D51B0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 34 | {A0C5CB5F-5568-40AB-B945-D6D2664D51B0}.Debug|Any CPU.Build.0 = Debug|Any CPU 35 | {A0C5CB5F-5568-40AB-B945-D6D2664D51B0}.Release|Any CPU.ActiveCfg = Release|Any CPU 36 | {A0C5CB5F-5568-40AB-B945-D6D2664D51B0}.Release|Any CPU.Build.0 = Release|Any CPU 37 | {39E21907-05A1-4C5C-B83E-EB225F88F34D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 38 | {39E21907-05A1-4C5C-B83E-EB225F88F34D}.Debug|Any CPU.Build.0 = Debug|Any CPU 39 | {39E21907-05A1-4C5C-B83E-EB225F88F34D}.Release|Any CPU.ActiveCfg = Release|Any CPU 40 | {39E21907-05A1-4C5C-B83E-EB225F88F34D}.Release|Any CPU.Build.0 = Release|Any CPU 41 | EndGlobalSection 42 | GlobalSection(SolutionProperties) = preSolution 43 | HideSolutionNode = FALSE 44 | EndGlobalSection 45 | GlobalSection(ExtensibilityGlobals) = postSolution 46 | SolutionGuid = {AE828436-E87F-4F94-ACCA-38F17DAF9005} 47 | EndGlobalSection 48 | EndGlobal 49 | --------------------------------------------------------------------------------