diff --git a/docs/en/Community-Articles/2021-11-29-Integrating-DevExpress-Reporting-To-ABP-MVC-Application/Integrating-DevExpress-Reporting-To-ABP-MVC-Application.md b/docs/en/Community-Articles/2021-11-29-Integrating-DevExpress-Reporting-To-ABP-MVC-Application/Integrating-DevExpress-Reporting-To-ABP-MVC-Application.md index 58a5796992..84e83d3560 100644 --- a/docs/en/Community-Articles/2021-11-29-Integrating-DevExpress-Reporting-To-ABP-MVC-Application/Integrating-DevExpress-Reporting-To-ABP-MVC-Application.md +++ b/docs/en/Community-Articles/2021-11-29-Integrating-DevExpress-Reporting-To-ABP-MVC-Application/Integrating-DevExpress-Reporting-To-ABP-MVC-Application.md @@ -714,4 +714,4 @@ To add these functionalities, update the `Viewer.cshtml` file under the `Pages/R ## Source Code -- You can download the source code from [here](https://github.com/gterdem/abp-devexpress-reporting-sample). \ No newline at end of file +- You can download the source code from [here](https://github.com/abpframework/abp-samples/tree/master/DevExtreme-Reports-Mvc). \ No newline at end of file diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Json/AbpJsonOptionsSetup.cs b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Json/AbpJsonOptionsSetup.cs index dabcb07643..116adca4e8 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Json/AbpJsonOptionsSetup.cs +++ b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Json/AbpJsonOptionsSetup.cs @@ -29,6 +29,5 @@ public class AbpJsonOptionsSetup : IConfigureOptions options.JsonSerializerOptions.Converters.Add(new AbpStringToBooleanConverter()); options.JsonSerializerOptions.Converters.Add(new ObjectToInferredTypesConverter()); - options.JsonSerializerOptions.Converters.Add(new AbpHasExtraPropertiesJsonConverterFactory()); } } diff --git a/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/ValueConverters/AbpJsonValueConverter.cs b/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/ValueConverters/AbpJsonValueConverter.cs index 9117e8c594..5be0cf6911 100644 --- a/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/ValueConverters/AbpJsonValueConverter.cs +++ b/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/ValueConverters/AbpJsonValueConverter.cs @@ -19,10 +19,16 @@ public class AbpJsonValueConverter : ValueConverter(s, deserializeOptions); + return JsonSerializer.Deserialize(s, DeserializeOptions); } } diff --git a/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/ValueConverters/ExtraPropertiesValueConverter.cs b/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/ValueConverters/ExtraPropertiesValueConverter.cs index 07a582fcb7..1aa9f48b07 100644 --- a/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/ValueConverters/ExtraPropertiesValueConverter.cs +++ b/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/ValueConverters/ExtraPropertiesValueConverter.cs @@ -41,6 +41,14 @@ public class ExtraPropertiesValueConverter : ValueConverter(extraPropertiesAsJson, deserializeOptions) ?? - new ExtraPropertyDictionary(); + var dictionary = JsonSerializer.Deserialize(extraPropertiesAsJson, DeserializeOptions) ?? + new ExtraPropertyDictionary(); if (entityType != null) { diff --git a/framework/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/DynamicProxying/ApiDescriptionFinder.cs b/framework/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/DynamicProxying/ApiDescriptionFinder.cs index ff0b8d6450..a899f71517 100644 --- a/framework/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/DynamicProxying/ApiDescriptionFinder.cs +++ b/framework/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/DynamicProxying/ApiDescriptionFinder.cs @@ -90,6 +90,11 @@ public class ApiDescriptionFinder : IApiDescriptionFinder, ITransientDependency return await Cache.GetAsync(baseUrl, () => GetApiDescriptionFromServerAsync(client, baseUrl)); } + private static readonly JsonSerializerOptions DeserializeOptions = new JsonSerializerOptions + { + PropertyNamingPolicy = JsonNamingPolicy.CamelCase + }; + protected virtual async Task GetApiDescriptionFromServerAsync( HttpClient client, string baseUrl) @@ -113,10 +118,7 @@ public class ApiDescriptionFinder : IApiDescriptionFinder, ITransientDependency var content = await response.Content.ReadAsStringAsync(); - var result = JsonSerializer.Deserialize(content, new JsonSerializerOptions - { - PropertyNamingPolicy = JsonNamingPolicy.CamelCase - }); + var result = JsonSerializer.Deserialize(content, DeserializeOptions); return result; } diff --git a/framework/src/Volo.Abp.Json/Volo/Abp/Json/SystemTextJson/AbpSystemTextJsonSerializerOptionsSetup.cs b/framework/src/Volo.Abp.Json/Volo/Abp/Json/SystemTextJson/AbpSystemTextJsonSerializerOptionsSetup.cs index 704ee5da31..1615581a9d 100644 --- a/framework/src/Volo.Abp.Json/Volo/Abp/Json/SystemTextJson/AbpSystemTextJsonSerializerOptionsSetup.cs +++ b/framework/src/Volo.Abp.Json/Volo/Abp/Json/SystemTextJson/AbpSystemTextJsonSerializerOptionsSetup.cs @@ -25,7 +25,6 @@ public class AbpSystemTextJsonSerializerOptionsSetup : IConfigureOptions JsonSerializerOptionsCache = new ConcurrentDictionary(); + protected virtual JsonSerializerOptions CreateJsonSerializerOptions(bool camelCase = true, bool indented = false) { - var settings = new JsonSerializerOptions(Options.JsonSerializerOptions); - - if (camelCase) + return JsonSerializerOptionsCache.GetOrAdd($"default{camelCase}{indented}", _ => { - settings.PropertyNamingPolicy = JsonNamingPolicy.CamelCase; - } + var settings = new JsonSerializerOptions(Options.JsonSerializerOptions); - if (indented) - { - settings.WriteIndented = true; - } + if (camelCase) + { + settings.PropertyNamingPolicy = JsonNamingPolicy.CamelCase; + } + + if (indented) + { + settings.WriteIndented = true; + } - return settings; + return settings; + }); } } diff --git a/framework/src/Volo.Abp.Json/Volo/Abp/Json/SystemTextJson/JsonConverters/AbpHasExtraPropertiesJsonConverter.cs b/framework/src/Volo.Abp.Json/Volo/Abp/Json/SystemTextJson/JsonConverters/AbpHasExtraPropertiesJsonConverter.cs deleted file mode 100644 index 22b5513417..0000000000 --- a/framework/src/Volo.Abp.Json/Volo/Abp/Json/SystemTextJson/JsonConverters/AbpHasExtraPropertiesJsonConverter.cs +++ /dev/null @@ -1,54 +0,0 @@ -using System; -using System.Linq; -using System.Text.Json; -using System.Text.Json.Serialization; -using Volo.Abp.Data; - -namespace Volo.Abp.Json.SystemTextJson.JsonConverters; - -public class AbpHasExtraPropertiesJsonConverter : JsonConverter - where T : IHasExtraProperties -{ - public override T Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) - { - var newOptions = JsonSerializerOptionsHelper.Create(options, x => x == this); - - var converterFactory = newOptions.Converters.FirstOrDefault(x => x is AbpHasExtraPropertiesJsonConverterFactory).As(); - var newConverterFactory = new AbpHasExtraPropertiesJsonConverterFactory(); - if (converterFactory != null) - { - newOptions.Converters.Remove(converterFactory); - newConverterFactory.AddExcludeTypes(converterFactory.GetExcludeTypes().ToArray()); - } - - newConverterFactory.AddExcludeTypes(typeToConvert); - newOptions.Converters.Add(newConverterFactory); - - var rootElement = JsonDocument.ParseValue(ref reader).RootElement; - if (rootElement.ValueKind == JsonValueKind.Object) - { - var extensibleObject = rootElement.Deserialize(newOptions); - - var extraPropertiesJsonProperty = rootElement.EnumerateObject().FirstOrDefault(x => x.Name.Equals(nameof(IHasExtraProperties.ExtraProperties), StringComparison.OrdinalIgnoreCase)); - - if (extraPropertiesJsonProperty.Value.ValueKind == JsonValueKind.Object) - { - var extraPropertyDictionary = extraPropertiesJsonProperty.Value.Deserialize(typeof(ExtraPropertyDictionary), newOptions); - ObjectHelper.TrySetProperty(extensibleObject, x => x.ExtraProperties, () => extraPropertyDictionary); - } - - return extensibleObject; - } - - throw new JsonException("RootElement's ValueKind is not Object!"); - } - - public override void Write(Utf8JsonWriter writer, T value, JsonSerializerOptions options) - { - var newOptions = JsonSerializerOptionsHelper.Create(options, x => - x == this || - x.GetType() == typeof(AbpHasExtraPropertiesJsonConverterFactory)); - - JsonSerializer.Serialize(writer, value, newOptions); - } -} diff --git a/framework/src/Volo.Abp.Json/Volo/Abp/Json/SystemTextJson/JsonConverters/AbpHasExtraPropertiesJsonConverterFactory.cs b/framework/src/Volo.Abp.Json/Volo/Abp/Json/SystemTextJson/JsonConverters/AbpHasExtraPropertiesJsonConverterFactory.cs deleted file mode 100644 index 3e7ccb0d59..0000000000 --- a/framework/src/Volo.Abp.Json/Volo/Abp/Json/SystemTextJson/JsonConverters/AbpHasExtraPropertiesJsonConverterFactory.cs +++ /dev/null @@ -1,64 +0,0 @@ -using System; -using System.Collections.Concurrent; -using System.Collections.Generic; -using System.Collections.Immutable; -using System.Reflection; -using System.Text.Json; -using System.Text.Json.Serialization; -using Volo.Abp.Data; - -namespace Volo.Abp.Json.SystemTextJson.JsonConverters; - -public class AbpHasExtraPropertiesJsonConverterFactory : JsonConverterFactory -{ - private static readonly ConcurrentDictionary CachedTypes = new ConcurrentDictionary(); - - private readonly List _excludeTypes = new List(); - - public virtual AbpHasExtraPropertiesJsonConverterFactory AddExcludeTypes(params Type[] excludeTypes) - { - _excludeTypes.AddIfNotContains(excludeTypes); - return this; - } - - public virtual IReadOnlyList GetExcludeTypes() - { - return _excludeTypes.ToImmutableList(); - } - - public override bool CanConvert(Type typeToConvert) - { - if (_excludeTypes.Contains(typeToConvert)) - { - return false; - } - - //Only for private or protected ExtraProperties. - if (typeof(IHasExtraProperties).IsAssignableFrom(typeToConvert)) - { - return CachedTypes.GetOrAdd(typeToConvert, type => - { - var property = type.GetProperty(nameof(IHasExtraProperties.ExtraProperties)); - if (property != null) - { - var setMethod = property.GetSetMethod(true); - return setMethod != null && (setMethod.IsPrivate || setMethod.IsFamily); - } - - return false; - }); - } - - return false; - } - - public override JsonConverter CreateConverter(Type typeToConvert, JsonSerializerOptions options) - { - return (JsonConverter)Activator.CreateInstance( - typeof(AbpHasExtraPropertiesJsonConverter<>).MakeGenericType(typeToConvert), - BindingFlags.Instance | BindingFlags.Public, - binder: null, - null, - culture: null)!; - } -} diff --git a/framework/src/Volo.Abp.Json/Volo/Abp/Json/SystemTextJson/JsonConverters/AbpStringToBooleanConverter.cs b/framework/src/Volo.Abp.Json/Volo/Abp/Json/SystemTextJson/JsonConverters/AbpStringToBooleanConverter.cs index e2b5176276..e704f2ceb0 100644 --- a/framework/src/Volo.Abp.Json/Volo/Abp/Json/SystemTextJson/JsonConverters/AbpStringToBooleanConverter.cs +++ b/framework/src/Volo.Abp.Json/Volo/Abp/Json/SystemTextJson/JsonConverters/AbpStringToBooleanConverter.cs @@ -8,6 +8,8 @@ namespace Volo.Abp.Json.SystemTextJson.JsonConverters; public class AbpStringToBooleanConverter : JsonConverter { + private JsonSerializerOptions _writeJsonSerializerOptions; + public override bool Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { if (reader.TokenType == JsonTokenType.String) @@ -29,8 +31,9 @@ public class AbpStringToBooleanConverter : JsonConverter public override void Write(Utf8JsonWriter writer, bool value, JsonSerializerOptions options) { - var newOptions = JsonSerializerOptionsHelper.Create(options, this); - var entityConverter = (JsonConverter)newOptions.GetConverter(typeof(bool)); - entityConverter.Write(writer, value, newOptions); + _writeJsonSerializerOptions ??= JsonSerializerOptionsHelper.Create(options, this); + var entityConverter = (JsonConverter)_writeJsonSerializerOptions.GetConverter(typeof(bool)); + + entityConverter.Write(writer, value, _writeJsonSerializerOptions); } } diff --git a/framework/src/Volo.Abp.Json/Volo/Abp/Json/SystemTextJson/JsonConverters/AbpStringToEnumConverter.cs b/framework/src/Volo.Abp.Json/Volo/Abp/Json/SystemTextJson/JsonConverters/AbpStringToEnumConverter.cs index baedc4d4d4..c4720c92db 100644 --- a/framework/src/Volo.Abp.Json/Volo/Abp/Json/SystemTextJson/JsonConverters/AbpStringToEnumConverter.cs +++ b/framework/src/Volo.Abp.Json/Volo/Abp/Json/SystemTextJson/JsonConverters/AbpStringToEnumConverter.cs @@ -10,6 +10,10 @@ public class AbpStringToEnumConverter : JsonConverter { private readonly JsonStringEnumConverter _innerJsonStringEnumConverter; + private JsonSerializerOptions _readJsonSerializerOptions; + + private JsonSerializerOptions _writeJsonSerializerOptions; + public AbpStringToEnumConverter() : this(namingPolicy: null, allowIntegerValues: true) { @@ -28,20 +32,20 @@ public class AbpStringToEnumConverter : JsonConverter public override T Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { - var newOptions = JsonSerializerOptionsHelper.Create(options, x => - x == this || - x.GetType() == typeof(AbpStringToEnumFactory)); + _readJsonSerializerOptions ??= JsonSerializerOptionsHelper.Create(options, x => + x == this || + x.GetType() == typeof(AbpStringToEnumFactory), + _innerJsonStringEnumConverter.CreateConverter(typeToConvert, options)); - newOptions.Converters.Add(_innerJsonStringEnumConverter.CreateConverter(typeToConvert, newOptions)); - return JsonSerializer.Deserialize(ref reader, newOptions); + return JsonSerializer.Deserialize(ref reader, _readJsonSerializerOptions); } public override void Write(Utf8JsonWriter writer, T value, JsonSerializerOptions options) { - var newOptions = JsonSerializerOptionsHelper.Create(options, x => + _writeJsonSerializerOptions ??= JsonSerializerOptionsHelper.Create(options, x => x == this || x.GetType() == typeof(AbpStringToEnumFactory)); - JsonSerializer.Serialize(writer, value, newOptions); + JsonSerializer.Serialize(writer, value, _writeJsonSerializerOptions); } } diff --git a/framework/src/Volo.Abp.Localization/Volo/Abp/Localization/Json/JsonLocalizationDictionaryBuilder.cs b/framework/src/Volo.Abp.Localization/Volo/Abp/Localization/Json/JsonLocalizationDictionaryBuilder.cs index de298edc6c..abbfb9b9c6 100644 --- a/framework/src/Volo.Abp.Localization/Volo/Abp/Localization/Json/JsonLocalizationDictionaryBuilder.cs +++ b/framework/src/Volo.Abp.Localization/Volo/Abp/Localization/Json/JsonLocalizationDictionaryBuilder.cs @@ -24,6 +24,14 @@ public static class JsonLocalizationDictionaryBuilder } } + private static readonly JsonSerializerOptions DeserializeOptions = new JsonSerializerOptions + { + PropertyNameCaseInsensitive = true, + DictionaryKeyPolicy = JsonNamingPolicy.CamelCase, + ReadCommentHandling = JsonCommentHandling.Skip, + AllowTrailingCommas = true + }; + /// /// Builds an from given json string. /// @@ -33,15 +41,7 @@ public static class JsonLocalizationDictionaryBuilder JsonLocalizationFile jsonFile; try { - var options = new JsonSerializerOptions - { - PropertyNameCaseInsensitive = true, - DictionaryKeyPolicy = JsonNamingPolicy.CamelCase, - ReadCommentHandling = JsonCommentHandling.Skip, - AllowTrailingCommas = true - }; - - jsonFile = JsonSerializer.Deserialize(jsonString, options); + jsonFile = JsonSerializer.Deserialize(jsonString, DeserializeOptions); } catch (JsonException ex) { diff --git a/framework/src/Volo.Abp.ObjectExtending/Volo/Abp/ObjectExtending/ExtensibleObject.cs b/framework/src/Volo.Abp.ObjectExtending/Volo/Abp/ObjectExtending/ExtensibleObject.cs index 611176b570..4479eeb648 100644 --- a/framework/src/Volo.Abp.ObjectExtending/Volo/Abp/ObjectExtending/ExtensibleObject.cs +++ b/framework/src/Volo.Abp.ObjectExtending/Volo/Abp/ObjectExtending/ExtensibleObject.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.ComponentModel.DataAnnotations; +using System.Text.Json.Serialization; using Volo.Abp.Data; using Volo.Abp.DynamicProxy; @@ -9,6 +10,7 @@ namespace Volo.Abp.ObjectExtending; [Serializable] public class ExtensibleObject : IHasExtraProperties, IValidatableObject { + [JsonInclude] public ExtraPropertyDictionary ExtraProperties { get; protected set; } public ExtensibleObject() diff --git a/framework/test/Volo.Abp.Json.Tests/Volo/Abp/Json/AbpStringToBoolean_Tests.cs b/framework/test/Volo.Abp.Json.Tests/Volo/Abp/Json/AbpStringToBoolean_Tests.cs new file mode 100644 index 0000000000..c46a616db0 --- /dev/null +++ b/framework/test/Volo.Abp.Json.Tests/Volo/Abp/Json/AbpStringToBoolean_Tests.cs @@ -0,0 +1,54 @@ +using System.Text.Json; +using Shouldly; +using Volo.Abp.Json.SystemTextJson.JsonConverters; +using Xunit; + +namespace Volo.Abp.Json +{ + public class AbpStringToBoolean_Tests + { + [Fact] + public void Test_Read() + { + var options = new JsonSerializerOptions() + { + Converters = + { + new AbpStringToBooleanConverter() + } + }; + + var testClass = JsonSerializer.Deserialize("{\"Enabled\": \"TrUe\"}", options); + testClass.ShouldNotBeNull(); + testClass.Enabled.ShouldBe(true); + + testClass = JsonSerializer.Deserialize("{\"Enabled\": true}", options); + testClass.ShouldNotBeNull(); + testClass.Enabled.ShouldBe(true); + } + + [Fact] + public void Test_Write() + { + var options = new JsonSerializerOptions() + { + Converters = + { + new AbpStringToBooleanConverter() + } + }; + + var testClassJson = JsonSerializer.Serialize(new TestClass() + { + Enabled = true + }); + + testClassJson.ShouldBe("{\"Enabled\":true}"); + } + + class TestClass + { + public bool Enabled { get; set; } + } + } +} diff --git a/framework/test/Volo.Abp.Json.Tests/Volo/Abp/Json/AbpStringToEnum_Tests.cs b/framework/test/Volo.Abp.Json.Tests/Volo/Abp/Json/AbpStringToEnum_Tests.cs new file mode 100644 index 0000000000..d3bba36534 --- /dev/null +++ b/framework/test/Volo.Abp.Json.Tests/Volo/Abp/Json/AbpStringToEnum_Tests.cs @@ -0,0 +1,55 @@ +using System; +using System.Text.Json; +using Shouldly; +using Volo.Abp.Json.SystemTextJson.JsonConverters; +using Xunit; + +namespace Volo.Abp.Json +{ + public class AbpStringToEnum_Tests + { + [Fact] + public void Test_Read() + { + var options = new JsonSerializerOptions() + { + Converters = + { + new AbpStringToEnumFactory() + } + }; + + var testClass = JsonSerializer.Deserialize("{\"Day\": \"Monday\"}", options); + testClass.ShouldNotBeNull(); + testClass.Day.ShouldBe(DayOfWeek.Monday); + + testClass = JsonSerializer.Deserialize("{\"Day\": 1}", options); + testClass.ShouldNotBeNull(); + testClass.Day.ShouldBe(DayOfWeek.Monday); + } + + [Fact] + public void Test_Write() + { + var options = new JsonSerializerOptions() + { + Converters = + { + new AbpStringToEnumFactory() + } + }; + + var testClassJson = JsonSerializer.Serialize(new TestClass() + { + Day = DayOfWeek.Monday + }); + + testClassJson.ShouldBe("{\"Day\":1}"); + } + + class TestClass + { + public DayOfWeek Day { get; set; } + } + } +} diff --git a/framework/test/Volo.Abp.Json.Tests/Volo/Abp/Json/AbpSystemTextJsonSerializerProvider_Tests.cs b/framework/test/Volo.Abp.Json.Tests/Volo/Abp/Json/AbpSystemTextJsonSerializerProvider_Tests.cs index daa6118c2f..21b3fd51bb 100644 --- a/framework/test/Volo.Abp.Json.Tests/Volo/Abp/Json/AbpSystemTextJsonSerializerProvider_Tests.cs +++ b/framework/test/Volo.Abp.Json.Tests/Volo/Abp/Json/AbpSystemTextJsonSerializerProvider_Tests.cs @@ -174,8 +174,7 @@ public class AbpSystemTextJsonSerializerProvider_Tests : AbpSystemTextJsonSerial { var json = "{\"name\":\"test\",\"extraProperties\":null}"; var extensibleObject = JsonSerializer.Deserialize(json); - extensibleObject.ExtraProperties.ShouldNotBeNull(); - extensibleObject.ExtraProperties.ShouldBeEmpty(); + extensibleObject.ExtraProperties.ShouldBeNull(); } [Fact] diff --git a/framework/test/Volo.Abp.Json.Tests/Volo/Abp/Json/AbpHasExtraPropertiesJsonConverter_Tests.cs b/framework/test/Volo.Abp.Json.Tests/Volo/Abp/Json/ExtensibleObject_Tests.cs similarity index 63% rename from framework/test/Volo.Abp.Json.Tests/Volo/Abp/Json/AbpHasExtraPropertiesJsonConverter_Tests.cs rename to framework/test/Volo.Abp.Json.Tests/Volo/Abp/Json/ExtensibleObject_Tests.cs index 9363ac8f72..bdd41f20b6 100644 --- a/framework/test/Volo.Abp.Json.Tests/Volo/Abp/Json/AbpHasExtraPropertiesJsonConverter_Tests.cs +++ b/framework/test/Volo.Abp.Json.Tests/Volo/Abp/Json/ExtensibleObject_Tests.cs @@ -7,11 +7,11 @@ using Xunit; namespace Volo.Abp.Json; -public class AbpHasExtraPropertiesJsonConverter_Tests : AbpJsonTestBase +public class ExtensibleObject_Tests: AbpJsonTestBase { private readonly IJsonSerializer _jsonSerializer; - public AbpHasExtraPropertiesJsonConverter_Tests() + public ExtensibleObject_Tests() { _jsonSerializer = GetRequiredService(); } @@ -60,16 +60,53 @@ public class AbpHasExtraPropertiesJsonConverter_Tests : AbpJsonTestBase fooDto.BarDtos.First().Name.ShouldBe("new-bar-dto"); fooDto.BarDtos.First().GetProperty("bar").ShouldBe("new-bar-value"); } + + [Fact] + public void SelfReference_Test() + { + var parentNodeDto = new NodeDto + { + Name = "parentNode", + }; + parentNodeDto.SetProperty("node", "parent-value"); + + var nodeDto = new NodeDto + { + Name = "node", + Parent = parentNodeDto + }; + nodeDto.SetProperty("node", "node-value"); + + var json = _jsonSerializer.Serialize(nodeDto); + + nodeDto = _jsonSerializer.Deserialize(json); + nodeDto.ShouldNotBeNull(); + nodeDto.Name.ShouldBe("node"); + nodeDto.GetProperty("node").ShouldBe("node-value"); + + nodeDto.Parent.ShouldNotBeNull(); + nodeDto.Parent.Name.ShouldBe("parentNode"); + nodeDto.Parent.GetProperty("node").ShouldBe("parent-value"); + } } -public class FooDto : ExtensibleObject +class FooDto : ExtensibleObject { public string Name { get; set; } public List BarDtos { get; set; } } -public class BarDto : ExtensibleObject +class BarDto : ExtensibleObject { public string Name { get; set; } + + public FooDto FooDto { get; set; } +} + +class NodeDto : ExtensibleObject +{ + public string Name { get; set; } + + public NodeDto Parent { get; set; } } diff --git a/framework/test/Volo.Abp.MemoryDb.Tests/Volo/Abp/MemoryDb/JsonConverters/EntityJsonConverter.cs b/framework/test/Volo.Abp.MemoryDb.Tests/Volo/Abp/MemoryDb/JsonConverters/EntityJsonConverter.cs index 320ae870c0..abbb5224bb 100644 --- a/framework/test/Volo.Abp.MemoryDb.Tests/Volo/Abp/MemoryDb/JsonConverters/EntityJsonConverter.cs +++ b/framework/test/Volo.Abp.MemoryDb.Tests/Volo/Abp/MemoryDb/JsonConverters/EntityJsonConverter.cs @@ -8,6 +8,8 @@ namespace Volo.Abp.MemoryDb.JsonConverters; public class EntityJsonConverter : JsonConverter where TEntity : Entity { + private JsonSerializerOptions _writeJsonSerializerOptions; + public override TEntity Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { var jsonDocument = JsonDocument.ParseValue(ref reader); @@ -32,7 +34,7 @@ public class EntityJsonConverter : JsonConverter public override void Write(Utf8JsonWriter writer, TEntity value, JsonSerializerOptions options) { - var newOptions = JsonSerializerOptionsHelper.Create(options, this); - JsonSerializer.Serialize(writer, value, newOptions); + _writeJsonSerializerOptions ??= JsonSerializerOptionsHelper.Create(options, this); + JsonSerializer.Serialize(writer, value, _writeJsonSerializerOptions); } } diff --git a/modules/feature-management/src/Volo.Abp.FeatureManagement.Application.Contracts/Volo/Abp/FeatureManagement/JsonConverters/SelectionStringValueItemSourceJsonConverter.cs b/modules/feature-management/src/Volo.Abp.FeatureManagement.Application.Contracts/Volo/Abp/FeatureManagement/JsonConverters/SelectionStringValueItemSourceJsonConverter.cs index adc9dbe296..286b2145b1 100644 --- a/modules/feature-management/src/Volo.Abp.FeatureManagement.Application.Contracts/Volo/Abp/FeatureManagement/JsonConverters/SelectionStringValueItemSourceJsonConverter.cs +++ b/modules/feature-management/src/Volo.Abp.FeatureManagement.Application.Contracts/Volo/Abp/FeatureManagement/JsonConverters/SelectionStringValueItemSourceJsonConverter.cs @@ -8,6 +8,10 @@ namespace Volo.Abp.FeatureManagement.JsonConverters; public class SelectionStringValueItemSourceJsonConverter : JsonConverter { + private JsonSerializerOptions _readJsonSerializerOptions; + + private JsonSerializerOptions _writeJsonSerializerOptions; + public override ISelectionStringValueItemSource Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { var rootElement = JsonDocument.ParseValue(ref reader).RootElement; @@ -16,10 +20,10 @@ public class SelectionStringValueItemSourceJsonConverter : JsonConverter(newOptions) ?? + itemsJsonProperty.Value.Deserialize(_readJsonSerializerOptions) ?? Array.Empty(); return new StaticSelectionStringValueItemSource(selectionStringValueItem.As()); @@ -28,10 +32,9 @@ public class SelectionStringValueItemSourceJsonConverter : JsonConverter { + private JsonSerializerOptions _readJsonSerializerOptions; + + private JsonSerializerOptions _writeJsonSerializerOptions; + public override IStringValueType Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { var rootElement = JsonDocument.ParseValue(ref reader).RootElement; @@ -17,14 +21,13 @@ public class StringValueTypeJsonConverter : JsonConverter { var name = nameJsonProperty.Value.GetString(); - var newOptions = JsonSerializerOptionsHelper.Create(options, this, new ValueValidatorJsonConverter(), - new SelectionStringValueItemSourceJsonConverter()); + _readJsonSerializerOptions ??= JsonSerializerOptionsHelper.Create(options, this, new ValueValidatorJsonConverter(), new SelectionStringValueItemSourceJsonConverter()); return name switch { - "SelectionStringValueType" => rootElement.Deserialize(newOptions), - "FreeTextStringValueType" => rootElement.Deserialize(newOptions), - "ToggleStringValueType" => rootElement.Deserialize(newOptions), + "SelectionStringValueType" => rootElement.Deserialize(_readJsonSerializerOptions), + "FreeTextStringValueType" => rootElement.Deserialize(_readJsonSerializerOptions), + "ToggleStringValueType" => rootElement.Deserialize(_readJsonSerializerOptions), _ => throw new ArgumentException($"{nameof(IStringValueType)} named {name} was not found!") }; } @@ -34,7 +37,7 @@ public class StringValueTypeJsonConverter : JsonConverter public override void Write(Utf8JsonWriter writer, IStringValueType value, JsonSerializerOptions options) { - var newOptions = JsonSerializerOptionsHelper.Create(options, this); - JsonSerializer.Serialize(writer, value, value.GetType(), newOptions); + _writeJsonSerializerOptions ??= JsonSerializerOptionsHelper.Create(options, this); + JsonSerializer.Serialize(writer, value, value.GetType(), _writeJsonSerializerOptions); } } diff --git a/modules/feature-management/src/Volo.Abp.FeatureManagement.Application.Contracts/Volo/Abp/FeatureManagement/JsonConverters/ValueValidatorJsonConverter.cs b/modules/feature-management/src/Volo.Abp.FeatureManagement.Application.Contracts/Volo/Abp/FeatureManagement/JsonConverters/ValueValidatorJsonConverter.cs index dfa738be61..8d1a60056e 100644 --- a/modules/feature-management/src/Volo.Abp.FeatureManagement.Application.Contracts/Volo/Abp/FeatureManagement/JsonConverters/ValueValidatorJsonConverter.cs +++ b/modules/feature-management/src/Volo.Abp.FeatureManagement.Application.Contracts/Volo/Abp/FeatureManagement/JsonConverters/ValueValidatorJsonConverter.cs @@ -10,6 +10,10 @@ namespace Volo.Abp.FeatureManagement.JsonConverters; public class ValueValidatorJsonConverter : JsonConverter { + private JsonSerializerOptions _readJsonSerializerOptions; + + private JsonSerializerOptions _writeJsonSerializerOptions; + public override IValueValidator Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { var rootElement = JsonDocument.ParseValue(ref reader).RootElement; @@ -22,8 +26,8 @@ public class ValueValidatorJsonConverter : JsonConverter var propertiesJsonProperty = rootElement.EnumerateObject().FirstOrDefault(x => x.Name.Equals(nameof(IValueValidator.Properties), StringComparison.OrdinalIgnoreCase)); if (propertiesJsonProperty.Value.ValueKind == JsonValueKind.Object) { - var newOptions = JsonSerializerOptionsHelper.Create(options, this, new ObjectToInferredTypesConverter()); - var properties = propertiesJsonProperty.Value.Deserialize>(newOptions); + _readJsonSerializerOptions ??= JsonSerializerOptionsHelper.Create(options, this, new ObjectToInferredTypesConverter()); + var properties = propertiesJsonProperty.Value.Deserialize>(_readJsonSerializerOptions); if (properties != null && properties.Any()) { foreach (var property in properties) @@ -41,8 +45,8 @@ public class ValueValidatorJsonConverter : JsonConverter public override void Write(Utf8JsonWriter writer, IValueValidator value, JsonSerializerOptions options) { - var newOptions = JsonSerializerOptionsHelper.Create(options, this); - JsonSerializer.Serialize(writer, value, value.GetType(), newOptions); + _writeJsonSerializerOptions ??= JsonSerializerOptionsHelper.Create(options, this); + JsonSerializer.Serialize(writer, value, value.GetType(), _writeJsonSerializerOptions); } protected virtual IValueValidator CreateValueValidatorByName(string name)