diff --git a/nanoFramework.Json/key.snk b/key.snk similarity index 100% rename from nanoFramework.Json/key.snk rename to key.snk diff --git a/nanoFramework.Json.Benchmark/DeserializationBenchmarks/ReferenceTypesDeserializationBenchmark.cs b/nanoFramework.Json.Benchmark/DeserializationBenchmarks/ReferenceTypesDeserializationBenchmark.cs index 1dedf940..f6595ab6 100644 --- a/nanoFramework.Json.Benchmark/DeserializationBenchmarks/ReferenceTypesDeserializationBenchmark.cs +++ b/nanoFramework.Json.Benchmark/DeserializationBenchmarks/ReferenceTypesDeserializationBenchmark.cs @@ -1,182 +1,125 @@ using nanoFramework.Benchmark; +using System; +using System.Collections; using nanoFramework.Benchmark.Attributes; using nanoFramework.Json.Benchmark.Base; using nanoFramework.Json.Test.Shared; -using System; -using System.Collections; namespace nanoFramework.Json.Benchmark.DeserializationBenchmarks { [IterationCount(5)] public class ReferenceTypesDeserializationBenchmark : BaseIterationBenchmark { - const string testString = "TestStringToSerialize"; - const short arrayElementCount = 5; - int[] intArray = new int[arrayElementCount]; - short[] shortArray = new short[arrayElementCount]; - private Person nestedTestClass; - private JsonTestClassComplex complexClass; - private JsonTestTown myTown; - private ArrayList arrayList; - - [Setup] - public void Setup() - { - var random = new Random(); - for (int i = 0; i < arrayElementCount; i++) - { - intArray[i] = random.Next(); - shortArray[i] = (short)random.Next(short.MaxValue); - } - - nestedTestClass = new Person() - { - FirstName = "John", - LastName = "Doe", - Birthday = new DateTime(1988, 4, 23), - ID = 27, - Address = null, - ArrayProperty = new string[] { "hello", "world" }, - Friend = new Person() - { - FirstName = "Bob", - LastName = "Smith", - Birthday = new DateTime(1983, 7, 3), - ID = 2, - Address = "123 Some St", - ArrayProperty = new string[] { "hi", "planet" }, - } - }; - - complexClass = new JsonTestClassComplex() - { - aInteger = 10, - aShort = 254, - aByte = 0x05, - aString = "A string", - aFloat = 1.2345f, - aDouble = 1.2345, - aBoolean = true, - Timestamp = DateTime.UtcNow, - FixedTimestamp = new DateTime(2020, 05, 01, 09, 30, 00), - intArray = new[] { 1, 3, 5, 7, 9 }, - shortArray = new[] { (short)1, (short)3, (short)5, (short)7, (short)9 }, - byteArray = new[] { (byte)0x22, (byte)0x23, (byte)0x24, (byte)0x25, (byte)0x26 }, - stringArray = new[] { "two", "four", "six", "eight" }, - floatArray = new[] { 1.1f, 3.3f, 5.5f, 7.7f, 9.9f }, - doubleArray = new[] { 1.12345, 3.3456, 5.56789, 7.78910, 9.910111213 }, - child1 = new JsonTestClassChild() { one = 1, two = 2, three = 3 }, - Child = new JsonTestClassChild() { one = 100, two = 200, three = 300 }, - nullObject = null, - nanFloat = float.NaN, - nanDouble = double.NaN, - }; - - myTown = new JsonTestTown - { - TownID = 1, - TownName = "myTown", - CompaniesInThisTown = new JsonTestCompany[] - { - new JsonTestCompany { CompanyID = 1, CompanyName = "AAA Amalgamated Industries" }, - new JsonTestCompany { CompanyID = 2, CompanyName = "BBB Amalgamated Industries" }, - new JsonTestCompany { CompanyID = 3, CompanyName = "CCC Amalgamated Industries" }, - new JsonTestCompany { CompanyID = 4, CompanyName = "DDD Amalgamated Industries" }, - new JsonTestCompany { CompanyID = 5, CompanyName = "EEE Amalgamated Industries" }, - new JsonTestCompany { CompanyID = 6, CompanyName = "FFF Amalgamated Industries" }, - new JsonTestCompany { CompanyID = 7, CompanyName = "GGG Amalgamated Industries" }, - new JsonTestCompany { CompanyID = 8, CompanyName = "HHH Amalgamated Industries" } - }, - EmployeesInThisTown = new JsonTestEmployee[] - { - new JsonTestEmployee - { - EmployeeID = 1, - EmployeeName = "John Smith", - CurrentEmployer = new JsonTestCompany { CompanyID = 3, CompanyName = "CCC Amalgamated Industries" }, - FormerEmployers = new JsonTestCompany[] - { - new JsonTestCompany { CompanyID = 2, CompanyName = "BBB Amalgamated Industries" }, - new JsonTestCompany { CompanyID = 5, CompanyName = "EEE Amalgamated Industries" }, + const string IntArrayJson = "[405421362,1082483948,1131707654,345242860,1111968802]"; + const string ShortArrayJson = "[12345,25463,22546,18879,12453]"; + const string StringJson = "some string"; + const string ArrayListJson = "[{\"stringtest\":\"hello world\",\"nulltest\":null,\"collection\":[-1,null,24.565657576,\"blah\",false]}]"; + const string s_AzureTwinsJsonTestPayload = @"{ + ""deviceId"": ""nanoDeepSleep"", + ""etag"": ""AAAAAAAAAAc="", + ""deviceEtag"": ""Njc2MzYzMTQ5"", + ""status"": ""enabled"", + ""statusUpdateTime"": ""0001-01-01T00:00:00Z"", + ""connectionState"": ""Disconnected"", + ""lastActivityTime"": ""2021-06-03T05:52:41.4683112Z"", + ""cloudToDeviceMessageCount"": 0, + ""authenticationType"": ""sas"", + ""x509Thumbprint"": { + ""primaryThumbprint"": null, + ""secondaryThumbprint"": null + }, + ""modelId"": """", + ""version"": 381, + ""properties"": { + ""desired"": { + ""TimeToSleep"": 30, + ""$metadata"": { + ""$lastUpdated"": ""2021-06-03T05:37:11.8120413Z"", + ""$lastUpdatedVersion"": 7, + ""TimeToSleep"": { + ""$lastUpdated"": ""2021-06-03T05:37:11.8120413Z"", + ""$lastUpdatedVersion"": 7 } - }, - new JsonTestEmployee - { - EmployeeID = 1, - EmployeeName = "Jim Smith", - CurrentEmployer = new JsonTestCompany { CompanyID = 7, CompanyName = "GGG Amalgamated Industries" }, - FormerEmployers = new JsonTestCompany[] - { - new JsonTestCompany { CompanyID = 4, CompanyName = "DDD Amalgamated Industries" }, - new JsonTestCompany { CompanyID = 1, CompanyName = "AAA Amalgamated Industries" }, - new JsonTestCompany { CompanyID = 6, CompanyName = "FFF Amalgamated Industries" }, + }, + ""$version"": 7 + }, + ""reported"": { + ""Firmware"": ""nanoFramework"", + ""TimeToSleep"": 30, + ""$metadata"": { + ""$lastUpdated"": ""2021-06-03T05:52:41.1232797Z"", + ""Firmware"": { + ""$lastUpdated"": ""2021-06-03T05:52:41.1232797Z"" + }, + ""TimeToSleep"": { + ""$lastUpdated"": ""2021-06-03T05:52:41.1232797Z"" } - } + }, + ""$version"": 374 } - }; + }, + ""capabilities"": { + ""iotEdge"": false + } + }"; - arrayList = new ArrayList() - { - { "testString" }, - { 42 }, - { null }, - { DateTime.UtcNow }, - { TimeSpan.FromSeconds(100) } - }; - } + const string NestedClassJson = "{\"FirstName\":\"John\",\"LastName\":\"Doe\",\"ArrayProperty\":[\"hello\",\"world\"],\"Address\":null,\"Birthday\":\"1988-04-23T00:00:00.0000000Z\",\"ID\":27,\"Friend\":{\"FirstName\":\"Bob\",\"LastName\":\"Smith\",\"ArrayProperty\":[\"hi\",\"planet\"],\"Address\":\"123 Some St\",\"Birthday\":\"1983-07-03T00:00:00.0000000Z\",\"ID\":2,\"Friend\":null}}"; + const string ComplexArrayJson = "{\"TownID\":1,\"EmployeesInThisTown\":[{\"CurrentEmployer\":{\"CompanyID\":3,\"CompanyName\":\"CCC Amalgamated Industries\"},\"EmployeeID\":1,\"FormerEmployers\":[{\"CompanyID\":2,\"CompanyName\":\"BBB Amalgamated Industries\"},{\"CompanyID\":5,\"CompanyName\":\"EEE Amalgamated Industries\"}],\"EmployeeName\":\"John Smith\"},{\"CurrentEmployer\":{\"CompanyID\":7,\"CompanyName\":\"GGG Amalgamated Industries\"},\"EmployeeID\":1,\"FormerEmployers\":[{\"CompanyID\":4,\"CompanyName\":\"DDD Amalgamated Industries\"},{\"CompanyID\":1,\"CompanyName\":\"AAA Amalgamated Industries\"},{\"CompanyID\":6,\"CompanyName\":\"FFF Amalgamated Industries\"}],\"EmployeeName\":\"Jim Smith\"}],\"TownName\":\"myTown\",\"CompaniesInThisTown\":[{\"CompanyID\":1,\"CompanyName\":\"AAA Amalgamated Industries\"},{\"CompanyID\":2,\"CompanyName\":\"BBB Amalgamated Industries\"},{\"CompanyID\":3,\"CompanyName\":\"CCC Amalgamated Industries\"},{\"CompanyID\":4,\"CompanyName\":\"DDD Amalgamated Industries\"},{\"CompanyID\":5,\"CompanyName\":\"EEE Amalgamated Industries\"},{\"CompanyID\":6,\"CompanyName\":\"FFF Amalgamated Industries\"},{\"CompanyID\":7,\"CompanyName\":\"GGG Amalgamated Industries\"},{\"CompanyID\":8,\"CompanyName\":\"HHH Amalgamated Industries\"}]}"; + + protected override int IterationCount => 20; [Benchmark] public void IntArray() { RunInIteration(() => { - JsonConvert.SerializeObject(intArray); + JsonConvert.DeserializeObject(IntArrayJson, typeof(int[])); }); } [Benchmark] - public void ShortArray() + public void ArrayList() { RunInIteration(() => { - JsonConvert.SerializeObject(shortArray); + JsonConvert.DeserializeObject(ArrayListJson, typeof(ArrayList)); }); } [Benchmark] - public void String() + public void ComplexObjectAzureTwinPayload() { - RunInIteration(() => - { - JsonConvert.SerializeObject(testString); - }); + JsonConvert.DeserializeObject(s_AzureTwinsJsonTestPayload, typeof(TwinPayload)); } [Benchmark] - public void NestedClass() + public void ShortArray() { - JsonConvert.SerializeObject(nestedTestClass); + RunInIteration(() => + { + JsonConvert.DeserializeObject(ShortArrayJson, typeof(short[])); + }); } [Benchmark] - public void ComplexObject() + public void String() { - JsonConvert.SerializeObject(complexClass); + RunInIteration(() => + { + JsonConvert.DeserializeObject(StringJson, typeof(string)); + }); } [Benchmark] - public void ComplexArrayObject() + public void NestedClass() { - JsonConvert.SerializeObject(myTown); + JsonConvert.DeserializeObject(NestedClassJson, typeof(Person)); } [Benchmark] - public void ArrayList() + public void ComplexArrayObject() { - RunInIteration(() => - { - JsonConvert.SerializeObject(arrayList); - }); + JsonConvert.DeserializeObject(ComplexArrayJson, typeof(JsonTestTown)); } } -} \ No newline at end of file +} diff --git a/nanoFramework.Json.Benchmark/DeserializationBenchmarks/ValueTypesDeserializationBenchmark.cs b/nanoFramework.Json.Benchmark/DeserializationBenchmarks/ValueTypesDeserializationBenchmark.cs index ecac85bc..dee9e84a 100644 --- a/nanoFramework.Json.Benchmark/DeserializationBenchmarks/ValueTypesDeserializationBenchmark.cs +++ b/nanoFramework.Json.Benchmark/DeserializationBenchmarks/ValueTypesDeserializationBenchmark.cs @@ -24,7 +24,7 @@ public void Short() RunInIteration(() => { // TODO: Return value should be of type short - var value = (int)JsonConvert.DeserializeObject(ShortJson, typeof(short)); + JsonConvert.DeserializeObject(ShortJson, typeof(short)); }); } @@ -34,7 +34,7 @@ public void TimeSpanT() RunInIteration(() => { // TODO: Return value should be of type timespan - var value = JsonConvert.DeserializeObject(TimeSpanJson, typeof(TimeSpan)); + JsonConvert.DeserializeObject(TimeSpanJson, typeof(TimeSpan)); }); } @@ -44,7 +44,7 @@ public void Float() RunInIteration(() => { // TODO: Return value should be of type float - var value = (double)JsonConvert.DeserializeObject(FloatJson, typeof(float)); + JsonConvert.DeserializeObject(FloatJson, typeof(float)); }); } @@ -53,7 +53,7 @@ public void Double() { RunInIteration(() => { - var value = (double)JsonConvert.DeserializeObject(DoubleJson, typeof(double)); + JsonConvert.DeserializeObject(DoubleJson, typeof(double)); }); } @@ -62,7 +62,7 @@ public void DateTimeT() { RunInIteration(() => { - var value = (DateTime)JsonConvert.DeserializeObject(DateTimeJson, typeof(DateTime)); + JsonConvert.DeserializeObject(DateTimeJson, typeof(DateTime)); }); } @@ -72,7 +72,7 @@ public void Long() RunInIteration(() => { // TODO: Return value should be of type long - var value = (int)JsonConvert.DeserializeObject(LongJson, typeof(long)); + JsonConvert.DeserializeObject(LongJson, typeof(long)); }); } @@ -88,7 +88,7 @@ public void EnumBoxed() { RunInIteration(() => { - var value = (Hashtable)JsonConvert.DeserializeObject(BoxedEnumJson, typeof(Hashtable)); + JsonConvert.DeserializeObject(BoxedEnumJson, typeof(Hashtable)); }); } } diff --git a/nanoFramework.Json.Benchmark/SerializationBenchmarks/ReferenceTypesSerializationBenchmark.cs b/nanoFramework.Json.Benchmark/SerializationBenchmarks/ReferenceTypesSerializationBenchmark.cs index 14519196..ea11e3a1 100644 --- a/nanoFramework.Json.Benchmark/SerializationBenchmarks/ReferenceTypesSerializationBenchmark.cs +++ b/nanoFramework.Json.Benchmark/SerializationBenchmarks/ReferenceTypesSerializationBenchmark.cs @@ -1,103 +1,82 @@ using nanoFramework.Benchmark; -using System; -using System.Collections; using nanoFramework.Benchmark.Attributes; using nanoFramework.Json.Benchmark.Base; using nanoFramework.Json.Test.Shared; +using System; +using System.Collections; namespace nanoFramework.Json.Benchmark.SerializationBenchmarks { [IterationCount(5)] public class ReferenceTypesSerializationBenchmark : BaseIterationBenchmark { - const string IntArrayJson = "[405421362,1082483948,1131707654,345242860,1111968802]"; - const string ShortArrayJson = "[12345,25463,22546,18879,12453]"; - const string StringJson = "some string"; - const string ArrayListJson = "[{\"stringtest\":\"hello world\",\"nulltest\":null,\"collection\":[-1,null,24.565657576,\"blah\",false]}]"; - const string s_AzureTwinsJsonTestPayload = @"{ - ""deviceId"": ""nanoDeepSleep"", - ""etag"": ""AAAAAAAAAAc="", - ""deviceEtag"": ""Njc2MzYzMTQ5"", - ""status"": ""enabled"", - ""statusUpdateTime"": ""0001-01-01T00:00:00Z"", - ""connectionState"": ""Disconnected"", - ""lastActivityTime"": ""2021-06-03T05:52:41.4683112Z"", - ""cloudToDeviceMessageCount"": 0, - ""authenticationType"": ""sas"", - ""x509Thumbprint"": { - ""primaryThumbprint"": null, - ""secondaryThumbprint"": null - }, - ""modelId"": """", - ""version"": 381, - ""properties"": { - ""desired"": { - ""TimeToSleep"": 30, - ""$metadata"": { - ""$lastUpdated"": ""2021-06-03T05:37:11.8120413Z"", - ""$lastUpdatedVersion"": 7, - ""TimeToSleep"": { - ""$lastUpdated"": ""2021-06-03T05:37:11.8120413Z"", - ""$lastUpdatedVersion"": 7 - } - }, - ""$version"": 7 - }, - ""reported"": { - ""Firmware"": ""nanoFramework"", - ""TimeToSleep"": 30, - ""$metadata"": { - ""$lastUpdated"": ""2021-06-03T05:52:41.1232797Z"", - ""Firmware"": { - ""$lastUpdated"": ""2021-06-03T05:52:41.1232797Z"" - }, - ""TimeToSleep"": { - ""$lastUpdated"": ""2021-06-03T05:52:41.1232797Z"" - } - }, - ""$version"": 374 - } - }, - ""capabilities"": { - ""iotEdge"": false + const string testString = "TestStringToSerialize"; + const short arrayElementCount = 5; + readonly int[] intArray = new int[arrayElementCount]; + readonly short[] shortArray = new short[arrayElementCount]; + private Person nestedTestClass; + private JsonTestClassComplex complexClass; + private JsonTestTown myTown; + private ArrayList arrayList; + + [Setup] + public void Setup() + { + var random = new Random(); + for (int i = 0; i < arrayElementCount; i++) + { + intArray[i] = random.Next(); + shortArray[i] = (short)random.Next(short.MaxValue); } - }"; - const string NestedClassJson = "{\"FirstName\":\"John\",\"LastName\":\"Doe\",\"ArrayProperty\":[\"hello\",\"world\"],\"Address\":null,\"Birthday\":\"1988-04-23T00:00:00.0000000Z\",\"ID\":27,\"Friend\":{\"FirstName\":\"Bob\",\"LastName\":\"Smith\",\"ArrayProperty\":[\"hi\",\"planet\"],\"Address\":\"123 Some St\",\"Birthday\":\"1983-07-03T00:00:00.0000000Z\",\"ID\":2,\"Friend\":null}}"; - const string ComplexArrayJson = "{\"TownID\":1,\"EmployeesInThisTown\":[{\"CurrentEmployer\":{\"CompanyID\":3,\"CompanyName\":\"CCC Amalgamated Industries\"},\"EmployeeID\":1,\"FormerEmployers\":[{\"CompanyID\":2,\"CompanyName\":\"BBB Amalgamated Industries\"},{\"CompanyID\":5,\"CompanyName\":\"EEE Amalgamated Industries\"}],\"EmployeeName\":\"John Smith\"},{\"CurrentEmployer\":{\"CompanyID\":7,\"CompanyName\":\"GGG Amalgamated Industries\"},\"EmployeeID\":1,\"FormerEmployers\":[{\"CompanyID\":4,\"CompanyName\":\"DDD Amalgamated Industries\"},{\"CompanyID\":1,\"CompanyName\":\"AAA Amalgamated Industries\"},{\"CompanyID\":6,\"CompanyName\":\"FFF Amalgamated Industries\"}],\"EmployeeName\":\"Jim Smith\"}],\"TownName\":\"myTown\",\"CompaniesInThisTown\":[{\"CompanyID\":1,\"CompanyName\":\"AAA Amalgamated Industries\"},{\"CompanyID\":2,\"CompanyName\":\"BBB Amalgamated Industries\"},{\"CompanyID\":3,\"CompanyName\":\"CCC Amalgamated Industries\"},{\"CompanyID\":4,\"CompanyName\":\"DDD Amalgamated Industries\"},{\"CompanyID\":5,\"CompanyName\":\"EEE Amalgamated Industries\"},{\"CompanyID\":6,\"CompanyName\":\"FFF Amalgamated Industries\"},{\"CompanyID\":7,\"CompanyName\":\"GGG Amalgamated Industries\"},{\"CompanyID\":8,\"CompanyName\":\"HHH Amalgamated Industries\"}]}"; + nestedTestClass = new Person() + { + FirstName = "John", + LastName = "Doe", + Birthday = new DateTime(1988, 4, 23), + ID = 27, + Address = null, + ArrayProperty = new string[] { "hello", "world" }, + Friend = new Person() + { + FirstName = "Bob", + LastName = "Smith", + Birthday = new DateTime(1983, 7, 3), + ID = 2, + Address = "123 Some St", + ArrayProperty = new string[] { "hi", "planet" }, + } + }; - protected override int IterationCount => 20; + complexClass = JsonTestClassComplex.CreateTestClass(); - [Benchmark] - public void IntArray() - { - RunInIteration(() => + myTown = JsonTestTown.CreateTestClass(); + + arrayList = new ArrayList() { - var dserResult = (int[])JsonConvert.DeserializeObject(IntArrayJson, typeof(int[])); - }); + { "testString" }, + { 42 }, + { null }, + { DateTime.UtcNow }, + { TimeSpan.FromSeconds(100) } + }; } [Benchmark] - public void ArrayList() + public void IntArray() { RunInIteration(() => { - var arrayList = (ArrayList)JsonConvert.DeserializeObject(ArrayListJson, typeof(ArrayList)); + JsonConvert.SerializeObject(intArray); }); } - [Benchmark] - public void ComplexObjectAzureTwinPayload() - { - var twinPayload = (TwinPayload)JsonConvert.DeserializeObject(s_AzureTwinsJsonTestPayload, typeof(TwinPayload)); - } - [Benchmark] public void ShortArray() { RunInIteration(() => { - var dserResult = (short[])JsonConvert.DeserializeObject(ShortArrayJson, typeof(short[])); + JsonConvert.SerializeObject(shortArray); }); } @@ -106,22 +85,35 @@ public void String() { RunInIteration(() => { - var dserResult = (string)JsonConvert.DeserializeObject(StringJson, typeof(string)); + JsonConvert.SerializeObject(testString); }); } [Benchmark] public void NestedClass() { - var desrResult = (Person)JsonConvert.DeserializeObject(NestedClassJson, typeof(Person)); + JsonConvert.SerializeObject(nestedTestClass); + } + + [Benchmark] + public void ComplexObject() + { + JsonConvert.SerializeObject(complexClass); } - // Sometime it may throw ++++ Exception System.InvalidCastException - CLR_E_INVALID_CAST (1) ++++ - // After re run it should work [Benchmark] public void ComplexArrayObject() { - var desrResult = (JsonTestTown)JsonConvert.DeserializeObject(ComplexArrayJson, typeof(JsonTestTown)); + JsonConvert.SerializeObject(myTown); + } + + [Benchmark] + public void ArrayList() + { + RunInIteration(() => + { + JsonConvert.SerializeObject(arrayList); + }); } } -} +} \ No newline at end of file diff --git a/nanoFramework.Json.Benchmark/nanoFramework.Json.Benchmark.nfproj b/nanoFramework.Json.Benchmark/nanoFramework.Json.Benchmark.nfproj index b2654a3a..14f5a7d8 100644 --- a/nanoFramework.Json.Benchmark/nanoFramework.Json.Benchmark.nfproj +++ b/nanoFramework.Json.Benchmark/nanoFramework.Json.Benchmark.nfproj @@ -21,12 +21,12 @@ - + - + diff --git a/nanoFramework.Json.Test.Shared/JsonTestClassComplex.cs b/nanoFramework.Json.Test.Shared/JsonTestClassComplex.cs index 1403cbb1..195438e7 100644 --- a/nanoFramework.Json.Test.Shared/JsonTestClassComplex.cs +++ b/nanoFramework.Json.Test.Shared/JsonTestClassComplex.cs @@ -28,5 +28,32 @@ public class JsonTestClassComplex private string dontSerializeStr = "dontPublish"; #pragma warning restore 0414 private string dontSerialize { get; set; } = "dontPublish"; + + internal static JsonTestClassComplex CreateTestClass() + { + return new JsonTestClassComplex() + { + aInteger = 10, + aShort = 254, + aByte = 0x05, + aString = "A string", + aFloat = 1.2345f, + aDouble = 1.2345, + aBoolean = true, + Timestamp = DateTime.UtcNow, + FixedTimestamp = new DateTime(2020, 05, 01, 09, 30, 00), + intArray = new[] { 1, 3, 5, 7, 9 }, + shortArray = new[] { (short)1, (short)3, (short)5, (short)7, (short)9 }, + byteArray = new[] { (byte)0x22, (byte)0x23, (byte)0x24, (byte)0x25, (byte)0x26 }, + stringArray = new[] { "two", "four", "six", "eight" }, + floatArray = new[] { 1.1f, 3.3f, 5.5f, 7.7f, 9.9f }, + doubleArray = new[] { 1.12345, 3.3456, 5.56789, 7.78910, 9.910111213 }, + child1 = new JsonTestClassChild() { one = 1, two = 2, three = 3 }, + Child = new JsonTestClassChild() { one = 100, two = 200, three = 300 }, + nullObject = null, + nanFloat = float.NaN, + nanDouble = double.NaN, + }; + } } } diff --git a/nanoFramework.Json.Test.Shared/JsonTestTown.cs b/nanoFramework.Json.Test.Shared/JsonTestTown.cs index 918c25e5..7eb31d9f 100644 --- a/nanoFramework.Json.Test.Shared/JsonTestTown.cs +++ b/nanoFramework.Json.Test.Shared/JsonTestTown.cs @@ -8,5 +8,51 @@ public class JsonTestTown public string TownName { get; set; } public JsonTestCompany[] CompaniesInThisTown { get; set; } public JsonTestEmployee[] EmployeesInThisTown { get; set; } + + internal static JsonTestTown CreateTestClass() + { + return new JsonTestTown() + { + TownID = 1, + TownName = "myTown", + CompaniesInThisTown = new JsonTestCompany[] + { + new JsonTestCompany { CompanyID = 1, CompanyName = "AAA Amalgamated Industries" }, + new JsonTestCompany { CompanyID = 2, CompanyName = "BBB Amalgamated Industries" }, + new JsonTestCompany { CompanyID = 3, CompanyName = "CCC Amalgamated Industries" }, + new JsonTestCompany { CompanyID = 4, CompanyName = "DDD Amalgamated Industries" }, + new JsonTestCompany { CompanyID = 5, CompanyName = "EEE Amalgamated Industries" }, + new JsonTestCompany { CompanyID = 6, CompanyName = "FFF Amalgamated Industries" }, + new JsonTestCompany { CompanyID = 7, CompanyName = "GGG Amalgamated Industries" }, + new JsonTestCompany { CompanyID = 8, CompanyName = "HHH Amalgamated Industries" } + }, + EmployeesInThisTown = new JsonTestEmployee[] + { + new JsonTestEmployee + { + EmployeeID = 1, + EmployeeName = "John Smith", + CurrentEmployer = new JsonTestCompany { CompanyID = 3, CompanyName = "CCC Amalgamated Industries" }, + FormerEmployers = new JsonTestCompany[] + { + new JsonTestCompany { CompanyID = 2, CompanyName = "BBB Amalgamated Industries" }, + new JsonTestCompany { CompanyID = 5, CompanyName = "EEE Amalgamated Industries" }, + } + }, + new JsonTestEmployee + { + EmployeeID = 1, + EmployeeName = "Jim Smith", + CurrentEmployer = new JsonTestCompany { CompanyID = 7, CompanyName = "GGG Amalgamated Industries" }, + FormerEmployers = new JsonTestCompany[] + { + new JsonTestCompany { CompanyID = 4, CompanyName = "DDD Amalgamated Industries" }, + new JsonTestCompany { CompanyID = 1, CompanyName = "AAA Amalgamated Industries" }, + new JsonTestCompany { CompanyID = 6, CompanyName = "FFF Amalgamated Industries" }, + } + } + } + }; + } } } diff --git a/nanoFramework.Json.Test/Converters/BoolConverterTests.cs b/nanoFramework.Json.Test/Converters/BoolConverterTests.cs new file mode 100644 index 00000000..e2f30725 --- /dev/null +++ b/nanoFramework.Json.Test/Converters/BoolConverterTests.cs @@ -0,0 +1,31 @@ +using nanoFramework.TestFramework; +using System; + +namespace nanoFramework.Json.Test.Converters +{ + [TestClass] + public class BoolConverterTests + { + /*[TestMethod] + [DataRow("true", true)] + [DataRow("false", false)] + public void ToType_ShouldReturnValidData(string value, bool expectedValue) + { + var converter = new Json.Converters.BoolConverter(); + var convertedValue = (bool)converter.ToType(value); + + Assert.Equal(expectedValue, convertedValue); + }*/ + + [TestMethod] + [DataRow(true, "true")] + [DataRow(false, "false")] + public void BoolConverter_ToJson_Should_ReturnValidData(bool value, string expectedValue) + { + var converter = new Json.Converters.BoolConverter(); + var convertedValue = converter.ToJson(value); + + Assert.Equal(expectedValue, convertedValue); + } + } +} diff --git a/nanoFramework.Json.Test/Converters/ByteConverterTests.cs b/nanoFramework.Json.Test/Converters/ByteConverterTests.cs new file mode 100644 index 00000000..ec5ad333 --- /dev/null +++ b/nanoFramework.Json.Test/Converters/ByteConverterTests.cs @@ -0,0 +1,31 @@ +using nanoFramework.TestFramework; +using System; + +namespace nanoFramework.Json.Test.Converters +{ + [TestClass] + public class ByteConverterTests + { + [TestMethod] + [DataRow("120", (byte)120)] + [DataRow("42", (byte)42)] + public void ByteConverter_ToType_ShouldReturnValidData(string value, byte expectedValue) + { + var converter = new Json.Converters.ByteConverter(); + var convertedValue = (byte)converter.ToType(value); + + Assert.Equal(expectedValue, convertedValue); + } + + [TestMethod] + [DataRow((byte)120, "120")] + [DataRow((byte)42, "42")] + public void ByteConverter_ToJson_Should_ReturnValidData(byte value, string expectedValue) + { + var converter = new Json.Converters.ByteConverter(); + var convertedValue = converter.ToJson(value); + + Assert.Equal(expectedValue, convertedValue); + } + } +} diff --git a/nanoFramework.Json.Test/Converters/ConvertersMappingTests.cs b/nanoFramework.Json.Test/Converters/ConvertersMappingTests.cs new file mode 100644 index 00000000..4592922b --- /dev/null +++ b/nanoFramework.Json.Test/Converters/ConvertersMappingTests.cs @@ -0,0 +1,90 @@ +using nanoFramework.Json.Converters; +using nanoFramework.TestFramework; +using System; +using System.Text; + +namespace nanoFramework.Json.Test.Converters +{ + [TestClass] + public class ConvertersMappingTests + { + class TestConverter : IConverter + { + public string ToJson(object value) + { + throw new NotImplementedException(); + } + + public object ToType(object value) + { + throw new NotImplementedException(); + } + } + + class TestConverter2 : IConverter + { + public string ToJson(object value) + { + throw new NotImplementedException(); + } + + public object ToType(object value) + { + throw new NotImplementedException(); + } + } + + [TestMethod] + public void ConvertersMappingT_Add_Should_AddTypeMapping() + { + ConvertersMapping.Add(typeof(IConverter), new TestConverter()); + + var converter = ConvertersMapping.ConversionTable[typeof(IConverter)]; + Assert.NotNull(converter); + + if (converter.GetType() != typeof(TestConverter)) + { + throw new InvalidOperationException("Invalid type returned."); + } + } + + [TestMethod] + public void ConvertersMappingT_Remove_Should_RemoveTypeMapping() + { + ConvertersMapping.Add(typeof(TestConverter), new TestConverter()); + ConvertersMapping.Remove(typeof(TestConverter)); + + var converterKeys = ConvertersMapping.ConversionTable.Keys; + foreach (var item in converterKeys) + { + var type = (Type)item; + if (type == typeof(TestConverter)) + { + throw new InvalidOperationException($"After removing {nameof(TestConverter)} type, it should not be in collection."); + } + } + } + + [TestMethod] + public void ConvertersMappingT_Replace_ShouldReplaceMapping() + { + ConvertersMapping.Add(typeof(TestConverter2), new TestConverter()); + ConvertersMapping.Replace(typeof(TestConverter2), new TestConverter2()); + + var converter = ConvertersMapping.ConversionTable[typeof(TestConverter2)]; + Assert.NotNull(converter); + + if (converter.GetType() != typeof(TestConverter2)) + { + throw new InvalidOperationException("Invalid type returned."); + } + } + + [Cleanup] + public void Cleanup() + { + ConvertersMapping.Remove(typeof(IConverter)); + ConvertersMapping.Remove(typeof(TestConverter2)); + } + } +} diff --git a/nanoFramework.Json.Test/Converters/DoubleConverterTests.cs b/nanoFramework.Json.Test/Converters/DoubleConverterTests.cs new file mode 100644 index 00000000..e88e0195 --- /dev/null +++ b/nanoFramework.Json.Test/Converters/DoubleConverterTests.cs @@ -0,0 +1,32 @@ +using nanoFramework.TestFramework; +using System; + +namespace nanoFramework.Json.Test.Converters +{ + [TestClass] + public class DoubleConverterTests + { + [TestMethod] + [DataRow("120.0", 120.0)] + [DataRow("42.5", 42.5)] + public void DoubleConverter_ToType_ShouldReturnValidData(string value, double expectedValue) + { + var converter = new Json.Converters.DoubleConverter(); + var convertedValue = (double)converter.ToType(value); + + Assert.Equal(expectedValue, convertedValue); + } + + [TestMethod] + [DataRow(120.5, "120.5")] + [DataRow(42.5, "42.5")] + [DataRow(double.NaN, "null")] + public void DoubleConverter_ToJson_Should_ReturnValidData(double value, string expectedValue) + { + var converter = new Json.Converters.DoubleConverter(); + var convertedValue = converter.ToJson(value); + + Assert.Equal(expectedValue, convertedValue); + } + } +} diff --git a/nanoFramework.Json.Test/Converters/FloatConverterTests.cs b/nanoFramework.Json.Test/Converters/FloatConverterTests.cs new file mode 100644 index 00000000..4ff47941 --- /dev/null +++ b/nanoFramework.Json.Test/Converters/FloatConverterTests.cs @@ -0,0 +1,32 @@ +using nanoFramework.TestFramework; +using System; + +namespace nanoFramework.Json.Test.Converters +{ + [TestClass] + public class FloatConverterTests + { + [TestMethod] + [DataRow("120.0", 120.0f)] + [DataRow("42.5", 42.5f)] + public void FloatConverter_ToType_ShouldReturnValidData(string value, float expectedValue) + { + var converter = new Json.Converters.FloatConverter(); + var convertedValue = (float)converter.ToType(value); + + Assert.Equal(expectedValue, convertedValue); + } + + [TestMethod] + [DataRow(120.5f, "120.5")] + [DataRow(42.5f, "42.5")] + [DataRow(float.NaN, "null")] + public void FloatConverter_ToJson_Should_ReturnValidData(float value, string expectedValue) + { + var converter = new Json.Converters.FloatConverter(); + var convertedValue = converter.ToJson(value); + + Assert.Equal(expectedValue, convertedValue); + } + } +} diff --git a/nanoFramework.Json.Test/Converters/IntConverterTests.cs b/nanoFramework.Json.Test/Converters/IntConverterTests.cs new file mode 100644 index 00000000..db7b93eb --- /dev/null +++ b/nanoFramework.Json.Test/Converters/IntConverterTests.cs @@ -0,0 +1,31 @@ +using nanoFramework.TestFramework; +using System; + +namespace nanoFramework.Json.Test.Converters +{ + [TestClass] + public class IntConverterTests + { + [TestMethod] + [DataRow("120", 120)] + [DataRow("45", 45)] + public void IntConverter_ToType_ShouldReturnValidData(string value, int expectedValue) + { + var converter = new Json.Converters.IntConverter(); + var convertedValue = (int)converter.ToType(value); + + Assert.Equal(expectedValue, convertedValue); + } + + [TestMethod] + [DataRow(120, "120")] + [DataRow(45, "45")] + public void IntConverter_ToJson_Should_ReturnValidData(int value, string expectedValue) + { + var converter = new Json.Converters.IntConverter(); + var convertedValue = converter.ToJson(value); + + Assert.Equal(expectedValue, convertedValue); + } + } +} diff --git a/nanoFramework.Json.Test/Converters/LongConverterTests.cs b/nanoFramework.Json.Test/Converters/LongConverterTests.cs new file mode 100644 index 00000000..fe3b8f63 --- /dev/null +++ b/nanoFramework.Json.Test/Converters/LongConverterTests.cs @@ -0,0 +1,31 @@ +using nanoFramework.TestFramework; +using System; + +namespace nanoFramework.Json.Test.Converters +{ + [TestClass] + public class LongConverterTests + { + [TestMethod] + [DataRow("120", (long)120)] + [DataRow("45", (long)45)] + public void LongConverter_ToType_ShouldReturnValidData(string value, long expectedValue) + { + var converter = new Json.Converters.LongConverter(); + var convertedValue = (long)converter.ToType(value); + + Assert.Equal(expectedValue, convertedValue); + } + + [TestMethod] + [DataRow((long)120, "120")] + [DataRow((long)45, "45")] + public void LongConverter_ToJson_Should_ReturnValidData(long value, string expectedValue) + { + var converter = new Json.Converters.LongConverter(); + var convertedValue = converter.ToJson(value); + + Assert.Equal(expectedValue, convertedValue); + } + } +} diff --git a/nanoFramework.Json.Test/Converters/SByteConverterTests.cs b/nanoFramework.Json.Test/Converters/SByteConverterTests.cs new file mode 100644 index 00000000..75f12a83 --- /dev/null +++ b/nanoFramework.Json.Test/Converters/SByteConverterTests.cs @@ -0,0 +1,31 @@ +using nanoFramework.TestFramework; +using System; + +namespace nanoFramework.Json.Test.Converters +{ + [TestClass] + public class SByteConverterTests + { + [TestMethod] + [DataRow("120", (sbyte)120)] + [DataRow("42", (sbyte)42)] + public void SByteConverter_ToType_ShouldReturnValidData(string value, sbyte expectedValue) + { + var converter = new Json.Converters.SByteConverter(); + var convertedValue = (sbyte)converter.ToType(value); + + Assert.Equal(expectedValue, convertedValue); + } + + [TestMethod] + [DataRow((sbyte)120, "120")] + [DataRow((sbyte)42, "42")] + public void SByteConverter_ToJson_Should_ReturnValidData(sbyte value, string expectedValue) + { + var converter = new Json.Converters.SByteConverter(); + var convertedValue = converter.ToJson(value); + + Assert.Equal(expectedValue, convertedValue); + } + } +} diff --git a/nanoFramework.Json.Test/Converters/ShortConverterTests.cs b/nanoFramework.Json.Test/Converters/ShortConverterTests.cs new file mode 100644 index 00000000..da88bd4a --- /dev/null +++ b/nanoFramework.Json.Test/Converters/ShortConverterTests.cs @@ -0,0 +1,31 @@ +using nanoFramework.TestFramework; +using System; + +namespace nanoFramework.Json.Test.Converters +{ + [TestClass] + public class ShortConverterTests + { + [TestMethod] + [DataRow("120", (short)120)] + [DataRow("45", (short)45)] + public void ShortConverter_ToType_ShouldReturnValidData(string value, short expectedValue) + { + var converter = new Json.Converters.ShortConverter(); + var convertedValue = (short)converter.ToType(value); + + Assert.Equal(expectedValue, convertedValue); + } + + [TestMethod] + [DataRow((short)120, "120")] + [DataRow((short)45, "45")] + public void ShortConverter_ToJson_Should_ReturnValidData(short value, string expectedValue) + { + var converter = new Json.Converters.ShortConverter(); + var convertedValue = converter.ToJson(value); + + Assert.Equal(expectedValue, convertedValue); + } + } +} diff --git a/nanoFramework.Json.Test/Converters/StringConverterTests.cs b/nanoFramework.Json.Test/Converters/StringConverterTests.cs new file mode 100644 index 00000000..a1a1e36c --- /dev/null +++ b/nanoFramework.Json.Test/Converters/StringConverterTests.cs @@ -0,0 +1,29 @@ +using nanoFramework.TestFramework; +using System; + +namespace nanoFramework.Json.Test.Converters +{ + [TestClass] + public class StringConverterTests + { + [TestMethod] + [DataRow("\"TestJson\"", "TestJson")] + public void StringConverter_ToType_ShouldReturnValidData(string value, string expectedValue) + { + var converter = new Json.Converters.StringConverter(); + var convertedValue = (string)converter.ToType(value); + + Assert.Equal(expectedValue, convertedValue); + } + + [TestMethod] + [DataRow("TestJson", "\"TestJson\"")] + public void StringConverter_ToJson_Should_ReturnValidData(string value, string expectedValue) + { + var converter = new Json.Converters.StringConverter(); + var convertedValue = converter.ToJson(value); + + Assert.Equal(expectedValue, convertedValue); + } + } +} diff --git a/nanoFramework.Json.Test/Converters/TimeSpanConverterTests.cs b/nanoFramework.Json.Test/Converters/TimeSpanConverterTests.cs new file mode 100644 index 00000000..2f8d992e --- /dev/null +++ b/nanoFramework.Json.Test/Converters/TimeSpanConverterTests.cs @@ -0,0 +1,30 @@ +using nanoFramework.TestFramework; +using System; + +namespace nanoFramework.Json.Test.Converters +{ + [TestClass] + public class TimeSpanConverterTests + { + [TestMethod] + [DataRow("10:00:00", 10)] + public void TimeSpanConverter_ToType_ShouldReturnValidData(string value, int expectedValueHours) + { + var converter = new Json.Converters.TimeSpanConverter(); + var convertedValue = (TimeSpan)converter.ToType(value); + + var expectedTimeSpanValue = TimeSpan.FromHours(expectedValueHours); + Assert.Equal(expectedTimeSpanValue.Ticks, convertedValue.Ticks); + } + + [TestMethod] + [DataRow(10, "\"10:00:00\"")] + public void TimeSpanConverter_ToJson_Should_ReturnValidData(int valueHours, string expectedValue) + { + var converter = new Json.Converters.TimeSpanConverter(); + var convertedValue = converter.ToJson(TimeSpan.FromHours(valueHours)); + + Assert.Equal(expectedValue, convertedValue); + } + } +} diff --git a/nanoFramework.Json.Test/Converters/UIntConverterTests.cs b/nanoFramework.Json.Test/Converters/UIntConverterTests.cs new file mode 100644 index 00000000..c25c2b6b --- /dev/null +++ b/nanoFramework.Json.Test/Converters/UIntConverterTests.cs @@ -0,0 +1,31 @@ +using nanoFramework.TestFramework; +using System; + +namespace nanoFramework.Json.Test.Converters +{ + [TestClass] + public class UIntConverterTests + { + [TestMethod] + [DataRow("120", (uint)120)] + [DataRow("45", (uint)45)] + public void UIntConverter_ToType_ShouldReturnValidData(string value, uint expectedValue) + { + var converter = new Json.Converters.UIntConverter(); + var convertedValue = (uint)converter.ToType(value); + + Assert.Equal(expectedValue, convertedValue); + } + + [TestMethod] + [DataRow((uint)120, "120")] + [DataRow((uint)45, "45")] + public void UIntConverter_ToJson_Should_ReturnValidData(uint value, string expectedValue) + { + var converter = new Json.Converters.UIntConverter(); + var convertedValue = converter.ToJson(value); + + Assert.Equal(expectedValue, convertedValue); + } + } +} diff --git a/nanoFramework.Json.Test/Converters/ULongConverterTests.cs b/nanoFramework.Json.Test/Converters/ULongConverterTests.cs new file mode 100644 index 00000000..1a7d2d1f --- /dev/null +++ b/nanoFramework.Json.Test/Converters/ULongConverterTests.cs @@ -0,0 +1,31 @@ +using nanoFramework.TestFramework; +using System; + +namespace nanoFramework.Json.Test.Converters +{ + [TestClass] + public class ULongConverterTests + { + [TestMethod] + [DataRow("120", (ulong)120)] + [DataRow("45", (ulong)45)] + public void ULongConverter_ToType_ShouldReturnValidData(string value, ulong expectedValue) + { + var converter = new Json.Converters.ULongConverter(); + var convertedValue = (ulong)converter.ToType(value); + + Assert.Equal(expectedValue, convertedValue); + } + + [TestMethod] + [DataRow((ulong)120, "120")] + [DataRow((ulong)45, "45")] + public void ULongConverter_ToJson_Should_ReturnValidData(ulong value, string expectedValue) + { + var converter = new Json.Converters.ULongConverter(); + var convertedValue = converter.ToJson(value); + + Assert.Equal(expectedValue, convertedValue); + } + } +} diff --git a/nanoFramework.Json.Test/Converters/UShortConverterTests.cs b/nanoFramework.Json.Test/Converters/UShortConverterTests.cs new file mode 100644 index 00000000..19343e7a --- /dev/null +++ b/nanoFramework.Json.Test/Converters/UShortConverterTests.cs @@ -0,0 +1,31 @@ +using nanoFramework.TestFramework; +using System; + +namespace nanoFramework.Json.Test.Converters +{ + [TestClass] + public class UShortConverterTests + { + [TestMethod] + [DataRow("120", (ushort)120)] + [DataRow("45", (ushort)45)] + public void UShortConverter_ToType_ShouldReturnValidData(string value, ushort expectedValue) + { + var converter = new Json.Converters.UShortConverter(); + var convertedValue = (ushort)converter.ToType(value); + + Assert.Equal(expectedValue, convertedValue); + } + + [TestMethod] + [DataRow((ushort)120, "120")] + [DataRow((ushort)45, "45")] + public void UShortConverter_ToJson_Should_ReturnValidData(ushort value, string expectedValue) + { + var converter = new Json.Converters.UShortConverter(); + var convertedValue = converter.ToJson(value); + + Assert.Equal(expectedValue, convertedValue); + } + } +} diff --git a/nanoFramework.Json.Test/JsonCustomTypeTests.cs b/nanoFramework.Json.Test/JsonCustomTypeTests.cs new file mode 100644 index 00000000..2fd1dc8e --- /dev/null +++ b/nanoFramework.Json.Test/JsonCustomTypeTests.cs @@ -0,0 +1,60 @@ +using nanoFramework.Json.Converters; +using nanoFramework.TestFramework; +using System; +using System.Text; + +namespace nanoFramework.Json.Test +{ + [TestClass] + public class JsonCustomTypeTests + { + private class TestObject + { + public int Value { get; set; } + } + + + private class CustomConverter : IConverter + { + public string ToJson(object value) + { + return "123"; + } + + public object ToType(object value) + { + return new TestObject() { Value = 321 }; + } + } + + [Setup] + public void Setup() + { + ConvertersMapping.Add(typeof(TestObject), new CustomConverter()); + } + + [Cleanup] + public void CleanUp() + { + ConvertersMapping.Remove(typeof(TestObject)); + } + + [TestMethod] + public void CustomMapping_Should_SerializeToGivenValue() + { + var obj = new TestObject() { Value = 5 }; + + var value = JsonConvert.SerializeObject(obj); + + Assert.Equal(value, "123"); + } + + [TestMethod] + public void CustomMapping_Should_DeserializeToGivenValue() + { + var obj = (TestObject)JsonConvert.DeserializeObject("{\"TestObject\" : \"whatever\"}", typeof(TestObject)); + + Assert.Equal(obj.Value, 321); + } + } +} diff --git a/nanoFramework.Json.Test/JsonUnitTests.cs b/nanoFramework.Json.Test/JsonUnitTests.cs index 91dcd9ab..222bb0a5 100644 --- a/nanoFramework.Json.Test/JsonUnitTests.cs +++ b/nanoFramework.Json.Test/JsonUnitTests.cs @@ -31,48 +31,7 @@ public void Can_serialize_and_deserialize_arrays_of_class_objects() { OutputHelper.WriteLine("Can_serialize_and_deserialize_arrays_of_class_objects() - Starting test..."); - JsonTestTown myTown = new JsonTestTown - { - TownID = 1, - TownName = "myTown", - CompaniesInThisTown = new JsonTestCompany[] - { - new JsonTestCompany { CompanyID = 1, CompanyName = "AAA Amalgamated Industries" }, - new JsonTestCompany { CompanyID = 2, CompanyName = "BBB Amalgamated Industries" }, - new JsonTestCompany { CompanyID = 3, CompanyName = "CCC Amalgamated Industries" }, - new JsonTestCompany { CompanyID = 4, CompanyName = "DDD Amalgamated Industries" }, - new JsonTestCompany { CompanyID = 5, CompanyName = "EEE Amalgamated Industries" }, - new JsonTestCompany { CompanyID = 6, CompanyName = "FFF Amalgamated Industries" }, - new JsonTestCompany { CompanyID = 7, CompanyName = "GGG Amalgamated Industries" }, - new JsonTestCompany { CompanyID = 8, CompanyName = "HHH Amalgamated Industries" } - }, - EmployeesInThisTown = new JsonTestEmployee[] - { - new JsonTestEmployee - { - EmployeeID = 1, - EmployeeName = "John Smith", - CurrentEmployer = new JsonTestCompany { CompanyID = 3, CompanyName = "CCC Amalgamated Industries" }, - FormerEmployers = new JsonTestCompany[] - { - new JsonTestCompany { CompanyID = 2, CompanyName = "BBB Amalgamated Industries" }, - new JsonTestCompany { CompanyID = 5, CompanyName = "EEE Amalgamated Industries" }, - } - }, - new JsonTestEmployee - { - EmployeeID = 1, - EmployeeName = "Jim Smith", - CurrentEmployer = new JsonTestCompany { CompanyID = 7, CompanyName = "GGG Amalgamated Industries" }, - FormerEmployers = new JsonTestCompany[] - { - new JsonTestCompany { CompanyID = 4, CompanyName = "DDD Amalgamated Industries" }, - new JsonTestCompany { CompanyID = 1, CompanyName = "AAA Amalgamated Industries" }, - new JsonTestCompany { CompanyID = 6, CompanyName = "FFF Amalgamated Industries" }, - } - } - } - }; + JsonTestTown myTown = JsonTestTown.CreateTestClass(); var result = JsonConvert.SerializeObject(myTown); JsonTestTown dserResult = (JsonTestTown)JsonConvert.DeserializeObject(result, typeof(JsonTestTown)); @@ -376,14 +335,14 @@ public void DeserialzieInvalidTimeSpan_Should_ThrowInvalidCaseException() try { // The method should throw InvalidCaseException as each strArr has at least one invalid value for TimeSpan - var _ = (JsonTestClassTimeSpan)JsonConvert.DeserializeObject(strArr[i], typeof(JsonTestClassTimeSpan)); + JsonConvert.DeserializeObject(strArr[i], typeof(JsonTestClassTimeSpan)); // If the method above haven't throw InvalidCastException then the test should fail - throw new Exception($"Should throw exception {nameof(InvalidCastException)}."); + throw new InvalidOperationException($"Should throw exception {nameof(InvalidCastException)}."); } catch (InvalidCastException) { - + // Deserialization should throw exception and test should not fail. } } @@ -451,29 +410,7 @@ public void Can_serialize_and_deserialize_escaped_string() public void Can_serialize_and_deserialize_complex_object() { OutputHelper.WriteLine("Can_serialize_and_deserialize_complex_object() - Starting test..."); - var test = new JsonTestClassComplex() - { - aInteger = 10, - aShort = 254, - aByte = 0x05, - aString = "A string", - aFloat = 1.2345f, - aDouble = 1.2345, - aBoolean = true, - Timestamp = DateTime.UtcNow, - FixedTimestamp = new DateTime(2020, 05, 01, 09, 30, 00), - intArray = new[] { 1, 3, 5, 7, 9 }, - shortArray = new[] { (short)1, (short)3, (short)5, (short)7, (short)9 }, - byteArray = new[] { (byte)0x22, (byte)0x23, (byte)0x24, (byte)0x25, (byte)0x26 }, - stringArray = new[] { "two", "four", "six", "eight" }, - floatArray = new[] { 1.1f, 3.3f, 5.5f, 7.7f, 9.9f }, - doubleArray = new[] { 1.12345, 3.3456, 5.56789, 7.78910, 9.910111213 }, - child1 = new JsonTestClassChild() { one = 1, two = 2, three = 3 }, - Child = new JsonTestClassChild() { one = 100, two = 200, three = 300 }, - nullObject = null, - nanFloat = float.NaN, - nanDouble = double.NaN, - }; + var test = JsonTestClassComplex.CreateTestClass(); var result = JsonConvert.SerializeObject(test); var dserResult = (JsonTestClassComplex)JsonConvert.DeserializeObject(result, typeof(JsonTestClassComplex)); @@ -568,7 +505,7 @@ public void Can_serialize_and_deserialize_float() OutputHelper.WriteLine("Starting float Object Test..."); var test = new JsonTestClassFloat() { - aFloat = 2567.454f, //TODO Deserialized float fails when number is greater than 3-4 DP with an extra `.` at the end. + aFloat = 2567.454f, //BUG: Deserialized float fails when number is greater than 3-4 DP with an extra `.` at the end. }; var result = JsonConvert.SerializeObject(test); @@ -576,7 +513,7 @@ public void Can_serialize_and_deserialize_float() OutputHelper.WriteLine($"After Type deserialization: {dserResult}"); - Assert.Equal(result, "{\"aFloat\":" + test.aFloat + "}", "Serialized float result is equal"); //TODO: better str handling! + Assert.Equal(result, "{\"aFloat\":" + test.aFloat + "}", "Serialized float result is equal"); Assert.Equal(test.aFloat, dserResult.aFloat, "Deserialized float Result is Equal"); OutputHelper.WriteLine("float Object Test Test succeeded"); @@ -618,7 +555,7 @@ public void Can_serialize_and_deserialize_double() OutputHelper.WriteLine($"After Type deserialization: {dserResult}"); - Assert.Equal(result, "{\"aDouble\":123.45669999}", "Serialized double result is a double"); //TODO: possible conversion issue (but can happen with conversions) + Assert.Equal(result, "{\"aDouble\":123.45669999}", "Serialized double result is a double"); OutputHelper.WriteLine("double Object Test Test succeeded"); OutputHelper.WriteLine(""); @@ -839,7 +776,6 @@ public void CanDeserializeAzureTwinProperties_04() Assert.NotNull(twinPayload, "Deserialization returned a null object"); Assert.Equal((string)twinPayload["authenticationType"], "sas", "authenticationType doesn't match"); - //Assert.Equal(((DateTime)twinPayload["statusUpdateTime"]).Ticks, DateTime.MinValue.Ticks, "statusUpdateTime doesn't match"); Assert.Equal((int)twinPayload["cloudToDeviceMessageCount"], 0, "cloudToDeviceMessageCount doesn't match"); Assert.Equal(((Hashtable)twinPayload["x509Thumbprint"]).Count, 2, "x509Thumbprint collection count doesn't match"); Assert.Equal((int)twinPayload["version"], 381, "version doesn't match"); @@ -1139,11 +1075,11 @@ public void DeserializeSingleTypesClassDeserialization() Assert.Equal((short)1234, deser.OneInt16, "Int16"); Assert.Equal((ushort)5678, deser.OneUInt16, "UInt16"); Assert.Equal(-789012, deser.OneInt32, "Int32"); - Assert.Equal((uint)78912, deser.OneUInt32, "UInt32"); - Assert.Equal((long)-1234567, deser.OneInt64, "Int64"); - Assert.Equal((ulong)1234567, deser.OneUInt64, "UInt64"); + Assert.Equal(78912, deser.OneUInt32, "UInt32"); + Assert.Equal(-1234567, deser.OneInt64, "Int64"); + Assert.Equal(1234567, deser.OneUInt64, "UInt64"); Assert.Equal((float)34.45, deser.OneSingle, "Single"); - Assert.Equal((double)45678.23, deser.OneDouble, "Double"); + Assert.Equal(45678.23, deser.OneDouble, "Double"); Assert.True(deser.OneBoolean, "Boolean true"); Assert.False(deser.TwoBoolean, "Boolean false"); } @@ -1183,9 +1119,9 @@ public void LongMaxValue() var serInt32 = JsonConvert.SerializeObject(singleInt32); var deserUInt64 = JsonConvert.DeserializeObject(serUInt64, typeof(SingleTypesClassDeserialization)) as SingleTypesClassDeserialization; - var deserInt64 = JsonConvert.DeserializeObject(serInt64, typeof(SingleTypesClassDeserialization)) as SingleTypesClassDeserialization; - var deserUInt32 = JsonConvert.DeserializeObject(serUInt32, typeof(SingleTypesClassDeserialization)) as SingleTypesClassDeserialization; - var deserInt32 = JsonConvert.DeserializeObject(serInt32, typeof(SingleTypesClassDeserialization)) as SingleTypesClassDeserialization; + JsonConvert.DeserializeObject(serInt64, typeof(SingleTypesClassDeserialization)); + JsonConvert.DeserializeObject(serUInt32, typeof(SingleTypesClassDeserialization)); + JsonConvert.DeserializeObject(serInt32, typeof(SingleTypesClassDeserialization)); Assert.Equal(deserUInt64.OneUInt64, singleUInt64.OneUInt64); Assert.Equal(deserUInt64.OneInt64, singleUInt64.OneInt64); @@ -1206,10 +1142,6 @@ public void CompleHashtableArraysList() [TestMethod] public void SerializeCosmosDbObject_01() { - // TODO need to fix ToString() from string with " inside - Assert.SkipTest("Skipping this test for now"); - - var valueAsJsonString = @"{""_count"":1,""Databases"":[{""_users"":""users/"",""_ts"":1644173816,""id"":""HomeAutomation"",""_rid"":""MfAzAA=="",""_colls"":""colls/"",""_etag"":""\""000020002-0000-0a00-0000-620019f80000\"""",""_self"":""dbs/MFzAA==/""}],""_rid"":null}"; CosmosDbDatabaseList dbObject = new CosmosDbDatabaseList(); diff --git a/nanoFramework.Json.Test/nanoFramework.Json.Test.nfproj b/nanoFramework.Json.Test/nanoFramework.Json.Test.nfproj index c8e9eedc..9337471c 100644 --- a/nanoFramework.Json.Test/nanoFramework.Json.Test.nfproj +++ b/nanoFramework.Json.Test/nanoFramework.Json.Test.nfproj @@ -1,8 +1,5 @@ - - - - + $(MSBuildExtensionsPath)\nanoFramework\v1.0\ @@ -24,8 +21,32 @@ true true + + true + + + ..\key.snk + + + false + + + + + + + + + + + + + + + + @@ -59,7 +80,6 @@ - diff --git a/nanoFramework.Json/Converters/BoolConverter.cs b/nanoFramework.Json/Converters/BoolConverter.cs new file mode 100644 index 00000000..d62b2014 --- /dev/null +++ b/nanoFramework.Json/Converters/BoolConverter.cs @@ -0,0 +1,24 @@ +using System; +using System.ComponentModel; + +namespace nanoFramework.Json.Converters +{ + internal sealed class BoolConverter : IConverter + { + /// + /// + /// + public string ToJson(object value) + { + return (bool)value ? "true" : "false"; + } + + /// + /// + /// + public object ToType(object value) + { + throw new NotImplementedException(); + } + } +} \ No newline at end of file diff --git a/nanoFramework.Json/Converters/ByteConverter.cs b/nanoFramework.Json/Converters/ByteConverter.cs new file mode 100644 index 00000000..6ff163df --- /dev/null +++ b/nanoFramework.Json/Converters/ByteConverter.cs @@ -0,0 +1,24 @@ +using System; +using System.ComponentModel; + +namespace nanoFramework.Json.Converters +{ + internal sealed class ByteConverter : IConverter + { + /// + /// + /// + public string ToJson(object value) + { + return value.ToString(); + } + + /// + /// + /// + public object ToType(object value) + { + return Convert.ToByte(value.ToString()); + } + } +} \ No newline at end of file diff --git a/nanoFramework.Json/Converters/CharConverter.cs b/nanoFramework.Json/Converters/CharConverter.cs new file mode 100644 index 00000000..c4a0282d --- /dev/null +++ b/nanoFramework.Json/Converters/CharConverter.cs @@ -0,0 +1,24 @@ +using System; +using System.Text; + +namespace nanoFramework.Json.Converters +{ + internal sealed class CharConverter : IConverter + { + /// + /// + /// + public string ToJson(object value) + { + return "\"" + value.ToString() + "\""; + } + + /// + /// + /// + public object ToType(object value) + { + return value.ToString()[0]; + } + } +} diff --git a/nanoFramework.Json/Converters/ConvertersMapping.cs b/nanoFramework.Json/Converters/ConvertersMapping.cs new file mode 100644 index 00000000..d2a57e5c --- /dev/null +++ b/nanoFramework.Json/Converters/ConvertersMapping.cs @@ -0,0 +1,62 @@ +using System; +using System.Collections; + +namespace nanoFramework.Json.Converters +{ + /// + /// Contains all converters for JSON. + /// + public static class ConvertersMapping + { + internal static readonly Hashtable ConversionTable = new Hashtable() + { + { typeof(short), new ShortConverter() }, + { typeof(ushort), new UShortConverter() }, + { typeof(int), new IntConverter() }, + { typeof(uint), new UIntConverter() }, + { typeof(long), new LongConverter() }, + { typeof(ulong), new ULongConverter() }, + { typeof(byte), new ByteConverter() }, + { typeof(sbyte), new SByteConverter() }, + { typeof(float), new FloatConverter()}, + { typeof(double), new DoubleConverter() }, + { typeof(bool), new BoolConverter() }, + { typeof(string), new StringConverter() }, + { typeof(TimeSpan), new TimeSpanConverter() }, + { typeof(DateTime), new DateTimeConverter() }, + { typeof(char), new CharConverter() }, + { typeof(Guid), new GuidConverter() }, + { typeof(DictionaryEntry), new DictionaryEntryConverter() } + }; + + /// + /// Adds new converter to collection to support more types. + /// + /// Type of object. + /// Converter instance which will be used to convert + public static void Add(Type type, IConverter converter) + { + ConversionTable.Add(type, converter); + } + + /// + /// Remove existing type converter. + /// + /// Type of object. + public static void Remove(Type type) + { + ConversionTable.Remove(type); + } + + /// + /// Remove and then adds converter for given type. + /// + /// Type of object. + /// Converter instance which will be used to convert + public static void Replace(Type type, IConverter converter) + { + ConversionTable.Remove(type); + ConversionTable.Add(type, converter); + } + } +} diff --git a/nanoFramework.Json/Converters/DateTimeConverter.cs b/nanoFramework.Json/Converters/DateTimeConverter.cs new file mode 100644 index 00000000..b609d9ad --- /dev/null +++ b/nanoFramework.Json/Converters/DateTimeConverter.cs @@ -0,0 +1,25 @@ +using System; +using System.Text; + +namespace nanoFramework.Json.Converters +{ + internal sealed class DateTimeConverter : IConverter + { + /// + /// + /// + public string ToJson(object value) + { + return "\"" + DateTimeExtensions.ToIso8601((DateTime)value) + "\""; + } + + /// + /// + /// + public object ToType(object value) + { + // Not sure hwo this one should work + throw new NotImplementedException(); + } + } +} diff --git a/nanoFramework.Json/Converters/DictionaryEntryConverter.cs b/nanoFramework.Json/Converters/DictionaryEntryConverter.cs new file mode 100644 index 00000000..b3609f6d --- /dev/null +++ b/nanoFramework.Json/Converters/DictionaryEntryConverter.cs @@ -0,0 +1,31 @@ +using System; +using System.Collections; +using System.Text; + +namespace nanoFramework.Json.Converters +{ + internal sealed class DictionaryEntryConverter : IConverter + { + /// + /// + /// + public string ToJson(object value) + { + Hashtable hashtable = new(); + + DictionaryEntry dic = (DictionaryEntry)value; + DictionaryEntry entry = dic; + hashtable.Add(entry.Key, entry.Value); + + return JsonSerializer.SerializeIDictionary(hashtable); + } + + /// + /// + /// + public object ToType(object value) + { + throw new NotImplementedException(); + } + } +} diff --git a/nanoFramework.Json/Converters/DoubleConverter.cs b/nanoFramework.Json/Converters/DoubleConverter.cs new file mode 100644 index 00000000..869226e3 --- /dev/null +++ b/nanoFramework.Json/Converters/DoubleConverter.cs @@ -0,0 +1,29 @@ +using System; +using System.ComponentModel; + +namespace nanoFramework.Json.Converters +{ + internal sealed class DoubleConverter : IConverter + { + /// + /// + /// + public string ToJson(object value) + { + if (double.IsNaN((double)value)) + { + return "null"; + } + + return value.ToString(); + } + + /// + /// + /// + public object ToType(object value) + { + return Convert.ToDouble(value.ToString()); + } + } +} \ No newline at end of file diff --git a/nanoFramework.Json/Converters/FloatConverter.cs b/nanoFramework.Json/Converters/FloatConverter.cs new file mode 100644 index 00000000..769f4a38 --- /dev/null +++ b/nanoFramework.Json/Converters/FloatConverter.cs @@ -0,0 +1,28 @@ +using System; + +namespace nanoFramework.Json.Converters +{ + internal sealed class FloatConverter : IConverter + { + /// + /// + /// + public string ToJson(object value) + { + if (float.IsNaN((float)value)) + { + return "null"; + } + + return value.ToString(); + } + + /// + /// + /// + public object ToType(object value) + { + return Convert.ToSingle(value.ToString()); + } + } +} \ No newline at end of file diff --git a/nanoFramework.Json/Converters/GuidConverter.cs b/nanoFramework.Json/Converters/GuidConverter.cs new file mode 100644 index 00000000..1b9e897e --- /dev/null +++ b/nanoFramework.Json/Converters/GuidConverter.cs @@ -0,0 +1,24 @@ +using System; +using System.Text; + +namespace nanoFramework.Json.Converters +{ + internal sealed class GuidConverter : IConverter + { + /// + /// + /// + public string ToJson(object value) + { + return "\"" + value.ToString() + "\""; + } + + /// + /// + /// + public object ToType(object value) + { + return new Guid(value.ToString()); + } + } +} diff --git a/nanoFramework.Json/Converters/IConverter.cs b/nanoFramework.Json/Converters/IConverter.cs new file mode 100644 index 00000000..e3717d11 --- /dev/null +++ b/nanoFramework.Json/Converters/IConverter.cs @@ -0,0 +1,25 @@ +using System; +using System.Text; + +namespace nanoFramework.Json.Converters +{ + /// + /// Interface for all JSON converters. + /// + public interface IConverter + { + /// + /// Converts JSON string to object. + /// + /// JSON value to convert from. + /// Object converted to type. + object ToType(object value); + + /// + /// Converts object into JSON string. + /// + /// Value to convert from. + /// String with JSON value. + string ToJson(object value); + } +} diff --git a/nanoFramework.Json/Converters/IntConverter.cs b/nanoFramework.Json/Converters/IntConverter.cs new file mode 100644 index 00000000..9f0687bd --- /dev/null +++ b/nanoFramework.Json/Converters/IntConverter.cs @@ -0,0 +1,23 @@ +using System; + +namespace nanoFramework.Json.Converters +{ + internal sealed class IntConverter : IConverter + { + /// + /// + /// + public string ToJson(object value) + { + return value.ToString(); + } + + /// + /// + /// + public object ToType(object value) + { + return Convert.ToInt32(value.ToString()); + } + } +} \ No newline at end of file diff --git a/nanoFramework.Json/Converters/LongConverter.cs b/nanoFramework.Json/Converters/LongConverter.cs new file mode 100644 index 00000000..73c59788 --- /dev/null +++ b/nanoFramework.Json/Converters/LongConverter.cs @@ -0,0 +1,23 @@ +using System; + +namespace nanoFramework.Json.Converters +{ + internal sealed class LongConverter : IConverter + { + /// + /// + /// + public string ToJson(object value) + { + return value.ToString(); + } + + /// + /// + /// + public object ToType(object value) + { + return Convert.ToInt64(value.ToString()); + } + } +} \ No newline at end of file diff --git a/nanoFramework.Json/Converters/SByteConverter.cs b/nanoFramework.Json/Converters/SByteConverter.cs new file mode 100644 index 00000000..53bb415a --- /dev/null +++ b/nanoFramework.Json/Converters/SByteConverter.cs @@ -0,0 +1,23 @@ +using System; + +namespace nanoFramework.Json.Converters +{ + internal sealed class SByteConverter : IConverter + { + /// + /// + /// + public string ToJson(object value) + { + return value.ToString(); + } + + /// + /// + /// + public object ToType(object value) + { + return Convert.ToSByte(value.ToString()); + } + } +} \ No newline at end of file diff --git a/nanoFramework.Json/Converters/ShortConverter.cs b/nanoFramework.Json/Converters/ShortConverter.cs new file mode 100644 index 00000000..45d653ff --- /dev/null +++ b/nanoFramework.Json/Converters/ShortConverter.cs @@ -0,0 +1,23 @@ +using System; + +namespace nanoFramework.Json.Converters +{ + internal sealed class ShortConverter : IConverter + { + /// + /// + /// + public string ToJson(object value) + { + return value.ToString(); + } + + /// + /// + /// + public object ToType(object value) + { + return Convert.ToInt16(value.ToString()); + } + } +} \ No newline at end of file diff --git a/nanoFramework.Json/Converters/StringConverter.cs b/nanoFramework.Json/Converters/StringConverter.cs new file mode 100644 index 00000000..f67787f5 --- /dev/null +++ b/nanoFramework.Json/Converters/StringConverter.cs @@ -0,0 +1,126 @@ +using System; +using System.Collections; +using System.Text; + +namespace nanoFramework.Json.Converters +{ + internal sealed class StringConverter : IConverter + { + internal static readonly Hashtable EscapableCharactersMapping = new Hashtable() + { + {'\n', 'n'}, + {'\r', 'r'}, + {'\"', '"' } + }; + + /// + /// + /// + public string ToJson(object value) + { + return "\"" + SerializeString((string)value) + "\""; + } + + internal static string SerializeString(string str) + { + // If the string is just fine (most are) then make a quick exit for improved performance + if (!StringContainsCharactersToEscape(str, false)) + { + return str; + } + + // Build a new string + // we know there is at least 1 char to escape + StringBuilder result = new(str.Length + 1); + + foreach (char ch in str) + { + var charToAppend = ch; + if (CheckIfCharIsRequiresEscape(charToAppend)) + { + result.Append('\\'); + charToAppend = (char)EscapableCharactersMapping[charToAppend]; + } + + result.Append(charToAppend); + } + + return result.ToString(); + } + + internal static bool CheckIfCharIsRequiresEscape(char chr) + { + foreach (var item in EscapableCharactersMapping.Keys) + { + if ((char)item == chr) + { + return true; + } + } + + return false; + } + + + internal static bool StringContainsCharactersToEscape(string str, bool deserializing) + { + foreach (var item in EscapableCharactersMapping.Keys) + { + var charToCheck = deserializing ? $"\\{EscapableCharactersMapping[item]}" : item.ToString(); + if (str.IndexOf(charToCheck) >= 0) + { + return true; + } + } + + return false; + } + + /// + /// + /// + public object ToType(object value) + { + var sourceString = value.ToString(); + //String by default has escaped \" at beggining and end, just remove them + var resultString = sourceString.Substring(1, sourceString.Length - 2); + if (StringContainsCharactersToEscape(resultString, true)) + { + var newString = new StringBuilder(); + //Last character can not be escaped, because it's last one + for (int i = 0; i < resultString.Length - 1; i++) + { + var curChar = resultString[i]; + var nextChar = resultString[i + 1]; + + if (curChar == '\\') + { + var charToAppend = GetEscapableCharKeyBasedOnValue(nextChar); + newString.Append(charToAppend); + i++; + continue; + } + newString.Append(curChar); + } + //Append last character skkiped by loop + newString.Append(resultString[resultString.Length - 1]); + return newString.ToString(); + } + return resultString; + } + + private static char GetEscapableCharKeyBasedOnValue(char inputChar) + { + foreach (var item in EscapableCharactersMapping.Keys) + { + var value = (char)EscapableCharactersMapping[item]; + if (value == inputChar) + { + return (char)item; + } + } + // in case inputChar is not supported + throw new InvalidOperationException(); + } + } +} \ No newline at end of file diff --git a/nanoFramework.Json/Converters/TimeSpanConverter.cs b/nanoFramework.Json/Converters/TimeSpanConverter.cs new file mode 100644 index 00000000..1d635312 --- /dev/null +++ b/nanoFramework.Json/Converters/TimeSpanConverter.cs @@ -0,0 +1,161 @@ +using System; + +namespace nanoFramework.Json.Converters +{ + internal sealed class TimeSpanConverter : IConverter + { + /// + /// + /// + public string ToJson(object value) + { + return "\"" + value.ToString() + "\""; + } + + /// + /// + /// + public object ToType(object value) + { + return ConvertFromString(value.ToString()); + } + + /// + /// Try converting a string value to a . + /// + /// to convert. + /// if conversion was successful. otherwise. + internal static TimeSpan ConvertFromString(string value) + { + // split string value with all possible separators + // format is: -ddddd.HH:mm:ss.fffffff + var timeSpanBits = value.Split(':', '.'); + + // sanity check + if (timeSpanBits.Length == 0) + { + return TimeSpan.Zero; + } + + // figure out where the separators are + int indexOfFirstDot = value.IndexOf('.'); + int indexOfSecondDot = indexOfFirstDot > -1 ? value.IndexOf('.', indexOfFirstDot + 1) : -1; + int indexOfFirstColon = value.IndexOf(':'); + int indexOfSecondColon = indexOfFirstColon > -1 ? value.IndexOf(':', indexOfFirstColon + 1) : -1; + + // sanity check for separators: all have to be ahead of string start + if (SeparatorCheck(timeSpanBits, indexOfFirstDot, indexOfSecondDot, indexOfFirstColon, indexOfSecondColon)) + { + throw new InvalidCastException(); + } + + // to have days, it has to have something before the 1st dot, or just have a single component + bool hasDays = (indexOfFirstDot > 0 && indexOfFirstDot < indexOfFirstColon) || timeSpanBits.Length == 1; + bool hasTicks = hasDays ? indexOfSecondDot > indexOfFirstDot : indexOfFirstDot > -1; + bool hasHours = indexOfFirstColon > 0; + bool hasMinutes = hasHours && indexOfFirstColon > -1; + bool hasSeconds = hasMinutes && indexOfSecondColon > -1; + + // sanity check for ticks without other time components + if (hasTicks && !hasHours) + { + throw new InvalidCastException(); + } + + // let the parsing start! + int days = 0; + if (hasDays + && !int.TryParse(timeSpanBits[0], out days)) + { + throw new InvalidCastException(); + } + + // bump the index if days component is present + int processIndex = hasDays ? 1 : 0; + + var hours = ParseValueFromString(hasHours, timeSpanBits, ref processIndex); + var minutes = ParseValueFromString(hasMinutes, timeSpanBits, ref processIndex); + var seconds = ParseValueFromString(hasSeconds, timeSpanBits, ref processIndex); + var ticks = HandleTicks(timeSpanBits, hasTicks, processIndex); + + // sanity check for valid ranges + if (IsInvalidTimeSpan(hours, minutes, seconds)) + { + throw new InvalidCastException(); + } + + // we should have everything now + return new TimeSpan(ticks).Add(new TimeSpan(days, hours, minutes, seconds, 0)); + } + + private static int HandleTicks(string[] timeSpanBits, bool hasTicks, int processIndex) + { + if (!hasTicks || processIndex > timeSpanBits.Length) + { + return 0; + } + + if (!int.TryParse(timeSpanBits[processIndex], out var ticks)) + { + throw new InvalidCastException(); + } + + // if ticks are under 999, that's milliseconds + if (ticks < 1_000) + { + ticks *= 10_000; + } + + return ticks; + } + + private static bool SeparatorCheck(string[] timeSpanBits, int indexOfFirstDot, int indexOfSecondDot, int indexOfFirstColon, int indexOfSecondColon) + { + return timeSpanBits.Length > 1 + && indexOfFirstDot <= 0 + && indexOfSecondDot <= 0 + && indexOfFirstColon <= 0 + && indexOfSecondColon <= 0; + } + + private static int ParseValueFromString(bool hasValue,string[] timeSpanBits, ref int processIndex) + { + if (!hasValue) + { + return 0; + } + + if (processIndex > timeSpanBits.Length) + { + return 0; + } + + if (!int.TryParse(timeSpanBits[processIndex++], out var value)) + { + throw new InvalidCastException(); + } + + return value; + } + + private static bool IsInvalidTimeSpan(int hour, int minutes, int seconds) + { + if (hour < 0 || hour >= 24) + { + return true; + } + + if (minutes < 0 || minutes >= 60) + { + return true; + } + + if (seconds < 0 || seconds >= 60) + { + return true; + } + + return false; + } + } +} \ No newline at end of file diff --git a/nanoFramework.Json/Converters/UIntConverter.cs b/nanoFramework.Json/Converters/UIntConverter.cs new file mode 100644 index 00000000..8ea0de14 --- /dev/null +++ b/nanoFramework.Json/Converters/UIntConverter.cs @@ -0,0 +1,23 @@ +using System; + +namespace nanoFramework.Json.Converters +{ + internal sealed class UIntConverter : IConverter + { + /// + /// + /// + public string ToJson(object value) + { + return value.ToString(); + } + + /// + /// + /// + public object ToType(object value) + { + return Convert.ToUInt32(value.ToString()); + } + } +} \ No newline at end of file diff --git a/nanoFramework.Json/Converters/ULongConverter.cs b/nanoFramework.Json/Converters/ULongConverter.cs new file mode 100644 index 00000000..5d756f60 --- /dev/null +++ b/nanoFramework.Json/Converters/ULongConverter.cs @@ -0,0 +1,23 @@ +using System; + +namespace nanoFramework.Json.Converters +{ + internal sealed class ULongConverter : IConverter + { + /// + /// + /// + public string ToJson(object value) + { + return value.ToString(); + } + + /// + /// + /// + public object ToType(object value) + { + return Convert.ToUInt64(value.ToString()); + } + } +} \ No newline at end of file diff --git a/nanoFramework.Json/Converters/UShortConverter.cs b/nanoFramework.Json/Converters/UShortConverter.cs new file mode 100644 index 00000000..13d9e618 --- /dev/null +++ b/nanoFramework.Json/Converters/UShortConverter.cs @@ -0,0 +1,23 @@ +using System; + +namespace nanoFramework.Json.Converters +{ + internal sealed class UShortConverter : IConverter + { + /// + /// + /// + public string ToJson(object value) + { + return value.ToString(); + } + + /// + /// + /// + public object ToType(object value) + { + return Convert.ToUInt16(value.ToString()); + } + } +} \ No newline at end of file diff --git a/nanoFramework.Json/JsonConvert.cs b/nanoFramework.Json/JsonConvert.cs index 9f187dcb..1b428daf 100644 --- a/nanoFramework.Json/JsonConvert.cs +++ b/nanoFramework.Json/JsonConvert.cs @@ -5,6 +5,7 @@ // using nanoFramework.Json; +using nanoFramework.Json.Converters; using System; using System.Collections; using System.IO; @@ -51,62 +52,14 @@ public static object DeserializeObject(string sourceString, Type type) { if (type == typeof(string)) { - return DeserializeStringObject(sourceString); + var stringConverter = (IConverter)ConvertersMapping.ConversionTable[typeof(string)]; + return stringConverter.ToType(sourceString); } var dserResult = Deserialize(sourceString); return PopulateObject((JsonToken)dserResult, type, "/"); } - private static char GetEscapableCharKeyBasedOnValue(char inputChar) - { - foreach (var item in JsonSerializer.EscapableCharactersMapping.Keys) - { - var value = (char)JsonSerializer.EscapableCharactersMapping[item]; - if (value == inputChar) - { - return (char)item; - } - } - - // in case inputChar is not supported - throw new InvalidOperationException(); - } - - private static string DeserializeStringObject(string sourceString) - { - //String by default has escaped \" at beggining and end, just remove them - var resultString = sourceString.Substring(1, sourceString.Length - 2); - - if (JsonSerializer.StringContainsCharactersToEscape(resultString, true)) - { - var newString = string.Empty; - - //Last character can not be escaped, because it's last one - for (int i = 0; i < resultString.Length - 1; i++) - { - var curChar = resultString[i]; - var nextChar = resultString[i + 1]; - - if (curChar == '\\') - { - var charToAppend = GetEscapableCharKeyBasedOnValue(nextChar); - newString += charToAppend; - i++; - continue; - } - - newString += curChar; - } - - //Append last character skkiped by loop - newString += resultString[resultString.Length - 1]; - return newString.ToString(); - } - - return resultString; - } - #if NANOFRAMEWORK_1_0 /// @@ -151,11 +104,10 @@ private static object Deserialize(StreamReader dr) } #endif - - private static object ConvertToType(Type sourceType, Type targetType, object value) + internal static object ConvertToType(Type sourceType, Type targetType, object value) { // No need to convert if values matches - if (sourceType.Name == targetType.Name) + if (sourceType == targetType) { return value; } @@ -165,50 +117,12 @@ private static object ConvertToType(Type sourceType, Type targetType, object val return ConvertToType(sourceType, targetType.GetElementType(), value); } - switch (targetType.Name) + if (ConvertersMapping.ConversionTable.Contains(targetType)) { - case nameof(Int16): - return Convert.ToInt16(value.ToString()); - - case nameof(UInt16): - return Convert.ToUInt16(value.ToString()); - - case nameof(Int32): - return Convert.ToInt32(value.ToString()); - - case nameof(UInt32): - return Convert.ToUInt32(value.ToString()); - - case nameof(Int64): - return Convert.ToInt64(value.ToString()); - - case nameof(UInt64): - return Convert.ToUInt64(value.ToString()); - - case nameof(Byte): - return Convert.ToByte(value.ToString()); - - case nameof(SByte): - return Convert.ToSByte(value.ToString()); - - case nameof(Single): - return Convert.ToSingle(value.ToString()); - - case nameof(Double): - return Convert.ToDouble(value.ToString()); - - case nameof(Boolean): - return Convert.ToBoolean(Convert.ToByte(value.ToString())); - - case nameof(String): - return value.ToString(); - - case nameof(TimeSpan): - return TimeSpanExtensions.TryConvertFromString(value.ToString()); - - default: - return value; + return ((IConverter)ConvertersMapping.ConversionTable[targetType]).ToType(value); } + + return value; } private static object PopulateObject(JsonToken rootToken, Type rootType, string rootPath) @@ -229,8 +143,6 @@ private static object PopulateObject(JsonToken rootToken, Type rootType, string if (rootElementType == null && rootType.FullName == "System.Collections.Hashtable") { - rootElementType = rootType; - Hashtable rootInstanceHashtable = new Hashtable(); foreach (var m in rootObject.Members) @@ -295,9 +207,11 @@ private static object PopulateObject(JsonToken rootToken, Type rootType, string return rootArrayList; } - // This is the object that gets populated and returned - // Create rootInstance from the rootType's constructor - object rootInstance = null; + if (ConvertersMapping.ConversionTable.Contains(rootType)) + { + var converter = (IConverter)ConvertersMapping.ConversionTable[rootType]; + return converter.ToType(rootObject); + } // Empty array of Types - GetConstructor didn't work unless given an empty array of Type[] Type[] types = { }; @@ -310,7 +224,9 @@ private static object PopulateObject(JsonToken rootToken, Type rootType, string throw new DeserializationException(); } - rootInstance = ci.Invoke(null); + // This is the object that gets populated and returned + // Create rootInstance from the rootType's constructor + var rootInstance = ci.Invoke(null); // If we haven't successfully created rootInstance, bail out if (rootInstance == null) diff --git a/nanoFramework.Json/JsonSerializer.cs b/nanoFramework.Json/JsonSerializer.cs index 67f6ed4b..00ee00e6 100644 --- a/nanoFramework.Json/JsonSerializer.cs +++ b/nanoFramework.Json/JsonSerializer.cs @@ -4,10 +4,10 @@ // See LICENSE file in the project root for full license information. // +using nanoFramework.Json.Converters; using System; using System.Collections; using System.Reflection; -using System.Text; namespace nanoFramework.Json { @@ -16,20 +16,14 @@ namespace nanoFramework.Json /// public class JsonSerializer { - internal static Hashtable EscapableCharactersMapping = new Hashtable() - { - {'\n', 'n'}, - {'\r', 'r'}, - {'\"', '"' } - }; - /// /// Convert an object to a JSON string. /// /// The value to convert. Supported types are: , , , , , , , , , , , , , , , , and . + /// Is the object top in hierarchy. Default true. /// The JSON object as a string or null when the value type is not supported. /// For objects, only internal properties with getters are converted. - internal static string SerializeObject(object o, bool topObject = true) + public static string SerializeObject(object o, bool topObject = true) { if (o == null) { @@ -45,46 +39,10 @@ internal static string SerializeObject(object o, bool topObject = true) return $"[{SerializeObject(o, false)}]"; } - switch (type.Name) + if (ConvertersMapping.ConversionTable.Contains(type)) { - case "Boolean": - return (bool)o ? "true" : "false"; - - case "TimeSpan": - case "Char": - case "Guid": - return "\"" + o.ToString() + "\""; - case "String": - return "\"" + SerializeString((string)o) + "\""; - - case "Single": - if (float.IsNaN((float)o)) - { - return "null"; - } - return o.ToString(); - - case "Double": - if (double.IsNaN((double)o)) - { - return "null"; - } - return o.ToString(); - - case "Decimal": - case "Float": - case "Byte": - case "SByte": - case "Int16": - case "UInt16": - case "Int32": - case "UInt32": - case "Int64": - case "UInt64": - return o.ToString(); - - case "DateTime": - return "\"" + DateTimeExtensions.ToIso8601((DateTime)o) + "\""; + var converter = (IConverter)ConvertersMapping.ConversionTable[type]; + return converter.ToJson(o); } if (type.IsEnum) @@ -104,64 +62,65 @@ internal static string SerializeObject(object o, bool topObject = true) return SerializeIEnumerable(enumerable); } - if (type == typeof(DictionaryEntry)) + if (type.IsClass) { - Hashtable hashtable = new(); - - if (o is DictionaryEntry) - { - DictionaryEntry dic = (DictionaryEntry)o; - DictionaryEntry entry = dic; - hashtable.Add(entry.Key, entry.Value); - } - - return SerializeIDictionary(hashtable); + return SerializeClass(o, type); } - if (type.IsClass) - { - Hashtable hashtable = new Hashtable(); + return null; + } - // Iterate through all of the methods, looking for internal GET properties - MethodInfo[] methods = type.GetMethods(); + private static string SerializeClass(object o, Type type) + { + Hashtable hashtable = new Hashtable(); + + // Iterate through all of the methods, looking for internal GET properties + MethodInfo[] methods = type.GetMethods(); - foreach (MethodInfo method in methods) + foreach (MethodInfo method in methods) + { + if (!ShouldSerializeMethod(method)) { - // We care only about property getters when serializing - if (!method.Name.StartsWith("get_")) - { - continue; - } + continue; + } - // Ignore abstract and virtual objects - if (method.IsAbstract) - { - continue; - } + object returnObject = method.Invoke(o, null); + hashtable.Add(method.Name.Substring(4), returnObject); + } - // Ignore delegates and MethodInfos - if ((method.ReturnType == typeof(Delegate)) || - (method.ReturnType == typeof(MulticastDelegate)) || - (method.ReturnType == typeof(MethodInfo))) - { - continue; - } + return SerializeIDictionary(hashtable); + } - // Ditto for DeclaringType - if ((method.DeclaringType == typeof(Delegate)) || - (method.DeclaringType == typeof(MulticastDelegate))) - { - continue; - } + private static bool ShouldSerializeMethod(MethodInfo method) + { + // We care only about property getters when serializing + if (!method.Name.StartsWith("get_")) + { + return false; + } - object returnObject = method.Invoke(o, null); - hashtable.Add(method.Name.Substring(4), returnObject); - } + // Ignore abstract and virtual objects + if (method.IsAbstract) + { + return false; + } - return SerializeIDictionary(hashtable); + // Ignore delegates and MethodInfos + if ((method.ReturnType == typeof(Delegate)) || + (method.ReturnType == typeof(MulticastDelegate)) || + (method.ReturnType == typeof(MethodInfo))) + { + return false; } - return null; + // Ditto for DeclaringType + if ((method.DeclaringType == typeof(Delegate)) || + (method.DeclaringType == typeof(MulticastDelegate))) + { + return false; + } + + return true; } /// @@ -212,64 +171,5 @@ internal static string SerializeIDictionary(IDictionary dictionary) return result; } - - internal static bool StringContainsCharactersToEscape(string str, bool deserializing) - { - foreach (var item in EscapableCharactersMapping.Keys) - { - var charToCheck = deserializing ? $"\\{EscapableCharactersMapping[item]}" : item.ToString(); - if (str.IndexOf(charToCheck) > 0) - { - return true; - } - } - - return false; - } - - internal static bool CheckIfCharIsRequiresEscape(char chr) - { - foreach (var item in EscapableCharactersMapping.Keys) - { - if ((char)item == chr) - { - return true; - } - } - - return false; - } - - /// - /// Safely serialize a String into a JSON string value, escaping all backslash and quote characters. - /// - /// The string to serialize. - /// The serialized JSON string. - protected static string SerializeString(string str) - { - // If the string is just fine (most are) then make a quick exit for improved performance - if (!StringContainsCharactersToEscape(str, false)) - { - return str; - } - - // Build a new string - // we know there is at least 1 char to escape - StringBuilder result = new(str.Length + 1); - - foreach (char ch in str) - { - var charToAppend = ch; - if (CheckIfCharIsRequiresEscape(charToAppend)) - { - result.Append('\\'); - charToAppend = (char)EscapableCharactersMapping[charToAppend]; - } - - result.Append(charToAppend); - } - - return result.ToString(); - } } -} +} \ No newline at end of file diff --git a/nanoFramework.Json/Properties/AssemblyInfo.cs b/nanoFramework.Json/Properties/AssemblyInfo.cs index 79c19614..9792a154 100644 --- a/nanoFramework.Json/Properties/AssemblyInfo.cs +++ b/nanoFramework.Json/Properties/AssemblyInfo.cs @@ -16,3 +16,9 @@ // update this whenever the native assembly signature changes // [assembly: AssemblyNativeVersion("0.0.0.0")] ///////////////////////////////////////////////////////////////// + +[assembly: InternalsVisibleTo("NFUnitTest, PublicKey="+ "00240000048000009400000006020000002400005253413100040000010001001120aa3e809b3d" + +"a4f65e1b1f65c0a3a1bf6335c39860ca41acb3c48de278c6b63c5df38239ec1f2e32d58cb897c8" + +"c174a5f8e78a9c0b6087d3aef373d7d0f3d9be67700fc2a5a38de1fb71b5b6f6046d841ff35abe" + +"e2e0b0840a6291a312be184eb311baff5fef0ff6895b9a5f2253aed32fb06b819134f6bb9d5314" + +"88a87ea2")] \ No newline at end of file diff --git a/nanoFramework.Json/TimeExtensions.cs b/nanoFramework.Json/TimeExtensions.cs index a90bf966..30ebe101 100644 --- a/nanoFramework.Json/TimeExtensions.cs +++ b/nanoFramework.Json/TimeExtensions.cs @@ -129,14 +129,7 @@ public static string ToIso8601(DateTime dt) /// private static bool IsNumeric(string str) { - foreach (char c in str) - { - if (!((c >= '0') && (c <= '9'))) - { - return false; - } - } - return true; + return int.TryParse(str, out int _); } /// @@ -213,120 +206,4 @@ internal static bool ConvertFromString(string value, out DateTime dateTime) return dateTime != DateTime.MaxValue; } } - - internal static class TimeSpanExtensions - { - /// - /// Try converting a string value to a . - /// - /// to convert. - /// converted from . - /// if conversion was successful. otherwise. - internal static TimeSpan TryConvertFromString(string value) - { - // split string value with all possible separators - // format is: -ddddd.HH:mm:ss.fffffff - var timeSpanBits = value.Split(':', '.'); - - // sanity check - if (timeSpanBits.Length == 0) - { - return TimeSpan.Zero; - } - - int days = 0; - int ticks = 0; - int hours = 0; - int minutes = 0; - int seconds = 0; - - // figure out where the separators are - int indexOfFirstDot = value.IndexOf('.'); - int indexOfSecondDot = indexOfFirstDot > -1 ? value.IndexOf('.', indexOfFirstDot + 1) : -1; - int indexOfFirstColon = value.IndexOf(':'); - int indexOfSecondColon = indexOfFirstColon > -1 ? value.IndexOf(':', indexOfFirstColon + 1) : -1; - - // sanity check for separators: all have to be ahead of string start - if (timeSpanBits.Length > 1 - && indexOfFirstDot <= 0 - && indexOfSecondDot <= 0 - && indexOfFirstColon <= 0 - && indexOfSecondColon <= 0) - { - throw new InvalidCastException(); - } - - // to have days, it has to have something before the 1st dot, or just have a single component - bool hasDays = (indexOfFirstDot > 0 && indexOfFirstDot < indexOfFirstColon) || timeSpanBits.Length == 1; - bool hasTicks = hasDays ? indexOfSecondDot > indexOfFirstDot : indexOfFirstDot > -1; - bool hasHours = indexOfFirstColon > 0; - bool hasMinutes = hasHours && indexOfFirstColon > -1; - bool hasSeconds = hasMinutes && indexOfSecondColon > -1; - - // sanity check for ticks without other time components - if (hasTicks && !hasHours) - { - throw new InvalidCastException(); - } - - // let the parsing start! - if (hasDays - && !int.TryParse(timeSpanBits[0], out days)) - { - throw new InvalidCastException(); - } - - // bump the index if days component is present - int processIndex = hasDays ? 1 : 0; - - if (hasHours && processIndex <= timeSpanBits.Length) - { - if (!int.TryParse(timeSpanBits[processIndex++], out hours)) - { - throw new InvalidCastException(); - } - } - - if (hasMinutes && processIndex <= timeSpanBits.Length) - { - if (!int.TryParse(timeSpanBits[processIndex++], out minutes)) - { - throw new InvalidCastException(); - } - } - - if (hasSeconds && processIndex <= timeSpanBits.Length) - { - if (!int.TryParse(timeSpanBits[processIndex++], out seconds)) - { - throw new InvalidCastException(); - } - } - - if (hasTicks && processIndex <= timeSpanBits.Length) - { - if (!int.TryParse(timeSpanBits[processIndex], out ticks)) - { - throw new InvalidCastException(); - } - - // if ticks are under 999, that's milliseconds - if (ticks < 1_000) - { - ticks *= 10_000; - } - } - - // sanity check for valid ranges - if ((hours >= 0 && hours < 24) - && (minutes >= 0 && minutes < 60) - && (seconds >= 0 && seconds < 60)) - { - // we should have everything now - return new TimeSpan(ticks).Add(new TimeSpan(days, hours, minutes, seconds, 0)); - } - - throw new InvalidCastException(); - } - } } diff --git a/nanoFramework.Json/nanoFramework.Json.nfproj b/nanoFramework.Json/nanoFramework.Json.nfproj index 84ce400f..16f48a14 100644 --- a/nanoFramework.Json/nanoFramework.Json.nfproj +++ b/nanoFramework.Json/nanoFramework.Json.nfproj @@ -24,13 +24,32 @@ true - key.snk + ..\key.snk false + + + + + + + + + + + + + + + + + + + @@ -45,7 +64,7 @@ - +