From 2aa6ec858b8d83b7df488d2df336bb1566d1090b Mon Sep 17 00:00:00 2001 From: "Valeriano A.R" Date: Mon, 14 Nov 2016 03:03:56 +0100 Subject: [PATCH] VAR.Json first commit --- .gitignore | 28 + LICENSE.txt | 21 + README.md | 55 ++ VAR.Json.Tests/Program.cs | 129 +++++ VAR.Json.Tests/Properties/AssemblyInfo.cs | 14 + VAR.Json.Tests/VAR.Json.Tests.csproj | 95 ++++ VAR.Json.Tests/tests/fail01.json | 1 + VAR.Json.Tests/tests/fail02.json | 1 + VAR.Json.Tests/tests/fail03.json | 1 + VAR.Json.Tests/tests/fail04.json | 1 + VAR.Json.Tests/tests/fail05.json | 1 + VAR.Json.Tests/tests/fail06.json | 1 + VAR.Json.Tests/tests/fail07.json | 1 + VAR.Json.Tests/tests/fail08.json | 1 + VAR.Json.Tests/tests/fail09.json | 1 + VAR.Json.Tests/tests/fail10.json | 1 + VAR.Json.Tests/tests/fail11.json | 1 + VAR.Json.Tests/tests/fail12.json | 1 + VAR.Json.Tests/tests/fail13.json | 1 + VAR.Json.Tests/tests/fail14.json | 1 + VAR.Json.Tests/tests/fail15.json | 1 + VAR.Json.Tests/tests/fail16.json | 1 + VAR.Json.Tests/tests/fail17.json | 1 + VAR.Json.Tests/tests/fail18.json | 1 + VAR.Json.Tests/tests/fail19.json | 1 + VAR.Json.Tests/tests/fail20.json | 1 + VAR.Json.Tests/tests/fail21.json | 1 + VAR.Json.Tests/tests/fail22.json | 1 + VAR.Json.Tests/tests/fail23.json | 1 + VAR.Json.Tests/tests/fail24.json | 1 + VAR.Json.Tests/tests/fail25.json | 1 + VAR.Json.Tests/tests/fail26.json | 1 + VAR.Json.Tests/tests/fail27.json | 2 + VAR.Json.Tests/tests/fail28.json | 2 + VAR.Json.Tests/tests/fail29.json | 1 + VAR.Json.Tests/tests/fail30.json | 1 + VAR.Json.Tests/tests/fail31.json | 1 + VAR.Json.Tests/tests/fail32.json | 1 + VAR.Json.Tests/tests/fail33.json | 1 + VAR.Json.Tests/tests/pass01.json | 58 ++ VAR.Json.Tests/tests/pass02.json | 1 + VAR.Json.Tests/tests/pass03.json | 6 + VAR.Json.sln | 34 ++ VAR.Json/JsonParser.cs | 637 ++++++++++++++++++++++ VAR.Json/JsonWriter.cs | 337 ++++++++++++ VAR.Json/ParserContext.cs | 84 +++ VAR.Json/Properties/AssemblyInfo.cs | 14 + VAR.Json/VAR.Json.csproj | 50 ++ 48 files changed, 1598 insertions(+) create mode 100644 .gitignore create mode 100644 LICENSE.txt create mode 100644 README.md create mode 100644 VAR.Json.Tests/Program.cs create mode 100644 VAR.Json.Tests/Properties/AssemblyInfo.cs create mode 100644 VAR.Json.Tests/VAR.Json.Tests.csproj create mode 100644 VAR.Json.Tests/tests/fail01.json create mode 100644 VAR.Json.Tests/tests/fail02.json create mode 100644 VAR.Json.Tests/tests/fail03.json create mode 100644 VAR.Json.Tests/tests/fail04.json create mode 100644 VAR.Json.Tests/tests/fail05.json create mode 100644 VAR.Json.Tests/tests/fail06.json create mode 100644 VAR.Json.Tests/tests/fail07.json create mode 100644 VAR.Json.Tests/tests/fail08.json create mode 100644 VAR.Json.Tests/tests/fail09.json create mode 100644 VAR.Json.Tests/tests/fail10.json create mode 100644 VAR.Json.Tests/tests/fail11.json create mode 100644 VAR.Json.Tests/tests/fail12.json create mode 100644 VAR.Json.Tests/tests/fail13.json create mode 100644 VAR.Json.Tests/tests/fail14.json create mode 100644 VAR.Json.Tests/tests/fail15.json create mode 100644 VAR.Json.Tests/tests/fail16.json create mode 100644 VAR.Json.Tests/tests/fail17.json create mode 100644 VAR.Json.Tests/tests/fail18.json create mode 100644 VAR.Json.Tests/tests/fail19.json create mode 100644 VAR.Json.Tests/tests/fail20.json create mode 100644 VAR.Json.Tests/tests/fail21.json create mode 100644 VAR.Json.Tests/tests/fail22.json create mode 100644 VAR.Json.Tests/tests/fail23.json create mode 100644 VAR.Json.Tests/tests/fail24.json create mode 100644 VAR.Json.Tests/tests/fail25.json create mode 100644 VAR.Json.Tests/tests/fail26.json create mode 100644 VAR.Json.Tests/tests/fail27.json create mode 100644 VAR.Json.Tests/tests/fail28.json create mode 100644 VAR.Json.Tests/tests/fail29.json create mode 100644 VAR.Json.Tests/tests/fail30.json create mode 100644 VAR.Json.Tests/tests/fail31.json create mode 100644 VAR.Json.Tests/tests/fail32.json create mode 100644 VAR.Json.Tests/tests/fail33.json create mode 100644 VAR.Json.Tests/tests/pass01.json create mode 100644 VAR.Json.Tests/tests/pass02.json create mode 100644 VAR.Json.Tests/tests/pass03.json create mode 100644 VAR.Json.sln create mode 100644 VAR.Json/JsonParser.cs create mode 100644 VAR.Json/JsonWriter.cs create mode 100644 VAR.Json/ParserContext.cs create mode 100644 VAR.Json/Properties/AssemblyInfo.cs create mode 100644 VAR.Json/VAR.Json.csproj diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..97266e7 --- /dev/null +++ b/.gitignore @@ -0,0 +1,28 @@ +#ignorar miniaturas creadas por windows +Thumbs.db +#Ignorar archivos construidos por Visual Studio +*.obj +*.exe +*.pdb +*.user +*.aps +*.pch +*.vspscc +*_i.c +*_p.c +*.ncb +*.suo +*.tlb +*.tlh +*.bak +*.cache +*.ilk +*.log +[Bb]in +[Dd]ebug*/ +*.lib +*.sbr +obj/ +[Rr]elease*/ +_ReSharper*/ +*.userprefs diff --git a/LICENSE.txt b/LICENSE.txt new file mode 100644 index 0000000..318247e --- /dev/null +++ b/LICENSE.txt @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2014-2015 Valeriano Alfonso Rodriguez + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..83352de --- /dev/null +++ b/README.md @@ -0,0 +1,55 @@ +# .Net library for JSON parsing + +## Usage + +### VAR.Json +Add the resulting assembly as reference in your projects, and this line on code: + + using VAR.Json; + +Parse any string with JSON content: + + var jsonParser = new JsonParser(); + object result = jsonParser("{\"Test\": 1}"); + +Serialize any object to JSON: + + var jsonWriter = new JsonWriter(); + string jsonText = jsonWriter(new List{1, 2, 3, 4}); + +## Building +A Visual Studio 2015 and 2010 solutions are provided. Simply, click build on the IDE. + +## Contributing +1. Fork it! +2. Create your feature branch: `git checkout -b my-new-feature` +3. Commit your changes: `git commit -am 'Add some feature'` +4. Push to the branch: `git push origin my-new-feature` +5. Submit a pull request :D + +## Credits +* Valeriano Alfonso Rodriguez. + +## License + + The MIT License (MIT) + + Copyright (c) 2014-2015 Valeriano Alfonso Rodriguez + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. diff --git a/VAR.Json.Tests/Program.cs b/VAR.Json.Tests/Program.cs new file mode 100644 index 0000000..8e05d73 --- /dev/null +++ b/VAR.Json.Tests/Program.cs @@ -0,0 +1,129 @@ +using System; +using System.IO; +using System.Text; + +namespace VAR.Json.Tests +{ + class Program + { + static void Main(string[] args) + { + // http://www.json.org/JSON_checker/ + + string currentPath = System.Reflection.Assembly.GetEntryAssembly().Location; + currentPath = FindPath(currentPath, "tests"); + + // Test all files + string[] files; + files = Directory.GetFiles(currentPath, "*.json"); + foreach (string file in files) + { + TestFile(file); + } + + Console.Read(); + } + + private static void TestFile(string fileName) + { + string testName = Path.GetFileNameWithoutExtension(fileName); + string fileContent = File.ReadAllText(fileName, Encoding.UTF8); + if (testName.StartsWith("fail")) + { + TestFailCase(testName, fileContent); + } + if (testName.StartsWith("pass")) + { + TestPassCase(testName, fileContent); + } + } + + private static void TestFailCase(string testName, string fileContent) + { + JsonParser parser = new JsonParser(); + object result; + try + { + result = parser.Parse(fileContent); + } + catch (Exception ex) + { + OutputFailure(testName, fileContent, ex); + return; + } + if (parser.Tainted == false) + { + OutputFailure(testName, fileContent, result); + return; + } + Console.Out.WriteLine("OK! {0}", testName); + } + + private static void TestPassCase(string testName, string fileContent) + { + JsonParser parser = new JsonParser(); + object result; + try + { + result = parser.Parse(fileContent); + } + catch (Exception ex) + { + OutputFailure(testName, fileContent, ex); + return; + } + if (parser.Tainted) + { + OutputFailure(testName, fileContent, result); + return; + } + Console.Out.WriteLine("OK! {0}", testName); + } + + private static void OutputFailure(string testName, string fileContent, object obj) + { + Console.Out.WriteLine("Failure! {0}", testName); + Console.Out.WriteLine("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"); + Console.Out.WriteLine("Content:\n{0}", fileContent); + Console.Out.WriteLine("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"); + if (obj is Exception) + { + Exception ex = obj as Exception; + Console.Out.WriteLine("Ex.Message: {0}", ex.Message); + Console.Out.WriteLine("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"); + Console.Out.WriteLine("Ex.Stacktrace:\n{0}", ex.StackTrace); + Console.Out.WriteLine("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"); + } + if (obj != null && (obj is Exception) == false) + { + JsonWriter writter = new JsonWriter(true); + Console.Out.WriteLine("Parsed:\n{0}", writter.Write(obj)); + Console.Out.WriteLine("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"); + } + } + + private static string FindPath(string currentPath, string directory) + { + do + { + string testPath = Path.Combine(currentPath, directory); + if (Directory.Exists(testPath)) + { + currentPath = testPath; + Console.Out.WriteLine(testPath); + break; + } + else + { + DirectoryInfo dirInfo = Directory.GetParent(currentPath); + if (dirInfo == null) + { + throw new Exception(string.Format("FindPath: Directory {0} not found", directory)); + } + currentPath = dirInfo.ToString(); + } + } while (string.IsNullOrEmpty(currentPath) == false); + return currentPath; + } + } +} diff --git a/VAR.Json.Tests/Properties/AssemblyInfo.cs b/VAR.Json.Tests/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..f40f1bc --- /dev/null +++ b/VAR.Json.Tests/Properties/AssemblyInfo.cs @@ -0,0 +1,14 @@ +using System.Reflection; +using System.Runtime.InteropServices; + +[assembly: AssemblyTitle("VAR.Json.Tests")] +[assembly: AssemblyDescription("Json Tests")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("VAR")] +[assembly: AssemblyProduct("VAR.Json.Tests")] +[assembly: AssemblyCopyright("Copyright © VAR 2016")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] +[assembly: ComVisible(false)] +[assembly: Guid("576297b8-423d-4533-b75a-f186ccff0d2a")] +[assembly: AssemblyVersion("1.0.*")] diff --git a/VAR.Json.Tests/VAR.Json.Tests.csproj b/VAR.Json.Tests/VAR.Json.Tests.csproj new file mode 100644 index 0000000..4736b13 --- /dev/null +++ b/VAR.Json.Tests/VAR.Json.Tests.csproj @@ -0,0 +1,95 @@ + + + + + Debug + AnyCPU + {576297B8-423D-4533-B75A-F186CCFF0D2A} + Exe + Properties + VAR.Json.Tests + VAR.Json.Tests + v4.6.1 + 512 + true + + + AnyCPU + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + AnyCPU + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + {28b3f937-145c-4fd4-a75b-a25ea4cc0428} + VAR.Json + + + + + \ No newline at end of file diff --git a/VAR.Json.Tests/tests/fail01.json b/VAR.Json.Tests/tests/fail01.json new file mode 100644 index 0000000..6216b86 --- /dev/null +++ b/VAR.Json.Tests/tests/fail01.json @@ -0,0 +1 @@ +"A JSON payload should be an object or array, not a string." \ No newline at end of file diff --git a/VAR.Json.Tests/tests/fail02.json b/VAR.Json.Tests/tests/fail02.json new file mode 100644 index 0000000..6b7c11e --- /dev/null +++ b/VAR.Json.Tests/tests/fail02.json @@ -0,0 +1 @@ +["Unclosed array" \ No newline at end of file diff --git a/VAR.Json.Tests/tests/fail03.json b/VAR.Json.Tests/tests/fail03.json new file mode 100644 index 0000000..168c81e --- /dev/null +++ b/VAR.Json.Tests/tests/fail03.json @@ -0,0 +1 @@ +{unquoted_key: "keys must be quoted"} \ No newline at end of file diff --git a/VAR.Json.Tests/tests/fail04.json b/VAR.Json.Tests/tests/fail04.json new file mode 100644 index 0000000..9de168b --- /dev/null +++ b/VAR.Json.Tests/tests/fail04.json @@ -0,0 +1 @@ +["extra comma",] \ No newline at end of file diff --git a/VAR.Json.Tests/tests/fail05.json b/VAR.Json.Tests/tests/fail05.json new file mode 100644 index 0000000..ddf3ce3 --- /dev/null +++ b/VAR.Json.Tests/tests/fail05.json @@ -0,0 +1 @@ +["double extra comma",,] \ No newline at end of file diff --git a/VAR.Json.Tests/tests/fail06.json b/VAR.Json.Tests/tests/fail06.json new file mode 100644 index 0000000..ed91580 --- /dev/null +++ b/VAR.Json.Tests/tests/fail06.json @@ -0,0 +1 @@ +[ , "<-- missing value"] \ No newline at end of file diff --git a/VAR.Json.Tests/tests/fail07.json b/VAR.Json.Tests/tests/fail07.json new file mode 100644 index 0000000..8a96af3 --- /dev/null +++ b/VAR.Json.Tests/tests/fail07.json @@ -0,0 +1 @@ +["Comma after the close"], \ No newline at end of file diff --git a/VAR.Json.Tests/tests/fail08.json b/VAR.Json.Tests/tests/fail08.json new file mode 100644 index 0000000..b28479c --- /dev/null +++ b/VAR.Json.Tests/tests/fail08.json @@ -0,0 +1 @@ +["Extra close"]] \ No newline at end of file diff --git a/VAR.Json.Tests/tests/fail09.json b/VAR.Json.Tests/tests/fail09.json new file mode 100644 index 0000000..5815574 --- /dev/null +++ b/VAR.Json.Tests/tests/fail09.json @@ -0,0 +1 @@ +{"Extra comma": true,} \ No newline at end of file diff --git a/VAR.Json.Tests/tests/fail10.json b/VAR.Json.Tests/tests/fail10.json new file mode 100644 index 0000000..5d8c004 --- /dev/null +++ b/VAR.Json.Tests/tests/fail10.json @@ -0,0 +1 @@ +{"Extra value after close": true} "misplaced quoted value" \ No newline at end of file diff --git a/VAR.Json.Tests/tests/fail11.json b/VAR.Json.Tests/tests/fail11.json new file mode 100644 index 0000000..76eb95b --- /dev/null +++ b/VAR.Json.Tests/tests/fail11.json @@ -0,0 +1 @@ +{"Illegal expression": 1 + 2} \ No newline at end of file diff --git a/VAR.Json.Tests/tests/fail12.json b/VAR.Json.Tests/tests/fail12.json new file mode 100644 index 0000000..77580a4 --- /dev/null +++ b/VAR.Json.Tests/tests/fail12.json @@ -0,0 +1 @@ +{"Illegal invocation": alert()} \ No newline at end of file diff --git a/VAR.Json.Tests/tests/fail13.json b/VAR.Json.Tests/tests/fail13.json new file mode 100644 index 0000000..379406b --- /dev/null +++ b/VAR.Json.Tests/tests/fail13.json @@ -0,0 +1 @@ +{"Numbers cannot have leading zeroes": 013} \ No newline at end of file diff --git a/VAR.Json.Tests/tests/fail14.json b/VAR.Json.Tests/tests/fail14.json new file mode 100644 index 0000000..0ed366b --- /dev/null +++ b/VAR.Json.Tests/tests/fail14.json @@ -0,0 +1 @@ +{"Numbers cannot be hex": 0x14} \ No newline at end of file diff --git a/VAR.Json.Tests/tests/fail15.json b/VAR.Json.Tests/tests/fail15.json new file mode 100644 index 0000000..fc8376b --- /dev/null +++ b/VAR.Json.Tests/tests/fail15.json @@ -0,0 +1 @@ +["Illegal backslash escape: \x15"] \ No newline at end of file diff --git a/VAR.Json.Tests/tests/fail16.json b/VAR.Json.Tests/tests/fail16.json new file mode 100644 index 0000000..3fe21d4 --- /dev/null +++ b/VAR.Json.Tests/tests/fail16.json @@ -0,0 +1 @@ +[\naked] \ No newline at end of file diff --git a/VAR.Json.Tests/tests/fail17.json b/VAR.Json.Tests/tests/fail17.json new file mode 100644 index 0000000..62b9214 --- /dev/null +++ b/VAR.Json.Tests/tests/fail17.json @@ -0,0 +1 @@ +["Illegal backslash escape: \017"] \ No newline at end of file diff --git a/VAR.Json.Tests/tests/fail18.json b/VAR.Json.Tests/tests/fail18.json new file mode 100644 index 0000000..edac927 --- /dev/null +++ b/VAR.Json.Tests/tests/fail18.json @@ -0,0 +1 @@ +[[[[[[[[[[[[[[[[[[[["Too deep"]]]]]]]]]]]]]]]]]]]] \ No newline at end of file diff --git a/VAR.Json.Tests/tests/fail19.json b/VAR.Json.Tests/tests/fail19.json new file mode 100644 index 0000000..3b9c46f --- /dev/null +++ b/VAR.Json.Tests/tests/fail19.json @@ -0,0 +1 @@ +{"Missing colon" null} \ No newline at end of file diff --git a/VAR.Json.Tests/tests/fail20.json b/VAR.Json.Tests/tests/fail20.json new file mode 100644 index 0000000..27c1af3 --- /dev/null +++ b/VAR.Json.Tests/tests/fail20.json @@ -0,0 +1 @@ +{"Double colon":: null} \ No newline at end of file diff --git a/VAR.Json.Tests/tests/fail21.json b/VAR.Json.Tests/tests/fail21.json new file mode 100644 index 0000000..6247457 --- /dev/null +++ b/VAR.Json.Tests/tests/fail21.json @@ -0,0 +1 @@ +{"Comma instead of colon", null} \ No newline at end of file diff --git a/VAR.Json.Tests/tests/fail22.json b/VAR.Json.Tests/tests/fail22.json new file mode 100644 index 0000000..a775258 --- /dev/null +++ b/VAR.Json.Tests/tests/fail22.json @@ -0,0 +1 @@ +["Colon instead of comma": false] \ No newline at end of file diff --git a/VAR.Json.Tests/tests/fail23.json b/VAR.Json.Tests/tests/fail23.json new file mode 100644 index 0000000..494add1 --- /dev/null +++ b/VAR.Json.Tests/tests/fail23.json @@ -0,0 +1 @@ +["Bad value", truth] \ No newline at end of file diff --git a/VAR.Json.Tests/tests/fail24.json b/VAR.Json.Tests/tests/fail24.json new file mode 100644 index 0000000..caff239 --- /dev/null +++ b/VAR.Json.Tests/tests/fail24.json @@ -0,0 +1 @@ +['single quote'] \ No newline at end of file diff --git a/VAR.Json.Tests/tests/fail25.json b/VAR.Json.Tests/tests/fail25.json new file mode 100644 index 0000000..8b7ad23 --- /dev/null +++ b/VAR.Json.Tests/tests/fail25.json @@ -0,0 +1 @@ +[" tab character in string "] \ No newline at end of file diff --git a/VAR.Json.Tests/tests/fail26.json b/VAR.Json.Tests/tests/fail26.json new file mode 100644 index 0000000..845d26a --- /dev/null +++ b/VAR.Json.Tests/tests/fail26.json @@ -0,0 +1 @@ +["tab\ character\ in\ string\ "] \ No newline at end of file diff --git a/VAR.Json.Tests/tests/fail27.json b/VAR.Json.Tests/tests/fail27.json new file mode 100644 index 0000000..6b01a2c --- /dev/null +++ b/VAR.Json.Tests/tests/fail27.json @@ -0,0 +1,2 @@ +["line +break"] \ No newline at end of file diff --git a/VAR.Json.Tests/tests/fail28.json b/VAR.Json.Tests/tests/fail28.json new file mode 100644 index 0000000..621a010 --- /dev/null +++ b/VAR.Json.Tests/tests/fail28.json @@ -0,0 +1,2 @@ +["line\ +break"] \ No newline at end of file diff --git a/VAR.Json.Tests/tests/fail29.json b/VAR.Json.Tests/tests/fail29.json new file mode 100644 index 0000000..47ec421 --- /dev/null +++ b/VAR.Json.Tests/tests/fail29.json @@ -0,0 +1 @@ +[0e] \ No newline at end of file diff --git a/VAR.Json.Tests/tests/fail30.json b/VAR.Json.Tests/tests/fail30.json new file mode 100644 index 0000000..8ab0bc4 --- /dev/null +++ b/VAR.Json.Tests/tests/fail30.json @@ -0,0 +1 @@ +[0e+] \ No newline at end of file diff --git a/VAR.Json.Tests/tests/fail31.json b/VAR.Json.Tests/tests/fail31.json new file mode 100644 index 0000000..1cce602 --- /dev/null +++ b/VAR.Json.Tests/tests/fail31.json @@ -0,0 +1 @@ +[0e+-1] \ No newline at end of file diff --git a/VAR.Json.Tests/tests/fail32.json b/VAR.Json.Tests/tests/fail32.json new file mode 100644 index 0000000..45cba73 --- /dev/null +++ b/VAR.Json.Tests/tests/fail32.json @@ -0,0 +1 @@ +{"Comma instead if closing brace": true, \ No newline at end of file diff --git a/VAR.Json.Tests/tests/fail33.json b/VAR.Json.Tests/tests/fail33.json new file mode 100644 index 0000000..ca5eb19 --- /dev/null +++ b/VAR.Json.Tests/tests/fail33.json @@ -0,0 +1 @@ +["mismatch"} \ No newline at end of file diff --git a/VAR.Json.Tests/tests/pass01.json b/VAR.Json.Tests/tests/pass01.json new file mode 100644 index 0000000..70e2685 --- /dev/null +++ b/VAR.Json.Tests/tests/pass01.json @@ -0,0 +1,58 @@ +[ + "JSON Test Pattern pass1", + {"object with 1 member":["array with 1 element"]}, + {}, + [], + -42, + true, + false, + null, + { + "integer": 1234567890, + "real": -9876.543210, + "e": 0.123456789e-12, + "E": 1.234567890E+34, + "": 23456789012E66, + "zero": 0, + "one": 1, + "space": " ", + "quote": "\"", + "backslash": "\\", + "controls": "\b\f\n\r\t", + "slash": "/ & \/", + "alpha": "abcdefghijklmnopqrstuvwyz", + "ALPHA": "ABCDEFGHIJKLMNOPQRSTUVWYZ", + "digit": "0123456789", + "0123456789": "digit", + "special": "`1~!@#$%^&*()_+-={':[,]}|;.?", + "hex": "\u0123\u4567\u89AB\uCDEF\uabcd\uef4A", + "true": true, + "false": false, + "null": null, + "array":[ ], + "object":{ }, + "address": "50 St. James Street", + "url": "http://www.JSON.org/", + "comment": "// /* */": " ", + " s p a c e d " :[1,2 , 3 + +, + +4 , 5 , 6 ,7 ],"compact":[1,2,3,4,5,6,7], + "jsontext": "{\"object with 1 member\":[\"array with 1 element\"]}", + "quotes": "" \u0022 %22 0x22 034 "", + "\/\\\"\uCAFE\uBABE\uAB98\uFCDE\ubcda\uef4A\b\f\n\r\t`1~!@#$%^&*()_+-=[]{}|;:',./<>?" +: "A key can be any string" + }, + 0.5 ,98.6 +, +99.44 +, + +1066, +1e1, +0.1e1, +1e-1, +1e00,2e+00,2e-00 +,"rosebud"] \ No newline at end of file diff --git a/VAR.Json.Tests/tests/pass02.json b/VAR.Json.Tests/tests/pass02.json new file mode 100644 index 0000000..d3c63c7 --- /dev/null +++ b/VAR.Json.Tests/tests/pass02.json @@ -0,0 +1 @@ +[[[[[[[[[[[[[[[[[[["Not too deep"]]]]]]]]]]]]]]]]]]] \ No newline at end of file diff --git a/VAR.Json.Tests/tests/pass03.json b/VAR.Json.Tests/tests/pass03.json new file mode 100644 index 0000000..4528d51 --- /dev/null +++ b/VAR.Json.Tests/tests/pass03.json @@ -0,0 +1,6 @@ +{ + "JSON Test Pattern pass3": { + "The outermost value": "must be an object or array.", + "In this test": "It is an object." + } +} diff --git a/VAR.Json.sln b/VAR.Json.sln new file mode 100644 index 0000000..94345d0 --- /dev/null +++ b/VAR.Json.sln @@ -0,0 +1,34 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 14 +VisualStudioVersion = 14.0.25420.1 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "VAR.Json", "VAR.Json\VAR.Json.csproj", "{28B3F937-145C-4FD4-A75B-A25EA4CC0428}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "VAR.Json.Tests", "VAR.Json.Tests\VAR.Json.Tests.csproj", "{576297B8-423D-4533-B75A-F186CCFF0D2A}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Notes", "Notes", "{4C23A421-5348-48F1-8B67-A4D43E616FDE}" + ProjectSection(SolutionItems) = preProject + LICENSE.txt = LICENSE.txt + README.md = README.md + EndProjectSection +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {28B3F937-145C-4FD4-A75B-A25EA4CC0428}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {28B3F937-145C-4FD4-A75B-A25EA4CC0428}.Debug|Any CPU.Build.0 = Debug|Any CPU + {28B3F937-145C-4FD4-A75B-A25EA4CC0428}.Release|Any CPU.ActiveCfg = Release|Any CPU + {28B3F937-145C-4FD4-A75B-A25EA4CC0428}.Release|Any CPU.Build.0 = Release|Any CPU + {576297B8-423D-4533-B75A-F186CCFF0D2A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {576297B8-423D-4533-B75A-F186CCFF0D2A}.Debug|Any CPU.Build.0 = Debug|Any CPU + {576297B8-423D-4533-B75A-F186CCFF0D2A}.Release|Any CPU.ActiveCfg = Release|Any CPU + {576297B8-423D-4533-B75A-F186CCFF0D2A}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/VAR.Json/JsonParser.cs b/VAR.Json/JsonParser.cs new file mode 100644 index 0000000..f3bf341 --- /dev/null +++ b/VAR.Json/JsonParser.cs @@ -0,0 +1,637 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Reflection; +using System.Text; + +namespace VAR.Json +{ + public class JsonParser + { + #region Declarations + + private const int MaxRecursiveCount = 20; + + private ParserContext _ctx; + private bool _tainted = false; + + private List _knownTypes = new List(); + + #endregion Declarations + + #region Properties + + public bool Tainted + { + get { return _tainted; } + } + + public List KnownTypes + { + get { return _knownTypes; } + } + + #endregion Properties + + #region Private methods + + private static Dictionary _dictProperties = new Dictionary(); + + private PropertyInfo[] Type_GetProperties(Type type) + { + PropertyInfo[] typeProperties = null; + if (_dictProperties.ContainsKey(type)) { typeProperties = _dictProperties[type]; } + else + { + lock (_dictProperties) + { + if (_dictProperties.ContainsKey(type)) { typeProperties = _dictProperties[type]; } + else + { + typeProperties = type.GetProperties(BindingFlags.Public | BindingFlags.OptionalParamBinding | BindingFlags.Instance); + _dictProperties.Add(type, typeProperties); + } + } + } + return typeProperties; + } + + private float CompareToType(Dictionary obj, Type type) + { + PropertyInfo[] typeProperties = Type_GetProperties(type); + int count = 0; + foreach (PropertyInfo prop in typeProperties) + { + if (obj.ContainsKey(prop.Name)) + { + count++; + } + } + return ((float)count / (float)typeProperties.Length); + } + + private object ConvertToType(Dictionary obj, Type type) + { + PropertyInfo[] typeProperties = Type_GetProperties(type); + object newObj = Activator.CreateInstance(type); + foreach (PropertyInfo prop in typeProperties) + { + if (obj.ContainsKey(prop.Name)) + { + prop.SetValue(newObj, Convert.ChangeType(obj[prop.Name], prop.PropertyType), null); + } + } + return newObj; + } + + private object TryConvertToTypes(Dictionary obj) + { + Type bestMatch = null; + float bestMatchFactor = 0.0f; + foreach (Type type in _knownTypes) + { + float matchFactor = CompareToType(obj, type); + if (matchFactor > bestMatchFactor) + { + bestMatch = type; + bestMatchFactor = matchFactor; + } + } + if (bestMatch != null) + { + return ConvertToType(obj, bestMatch); + } + return obj; + } + + private int ParseHexShort() + { + int value = 0; + for (int i = 0; i < 4; i++) + { + char c = _ctx.Next(); + if (char.IsDigit(c)) + { + value = (value << 4) | (c - '0'); + } + else + { + c = char.ToLower(c); + if (c >= 'a' && c <= 'f') + { + value = (value << 4) | ((c - 'a') + 10); + } + } + } + return value; + } + + private string ParseQuotedString() + { + StringBuilder scratch = new StringBuilder(); + char c = _ctx.SkipWhite(); + if (c == '"') + { + c = _ctx.Next(); + } + do + { + if (c == '\\') + { + c = _ctx.Next(); + if (c == '"') + { + scratch.Append('"'); + } + else if (c == '\\') + { + scratch.Append('\\'); + } + else if (c == '/') + { + scratch.Append('/'); + } + else if (c == 'b') + { + scratch.Append('\b'); + } + else if (c == 'f') + { + scratch.Append('\f'); + } + else if (c == 'n') + { + scratch.Append('\n'); + } + else if (c == 'r') + { + scratch.Append('\r'); + } + else if (c == 't') + { + scratch.Append('\t'); + } + else if (c == 'u') + { + scratch.Append((char)ParseHexShort()); + } + else + { + // StrictRules: Mark as tainted on unknown escaped character + _tainted = true; + } + c = _ctx.Next(); + } + else if (c == '"') + { + _ctx.Next(); + break; + } + else + { + // StrictRules: Mark as tainted on ilegal characters + if (c == '\t' || c == '\n') { _tainted = true; } + + scratch.Append(c); + c = _ctx.Next(); + } + } while (!_ctx.AtEnd()); + return scratch.ToString(); + } + + private string ParseSingleQuotedString() + { + StringBuilder scratch = new StringBuilder(); + char c = _ctx.SkipWhite(); + if (c == '\'') + { + c = _ctx.Next(); + } + do + { + if (c == '\\') + { + c = _ctx.Next(); + if (c == '\'') + { + scratch.Append('\''); + } + else if (c == '\\') + { + scratch.Append('\\'); + } + else if (c == '/') + { + scratch.Append('/'); + } + else if (c == 'b') + { + scratch.Append('\b'); + } + else if (c == 'f') + { + scratch.Append('\f'); + } + else if (c == 'n') + { + scratch.Append('\n'); + } + else if (c == 'r') + { + scratch.Append('\r'); + } + else if (c == 't') + { + scratch.Append('\t'); + } + else if (c == 'u') + { + scratch.Append((char)ParseHexShort()); + } + else + { + // StrictRules: Mark as tainted on unknown escaped character + _tainted = true; + } + c = _ctx.Next(); + } + else if (c == '\'') + { + _ctx.Next(); + break; + } + else + { + // StrictRules: Mark as tainted on ilegal characters + if (c == '\t' || c == '\n') { _tainted = true; } + + scratch.Append(c); + c = _ctx.Next(); + } + } while (!_ctx.AtEnd()); + return scratch.ToString(); + } + + private string ParseString(bool mustBeQuoted = false) + { + char c = _ctx.SkipWhite(); + if (c == '"') + { + return ParseQuotedString(); + } + if (c == '\'') + { + _tainted = true; + return ParseSingleQuotedString(); + } + if (mustBeQuoted) { _tainted = true; } + StringBuilder scratch = new StringBuilder(); + + while (!_ctx.AtEnd() + && (char.IsLetter(c) || char.IsDigit(c) || c == '_')) + { + scratch.Append(c); + c = _ctx.Next(); + } + + return scratch.ToString(); + } + + private object ParseNumber() + { + StringBuilder scratch = new StringBuilder(); + bool isFloat = false; + bool isExp = false; + int numberLenght = 0; + int expLenght = 0; + char c; + c = _ctx.SkipWhite(); + + // Sign + if (c == '-') + { + scratch.Append('-'); + c = _ctx.Next(); + } + + // Integer part + bool leadingZeroes = true; + int leadingZeroesLenght = 0; + while (char.IsDigit(c)) + { + // Count leading zeroes + if (leadingZeroes && c == '0') { leadingZeroesLenght++; } + else { leadingZeroes = false; } + + scratch.Append(c); + c = _ctx.Next(); + numberLenght++; + } + + // StrictRules: Mark as tainted with leading zeroes + if ((leadingZeroesLenght > 0 && leadingZeroesLenght != numberLenght) || leadingZeroesLenght > 1) + { + _tainted = true; + } + + // Decimal part + if (c == '.') + { + isFloat = true; + scratch.Append("."); + c = _ctx.Next(); + while (char.IsDigit(c)) + { + scratch.Append(c); + c = _ctx.Next(); + numberLenght++; + } + } + + if (numberLenght == 0) + { + _tainted = true; + return null; + } + + // Exponential part + if (c == 'e' || c == 'E') + { + isFloat = true; + isExp = true; + scratch.Append('E'); + c = _ctx.Next(); + if (c == '+' || c == '-') + { + scratch.Append(c); + c = _ctx.Next(); + } + while (char.IsDigit(c)) + { + scratch.Append(c); + c = _ctx.Next(); + numberLenght++; + expLenght++; + } + } + + if (isExp && expLenght == 0) + { + _tainted = true; + return null; + } + + // Build number from the parsed string + string s = scratch.ToString(); + if (isFloat) + { + if (numberLenght < 17) + { + return Convert.ToDouble(s, CultureInfo.InvariantCulture); + } + else + { + return Convert.ToDecimal(s, CultureInfo.InvariantCulture); + } + } + else + { + return Convert.ToInt32(s); + } + } + + private List ParseArray(int recursiveCount = 1) + { + // StrictRules: Mark as tainted when MaxRecursiveCount is exceeded + if (recursiveCount >= MaxRecursiveCount) { _tainted = true; } + + bool correct = false; + char c = _ctx.SkipWhite(); + List array = new List(); + if (c == '[') + { + _ctx.Next(); + } + bool? expectValue = null; + do + { + c = _ctx.SkipWhite(); + if (c == ']') + { + // StrictRules: Mark as tainted when unexpected end of array + if (expectValue == true) { _tainted = true; } + correct = true; + _ctx.Next(); + break; + } + else if (c == ',') + { + // StrictRules: Mark as tainted when unexpected comma on array + if (expectValue == true || array.Count == 0) { _tainted = true; } + + _ctx.Next(); + expectValue = true; + } + else + { + // StrictRules: Mark as tainted when unexpected value on array + if (expectValue == false) { _tainted = true; } + + array.Add(ParseValue(recursiveCount + 1)); + expectValue = false; + } + } while (!_ctx.AtEnd()); + if (correct == false) + { + _tainted = true; + } + return array; + } + + private Dictionary ParseObject(int recursiveCount = 1) + { + // StrictRules: Mark as tainted when MaxRecursiveCount is exceeded + if (recursiveCount >= MaxRecursiveCount) { _tainted = true; } + + bool correct = false; + char c = _ctx.SkipWhite(); + Dictionary obj = new Dictionary(); + if (c == '{') + { + _ctx.Next(); + } + string attributeName = null; + object attributeValue; + bool? expectedKey = null; + bool? expectedValue = null; + do + { + c = _ctx.SkipWhite(); + if (c == ':') + { + _ctx.Next(); + if (expectedValue == true) + { + attributeValue = ParseValue(recursiveCount + 1); + obj.Add(attributeName, attributeValue); + expectedKey = null; + expectedValue = false; + } + } + else if (c == ',') + { + _ctx.Next(); + c = _ctx.SkipWhite(); + expectedKey = true; + expectedValue = false; + } + else if (c == '}') + { + // StrictRules: Mark as tainted on unexpected end of object + if(expectedValue == true || expectedKey == true) + { + _tainted = true; + } + correct = true; + _ctx.Next(); + break; + } + else + { + if (expectedKey != false) + { + attributeName = ParseString(true); + c = _ctx.SkipWhite(); + expectedKey = false; + expectedValue = true; + } + else + { + // Unexpected character + _tainted = true; + break; + } + } + } while (!_ctx.AtEnd()); + if (correct == false) + { + _tainted = true; + } + return obj; + } + + private object ParseValue(int recusiveCount = 1) + { + object token = null; + char c = _ctx.SkipWhite(); + switch (c) + { + case '"': + token = ParseQuotedString(); + break; + + case '\'': + // StrictRules: Mark as tainted when parsing single quoted strings + _tainted = true; + token = ParseSingleQuotedString(); + break; + + case '{': + Dictionary obj = ParseObject(recusiveCount); + token = TryConvertToTypes(obj); + break; + + case '[': + token = ParseArray(recusiveCount); + break; + + default: + if (char.IsDigit(c) || c == '-') + { + token = ParseNumber(); + } + else + { + string aux = ParseString(); + if (aux.CompareTo("true") == 0) + { + token = true; + } + else if (aux.CompareTo("false") == 0) + { + token = false; + } + else if (aux.CompareTo("null") == 0) + { + token = null; + } + else + { + // Unexpected string + if (aux.Length == 0) + { + _ctx.Next(); + } + _tainted = true; + token = null; + } + } + break; + } + return token; + } + + private string CleanIdentifier(string input) + { + int i; + char c; + i = input.Length - 1; + if (i < 0) + { + return input; + } + c = input[i]; + while (char.IsLetter(c) || char.IsDigit(c) || c == '_') + { + i--; + if (i < 0) + { + break; + } + c = input[i]; + } + return input.Substring(i + 1); + } + + #endregion Private methods + + #region Public methods + + public object Parse(string text) + { + // Get the first object + _ctx = new ParserContext(text); + _tainted = false; + _ctx.Mark(); + object obj = ParseValue(); + _ctx.SkipWhite(); + if (_ctx.AtEnd()) + { + // StrictRules: Mark as tainted when top level is not object or array + if (obj is string || obj is decimal || obj is int || obj is double || obj is float) + { + _tainted = true; + } + + return obj; + } + + // StrictRules: Mark as tainted when there is more content + _tainted = true; + + return obj; + } + + #endregion Public methods + } +} \ No newline at end of file diff --git a/VAR.Json/JsonWriter.cs b/VAR.Json/JsonWriter.cs new file mode 100644 index 0000000..8a89237 --- /dev/null +++ b/VAR.Json/JsonWriter.cs @@ -0,0 +1,337 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Reflection; +using System.Text; + +namespace VAR.Json +{ + public class JsonWriter + { + #region Declarations + + private bool _indent = false; + private bool _useTabForIndent = false; + private int _indentChars = 4; + private int _indentThresold = 3; + + #endregion Declarations + + #region Creator + + public JsonWriter() + { + } + + public JsonWriter(int indentChars) + { + this._indent = true; + this._indentChars = indentChars; + this._useTabForIndent = false; + } + + public JsonWriter(bool useTabForIndent) + { + this._indent = true; + this._useTabForIndent = useTabForIndent; + } + + #endregion Creator + + #region Private methods + + private bool IsValue(Object obj) + { + if (obj == null) + { + return true; + } + if ((obj is float) || (obj is double) || + (obj is System.Int16) || (obj is System.Int32) || (obj is System.Int64) + || (obj is String) || (obj is Boolean)) + { + return true; + } + return false; + } + + private void WriteIndent(StringBuilder sbOutput, int level) + { + if (!_indent) + { + return; + } + sbOutput.Append('\n'); + if (_useTabForIndent) + { + for (int i = 0; i < level; i++) { sbOutput.Append('\t'); } + } + else + { + int n = level * _indentChars; + for (int i = 0; i < n; i++) { sbOutput.Append(' '); } + } + } + + private void WriteString(StringBuilder sbOutput, string str) + { + sbOutput.Append('"'); + char c; + int n = str.Length; + for (int i = 0; i < n; i++) + { + c = str[i]; + if (c == '"') { sbOutput.Append("\\\""); } + else if (c == '\\') { sbOutput.Append("\\\\"); } + else if (c == '/') { sbOutput.Append("\\/"); } + else if (c == '\b') { sbOutput.Append("\\b"); } + else if (c == '\f') { sbOutput.Append("\\f"); } + else if (c == '\n') { sbOutput.Append("\\n"); } + else if (c == '\r') { sbOutput.Append("\\r"); } + else if (c == '\t') { sbOutput.Append("\\t"); } + else if (c < 32 || c >= 127) { sbOutput.AppendFormat("\\u{0:X04}", (int)c); } + else { sbOutput.Append(c); } + } + sbOutput.Append('"'); + } + + private void WriteValue(StringBuilder sbOutput, Object obj, int level, bool useReflection) + { + if (obj == null || obj is DBNull) + { + // NULL + sbOutput.Append("null"); + } + else if ((obj is float) || (obj is double) || + (obj is System.Int16) || (obj is System.Int32) || (obj is System.Int64)) + { + // Numbers + sbOutput.Append(obj.ToString()); + } + else if (obj is String) + { + // Strings + WriteString(sbOutput, (String)obj); + } + else if (obj is Boolean) + { + // Booleans + sbOutput.Append(((Boolean)obj) ? "true" : "false"); + } + else if (obj is DateTime) + { + // DateTime + sbOutput.Append('"'); + sbOutput.Append(((DateTime)obj).ToString("yyyy-MM-ddTHH:mm:ssZ")); + sbOutput.Append('"'); + } + else if (obj is IDictionary) + { + // Objects + WriteObject(sbOutput, obj, level); + } + else if (obj is IEnumerable) + { + // Array/List + WriteList(sbOutput, obj, level); + } + else + { + if (useReflection) + { + // Reflected object + WriteReflectedObject(sbOutput, obj, level); + } + else + { + WriteString(sbOutput, Convert.ToString(obj)); + } + } + } + + private void WriteList(StringBuilder sbOutput, Object obj, int level) + { + IEnumerable list = (IEnumerable)obj; + int n = 0; + + // Check if it is a leaf object + bool isLeaf = true; + foreach (object childObj in list) + { + if (!IsValue(childObj)) + { + isLeaf = false; + } + n++; + } + + // Empty + if (n == 0) + { + sbOutput.Append("[ ]"); + return; + } + + // Write array + bool first = true; + sbOutput.Append("[ "); + if (!isLeaf || n > _indentThresold) + { + WriteIndent(sbOutput, level + 1); + } + foreach (object childObj in list) + { + if (!first) + { + sbOutput.Append(", "); + if (!isLeaf || n > _indentThresold) + { + WriteIndent(sbOutput, level + 1); + } + } + first = false; + WriteValue(sbOutput, childObj, level + 1, true); + } + if (!isLeaf || n > _indentThresold) + { + WriteIndent(sbOutput, level); + } + sbOutput.Append(" ]"); + } + + private void WriteObject(StringBuilder sbOutput, Object obj, int level) + { + IDictionary map = (IDictionary)obj; + int n = map.Count; + + // Empty + if (map.Count == 0) + { + sbOutput.Append("{ }"); + return; + } + + // Check if it is a leaf object + bool isLeaf = true; + foreach (object value in map.Values) + { + if (!IsValue(value)) + { + isLeaf = false; + break; + } + } + + // Write object + bool first = true; + sbOutput.Append("{ "); + if (!isLeaf || n > _indentThresold) + { + WriteIndent(sbOutput, level + 1); + } + foreach (object key in map.Keys) + { + object value = map[key]; + if (!first) + { + sbOutput.Append(", "); + if (!isLeaf || n > _indentThresold) + { + WriteIndent(sbOutput, level + 1); + } + } + first = false; + WriteString(sbOutput, Convert.ToString(key)); + sbOutput.Append(": "); + WriteValue(sbOutput, value, level + 1, true); + } + if (!isLeaf || n > _indentThresold) + { + WriteIndent(sbOutput, level); + } + sbOutput.Append(" }"); + } + + private void WriteReflectedObject(StringBuilder sbOutput, Object obj, int level) + { + Type type = obj.GetType(); + PropertyInfo[] rawProperties = type.GetProperties(BindingFlags.Public | BindingFlags.Instance); + List properties = new List(); + foreach (PropertyInfo property in rawProperties) + { + if (property.CanRead) + { + properties.Add(property); + } + } + int n = properties.Count; + + // Empty + if (n == 0) + { + sbOutput.Append("{ }"); + return; + } + + // Check if it is a leaf object + bool isLeaf = true; + foreach (PropertyInfo property in properties) + { + object value = property.GetValue(obj, null); + if (!IsValue(value)) + { + isLeaf = false; + break; + } + } + + // Write object + bool first = true; + sbOutput.Append("{ "); + if (!isLeaf || n > _indentThresold) + { + WriteIndent(sbOutput, level + 1); + } + foreach (PropertyInfo property in properties) + { + object value = null; + MethodInfo getMethod = property.GetGetMethod(); + ParameterInfo[] parameters = getMethod.GetParameters(); + if (parameters.Length == 0) + { + value = property.GetValue(obj, null); + } + if (!first) + { + sbOutput.Append(", "); + if (!isLeaf || n > _indentThresold) + { + WriteIndent(sbOutput, level + 1); + } + } + first = false; + WriteString(sbOutput, property.Name); + sbOutput.Append(": "); + WriteValue(sbOutput, value, level + 1, false); + } + if (!isLeaf || n > _indentThresold) + { + WriteIndent(sbOutput, level); + } + sbOutput.Append(" }"); + } + + #endregion Private methods + + #region Public methods + + public String Write(Object obj) + { + StringBuilder sbOutput = new StringBuilder(); + WriteValue(sbOutput, obj, 0, true); + return sbOutput.ToString(); + } + + #endregion Public methods + } +} \ No newline at end of file diff --git a/VAR.Json/ParserContext.cs b/VAR.Json/ParserContext.cs new file mode 100644 index 0000000..b41fd84 --- /dev/null +++ b/VAR.Json/ParserContext.cs @@ -0,0 +1,84 @@ +using System; + +namespace VAR.Json +{ + public class ParserContext + { + #region Declarations + + private string _text; + private int _length; + private int _i; + private int _markStart; + + #endregion Declarations + + #region Creator + + public ParserContext(string text) + { + _text = text; + _length = text.Length; + _i = 0; + _markStart = 0; + } + + #endregion Creator + + #region Public methods + + public char SkipWhite() + { + while (_i < _length && char.IsWhiteSpace(_text[_i])) + { + _i++; + } + if (AtEnd()) + { + return (char)0; + } + return _text[_i]; + } + + public char Next() + { + _i++; + if (AtEnd()) + { + return (char)0; + } + return _text[_i]; + } + + public bool AtEnd() + { + return _i >= _length; + } + + public void Mark() + { + _markStart = _i; + } + + public string GetMarked() + { + if (_i < _length && _markStart < _length) + { + return _text.Substring(_markStart, _i - _markStart); + } + else + { + if (_markStart < _length) + { + return _text.Substring(_markStart, _length - _markStart); + } + else + { + return string.Empty; + } + } + } + + #endregion Public methods + } +} \ No newline at end of file diff --git a/VAR.Json/Properties/AssemblyInfo.cs b/VAR.Json/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..a558758 --- /dev/null +++ b/VAR.Json/Properties/AssemblyInfo.cs @@ -0,0 +1,14 @@ +using System.Reflection; +using System.Runtime.InteropServices; + +[assembly: AssemblyTitle("VAR.Json")] +[assembly: AssemblyDescription("Json Library")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("VAR")] +[assembly: AssemblyProduct("VAR.Json")] +[assembly: AssemblyCopyright("Copyright © VAR 2016")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] +[assembly: ComVisible(false)] +[assembly: Guid("28b3f937-145c-4fd4-a75b-a25ea4cc0428")] +[assembly: AssemblyVersion("1.0.*")] \ No newline at end of file diff --git a/VAR.Json/VAR.Json.csproj b/VAR.Json/VAR.Json.csproj new file mode 100644 index 0000000..00abf9a --- /dev/null +++ b/VAR.Json/VAR.Json.csproj @@ -0,0 +1,50 @@ + + + + + Debug + AnyCPU + {28B3F937-145C-4FD4-A75B-A25EA4CC0428} + Library + Properties + VAR.Json + VAR.Json + v4.6.1 + 512 + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + + + + + + + + + + + \ No newline at end of file