From d765d0b2e8d03b74144e7bdd314e19334fc77fc8 Mon Sep 17 00:00:00 2001 From: Mikhail Preyskurantov <5574159+mpreyskurantov@users.noreply.github.com> Date: Thu, 17 Apr 2025 19:36:36 +0300 Subject: [PATCH 1/3] add default Write / Serialization functionality / behavior --- net/DevExtreme.AspNet.Data/Compatibility.cs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/net/DevExtreme.AspNet.Data/Compatibility.cs b/net/DevExtreme.AspNet.Data/Compatibility.cs index ecdc0c6f..5aa17752 100644 --- a/net/DevExtreme.AspNet.Data/Compatibility.cs +++ b/net/DevExtreme.AspNet.Data/Compatibility.cs @@ -66,7 +66,7 @@ static object GetNumber(ref Utf8JsonReader reader) { throw new NotImplementedException(); } - public override void Write(Utf8JsonWriter writer, string value, JsonSerializerOptions options) => throw new NotImplementedException(); + public override void Write(Utf8JsonWriter writer, string value, JsonSerializerOptions options) => writer.WriteStringValue(value); } class ListConverter : JsonConverter { @@ -75,7 +75,10 @@ public override IList Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSe return Compatibility.UnwrapList(deserializedList); } - public override void Write(Utf8JsonWriter writer, IList value, JsonSerializerOptions options) => throw new NotImplementedException(); + public override void Write(Utf8JsonWriter writer, IList value, JsonSerializerOptions options) { + var serializedList = JsonSerializer.Serialize(value); + writer.WriteStringValue(serializedList); + } } } From 6cc97816c68dc2fffd329ac8c39a42222c3b5d92 Mon Sep 17 00:00:00 2001 From: Mikhail Preyskurantov <5574159+mpreyskurantov@users.noreply.github.com> Date: Mon, 21 Apr 2025 19:22:31 +0300 Subject: [PATCH 2/3] use more natural JsonSerializer.Serialize overload to keep list/array string instead of raw string + unit tests --- .../DeserializeTests.cs | 12 ++--- .../SerializeTests.cs | 54 +++++++++++++++++++ .../SerializeTestsEx.cs | 51 ++++++++++++++++++ net/DevExtreme.AspNet.Data/Compatibility.cs | 5 +- .../Helpers/DataSourceLoadOptionsParser.cs | 3 ++ 5 files changed, 114 insertions(+), 11 deletions(-) create mode 100644 net/DevExtreme.AspNet.Data.Tests/SerializeTests.cs create mode 100644 net/DevExtreme.AspNet.Data.Tests/SerializeTestsEx.cs diff --git a/net/DevExtreme.AspNet.Data.Tests/DeserializeTests.cs b/net/DevExtreme.AspNet.Data.Tests/DeserializeTests.cs index 5f57da8d..aecc816c 100644 --- a/net/DevExtreme.AspNet.Data.Tests/DeserializeTests.cs +++ b/net/DevExtreme.AspNet.Data.Tests/DeserializeTests.cs @@ -1,25 +1,23 @@ -using System.Collections; +using DevExtreme.AspNet.Data.Helpers; + +using System.Collections; using System.Text.Json; using Xunit; namespace DevExtreme.AspNet.Data.Tests { public class DeserializeTests { - static readonly JsonSerializerOptions TESTS_DEFAULT_SERIALIZER_OPTIONS = new JsonSerializerOptions(JsonSerializerDefaults.Web) { - Converters = { new ListConverter() } - }; - [Theory] [InlineData(@"[""fieldName"",""="",null]")] [InlineData(@"[[""fieldName1"",""="",""""],""and"",[""fieldName2"",""="",null]]")] public void FilterOperandValueCanBeNull(string rawJsonCriteria) { - var deserializedList = JsonSerializer.Deserialize(rawJsonCriteria, TESTS_DEFAULT_SERIALIZER_OPTIONS); + var deserializedList = JsonSerializer.Deserialize(rawJsonCriteria, DataSourceLoadOptionsParser.DEFAULT_SERIALIZER_OPTIONS); Assert.Equal(3, deserializedList.Count); } [Fact] public void FilterOperandValueCanBeObject() { - var deserializedList = JsonSerializer.Deserialize(@"[""fieldName1"",""="",{""Value"":0}]", TESTS_DEFAULT_SERIALIZER_OPTIONS); + var deserializedList = JsonSerializer.Deserialize(@"[""fieldName1"",""="",{""Value"":0}]", DataSourceLoadOptionsParser.DEFAULT_SERIALIZER_OPTIONS); Assert.Equal(3, deserializedList.Count); Assert.Equal("{\"Value\":0}", deserializedList[2].ToString()); } diff --git a/net/DevExtreme.AspNet.Data.Tests/SerializeTests.cs b/net/DevExtreme.AspNet.Data.Tests/SerializeTests.cs new file mode 100644 index 00000000..be5e9000 --- /dev/null +++ b/net/DevExtreme.AspNet.Data.Tests/SerializeTests.cs @@ -0,0 +1,54 @@ +//using DevExtreme.AspNet.Data.Helpers; + +using System; +using System.Collections.Generic; +using System.Text.Json; +using Xunit; + +namespace DevExtreme.AspNet.Data.Tests { + + public class SerializeTests { + [Fact] + public void SerializeEmptyOptions() { + Assert.Equal(@"{""RequireTotalCount"":false,""RequireGroupCount"":false,""IsCountQuery"":false,""IsSummaryQuery"":false,""Skip"":0,""Take"":0,""Sort"":null,""Group"":null,""Filter"":null,""TotalSummary"":null,""GroupSummary"":null,""Select"":null,""PreSelect"":null,""RemoteSelect"":null,""RemoteGrouping"":null,""ExpandLinqSumType"":null,""PrimaryKey"":null,""DefaultSort"":null,""StringToLower"":null,""PaginateViaPrimaryKey"":null,""SortByPrimaryKey"":null,""AllowAsyncOverSync"":false}", + JsonSerializer.Serialize(new DataSourceLoadOptionsBase())); + } + + [Fact] + public void SerializeDeserializeConvertersAffectedOptions() { + var loadOptionsStrGroup = @"""Group"":[{""GroupInterval"":""100"",""IsExpanded"":null,""Selector"":""freight"",""Desc"":false}]"; + var loadOptionsStrFilter = @"""Filter"":[[""orderDate"",""\u003E="",""2011-12-13T14:15:16""],""and"",[""orderDate"",""\u003C"",""2011-12-13T14:15:17""]]"; + + var loadOptions = new DataSourceLoadOptionsBase() { + Group = new GroupingInfo[] { + new GroupingInfo() { + GroupInterval = "100", + Selector = "freight" + } + }, + Filter = new List() { + new List() { "orderDate", ">=", new DateTime(2011, 12, 13, 14, 15, 16) }, + "and", + new List() { "orderDate", "<", new DateTime(2011, 12, 13, 14, 15, 17) } + } + }; + + var loadOptionsSerialized = JsonSerializer.Serialize(loadOptions); + Assert.Contains($"{loadOptionsStrGroup},{loadOptionsStrFilter}", loadOptionsSerialized); + + var loadOptionsDeserialized = JsonSerializer.Deserialize( + loadOptionsSerialized + //, DataSourceLoadOptionsParser.DEFAULT_SERIALIZER_OPTIONS + // does not require options, because deserializes from just serialized instance + ); + Assert.Equal("100", loadOptionsDeserialized.Group[0].GroupInterval); + Assert.Equal("freight", loadOptionsDeserialized.Group[0].Selector); + Assert.Equal(3, loadOptionsDeserialized.Filter.Count); + var strFilter0STJ = (IList)loadOptionsDeserialized.Filter[0]; + Assert.Equal("orderDate", strFilter0STJ[0]); + Assert.Equal(">=", strFilter0STJ[1]); + Assert.Equal("2011-12-13T14:15:16", strFilter0STJ[2]); + } + } + +} diff --git a/net/DevExtreme.AspNet.Data.Tests/SerializeTestsEx.cs b/net/DevExtreme.AspNet.Data.Tests/SerializeTestsEx.cs new file mode 100644 index 00000000..342a91c4 --- /dev/null +++ b/net/DevExtreme.AspNet.Data.Tests/SerializeTestsEx.cs @@ -0,0 +1,51 @@ +#if NEWTONSOFT_TESTS +using System; +using System.Collections.Generic; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using Xunit; + +namespace DevExtreme.AspNet.Data.Tests { + + public class SerializeTestsEx { + [Fact] + public void SerializeEmptyOptions() { + Assert.Equal(@"{""RequireTotalCount"":false,""RequireGroupCount"":false,""IsCountQuery"":false,""IsSummaryQuery"":false,""Skip"":0,""Take"":0,""Sort"":null,""Group"":null,""Filter"":null,""TotalSummary"":null,""GroupSummary"":null,""Select"":null,""PreSelect"":null,""RemoteSelect"":null,""RemoteGrouping"":null,""ExpandLinqSumType"":null,""PrimaryKey"":null,""DefaultSort"":null,""StringToLower"":null,""PaginateViaPrimaryKey"":null,""SortByPrimaryKey"":null,""AllowAsyncOverSync"":false}", + JsonConvert.SerializeObject(new DataSourceLoadOptionsBase())); + } + + [Fact] + public void SerializeDeserializeConvertersAffectedOptions() { + var loadOptionsStrGroup = @"""Group"":[{""GroupInterval"":""100"",""IsExpanded"":null,""Selector"":""freight"",""Desc"":false}]"; + var loadOptionsStrFilter = @"""Filter"":[[""orderDate"","">="",""2011-12-13T14:15:16""],""and"",[""orderDate"",""<"",""2011-12-13T14:15:17""]]"; + + var loadOptions = new DataSourceLoadOptionsBase() { + Group = new GroupingInfo[] { + new GroupingInfo() { + GroupInterval = "100", + Selector = "freight" + } + }, + Filter = new List() { + new List() { "orderDate", ">=", new DateTime(2011, 12, 13, 14, 15, 16) }, + "and", + new List() { "orderDate", "<", new DateTime(2011, 12, 13, 14, 15, 17) } + } + }; + + var loadOptionsSerialized = JsonConvert.SerializeObject(loadOptions); + Assert.Contains($"{loadOptionsStrGroup},{loadOptionsStrFilter}", loadOptionsSerialized); + + var loadOptionsDeserialized = JsonConvert.DeserializeObject(loadOptionsSerialized); + Assert.Equal("100", loadOptionsDeserialized.Group[0].GroupInterval); + Assert.Equal("freight", loadOptionsDeserialized.Group[0].Selector); + Assert.Equal(3, loadOptionsDeserialized.Filter.Count); + var strFilter0NTSF = (JArray)loadOptionsDeserialized.Filter[0]; + Assert.Equal("orderDate", strFilter0NTSF[0]); + Assert.Equal(">=", strFilter0NTSF[1]); + Assert.Equal("2011-12-13T14:15:16", strFilter0NTSF[2]); + } + } + +} +#endif diff --git a/net/DevExtreme.AspNet.Data/Compatibility.cs b/net/DevExtreme.AspNet.Data/Compatibility.cs index 5aa17752..af4cc6e8 100644 --- a/net/DevExtreme.AspNet.Data/Compatibility.cs +++ b/net/DevExtreme.AspNet.Data/Compatibility.cs @@ -75,10 +75,7 @@ public override IList Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSe return Compatibility.UnwrapList(deserializedList); } - public override void Write(Utf8JsonWriter writer, IList value, JsonSerializerOptions options) { - var serializedList = JsonSerializer.Serialize(value); - writer.WriteStringValue(serializedList); - } + public override void Write(Utf8JsonWriter writer, IList value, JsonSerializerOptions options) => JsonSerializer.Serialize(writer, value, value.GetType(), options); } } diff --git a/net/DevExtreme.AspNet.Data/Helpers/DataSourceLoadOptionsParser.cs b/net/DevExtreme.AspNet.Data/Helpers/DataSourceLoadOptionsParser.cs index 85403e0f..b9ee9303 100644 --- a/net/DevExtreme.AspNet.Data/Helpers/DataSourceLoadOptionsParser.cs +++ b/net/DevExtreme.AspNet.Data/Helpers/DataSourceLoadOptionsParser.cs @@ -8,6 +8,9 @@ namespace DevExtreme.AspNet.Data.Helpers { /// A parser for the data processing settings. /// public static class DataSourceLoadOptionsParser { +#if DEBUG + internal +#endif static readonly JsonSerializerOptions DEFAULT_SERIALIZER_OPTIONS = new JsonSerializerOptions(JsonSerializerDefaults.Web) { Converters = { new ListConverter() } }; From d04a88e90ec9f4c459152ed229b2b6d0b6a59a88 Mon Sep 17 00:00:00 2001 From: Mikhail Preyskurantov <5574159+mpreyskurantov@users.noreply.github.com> Date: Mon, 21 Apr 2025 20:06:40 +0300 Subject: [PATCH 3/3] unit tests: assert against instances (net values, not strings) --- .../SerializeTests.cs | 21 ++++++++++++------- .../SerializeTestsEx.cs | 15 ++++++------- 2 files changed, 22 insertions(+), 14 deletions(-) diff --git a/net/DevExtreme.AspNet.Data.Tests/SerializeTests.cs b/net/DevExtreme.AspNet.Data.Tests/SerializeTests.cs index be5e9000..32dee49e 100644 --- a/net/DevExtreme.AspNet.Data.Tests/SerializeTests.cs +++ b/net/DevExtreme.AspNet.Data.Tests/SerializeTests.cs @@ -41,13 +41,20 @@ public void SerializeDeserializeConvertersAffectedOptions() { //, DataSourceLoadOptionsParser.DEFAULT_SERIALIZER_OPTIONS // does not require options, because deserializes from just serialized instance ); - Assert.Equal("100", loadOptionsDeserialized.Group[0].GroupInterval); - Assert.Equal("freight", loadOptionsDeserialized.Group[0].Selector); - Assert.Equal(3, loadOptionsDeserialized.Filter.Count); - var strFilter0STJ = (IList)loadOptionsDeserialized.Filter[0]; - Assert.Equal("orderDate", strFilter0STJ[0]); - Assert.Equal(">=", strFilter0STJ[1]); - Assert.Equal("2011-12-13T14:15:16", strFilter0STJ[2]); + Assert.Equal(loadOptions.Group[0].GroupInterval, loadOptionsDeserialized.Group[0].GroupInterval); + Assert.Equal(loadOptions.Group[0].Selector, loadOptionsDeserialized.Group[0].Selector); + Assert.Equal(loadOptions.Filter.Count, loadOptionsDeserialized.Filter.Count); + var filter0Orig = (IList)loadOptions.Filter[0]; + var filter0STJ = (IList)loadOptionsDeserialized.Filter[0]; + Assert.Equal(filter0Orig[0], filter0STJ[0]); + Assert.Equal(filter0Orig[1], filter0STJ[1]); + //TODO: + //https://github.com/dotnet/runtime/issues/31423 + //https://learn.microsoft.com/en-us/dotnet/standard/datetime/system-text-json-support + //https://github.com/DevExpress/DevExtreme.AspNet.Data/blob/master/js/dx.aspnet.data.js -> serializeDate + //Assert.Equal(filter0Orig[2], filter0STJ[2]); + Assert.Equal("2011-12-13T14:15:16", filter0STJ[2]); + } } diff --git a/net/DevExtreme.AspNet.Data.Tests/SerializeTestsEx.cs b/net/DevExtreme.AspNet.Data.Tests/SerializeTestsEx.cs index 342a91c4..324e1f02 100644 --- a/net/DevExtreme.AspNet.Data.Tests/SerializeTestsEx.cs +++ b/net/DevExtreme.AspNet.Data.Tests/SerializeTestsEx.cs @@ -37,13 +37,14 @@ public void SerializeDeserializeConvertersAffectedOptions() { Assert.Contains($"{loadOptionsStrGroup},{loadOptionsStrFilter}", loadOptionsSerialized); var loadOptionsDeserialized = JsonConvert.DeserializeObject(loadOptionsSerialized); - Assert.Equal("100", loadOptionsDeserialized.Group[0].GroupInterval); - Assert.Equal("freight", loadOptionsDeserialized.Group[0].Selector); - Assert.Equal(3, loadOptionsDeserialized.Filter.Count); - var strFilter0NTSF = (JArray)loadOptionsDeserialized.Filter[0]; - Assert.Equal("orderDate", strFilter0NTSF[0]); - Assert.Equal(">=", strFilter0NTSF[1]); - Assert.Equal("2011-12-13T14:15:16", strFilter0NTSF[2]); + Assert.Equal(loadOptions.Group[0].GroupInterval, loadOptionsDeserialized.Group[0].GroupInterval); + Assert.Equal(loadOptions.Group[0].Selector, loadOptionsDeserialized.Group[0].Selector); + Assert.Equal(loadOptions.Filter.Count, loadOptionsDeserialized.Filter.Count); + var filter0Orig = (IList)loadOptions.Filter[0]; + var filter0NTSF = (JArray)loadOptionsDeserialized.Filter[0]; + Assert.Equal(filter0Orig[0], ((JValue)filter0NTSF[0]).Value); + Assert.Equal(filter0Orig[1], ((JValue)filter0NTSF[1]).Value); + Assert.Equal(filter0Orig[2], ((JValue)filter0NTSF[2]).Value); } }