diff --git a/LICENSE.txt b/LICENSE.txt index 41c79b1..02ab08d 100644 --- a/LICENSE.txt +++ b/LICENSE.txt @@ -1,6 +1,6 @@ The MIT License (MIT) -Copyright (c) 2016-2022 Valeriano Alfonso Rodriguez +Copyright (c) 2016-2025 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 diff --git a/VAR.Json.Tests/JsonParser_Tests.cs b/VAR.Json.Tests/JsonParser_Tests.cs index 0b4d264..a9b1d1f 100644 --- a/VAR.Json.Tests/JsonParser_Tests.cs +++ b/VAR.Json.Tests/JsonParser_Tests.cs @@ -1,406 +1,409 @@ using System.Collections.Generic; using Xunit; -namespace VAR.Json.Tests +namespace VAR.Json.Tests; + +public class JsonParser_Tests { - public class JsonParser_Tests + #region Parse + + private class SwallowObject { - #region Parse + public string? Text { get; set; } + public int Number { get; set; } + } - private class SwallowObject - { - public string Text { get; set; } - public int Number { get; set; } - } + [Fact] + public void Parse__SwallowObject() + { + JsonParser parser = new(); + parser.KnownTypes.Add(typeof(SwallowObject)); + SwallowObject? result = parser.Parse(@"{""Text"": ""AAAA"", ""Number"": 42}") as SwallowObject; + Assert.False(parser.Tainted); + Assert.Equal("AAAA", result?.Text); + Assert.Equal(42, result?.Number); + } - [Fact] - public void Parse__SwallowObject() - { - JsonParser parser = new JsonParser(); - parser.KnownTypes.Add(typeof(SwallowObject)); - SwallowObject result = parser.Parse(@"{""Text"": ""AAAA"", ""Number"": 42}") as SwallowObject; - Assert.False(parser.Tainted); - Assert.Equal("AAAA", result?.Text); - Assert.Equal(42, result.Number); - } + private class DeeperObject_L1 + { + public string? Name { get; set; } + public SwallowObject? Object { get; set; } + } - private class DeeperObject_L1 - { - public string Name { get; set; } - public SwallowObject Object { get; set; } - } + [Fact] + public void Parse__DeeperObject_L1() + { + JsonParser parser = new(); + parser.KnownTypes.Add(typeof(SwallowObject)); + parser.KnownTypes.Add(typeof(DeeperObject_L1)); + DeeperObject_L1? result = + parser.Parse(@"{""Name"": ""Thing"", ""Object"": {""Text"": ""AAAA"", ""Number"": 42}}") as + DeeperObject_L1; + Assert.False(parser.Tainted); + Assert.Equal("Thing", result?.Name); + Assert.Equal("AAAA", result?.Object?.Text); + Assert.Equal(42, result?.Object?.Number); + } - [Fact] - public void Parse__DeeperObject_L1() - { - JsonParser parser = new JsonParser(); - parser.KnownTypes.Add(typeof(SwallowObject)); - parser.KnownTypes.Add(typeof(DeeperObject_L1)); - DeeperObject_L1 result = - parser.Parse(@"{""Name"": ""Thing"", ""Object"": {""Text"": ""AAAA"", ""Number"": 42}}") as - DeeperObject_L1; - Assert.False(parser.Tainted); - Assert.Equal("Thing", result.Name); - Assert.Equal("AAAA", result.Object.Text); - Assert.Equal(42, result.Object.Number); - } + private class DeeperObject_L2 + { + public int Count { get; set; } + public DeeperObject_L1? Object { get; set; } + } - private class DeeperObject_L2 - { - public int Count { get; set; } - public DeeperObject_L1 Object { get; set; } - } + [Fact] + public void Parse__DeeperObject_L2() + { + JsonParser parser = new(); + parser.KnownTypes.Add(typeof(SwallowObject)); + parser.KnownTypes.Add(typeof(DeeperObject_L1)); + parser.KnownTypes.Add(typeof(DeeperObject_L2)); + DeeperObject_L2? result = + parser.Parse( + @"{""Count"": 1, ""Object"": {""Name"": ""Thing"", ""Object"": {""Text"": ""AAAA"", ""Number"": 42}}}") + as DeeperObject_L2; + Assert.False(parser.Tainted); + Assert.NotNull(result); + Assert.Equal(1, result?.Count); + Assert.Equal("Thing", result?.Object?.Name); + Assert.Equal("AAAA", result?.Object?.Object?.Text); + Assert.Equal(42, result?.Object?.Object?.Number); + } - [Fact] - public void Parse__DeeperObject_L2() - { - JsonParser parser = new JsonParser(); - parser.KnownTypes.Add(typeof(SwallowObject)); - parser.KnownTypes.Add(typeof(DeeperObject_L1)); - parser.KnownTypes.Add(typeof(DeeperObject_L2)); - DeeperObject_L2 result = - parser.Parse( - @"{""Count"": 1, ""Object"": {""Name"": ""Thing"", ""Object"": {""Text"": ""AAAA"", ""Number"": 42}}}") - as DeeperObject_L2; - Assert.False(parser.Tainted); - Assert.Equal(1, result.Count); - Assert.Equal("Thing", result.Object.Name); - Assert.Equal("AAAA", result.Object.Object.Text); - Assert.Equal(42, result.Object.Object.Number); - } + [Fact] + public void Parse__SwallowObjectArray() + { + JsonParser parser = new(); + parser.KnownTypes.Add(typeof(SwallowObject)); + List? result = parser.Parse(@"[{""Text"": ""AAAA"", ""Number"": 42}]") as List; + Assert.False(parser.Tainted); + Assert.NotNull(result); + Assert.Single(result); + Assert.Equal("AAAA", result?[0].Text); + Assert.Equal(42, result?[0].Number); + } - [Fact] - public void Parse__SwallowObjectArray() - { - JsonParser parser = new JsonParser(); - parser.KnownTypes.Add(typeof(SwallowObject)); - List result = parser.Parse(@"[{""Text"": ""AAAA"", ""Number"": 42}]") as List; - Assert.False(parser.Tainted); - Assert.Single(result); - Assert.Equal("AAAA", result[0].Text); - Assert.Equal(42, result[0].Number); - } + private class DeeperObjectArray_L1 + { + public int Count { get; set; } + public List? Array { get; set; } + } - private class DeeperObjectArray_L1 - { - public int Count { get; set; } - public List Array { get; set; } - } + [Fact] + public void Parse__DeeperObjectArray_L1() + { + JsonParser parser = new(); + parser.KnownTypes.Add(typeof(SwallowObject)); + parser.KnownTypes.Add(typeof(DeeperObjectArray_L1)); + DeeperObjectArray_L1? result = + parser.Parse(@"{""Count"": 1, ""Array"": [{""Text"": ""AAAA"", ""Number"": 42}]}") as + DeeperObjectArray_L1; + Assert.False(parser.Tainted); + Assert.NotNull(result); + Assert.Equal(1, result?.Count); + Assert.Equal("AAAA", result?.Array?[0].Text); + Assert.Equal(42, result?.Array?[0].Number); + } - [Fact] - public void Parse__DeeperObjectArray_L1() - { - JsonParser parser = new JsonParser(); - parser.KnownTypes.Add(typeof(SwallowObject)); - parser.KnownTypes.Add(typeof(DeeperObjectArray_L1)); - DeeperObjectArray_L1 result = - parser.Parse(@"{""Count"": 1, ""Array"": [{""Text"": ""AAAA"", ""Number"": 42}]}") as - DeeperObjectArray_L1; - Assert.False(parser.Tainted); - Assert.Equal(1, result.Count); - Assert.Equal("AAAA", result.Array[0].Text); - Assert.Equal(42, result.Array[0].Number); - } + private class DeeperObjectArray_L2 + { + public string Name { get; set; } = string.Empty; + public List Objects { get; set; } = []; + } - private class DeeperObjectArray_L2 - { - public string Name { get; set; } - public List Objects { get; set; } - } + [Fact] + public void Parse__DeeperObjectArray_L2() + { + JsonParser parser = new(); + parser.KnownTypes.Add(typeof(SwallowObject)); + parser.KnownTypes.Add(typeof(DeeperObjectArray_L1)); + parser.KnownTypes.Add(typeof(DeeperObjectArray_L2)); + DeeperObjectArray_L2? result = + parser.Parse( + @"{""Name"": ""Thing"", ""Objects"": [{""Count"": 1, ""Array"": [{""Text"": ""AAAA"", ""Number"": 42}]}]}") + as DeeperObjectArray_L2; + Assert.False(parser.Tainted); + Assert.Equal("Thing", result?.Name); + Assert.Equal(1, result?.Objects[0].Count); + Assert.Equal("AAAA", result?.Objects[0].Array?[0].Text); + Assert.Equal(42, result?.Objects[0].Array?[0].Number); + } - [Fact] - public void Parse__DeeperObjectArray_L2() - { - JsonParser parser = new JsonParser(); - parser.KnownTypes.Add(typeof(SwallowObject)); - parser.KnownTypes.Add(typeof(DeeperObjectArray_L1)); - parser.KnownTypes.Add(typeof(DeeperObjectArray_L2)); - DeeperObjectArray_L2 result = - parser.Parse( - @"{""Name"": ""Thing"", ""Objects"": [{""Count"": 1, ""Array"": [{""Text"": ""AAAA"", ""Number"": 42}]}]}") - as DeeperObjectArray_L2; - Assert.False(parser.Tainted); - Assert.Equal("Thing", result.Name); - Assert.Equal(1, result.Objects[0].Count); - Assert.Equal("AAAA", result.Objects[0].Array[0].Text); - Assert.Equal(42, result.Objects[0].Array[0].Number); - } + #endregion Parse - #endregion Parse + #region Validity tests - #region Validity tests + [Fact] + public void Parse__Validity_Fail01() + { + JsonParser parser = new(); + parser.Parse(@"""A JSON payload should be an object or array, not a string."""); + Assert.True(parser.Tainted); + } - [Fact] - public void Parse__Validity_Fail01() - { - JsonParser parser = new JsonParser(); - parser.Parse(@"""A JSON payload should be an object or array, not a string."""); - Assert.True(parser.Tainted); - } + [Fact] + public void Parse__Validity_Fail02() + { + JsonParser parser = new(); + parser.Parse(@"[""Unclosed array"""); + Assert.True(parser.Tainted); + } - [Fact] - public void Parse__Validity_Fail02() - { - JsonParser parser = new JsonParser(); - parser.Parse(@"[""Unclosed array"""); - Assert.True(parser.Tainted); - } + [Fact] + public void Parse__Validity_Fail03() + { + JsonParser parser = new(); + parser.Parse(@"{unquoted_key: ""keys must be quoted""}"); + Assert.True(parser.Tainted); + } - [Fact] - public void Parse__Validity_Fail03() - { - JsonParser parser = new JsonParser(); - parser.Parse(@"{unquoted_key: ""keys must be quoted""}"); - Assert.True(parser.Tainted); - } + [Fact] + public void Parse__Validity_Fail04() + { + JsonParser parser = new(); + parser.Parse(@"[""extra comma"",]"); + Assert.True(parser.Tainted); + } - [Fact] - public void Parse__Validity_Fail04() - { - JsonParser parser = new JsonParser(); - parser.Parse(@"[""extra comma"",]"); - Assert.True(parser.Tainted); - } + [Fact] + public void Parse__Validity_Fail05() + { + JsonParser parser = new(); + parser.Parse(@"[""double extra comma"",,]"); + Assert.True(parser.Tainted); + } - [Fact] - public void Parse__Validity_Fail05() - { - JsonParser parser = new JsonParser(); - parser.Parse(@"[""double extra comma"",,]"); - Assert.True(parser.Tainted); - } + [Fact] + public void Parse__Validity_Fail06() + { + JsonParser parser = new(); + parser.Parse(@"[ , ""<-- missing value""]"); + Assert.True(parser.Tainted); + } - [Fact] - public void Parse__Validity_Fail06() - { - JsonParser parser = new JsonParser(); - parser.Parse(@"[ , ""<-- missing value""]"); - Assert.True(parser.Tainted); - } + [Fact] + public void Parse__Validity_Fail07() + { + JsonParser parser = new(); + parser.Parse(@"[""Comma after the close""],"); + Assert.True(parser.Tainted); + } - [Fact] - public void Parse__Validity_Fail07() - { - JsonParser parser = new JsonParser(); - parser.Parse(@"[""Comma after the close""],"); - Assert.True(parser.Tainted); - } + [Fact] + public void Parse__Validity_Fail08() + { + JsonParser parser = new(); + parser.Parse(@"[""Extra close""]]"); + Assert.True(parser.Tainted); + } - [Fact] - public void Parse__Validity_Fail08() - { - JsonParser parser = new JsonParser(); - parser.Parse(@"[""Extra close""]]"); - Assert.True(parser.Tainted); - } + [Fact] + public void Parse__Validity_Fail09() + { + JsonParser parser = new(); + parser.Parse(@"{""Extra comma"": true,}"); + Assert.True(parser.Tainted); + } - [Fact] - public void Parse__Validity_Fail09() - { - JsonParser parser = new JsonParser(); - parser.Parse(@"{""Extra comma"": true,}"); - Assert.True(parser.Tainted); - } + [Fact] + public void Parse__Validity_Fail10() + { + JsonParser parser = new(); + parser.Parse(@"{""Extra value after close"": true} ""misplaced quoted value"""); + Assert.True(parser.Tainted); + } - [Fact] - public void Parse__Validity_Fail10() - { - JsonParser parser = new JsonParser(); - parser.Parse(@"{""Extra value after close"": true} ""misplaced quoted value"""); - Assert.True(parser.Tainted); - } + [Fact] + public void Parse__Validity_Fail11() + { + JsonParser parser = new(); + parser.Parse(@"{""Illegal expression"": 1 + 2}"); + Assert.True(parser.Tainted); + } - [Fact] - public void Parse__Validity_Fail11() - { - JsonParser parser = new JsonParser(); - parser.Parse(@"{""Illegal expression"": 1 + 2}"); - Assert.True(parser.Tainted); - } + [Fact] + public void Parse__Validity_Fail12() + { + JsonParser parser = new(); + parser.Parse(@"{""Illegal invocation"": alert()}"); + Assert.True(parser.Tainted); + } - [Fact] - public void Parse__Validity_Fail12() - { - JsonParser parser = new JsonParser(); - parser.Parse(@"{""Illegal invocation"": alert()}"); - Assert.True(parser.Tainted); - } + [Fact] + public void Parse__Validity_Fail13() + { + JsonParser parser = new(); + parser.Parse(@"{""Numbers cannot have leading zeroes"": 013}"); + Assert.True(parser.Tainted); + } - [Fact] - public void Parse__Validity_Fail13() - { - JsonParser parser = new JsonParser(); - parser.Parse(@"{""Numbers cannot have leading zeroes"": 013}"); - Assert.True(parser.Tainted); - } + [Fact] + public void Parse__Validity_Fail14() + { + JsonParser parser = new(); + parser.Parse(@"{""Numbers cannot be hex"": 0x14}"); + Assert.True(parser.Tainted); + } - [Fact] - public void Parse__Validity_Fail14() - { - JsonParser parser = new JsonParser(); - parser.Parse(@"{""Numbers cannot be hex"": 0x14}"); - Assert.True(parser.Tainted); - } + [Fact] + public void Parse__Validity_Fail15() + { + JsonParser parser = new(); + parser.Parse(@"[""Illegal backslash escape: \x15""]"); + Assert.True(parser.Tainted); + } - [Fact] - public void Parse__Validity_Fail15() - { - JsonParser parser = new JsonParser(); - parser.Parse(@"[""Illegal backslash escape: \x15""]"); - Assert.True(parser.Tainted); - } + [Fact] + public void Parse__Validity_Fail16() + { + JsonParser parser = new(); + parser.Parse(@"[\naked]"); + Assert.True(parser.Tainted); + } - [Fact] - public void Parse__Validity_Fail16() - { - JsonParser parser = new JsonParser(); - parser.Parse(@"[\naked]"); - Assert.True(parser.Tainted); - } + [Fact] + public void Parse__Validity_Fail17() + { + JsonParser parser = new(); + parser.Parse(@"[""Illegal backslash escape: \017""]"); + Assert.True(parser.Tainted); + } - [Fact] - public void Parse__Validity_Fail17() - { - JsonParser parser = new JsonParser(); - parser.Parse(@"[""Illegal backslash escape: \017""]"); - Assert.True(parser.Tainted); - } + [Fact] + public void Parse__Validity_Fail18() + { + JsonParser parser = new(); + parser.Parse(@"[[[[[[[[[[[[[[[[[[[[""Too deep""]]]]]]]]]]]]]]]]]]]]"); + Assert.True(parser.Tainted); + } - [Fact] - public void Parse__Validity_Fail18() - { - JsonParser parser = new JsonParser(); - parser.Parse(@"[[[[[[[[[[[[[[[[[[[[""Too deep""]]]]]]]]]]]]]]]]]]]]"); - Assert.True(parser.Tainted); - } + [Fact] + public void Parse__Validity_Fail19() + { + JsonParser parser = new(); + parser.Parse(@"{""Missing colon"" null}"); + Assert.True(parser.Tainted); + } - [Fact] - public void Parse__Validity_Fail19() - { - JsonParser parser = new JsonParser(); - parser.Parse(@"{""Missing colon"" null}"); - Assert.True(parser.Tainted); - } + [Fact] + public void Parse__Validity_Fail20() + { + JsonParser parser = new(); + parser.Parse(@"{""Double colon"":: null}"); + Assert.True(parser.Tainted); + } - [Fact] - public void Parse__Validity_Fail20() - { - JsonParser parser = new JsonParser(); - parser.Parse(@"{""Double colon"":: null}"); - Assert.True(parser.Tainted); - } + [Fact] + public void Parse__Validity_Fail21() + { + JsonParser parser = new(); + parser.Parse(@"{""Comma instead of colon"", null}"); + Assert.True(parser.Tainted); + } - [Fact] - public void Parse__Validity_Fail21() - { - JsonParser parser = new JsonParser(); - parser.Parse(@"{""Comma instead of colon"", null}"); - Assert.True(parser.Tainted); - } + [Fact] + public void Parse__Validity_Fail22() + { + JsonParser parser = new(); + parser.Parse(@"[""Colon instead of comma"": false]"); + Assert.True(parser.Tainted); + } - [Fact] - public void Parse__Validity_Fail22() - { - JsonParser parser = new JsonParser(); - parser.Parse(@"[""Colon instead of comma"": false]"); - Assert.True(parser.Tainted); - } + [Fact] + public void Parse__Validity_Fail23() + { + JsonParser parser = new(); + parser.Parse(@"[""Bad value"", truth]"); + Assert.True(parser.Tainted); + } - [Fact] - public void Parse__Validity_Fail23() - { - JsonParser parser = new JsonParser(); - parser.Parse(@"[""Bad value"", truth]"); - Assert.True(parser.Tainted); - } + [Fact] + public void Parse__Validity_Fail24() + { + JsonParser parser = new(); + parser.Parse(@"['single quote']"); + Assert.True(parser.Tainted); + } - [Fact] - public void Parse__Validity_Fail24() - { - JsonParser parser = new JsonParser(); - parser.Parse(@"['single quote']"); - Assert.True(parser.Tainted); - } + [Fact] + public void Parse__Validity_Fail25() + { + JsonParser parser = new(); + parser.Parse(@"["" tab character in string ""]"); + Assert.True(parser.Tainted); + } - [Fact] - public void Parse__Validity_Fail25() - { - JsonParser parser = new JsonParser(); - parser.Parse(@"["" tab character in string ""]"); - Assert.True(parser.Tainted); - } + [Fact] + public void Parse__Validity_Fail26() + { + JsonParser parser = new(); + parser.Parse(@"[""tab\ character\ in\ string\ ""]"); + Assert.True(parser.Tainted); + } - [Fact] - public void Parse__Validity_Fail26() - { - JsonParser parser = new JsonParser(); - parser.Parse(@"[""tab\ character\ in\ string\ ""]"); - Assert.True(parser.Tainted); - } - - [Fact] - public void Parse__Validity_Fail27() - { - JsonParser parser = new JsonParser(); - parser.Parse(@"[""line + [Fact] + public void Parse__Validity_Fail27() + { + JsonParser parser = new(); + parser.Parse(@"[""line break""]"); - Assert.True(parser.Tainted); - } + Assert.True(parser.Tainted); + } - [Fact] - public void Parse__Validity_Fail28() - { - JsonParser parser = new JsonParser(); - parser.Parse(@"[""line\ + [Fact] + public void Parse__Validity_Fail28() + { + JsonParser parser = new(); + parser.Parse(@"[""line\ break""]"); - Assert.True(parser.Tainted); - } + Assert.True(parser.Tainted); + } - [Fact] - public void Parse__Validity_Fail29() - { - JsonParser parser = new JsonParser(); - parser.Parse(@"[0e]"); - Assert.True(parser.Tainted); - } + [Fact] + public void Parse__Validity_Fail29() + { + JsonParser parser = new(); + parser.Parse(@"[0e]"); + Assert.True(parser.Tainted); + } - [Fact] - public void Parse__Validity_Fail30() - { - JsonParser parser = new JsonParser(); - parser.Parse(@"[0e+]"); - Assert.True(parser.Tainted); - } + [Fact] + public void Parse__Validity_Fail30() + { + JsonParser parser = new(); + parser.Parse(@"[0e+]"); + Assert.True(parser.Tainted); + } - [Fact] - public void Parse__Validity_Fail31() - { - JsonParser parser = new JsonParser(); - parser.Parse(@"[0e+-1]"); - Assert.True(parser.Tainted); - } + [Fact] + public void Parse__Validity_Fail31() + { + JsonParser parser = new(); + parser.Parse(@"[0e+-1]"); + Assert.True(parser.Tainted); + } - [Fact] - public void Parse__Validity_Fail32() - { - JsonParser parser = new JsonParser(); - parser.Parse(@"{""Comma instead if closing brace"": true,"); - Assert.True(parser.Tainted); - } + [Fact] + public void Parse__Validity_Fail32() + { + JsonParser parser = new(); + parser.Parse(@"{""Comma instead if closing brace"": true,"); + Assert.True(parser.Tainted); + } - [Fact] - public void Parse__Validity_Fail33() - { - JsonParser parser = new JsonParser(); - parser.Parse(@"[""mismatch""}"); - Assert.True(parser.Tainted); - } + [Fact] + public void Parse__Validity_Fail33() + { + JsonParser parser = new(); + parser.Parse(@"[""mismatch""}"); + Assert.True(parser.Tainted); + } - [Fact] - public void Parse__Validity_Pass01() - { - JsonParser parser = new JsonParser(); - parser.Parse(@"[ + [Fact] + public void Parse__Validity_Pass01() + { + JsonParser parser = new(); + parser.Parse(@"[ ""JSON Test Pattern pass1"", {""object with 1 member"":[""array with 1 element""]}, {}, @@ -458,31 +461,30 @@ break""]"); 1e-1, 1e00,2e+00,2e-00 ,""rosebud""]"); - Assert.False(parser.Tainted); - } + Assert.False(parser.Tainted); + } - [Fact] - public void Parse__Validity_Pass02() - { - JsonParser parser = new JsonParser(); - parser.Parse(@"[[[[[[[[[[[[[[[[[[[""Not too deep""]]]]]]]]]]]]]]]]]]]"); - Assert.False(parser.Tainted); - } + [Fact] + public void Parse__Validity_Pass02() + { + JsonParser parser = new(); + parser.Parse(@"[[[[[[[[[[[[[[[[[[[""Not too deep""]]]]]]]]]]]]]]]]]]]"); + Assert.False(parser.Tainted); + } - [Fact] - public void Parse__Validity_Pass03() - { - JsonParser parser = new JsonParser(); - parser.Parse(@"{ + [Fact] + public void Parse__Validity_Pass03() + { + JsonParser parser = new(); + parser.Parse(@"{ ""JSON Test Pattern pass3"": { ""The outermost value"": ""must be an object or array."", ""In this test"": ""It is an object."" } } "); - Assert.False(parser.Tainted); - } - - #endregion Validity tests + Assert.False(parser.Tainted); } + + #endregion Validity tests } \ No newline at end of file diff --git a/VAR.Json.Tests/VAR.Json.Tests.csproj b/VAR.Json.Tests/VAR.Json.Tests.csproj index 5f882ac..192ffb9 100644 --- a/VAR.Json.Tests/VAR.Json.Tests.csproj +++ b/VAR.Json.Tests/VAR.Json.Tests.csproj @@ -1,9 +1,11 @@ - net5.0 + net9.0 false + enable + default diff --git a/VAR.Json.sln.DotSettings b/VAR.Json.sln.DotSettings index 5de9805..8e639fd 100644 --- a/VAR.Json.sln.DotSettings +++ b/VAR.Json.sln.DotSettings @@ -52,4 +52,25 @@ <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb_AaBb" /> <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb_AaBb" /> + <Policy><Descriptor Staticness="Static" AccessRightKinds="Private" Description="Static readonly fields (private)"><ElementKinds><Kind Name="READONLY_FIELD" /></ElementKinds></Descriptor><Policy Inspect="True" WarnAboutPrefixesAndSuffixes="False" Prefix="_" Suffix="" Style="aaBb_AaBb" /></Policy> + <Policy><Descriptor Staticness="Any" AccessRightKinds="Private" Description="Constant fields (private)"><ElementKinds><Kind Name="CONSTANT_FIELD" /></ElementKinds></Descriptor><Policy Inspect="True" WarnAboutPrefixesAndSuffixes="False" Prefix="" Suffix="" Style="AaBb_AaBb" /></Policy> + <Policy><Descriptor Staticness="Instance" AccessRightKinds="Private" Description="Instance fields (private)"><ElementKinds><Kind Name="FIELD" /><Kind Name="READONLY_FIELD" /></ElementKinds></Descriptor><Policy Inspect="True" WarnAboutPrefixesAndSuffixes="False" Prefix="_" Suffix="" Style="aaBb_AaBb" /></Policy> + <Policy><Descriptor Staticness="Instance" AccessRightKinds="Protected, ProtectedInternal, Internal, Public, PrivateProtected" Description="Instance fields (not private)"><ElementKinds><Kind Name="FIELD" /><Kind Name="READONLY_FIELD" /></ElementKinds></Descriptor><Policy Inspect="True" WarnAboutPrefixesAndSuffixes="False" Prefix="" Suffix="" Style="AaBb_AaBb" /></Policy> + <Policy><Descriptor Staticness="Any" AccessRightKinds="Any" Description="Local variables"><ElementKinds><Kind Name="LOCAL_VARIABLE" /></ElementKinds></Descriptor><Policy Inspect="True" WarnAboutPrefixesAndSuffixes="False" Prefix="" Suffix="" Style="aaBb_AaBb" /></Policy> + <Policy><Descriptor Staticness="Any" AccessRightKinds="Protected, ProtectedInternal, Internal, Public, PrivateProtected" Description="Constant fields (not private)"><ElementKinds><Kind Name="CONSTANT_FIELD" /></ElementKinds></Descriptor><Policy Inspect="True" WarnAboutPrefixesAndSuffixes="False" Prefix="" Suffix="" Style="AaBb_AaBb" /></Policy> + <Policy><Descriptor Staticness="Static" AccessRightKinds="Protected, ProtectedInternal, Internal, Public, PrivateProtected" Description="Static fields (not private)"><ElementKinds><Kind Name="FIELD" /></ElementKinds></Descriptor><Policy Inspect="True" WarnAboutPrefixesAndSuffixes="False" Prefix="" Suffix="" Style="AaBb_AaBb" /></Policy> + <Policy><Descriptor Staticness="Any" AccessRightKinds="Any" Description="Methods"><ElementKinds><Kind Name="METHOD" /></ElementKinds></Descriptor><Policy Inspect="True" WarnAboutPrefixesAndSuffixes="False" Prefix="" Suffix="" Style="AaBb_AaBb" /></Policy> + <Policy><Descriptor Staticness="Any" AccessRightKinds="Any" Description="Parameters"><ElementKinds><Kind Name="PARAMETER" /></ElementKinds></Descriptor><Policy Inspect="True" WarnAboutPrefixesAndSuffixes="False" Prefix="" Suffix="" Style="aaBb_AaBb" /></Policy> + <Policy><Descriptor Staticness="Any" AccessRightKinds="Any" Description="Enum members"><ElementKinds><Kind Name="ENUM_MEMBER" /></ElementKinds></Descriptor><Policy Inspect="True" WarnAboutPrefixesAndSuffixes="False" Prefix="" Suffix="" Style="AaBb_AaBb"><ExtraRule Prefix="T" Suffix="" Style="AaBb_AaBb" /></Policy></Policy> + <Policy><Descriptor Staticness="Any" AccessRightKinds="Any" Description="Types and namespaces"><ElementKinds><Kind Name="NAMESPACE" /><Kind Name="CLASS" /><Kind Name="STRUCT" /><Kind Name="ENUM" /><Kind Name="DELEGATE" /></ElementKinds></Descriptor><Policy Inspect="True" WarnAboutPrefixesAndSuffixes="False" Prefix="" Suffix="" Style="AaBb_AaBb" /></Policy> + <Policy><Descriptor Staticness="Any" AccessRightKinds="Any" Description="Local constants"><ElementKinds><Kind Name="LOCAL_CONSTANT" /></ElementKinds></Descriptor><Policy Inspect="True" WarnAboutPrefixesAndSuffixes="False" Prefix="" Suffix="" Style="AaBb_AaBb" /></Policy> + <Policy><Descriptor Staticness="Any" AccessRightKinds="Any" Description="Interfaces"><ElementKinds><Kind Name="INTERFACE" /></ElementKinds></Descriptor><Policy Inspect="True" WarnAboutPrefixesAndSuffixes="False" Prefix="I" Suffix="" Style="AaBb_AaBb" /></Policy> + <Policy><Descriptor Staticness="Any" AccessRightKinds="Any" Description="Properties"><ElementKinds><Kind Name="PROPERTY" /></ElementKinds></Descriptor><Policy Inspect="True" WarnAboutPrefixesAndSuffixes="False" Prefix="" Suffix="" Style="AaBb_AaBb" /></Policy> + <Policy><Descriptor Staticness="Static" AccessRightKinds="Protected, ProtectedInternal, Internal, Public, PrivateProtected" Description="Static readonly fields (not private)"><ElementKinds><Kind Name="READONLY_FIELD" /></ElementKinds></Descriptor><Policy Inspect="True" WarnAboutPrefixesAndSuffixes="False" Prefix="" Suffix="" Style="AaBb" /></Policy> + <Policy><Descriptor Staticness="Static" AccessRightKinds="Private" Description="Static fields (private)"><ElementKinds><Kind Name="FIELD" /></ElementKinds></Descriptor><Policy Inspect="True" WarnAboutPrefixesAndSuffixes="False" Prefix="_" Suffix="" Style="aaBb_AaBb" /></Policy> + True + True + True + True + True \ No newline at end of file diff --git a/VAR.Json/JsonParser.cs b/VAR.Json/JsonParser.cs index a90e5ed..c9b4ffc 100644 --- a/VAR.Json/JsonParser.cs +++ b/VAR.Json/JsonParser.cs @@ -4,706 +4,697 @@ using System.Globalization; using System.Reflection; using System.Text; -namespace VAR.Json +namespace VAR.Json; + +public class JsonParser { - public class JsonParser + #region Declarations + + private const int MaxRecursiveCount = 20; + + private readonly ParserContext _ctx = new(); + private bool _tainted; + + private readonly List _knownTypes = []; + + #endregion Declarations + + #region Properties + + public bool Tainted => _tainted; + + public List KnownTypes => _knownTypes; + + #endregion Properties + + #region Private methods + + private static readonly Dictionary _dictProperties = new(); + + private PropertyInfo[] Type_GetProperties(Type type) { - #region Declarations - - private const int MaxRecursiveCount = 20; - - private ParserContext _ctx; - private bool _tainted; - - private readonly List _knownTypes = new List(); - - #endregion Declarations - - #region Properties - - public bool Tainted => _tainted; - - public List KnownTypes => _knownTypes; - - #endregion Properties - - #region Private methods - - private static readonly Dictionary _dictProperties = - new Dictionary(); - - private PropertyInfo[] Type_GetProperties(Type type) + PropertyInfo[] typeProperties; + lock (_dictProperties) { - PropertyInfo[] typeProperties; - lock (_dictProperties) + if (_dictProperties.ContainsKey(type)) { typeProperties = _dictProperties[type]; } + else { - if (_dictProperties.ContainsKey(type)) { typeProperties = _dictProperties[type]; } - else - { - typeProperties = type.GetProperties(BindingFlags.Public | BindingFlags.OptionalParamBinding | - BindingFlags.Instance); - _dictProperties.Add(type, typeProperties); - } + 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 typeProperties; + } - return count / (float)typeProperties.Length; + 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++; + } } - private object ConvertToType(Dictionary obj, Type type) + return count / (float)typeProperties.Length; + } + + private object ConvertToType(Dictionary obj, Type type) + { + PropertyInfo[] typeProperties = Type_GetProperties(type); + object newObj = ObjectActivator.CreateInstance(type); + foreach (PropertyInfo prop in typeProperties) { - PropertyInfo[] typeProperties = Type_GetProperties(type); - object newObj = ObjectActivator.CreateInstance(type); - foreach (PropertyInfo prop in typeProperties) + if (obj.ContainsKey(prop.Name)) { - if (obj.ContainsKey(prop.Name)) + Type? underliningType = Nullable.GetUnderlyingType(prop.PropertyType); + Type effectiveType = underliningType ?? prop.PropertyType; + object? valueOrig = obj[prop.Name]; + object? valueDest; + if (underliningType != null && valueOrig == null) { - Type underliningType = Nullable.GetUnderlyingType(prop.PropertyType); - Type effectiveType = underliningType ?? prop.PropertyType; - object valueOrig = obj[prop.Name]; - object valueDest; - if (underliningType != null && valueOrig == null) - { - valueDest = null; - } - else if (effectiveType == typeof(Guid) && valueOrig is string valGuid) - { - valueDest = new Guid(valGuid); - } - else - { - try - { - valueDest = Convert.ChangeType(obj[prop.Name], effectiveType); - } - catch (Exception) - { - continue; - } - } - - prop.SetValue(newObj, valueDest, null); + valueDest = 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) + else if (effectiveType == typeof(Guid) && valueOrig is string valGuid) { - bestMatch = type; - bestMatchFactor = matchFactor; - } - } - - if (bestMatch != null) - { - try - { - object newObj = ConvertToType(obj, bestMatch); - return newObj; - } - catch (Exception) - { - // ignored - } - } - - 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'); + valueDest = new Guid(valGuid); } else { - c = char.ToLower(c); - if (c >= 'a' && c <= 'f') + try { - value = (value << 4) | ((c - 'a') + 10); + valueDest = Convert.ChangeType(obj[prop.Name], effectiveType); + } + catch (Exception) + { + continue; } } - } - return value; + prop.SetValue(newObj, valueDest, null); + } } - private string ParseQuotedString() + return newObj; + } + + private object TryConvertToTypes(Dictionary obj) + { + Type? bestMatch = null; + float bestMatchFactor = 0.0f; + foreach (Type type in _knownTypes) { - StringBuilder scratch = new StringBuilder(); - char c = _ctx.SkipWhite(); - if (c == '"') + float matchFactor = CompareToType(obj, type); + if (matchFactor > bestMatchFactor) + { + bestMatch = type; + bestMatchFactor = matchFactor; + } + } + + if (bestMatch != null) + { + try + { + object newObj = ConvertToType(obj, bestMatch); + return newObj; + } + catch (Exception) + { + // ignored + } + } + + 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(); + char c = _ctx.SkipWhite(); + if (c == '"') + { + c = _ctx.Next(); + } + + do + { + if (c == '\\') { c = _ctx.Next(); - } - - do - { - if (c == '\\') + 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(); + scratch.Append('"'); } - else if (c == '"') + else if (c == '\\') { - _ctx.Next(); - break; + 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 illegal characters - if (c == '\t' || c == '\n') { _tainted = true; } - - scratch.Append(c); - c = _ctx.Next(); + // StrictRules: Mark as tainted on unknown escaped character + _tainted = true; } - } while (!_ctx.AtEnd()); - return scratch.ToString(); - } - - private string ParseSingleQuotedString() - { - StringBuilder scratch = new StringBuilder(); - char c = _ctx.SkipWhite(); - if (c == '\'') - { c = _ctx.Next(); } - - do + else if (c == '"') { - 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 illegal 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(); + _ctx.Next(); + break; } - - if (c == '\'') + else { - _tainted = true; - return ParseSingleQuotedString(); - } + // StrictRules: Mark as tainted on illegal characters + if (c == '\t' || c == '\n') { _tainted = true; } - if (mustBeQuoted) { _tainted = true; } - - StringBuilder scratch = new StringBuilder(); - - while (!_ctx.AtEnd() - && (char.IsLetter(c) || char.IsDigit(c) || c == '_')) - { scratch.Append(c); c = _ctx.Next(); } + } while (!_ctx.AtEnd()); - return scratch.ToString(); + return scratch.ToString(); + } + + private string ParseSingleQuotedString() + { + StringBuilder scratch = new(); + char c = _ctx.SkipWhite(); + if (c == '\'') + { + c = _ctx.Next(); } - private object ParseNumber() + do { - StringBuilder scratch = new StringBuilder(); - bool isFloat = false; - bool isExp = false; - int numberLenght = 0; - int expLenght = 0; - char c = _ctx.SkipWhite(); - - // Sign - if (c == '-') + if (c == '\\') { - scratch.Append('-'); + 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 illegal characters + if (c == '\t' || c == '\n') { _tainted = true; } - // Integer part - bool leadingZeroes = true; - int leadingZeroesLenght = 0; + 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(); + + 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(); + bool isFloat = false; + bool isExp = false; + int numberLenght = 0; + int expLenght = 0; + char 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)) { - // 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; - } + if (numberLenght == 0) + { + _tainted = true; + return null; + } - // Decimal part - if (c == '.') + // Exponential part + if (c == 'e' || c == 'E') + { + isFloat = true; + isExp = true; + scratch.Append('E'); + c = _ctx.Next(); + if (c == '+' || c == '-') { - isFloat = true; - scratch.Append("."); + scratch.Append(c); c = _ctx.Next(); - while (char.IsDigit(c)) - { - scratch.Append(c); - c = _ctx.Next(); - numberLenght++; - } } - if (numberLenght == 0) + while (char.IsDigit(c)) { - _tainted = true; - return null; - } - - // Exponential part - if (c == 'e' || c == 'E') - { - isFloat = true; - isExp = true; - scratch.Append('E'); + scratch.Append(c); c = _ctx.Next(); - if (c == '+' || c == '-') - { - scratch.Append(c); - c = _ctx.Next(); - } - - while (char.IsDigit(c)) - { - scratch.Append(c); - c = _ctx.Next(); - numberLenght++; - expLenght++; - } + numberLenght++; + expLenght++; } + } - if (isExp && expLenght == 0) + if (isExp && expLenght == 0) + { + _tainted = true; + return null; + } + + // Build number from the parsed string + string s = scratch.ToString(); + if (!isFloat) { return Convert.ToInt32(s); } + + if (numberLenght < 17) + { + return Convert.ToDouble(s, CultureInfo.InvariantCulture); + } + + return Convert.ToDecimal(s, CultureInfo.InvariantCulture); + } + + private object? 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 = []; + Type? arrayContentType = null; + bool hasSameType = true; + bool hasNulls = false; + if (c == '[') + { + _ctx.Next(); + } + + bool? expectValue = null; + do + { + c = _ctx.SkipWhite(); + if (c == ']') { - _tainted = true; - return null; + // StrictRules: Mark as tainted when unexpected end of array + if (expectValue == true) { _tainted = true; } + + correct = true; + _ctx.Next(); + break; } - - // Build number from the parsed string - string s = scratch.ToString(); - if (isFloat) + else if (c == ',') { - if (numberLenght < 17) - { - return Convert.ToDouble(s, CultureInfo.InvariantCulture); - } - else - { - return Convert.ToDecimal(s, CultureInfo.InvariantCulture); - } + // StrictRules: Mark as tainted when unexpected comma on array + if (expectValue == true || array.Count == 0) { _tainted = true; } + + _ctx.Next(); + expectValue = true; } else { - return Convert.ToInt32(s); - } - } + // StrictRules: Mark as tainted when unexpected value on array + if (expectValue == false) { _tainted = true; } - private object ParseArray(int recursiveCount = 1) - { - // StrictRules: Mark as tainted when MaxRecursiveCount is exceeded - if (recursiveCount >= MaxRecursiveCount) { _tainted = true; } + object? value = ParseValue(recursiveCount + 1); + array.Add(value); + expectValue = false; - bool correct = false; - char c = _ctx.SkipWhite(); - List array = new List(); - Type arrayContentType = null; - bool hasSameType = true; - bool hasNulls = false; - if (c == '[') - { - _ctx.Next(); - } - - bool? expectValue = null; - do - { - c = _ctx.SkipWhite(); - if (c == ']') + if (hasSameType) { - // StrictRules: Mark as tainted when unexpected end of array - if (expectValue == true) { _tainted = true; } + Type? valueType = value?.GetType(); + if (valueType == null) { hasNulls = 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; } - - object value = ParseValue(recursiveCount + 1); - array.Add(value); - expectValue = false; - - if (hasSameType) + if (arrayContentType == null || arrayContentType == valueType) { - Type valueType = value?.GetType(); - if (valueType == null) { hasNulls = true; } - - if (arrayContentType == null || arrayContentType == valueType) - { - arrayContentType = valueType; - } - else - { - hasSameType = false; - } + arrayContentType = valueType; + } + else + { + hasSameType = false; } } - } while (!_ctx.AtEnd()); - - if (correct == false) - { - _tainted = true; } + } while (!_ctx.AtEnd()); - object result = array; - bool isNullableType = arrayContentType?.IsClass == true; - if (hasSameType && arrayContentType != null && (isNullableType || (hasNulls == false))) - { - Type enumerableType = typeof(System.Linq.Enumerable); - MethodInfo castMethod = enumerableType.GetMethod("Cast")?.MakeGenericMethod(arrayContentType); - MethodInfo toListMethod = enumerableType.GetMethod("ToList")?.MakeGenericMethod(arrayContentType); - IEnumerable itemsToCast = array; - object castedItems = castMethod?.Invoke(null, new object[] { itemsToCast }); - result = toListMethod?.Invoke(null, new[] { castedItems }); - } - - return result; + if (correct == false) + { + _tainted = true; } - private object ParseObject(int recursiveCount = 1) + object? result = array; + bool isNullableType = arrayContentType?.IsClass == true; + if (hasSameType && arrayContentType != null && (isNullableType || (hasNulls == false))) { - // StrictRules: Mark as tainted when MaxRecursiveCount is exceeded - if (recursiveCount >= MaxRecursiveCount) { _tainted = true; } + Type enumerableType = typeof(System.Linq.Enumerable); + MethodInfo? castMethod = enumerableType.GetMethod("Cast")?.MakeGenericMethod(arrayContentType); + MethodInfo? toListMethod = enumerableType.GetMethod("ToList")?.MakeGenericMethod(arrayContentType); + IEnumerable itemsToCast = array; + object? castedItems = castMethod?.Invoke(null, [itemsToCast,]); + result = toListMethod?.Invoke(null, [castedItems,]); + } - bool correct = false; - char c = _ctx.SkipWhite(); - Dictionary obj = new Dictionary(); - if (c == '{') + return result; + } + + private object 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 = string.Empty; + bool? expectedKey = null; + bool? expectedValue = null; + do + { + c = _ctx.SkipWhite(); + if (c == ':') { _ctx.Next(); - } - - string attributeName = null; - bool? expectedKey = null; - bool? expectedValue = null; - do - { - c = _ctx.SkipWhite(); - if (c == ':') + if (expectedValue == true) { - _ctx.Next(); - if (expectedValue == true) - { - object attributeValue = ParseValue(recursiveCount + 1); - obj.Add(attributeName, attributeValue); - expectedKey = null; - expectedValue = false; - } - } - else if (c == ',') - { - _ctx.Next(); - _ctx.SkipWhite(); - expectedKey = true; + object? attributeValue = ParseValue(recursiveCount + 1); + obj.Add(attributeName, attributeValue); + expectedKey = null; expectedValue = false; } - else if (c == '}') + } + else if (c == ',') + { + _ctx.Next(); + _ctx.SkipWhite(); + expectedKey = true; + expectedValue = false; + } + else if (c == '}') + { + // StrictRules: Mark as tainted on unexpected end of object + if (expectedValue == true || expectedKey == true) { - // StrictRules: Mark as tainted on unexpected end of object - if (expectedValue == true || expectedKey == true) - { - _tainted = true; - } + _tainted = true; + } - correct = true; - _ctx.Next(); - break; + correct = true; + _ctx.Next(); + break; + } + else + { + if (expectedKey != false) + { + attributeName = ParseString(true); + _ctx.SkipWhite(); + expectedKey = false; + expectedValue = true; } else { - if (expectedKey != false) + // Unexpected character + _tainted = true; + break; + } + } + } while (!_ctx.AtEnd()); + + if (correct == false) + { + _tainted = true; + } + + object result = TryConvertToTypes(obj); + return result; + } + + private object? ParseValue(int recursiveCount = 1) + { + char c = _ctx.SkipWhite(); + object? token; + switch (c) + { + case '"': + token = ParseQuotedString(); + break; + + case '\'': + // StrictRules: Mark as tainted when parsing single quoted strings + _tainted = true; + token = ParseSingleQuotedString(); + break; + + case '{': + token = ParseObject(recursiveCount); + break; + + case '[': + token = ParseArray(recursiveCount); + break; + + default: + if (char.IsDigit(c) || c == '-') + { + token = ParseNumber(); + } + else + { + string aux = ParseString(); + if (aux.Equals("true")) { - attributeName = ParseString(true); - _ctx.SkipWhite(); - expectedKey = false; - expectedValue = true; + token = true; + } + else if (aux.Equals("false")) + { + token = false; + } + else if (aux.Equals("null")) + { + token = null; } else { - // Unexpected character + // Unexpected string + if (aux.Length == 0) + { + _ctx.Next(); + } + _tainted = true; - break; + token = null; } } - } while (!_ctx.AtEnd()); - if (correct == false) + break; + } + + return token; + } + + #endregion Private methods + + #region Public methods + + public object? Parse(string text) + { + // Get the first object + _ctx.SetText(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; } - object result = TryConvertToTypes(obj); - return result; - } - - private object ParseValue(int recursiveCount = 1) - { - char c = _ctx.SkipWhite(); - object token; - switch (c) - { - case '"': - token = ParseQuotedString(); - break; - - case '\'': - // StrictRules: Mark as tainted when parsing single quoted strings - _tainted = true; - token = ParseSingleQuotedString(); - break; - - case '{': - token = ParseObject(recursiveCount); - break; - - case '[': - token = ParseArray(recursiveCount); - break; - - default: - if (char.IsDigit(c) || c == '-') - { - token = ParseNumber(); - } - else - { - string aux = ParseString(); - if (aux.Equals("true")) - { - token = true; - } - else if (aux.Equals("false")) - { - token = false; - } - else if (aux.Equals("null")) - { - token = null; - } - else - { - // Unexpected string - if (aux.Length == 0) - { - _ctx.Next(); - } - - _tainted = true; - token = null; - } - } - - break; - } - - return token; - } - - #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; } - private static JsonParser _currentInstance; + // StrictRules: Mark as tainted when there is more content + _tainted = true; - public static object ParseText(string text, params Type[] knownTypes) + return obj; + } + + private static JsonParser? _currentInstance; + + public static object? ParseText(string text, params Type[] knownTypes) + { + if (_currentInstance == null) { - if (_currentInstance == null) - { - _currentInstance = new JsonParser(); - } - - _currentInstance.KnownTypes.Clear(); - _currentInstance.KnownTypes.AddRange(knownTypes); - return _currentInstance.Parse(text); + _currentInstance = new JsonParser(); } - #endregion Public methods + _currentInstance.KnownTypes.Clear(); + _currentInstance.KnownTypes.AddRange(knownTypes); + return _currentInstance.Parse(text); } + + #endregion Public methods } \ No newline at end of file diff --git a/VAR.Json/JsonWriter.cs b/VAR.Json/JsonWriter.cs index 1c8ab55..dc82993 100644 --- a/VAR.Json/JsonWriter.cs +++ b/VAR.Json/JsonWriter.cs @@ -5,477 +5,468 @@ using System.IO; using System.Linq; using System.Reflection; -namespace VAR.Json +namespace VAR.Json; + +public class JsonWriterConfiguration { - public class JsonWriterConfiguration + private readonly bool _indent; + + public bool Indent => _indent; + + private readonly bool _useTabForIndent; + + public bool UseTabForIndent => _useTabForIndent; + + private readonly int _indentChars; + + public int IndentChars => _indentChars; + + private readonly int _indentThreshold; + + public int IndentThreshold => _indentThreshold; + + public JsonWriterConfiguration( + bool indent = false, + bool useTabForIndent = false, + int indentChars = 4, + int indentThreshold = 3) { - private readonly bool _indent; + _indent = indent; + _useTabForIndent = useTabForIndent; + _indentChars = indentChars; + _indentThreshold = indentThreshold; + } - public bool Indent => _indent; + public bool Equals(JsonWriterConfiguration other) => + other.Indent == Indent && + other.UseTabForIndent == UseTabForIndent && + other.IndentChars == IndentChars && + other.IndentThreshold == IndentThreshold && + true; - private readonly bool _useTabForIndent; - - public bool UseTabForIndent => _useTabForIndent; - - private readonly int _indentChars; - - public int IndentChars => _indentChars; - - private readonly int _indentThreshold; - - public int IndentThreshold => _indentThreshold; - - public JsonWriterConfiguration( - bool indent = false, - bool useTabForIndent = false, - int indentChars = 4, - int indentThreshold = 3) + public override bool Equals(object? other) + { + if (other is JsonWriterConfiguration configuration) { - _indent = indent; - _useTabForIndent = useTabForIndent; - _indentChars = indentChars; - _indentThreshold = indentThreshold; + return Equals(configuration); } - public bool Equals(JsonWriterConfiguration other) + return false; + } + + public override int GetHashCode() + { + return _indent.GetHashCode() ^ _useTabForIndent.GetHashCode() ^ _indentChars.GetHashCode() ^ _indentThreshold.GetHashCode(); + } +} + +public class JsonWriter +{ + #region Declarations + + private readonly JsonWriterConfiguration _config; + + #endregion Declarations + + #region Creator + + public JsonWriter(JsonWriterConfiguration? config = null) + { + _config = config ?? new JsonWriterConfiguration(); + } + + #endregion Creator + + #region Private methods + + private bool IsValue(object? obj) + { + if (obj == null) { - return - other.Indent == Indent && - other.UseTabForIndent == UseTabForIndent && - other.IndentChars == IndentChars && - other.IndentThreshold == IndentThreshold && - true; + return true; } - public override bool Equals(object other) + if ( + (obj is float) || + (obj is double) || + (obj is short) || + (obj is int) || + (obj is long) || + (obj is string) || + (obj is bool) || + false) { - if (other is JsonWriterConfiguration configuration) - { - return Equals(configuration); - } - - return false; + return true; } - public override int GetHashCode() + return false; + } + + private void WriteIndent(TextWriter textWriter, int level) + { + if (!_config.Indent) { - return _indent.GetHashCode() ^ _useTabForIndent.GetHashCode() ^ _indentChars.GetHashCode() ^ - _indentThreshold.GetHashCode(); + return; + } + + textWriter.Write('\n'); + if (_config.UseTabForIndent) + { + for (int i = 0; i < level; i++) { textWriter.Write('\t'); } + } + else + { + int n = level * _config.IndentChars; + for (int i = 0; i < n; i++) { textWriter.Write(' '); } } } - public class JsonWriter + private void WriteString(TextWriter textWriter, string str) { - #region Declarations - - private readonly JsonWriterConfiguration _config; - - #endregion Declarations - - #region Creator - - public JsonWriter(JsonWriterConfiguration config = null) + textWriter.Write('"'); + int n = str.Length; + for (int i = 0; i < n; i++) { - _config = config ?? new JsonWriterConfiguration(); + char c = str[i]; + if (c == '"') { textWriter.Write("\\\""); } + else if (c == '\\') { textWriter.Write("\\\\"); } + else if (c == '/') { textWriter.Write("\\/"); } + else if (c == '\b') { textWriter.Write("\\b"); } + else if (c == '\f') { textWriter.Write("\\f"); } + else if (c == '\n') { textWriter.Write("\\n"); } + else if (c == '\r') { textWriter.Write("\\r"); } + else if (c == '\t') { textWriter.Write("\\t"); } + else if (c < 32 || c >= 127) { textWriter.Write("\\u{0:X04}", (int)c); } + else { textWriter.Write(c); } } - #endregion Creator + textWriter.Write('"'); + } - #region Private methods - - private bool IsValue(object obj) + private void WriteValue(TextWriter textWriter, object? obj, List parentLevels) + { + if (obj == null || obj is DBNull) { - if (obj == null) - { - return true; - } - - if ( - (obj is float) || - (obj is double) || - (obj is short) || - (obj is int) || - (obj is long) || - (obj is string) || - (obj is bool) || - false) - { - return true; - } - - return false; + // NULL + textWriter.Write("null"); } - - private void WriteIndent(TextWriter textWriter, int level) + else if ( + (obj is float) || + (obj is double) || + (obj is short) || + (obj is int) || + (obj is long) || + false) { - if (!_config.Indent) - { - return; - } - - textWriter.Write('\n'); - if (_config.UseTabForIndent) - { - for (int i = 0; i < level; i++) { textWriter.Write('\t'); } - } - else - { - int n = level * _config.IndentChars; - for (int i = 0; i < n; i++) { textWriter.Write(' '); } - } + // Numbers + textWriter.Write(obj.ToString()); } - - private void WriteString(TextWriter textWriter, string str) - { - textWriter.Write('"'); - int n = str.Length; - for (int i = 0; i < n; i++) + else + switch (obj) { - char c = str[i]; - if (c == '"') { textWriter.Write("\\\""); } - else if (c == '\\') { textWriter.Write("\\\\"); } - else if (c == '/') { textWriter.Write("\\/"); } - else if (c == '\b') { textWriter.Write("\\b"); } - else if (c == '\f') { textWriter.Write("\\f"); } - else if (c == '\n') { textWriter.Write("\\n"); } - else if (c == '\r') { textWriter.Write("\\r"); } - else if (c == '\t') { textWriter.Write("\\t"); } - else if (c < 32 || c >= 127) { textWriter.Write("\\u{0:X04}", (int)c); } - else { textWriter.Write(c); } - } - - textWriter.Write('"'); - } - - private void WriteValue(TextWriter textWriter, object obj, List parentLevels) - { - if (obj == null || obj is DBNull) - { - // NULL - textWriter.Write("null"); - } - else if ( - (obj is float) || - (obj is double) || - (obj is short) || - (obj is int) || - (obj is long) || - false) - { - // Numbers - textWriter.Write(obj.ToString()); - } - else - switch (obj) - { - case string valString: - // Strings - WriteString(textWriter, valString); - break; - case bool valBool: - // Booleans - textWriter.Write(valBool ? "true" : "false"); - break; - case DateTime valDateTime: - // DateTime - textWriter.Write('"'); - textWriter.Write(valDateTime.ToString("yyyy-MM-ddTHH:mm:ss")); - textWriter.Write('"'); - break; - case IDictionary _: - // Objects - WriteObject(textWriter, obj, parentLevels); - break; - case IEnumerable _: - // Array/List - WriteList(textWriter, obj, parentLevels); - break; - default: - // Reflected object - WriteReflectedObject(textWriter, obj, parentLevels); - break; - } - } - - private void WriteList(TextWriter textWriter, object obj, List parentLevels) - { - IEnumerable list = ((IEnumerable)obj).Cast().ToList(); - 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) - { - textWriter.Write("[ ]"); - return; - } - - // Write array - bool first = true; - textWriter.Write("[ "); - if (!isLeaf || n > _config.IndentThreshold) - { - WriteIndent(textWriter, parentLevels.Count + 1); - } - - foreach (object childObj in list) - { - if (!first) - { - textWriter.Write(", "); - if (!isLeaf || n > _config.IndentThreshold) - { - WriteIndent(textWriter, parentLevels.Count + 1); - } - } - - first = false; - parentLevels.Add(obj); - WriteValue(textWriter, childObj, parentLevels); - parentLevels.Remove(obj); - } - - if (!isLeaf || n > _config.IndentThreshold) - { - WriteIndent(textWriter, parentLevels.Count); - } - - textWriter.Write(" ]"); - } - - private void WriteObject(TextWriter textWriter, object obj, List parentLevels) - { - IDictionary map = (IDictionary)obj; - int n = map.Count; - - // Empty - if (map.Count == 0) - { - textWriter.Write("{ }"); - return; - } - - // Check if it is a leaf object - bool isLeaf = true; - foreach (object value in map.Values) - { - if (!IsValue(value)) - { - isLeaf = false; + case string valString: + // Strings + WriteString(textWriter, valString); break; - } + case bool valBool: + // Booleans + textWriter.Write(valBool ? "true" : "false"); + break; + case DateTime valDateTime: + // DateTime + textWriter.Write('"'); + textWriter.Write(valDateTime.ToString("yyyy-MM-ddTHH:mm:ss")); + textWriter.Write('"'); + break; + case IDictionary _: + // Objects + WriteObject(textWriter, obj, parentLevels); + break; + case IEnumerable _: + // Array/List + WriteList(textWriter, obj, parentLevels); + break; + default: + // Reflected object + WriteReflectedObject(textWriter, obj, parentLevels); + break; + } + } + + private void WriteList(TextWriter textWriter, object obj, List parentLevels) + { + IEnumerable list = ((IEnumerable)obj).Cast().ToList(); + int n = 0; + + // Check if it is a leaf object + bool isLeaf = true; + foreach (object childObj in list) + { + if (!IsValue(childObj)) + { + isLeaf = false; } - // Write object - bool first = true; - textWriter.Write("{ "); - if (!isLeaf || n > _config.IndentThreshold) - { - WriteIndent(textWriter, parentLevels.Count + 1); - } + n++; + } - foreach (object key in map.Keys) + // Empty + if (n == 0) + { + textWriter.Write("[ ]"); + return; + } + + // Write array + bool first = true; + textWriter.Write("[ "); + if (!isLeaf || n > _config.IndentThreshold) + { + WriteIndent(textWriter, parentLevels.Count + 1); + } + + foreach (object childObj in list) + { + if (!first) { - object value = map[key]; - if (!first) + textWriter.Write(", "); + if (!isLeaf || n > _config.IndentThreshold) { - textWriter.Write(", "); - if (!isLeaf || n > _config.IndentThreshold) - { - WriteIndent(textWriter, parentLevels.Count + 1); - } + WriteIndent(textWriter, parentLevels.Count + 1); } + } - first = false; - WriteString(textWriter, Convert.ToString(key)); - textWriter.Write(": "); - parentLevels.Add(obj); + first = false; + parentLevels.Add(obj); + WriteValue(textWriter, childObj, parentLevels); + parentLevels.Remove(obj); + } + + if (!isLeaf || n > _config.IndentThreshold) + { + WriteIndent(textWriter, parentLevels.Count); + } + + textWriter.Write(" ]"); + } + + private void WriteObject(TextWriter textWriter, object obj, List parentLevels) + { + IDictionary map = (IDictionary)obj; + int n = map.Count; + + // Empty + if (map.Count == 0) + { + textWriter.Write("{ }"); + 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; + textWriter.Write("{ "); + if (!isLeaf || n > _config.IndentThreshold) + { + WriteIndent(textWriter, parentLevels.Count + 1); + } + + foreach (object key in map.Keys) + { + object? value = map[key]; + if (!first) + { + textWriter.Write(", "); + if (!isLeaf || n > _config.IndentThreshold) + { + WriteIndent(textWriter, parentLevels.Count + 1); + } + } + + first = false; + WriteString(textWriter, Convert.ToString(key) ?? string.Empty); + textWriter.Write(": "); + parentLevels.Add(obj); + WriteValue(textWriter, value, parentLevels); + parentLevels.Remove(obj); + } + + if (!isLeaf || n > _config.IndentThreshold) + { + WriteIndent(textWriter, parentLevels.Count); + } + + textWriter.Write(" }"); + } + + private void WriteReflectedObject(TextWriter textWriter, object obj, List parentLevels) + { + Type type = obj.GetType(); + PropertyInfo[] rawProperties = type.GetProperties(BindingFlags.Public | BindingFlags.Instance); + List properties = []; + foreach (PropertyInfo property in rawProperties) + { + if (property.CanRead == false) { continue; } + + properties.Add(property); + } + + int n = properties.Count; + + // Empty + if (n == 0) + { + textWriter.Write("{ }"); + 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; + textWriter.Write("{ "); + if (!isLeaf || n > _config.IndentThreshold) + { + WriteIndent(textWriter, parentLevels.Count + 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) + { + textWriter.Write(", "); + if (!isLeaf || n > _config.IndentThreshold) + { + WriteIndent(textWriter, parentLevels.Count + 1); + } + } + + first = false; + WriteString(textWriter, property.Name); + textWriter.Write(": "); + parentLevels.Add(obj); + if (value != obj && parentLevels.Contains(value) == false) + { WriteValue(textWriter, value, parentLevels); - parentLevels.Remove(obj); } - - if (!isLeaf || n > _config.IndentThreshold) + else { - WriteIndent(textWriter, parentLevels.Count); + WriteValue(textWriter, null, parentLevels); } - textWriter.Write(" }"); + parentLevels.Remove(obj); } - private void WriteReflectedObject(TextWriter textWriter, object obj, List parentLevels) + if (!isLeaf || n > _config.IndentThreshold) { - Type type = obj.GetType(); - PropertyInfo[] rawProperties = type.GetProperties(BindingFlags.Public | BindingFlags.Instance); - List properties = new List(); - foreach (PropertyInfo property in rawProperties) - { - if (property.CanRead == false) { continue; } - - properties.Add(property); - } - - int n = properties.Count; - - // Empty - if (n == 0) - { - textWriter.Write("{ }"); - 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; - textWriter.Write("{ "); - if (!isLeaf || n > _config.IndentThreshold) - { - WriteIndent(textWriter, parentLevels.Count + 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) - { - textWriter.Write(", "); - if (!isLeaf || n > _config.IndentThreshold) - { - WriteIndent(textWriter, parentLevels.Count + 1); - } - } - - first = false; - WriteString(textWriter, property.Name); - textWriter.Write(": "); - parentLevels.Add(obj); - if (value != obj && parentLevels.Contains(value) == false) - { - WriteValue(textWriter, value, parentLevels); - } - else - { - WriteValue(textWriter, null, parentLevels); - } - - parentLevels.Remove(obj); - } - - if (!isLeaf || n > _config.IndentThreshold) - { - WriteIndent(textWriter, parentLevels.Count); - } - - textWriter.Write(" }"); + WriteIndent(textWriter, parentLevels.Count); } - #endregion Private methods + textWriter.Write(" }"); + } - #region Public methods + #endregion Private methods - public TextWriter Write(object obj, TextWriter textWriter) + #region Public methods + + public TextWriter Write(object obj, TextWriter? textWriter) + { + textWriter ??= new StringWriter(); + + WriteValue(textWriter, obj, []); + return textWriter; + } + + public string Write(object obj) + { + StringWriter textWriter = new(); + WriteValue(textWriter, obj, []); + return textWriter.ToString(); + } + + private static readonly Dictionary _dictInstances = new(); + + public static string WriteObject(object obj, + JsonWriterConfiguration? config = null, + bool indent = false, + bool useTabForIndent = false, + int indentChars = 4, + int indentThreshold = 3) + { + JsonWriter? jsonWriter = null; + + if (config != null) { - if (textWriter == null) + if (_dictInstances.ContainsKey(config) == false) { - textWriter = new StringWriter(); + jsonWriter = new JsonWriter(config); + _dictInstances.Add(config, jsonWriter); } - - WriteValue(textWriter, obj, new List()); - return textWriter; - } - - public string Write(object obj) - { - StringWriter textWriter = new StringWriter(); - WriteValue(textWriter, obj, new List()); - return textWriter.ToString(); - } - - private static readonly Dictionary _dictInstances = - new Dictionary(); - - public static string WriteObject(object obj, - JsonWriterConfiguration config = null, - bool indent = false, - bool useTabForIndent = false, - int indentChars = 4, - int indentThreshold = 3) - { - JsonWriter jsonWriter = null; - - if (config != null) + else { - if (_dictInstances.ContainsKey(config) == false) - { - jsonWriter = new JsonWriter(config); - _dictInstances.Add(config, jsonWriter); - } - else - { - jsonWriter = _dictInstances[config]; - } - - return jsonWriter.Write(obj); + jsonWriter = _dictInstances[config]; } - foreach (KeyValuePair pair in _dictInstances) - { - if ( - pair.Key.Indent == indent && - pair.Key.UseTabForIndent == useTabForIndent && - pair.Key.IndentChars == indentChars && - pair.Key.IndentThreshold == indentThreshold && - true) - { - jsonWriter = pair.Value; - break; - } - } - - if (jsonWriter != null) - { - return jsonWriter.Write(obj); - } - - JsonWriterConfiguration jsonWriterConfiguration = new JsonWriterConfiguration( - indent: indent, - useTabForIndent: useTabForIndent, - indentChars: indentChars, - indentThreshold: indentThreshold); - jsonWriter = new JsonWriter(jsonWriterConfiguration); - _dictInstances.Add(jsonWriterConfiguration, jsonWriter); - return jsonWriter.Write(obj); } - #endregion Public methods + foreach (KeyValuePair pair in _dictInstances) + { + if ( + pair.Key.Indent == indent && + pair.Key.UseTabForIndent == useTabForIndent && + pair.Key.IndentChars == indentChars && + pair.Key.IndentThreshold == indentThreshold && + true) + { + jsonWriter = pair.Value; + break; + } + } + + if (jsonWriter != null) + { + return jsonWriter.Write(obj); + } + + JsonWriterConfiguration jsonWriterConfiguration = new( + indent: indent, + useTabForIndent: useTabForIndent, + indentChars: indentChars, + indentThreshold: indentThreshold); + jsonWriter = new JsonWriter(jsonWriterConfiguration); + _dictInstances.Add(jsonWriterConfiguration, jsonWriter); + + return jsonWriter.Write(obj); } + + #endregion Public methods } \ No newline at end of file diff --git a/VAR.Json/ObjectActivator.cs b/VAR.Json/ObjectActivator.cs index 3d5ec78..ca5c574 100644 --- a/VAR.Json/ObjectActivator.cs +++ b/VAR.Json/ObjectActivator.cs @@ -2,34 +2,31 @@ using System.Collections.Generic; using System.Linq.Expressions; -namespace VAR.Json +namespace VAR.Json; + +public static class ObjectActivator { - public static class ObjectActivator + private static readonly Dictionary> _creators = new(); + + private static Func GetLambdaNew(Type type) { - private static readonly Dictionary> _creators = new Dictionary>(); - - private static Func GetLambdaNew(Type type) + lock (_creators) { - lock (_creators) - { - if (_creators.ContainsKey(type)) - { - return _creators[type]; - } + if (_creators.TryGetValue(type, out Func? creator)) { return creator; } - NewExpression newExp = Expression.New(type); - LambdaExpression lambda = Expression.Lambda(typeof(Func), newExp); - Func compiledLambdaNew = (Func)lambda.Compile(); + NewExpression newExp = Expression.New(type); + LambdaExpression lambda = Expression.Lambda(typeof(Func), newExp); + Func compiledLambdaNew = (Func)lambda.Compile(); - _creators.Add(type, compiledLambdaNew); - return _creators[type]; - } - } + _creators.Add(type, compiledLambdaNew); - public static object CreateInstance(Type type) - { - Func creator = GetLambdaNew(type); - return creator(); + return _creators[type]; } } + + public static object CreateInstance(Type type) + { + Func creator = GetLambdaNew(type); + return creator(); + } } \ No newline at end of file diff --git a/VAR.Json/ParserContext.cs b/VAR.Json/ParserContext.cs index 4d92a7a..8447cf7 100644 --- a/VAR.Json/ParserContext.cs +++ b/VAR.Json/ParserContext.cs @@ -1,85 +1,73 @@ -namespace VAR.Json +namespace VAR.Json; + +public class ParserContext { - public class ParserContext + #region Declarations + + private string _text = string.Empty; + private int _length; + private int _i; + private int _markStart; + + #endregion Declarations + + #region Public methods + + public void SetText(string text) { - #region Declarations - - private readonly string _text; - private readonly 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() + _text = text; + _length = text.Length; + _i = 0; + _markStart = 0; + } + + public char SkipWhite() + { + while (_i < _length && char.IsWhiteSpace(_text[_i])) { _i++; - if (AtEnd()) - { - return (char)0; - } - - return _text[_i]; } - public bool AtEnd() + if (AtEnd()) { - return _i >= _length; + return (char)0; } - 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 + 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); + } + + return _markStart < _length + ? _text.Substring(_markStart, _length - _markStart) + : string.Empty; + } + + #endregion Public methods } \ No newline at end of file diff --git a/VAR.Json/VAR.Json.csproj b/VAR.Json/VAR.Json.csproj index a518a3e..ab6f79c 100644 --- a/VAR.Json/VAR.Json.csproj +++ b/VAR.Json/VAR.Json.csproj @@ -1,9 +1,11 @@  - netstandard2.0 + net9.0 Library true true + enable + default VAR.Json