From 243bfd6fc0877de08b5ad61942f27c2f3f435f00 Mon Sep 17 00:00:00 2001 From: maliming Date: Fri, 13 Nov 2020 22:50:34 +0800 Subject: [PATCH 1/5] Handling the case where JSON is null or empty. Resolve #6191 --- .../JsonConverters/AbpDateTimeConverter.cs | 24 +++- .../AbpHasExtraPropertiesJsonConverter.cs | 20 ++- .../AbpNullableDateTimeConverter.cs | 21 ++- ...pSystemTextJsonSerializerProvider_Tests.cs | 131 +++++++++++++++--- .../JsonConverters/EntityJsonConverter.cs | 23 +-- 5 files changed, 183 insertions(+), 36 deletions(-) diff --git a/framework/src/Volo.Abp.Json/Volo/Abp/Json/SystemTextJson/JsonConverters/AbpDateTimeConverter.cs b/framework/src/Volo.Abp.Json/Volo/Abp/Json/SystemTextJson/JsonConverters/AbpDateTimeConverter.cs index a87add029e..e35589830d 100644 --- a/framework/src/Volo.Abp.Json/Volo/Abp/Json/SystemTextJson/JsonConverters/AbpDateTimeConverter.cs +++ b/framework/src/Volo.Abp.Json/Volo/Abp/Json/SystemTextJson/JsonConverters/AbpDateTimeConverter.cs @@ -1,4 +1,5 @@ using System; +using System.Globalization; using System.Text.Json; using System.Text.Json.Serialization; using Microsoft.Extensions.Options; @@ -20,7 +21,28 @@ namespace Volo.Abp.Json.SystemTextJson.JsonConverters public override DateTime Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { - return _clock.Normalize(reader.GetDateTime()); + if (!_options.DefaultDateTimeFormat.IsNullOrWhiteSpace()) + { + if (reader.TokenType == JsonTokenType.String) + { + var s = reader.GetString(); + if (DateTime.TryParseExact(s, _options.DefaultDateTimeFormat, CultureInfo.InvariantCulture, DateTimeStyles.AdjustToUniversal, out var d1)) + { + return _clock.Normalize(d1); + } + + throw new JsonException($"{s} cannot parse to DateTime({_options.DefaultDateTimeFormat})!"); + } + + throw new JsonException("reader TokenType is not String!"); + } + + if (reader.TryGetDateTime(out var d2)) + { + return _clock.Normalize(d2); + } + + throw new JsonException("reader can't get datetime!"); } public override void Write(Utf8JsonWriter writer, DateTime value, JsonSerializerOptions options) 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 index 40227dd5c3..e5e3f5e187 100644 --- 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 @@ -16,15 +16,21 @@ namespace Volo.Abp.Json.SystemTextJson.JsonConverters x.GetType() == typeof(AbpHasExtraPropertiesJsonConverterFactory)); var rootElement = JsonDocument.ParseValue(ref reader).RootElement; - var extensibleObject = JsonSerializer.Deserialize(rootElement.GetRawText(), newOptions); + if (rootElement.ValueKind == JsonValueKind.Object) + { + var extensibleObject = JsonSerializer.Deserialize(rootElement.GetRawText(), newOptions); - var extraProperties = rootElement.EnumerateObject().FirstOrDefault(x => - x.Name.Equals(nameof(IHasExtraProperties.ExtraProperties), StringComparison.OrdinalIgnoreCase)) - .Value.GetRawText(); - var extraPropertyDictionary = JsonSerializer.Deserialize(extraProperties, typeof(ExtraPropertyDictionary), newOptions); - ObjectHelper.TrySetProperty(extensibleObject, x => x.ExtraProperties, () => extraPropertyDictionary); + var extraProperties = rootElement.EnumerateObject().FirstOrDefault(x => x.Name.Equals(nameof(IHasExtraProperties.ExtraProperties), StringComparison.OrdinalIgnoreCase)); + if (extraProperties.Value.ValueKind == JsonValueKind.Object) + { + var extraPropertyDictionary = JsonSerializer.Deserialize(extraProperties.Value.GetRawText(), typeof(ExtraPropertyDictionary), newOptions); + ObjectHelper.TrySetProperty(extensibleObject, x => x.ExtraProperties, () => extraPropertyDictionary); + } - return extensibleObject; + return extensibleObject; + } + + throw new JsonException("RootElement ValueKind is not Object!"); } public override void Write(Utf8JsonWriter writer, T value, JsonSerializerOptions options) diff --git a/framework/src/Volo.Abp.Json/Volo/Abp/Json/SystemTextJson/JsonConverters/AbpNullableDateTimeConverter.cs b/framework/src/Volo.Abp.Json/Volo/Abp/Json/SystemTextJson/JsonConverters/AbpNullableDateTimeConverter.cs index bad650b4d8..3a603af994 100644 --- a/framework/src/Volo.Abp.Json/Volo/Abp/Json/SystemTextJson/JsonConverters/AbpNullableDateTimeConverter.cs +++ b/framework/src/Volo.Abp.Json/Volo/Abp/Json/SystemTextJson/JsonConverters/AbpNullableDateTimeConverter.cs @@ -1,4 +1,5 @@ using System; +using System.Globalization; using System.Text.Json; using System.Text.Json.Serialization; using Microsoft.Extensions.Options; @@ -20,9 +21,25 @@ namespace Volo.Abp.Json.SystemTextJson.JsonConverters public override DateTime? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { - if (reader.TryGetDateTime(out var dateTime)) + if (!_options.DefaultDateTimeFormat.IsNullOrWhiteSpace()) { - return _clock.Normalize(dateTime); + if (reader.TokenType == JsonTokenType.String) + { + var s = reader.GetString(); + if (DateTime.TryParseExact(s, _options.DefaultDateTimeFormat, CultureInfo.InvariantCulture, DateTimeStyles.AdjustToUniversal, out var d1)) + { + return _clock.Normalize(d1); + } + + throw new JsonException($"{s} cannot parse to DateTime({_options.DefaultDateTimeFormat})!"); + } + + throw new JsonException("reader TokenType is not String!"); + } + + if (reader.TryGetDateTime(out var d2)) + { + return _clock.Normalize(d2); } return null; 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 da49f7ebc6..3c32a99dec 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 @@ -1,4 +1,6 @@ -using Shouldly; +using System; +using Microsoft.Extensions.DependencyInjection; +using Shouldly; using Volo.Abp.Data; using Volo.Abp.Json.SystemTextJson; using Volo.Abp.ObjectExtending; @@ -8,23 +10,23 @@ namespace Volo.Abp.Json { public class AbpSystemTextJsonSerializerProvider_Tests : AbpJsonTestBase { - private readonly AbpSystemTextJsonSerializerProvider _jsonSerializer; + protected AbpSystemTextJsonSerializerProvider JsonSerializer; public AbpSystemTextJsonSerializerProvider_Tests() { - _jsonSerializer = GetRequiredService(); + JsonSerializer = GetRequiredService(); } [Fact] public void Serialize_Deserialize_With_Boolean() { var json = "{\"name\":\"abp\",\"IsDeleted\":\"fAlSe\"}"; - var file = _jsonSerializer.Deserialize(json); + var file = JsonSerializer.Deserialize(json); file.Name.ShouldBe("abp"); file.IsDeleted.ShouldBeFalse(); file.IsDeleted = false; - var newJson = _jsonSerializer.Serialize(file); + var newJson = JsonSerializer.Serialize(file); newJson.ShouldBe("{\"name\":\"abp\",\"isDeleted\":false}"); } @@ -32,19 +34,19 @@ namespace Volo.Abp.Json public void Serialize_Deserialize_With_Nullable_Boolean() { var json = "{\"name\":\"abp\",\"IsDeleted\":null}"; - var file = _jsonSerializer.Deserialize(json); + var file = JsonSerializer.Deserialize(json); file.Name.ShouldBe("abp"); file.IsDeleted.ShouldBeNull(); - var newJson = _jsonSerializer.Serialize(file); + var newJson = JsonSerializer.Serialize(file); newJson.ShouldBe("{\"name\":\"abp\",\"isDeleted\":null}"); json = "{\"name\":\"abp\",\"IsDeleted\":\"true\"}"; - file = _jsonSerializer.Deserialize(json); + file = JsonSerializer.Deserialize(json); file.IsDeleted.ShouldNotBeNull(); file.IsDeleted.Value.ShouldBeTrue(); - newJson = _jsonSerializer.Serialize(file); + newJson = JsonSerializer.Serialize(file); newJson.ShouldBe("{\"name\":\"abp\",\"isDeleted\":true}"); } @@ -52,11 +54,11 @@ namespace Volo.Abp.Json public void Serialize_Deserialize_With_Enum() { var json = "{\"name\":\"abp\",\"type\":\"Exe\"}"; - var file = _jsonSerializer.Deserialize(json); + var file = JsonSerializer.Deserialize(json); file.Name.ShouldBe("abp"); file.Type.ShouldBe(FileType.Exe); - var newJson = _jsonSerializer.Serialize(file); + var newJson = JsonSerializer.Serialize(file); newJson.ShouldBe("{\"name\":\"abp\",\"type\":2}"); } @@ -64,35 +66,73 @@ namespace Volo.Abp.Json public void Serialize_Deserialize_With_Nullable_Enum() { var json = "{\"name\":\"abp\",\"type\":null}"; - var file = _jsonSerializer.Deserialize(json); + var file = JsonSerializer.Deserialize(json); file.Name.ShouldBe("abp"); file.Type.ShouldBeNull(); - var newJson = _jsonSerializer.Serialize(file); + var newJson = JsonSerializer.Serialize(file); newJson.ShouldBe("{\"name\":\"abp\",\"type\":null}"); json = "{\"name\":\"abp\",\"type\":\"Exe\"}"; - file = _jsonSerializer.Deserialize(json); + file = JsonSerializer.Deserialize(json); file.Type.ShouldNotBeNull(); file.Type.ShouldBe(FileType.Exe); - newJson = _jsonSerializer.Serialize(file); + newJson = JsonSerializer.Serialize(file); newJson.ShouldBe("{\"name\":\"abp\",\"type\":2}"); } - [Fact] public void Serialize_Deserialize_ExtensibleObject() { var json = "{\"name\":\"test\",\"extraProperties\":{\"One\":\"123\",\"Two\":456}}"; - var extensibleObject = _jsonSerializer.Deserialize(json); + var extensibleObject = JsonSerializer.Deserialize(json); extensibleObject.GetProperty("One").ShouldBe("123"); extensibleObject.GetProperty("Two").ShouldBe(456); - var newJson = _jsonSerializer.Serialize(extensibleObject); + var newJson = JsonSerializer.Serialize(extensibleObject); newJson.ShouldBe(json); } + [Fact] + public void Serialize_Deserialize_ExtensibleObject_With_ExtraProperties_String() + { + var json = "{\"name\":\"test\"}"; + var extensibleObject = JsonSerializer.Deserialize(json); + extensibleObject.ExtraProperties.ShouldNotBeNull(); + extensibleObject.ExtraProperties.ShouldBeEmpty(); + } + + [Fact] + public void Serialize_Deserialize_With_Datetime() + { + var json = "{\"name\":\"abp\",\"creationTime\":\"2020-11-20T00:00:00\"}"; + var file = JsonSerializer.Deserialize(json); + file.CreationTime.Year.ShouldBe(2020); + file.CreationTime.Month.ShouldBe(11); + file.CreationTime.Day.ShouldBe(20); + } + + [Fact] + public void Serialize_Deserialize_With_Nullable_Datetime() + { + var json = "{\"name\":\"abp\",\"creationTime\":null}"; + var file = JsonSerializer.Deserialize(json); + file.CreationTime.ShouldBeNull(); + + json = "{\"name\":\"abp\"}"; + file = JsonSerializer.Deserialize(json); + file.CreationTime.ShouldBeNull(); + + json = "{\"name\":\"abp\",\"creationTime\":\"2020-11-20T00:00:00\"}"; + file = JsonSerializer.Deserialize(json); + file.CreationTime.ShouldNotBeNull(); + + file.CreationTime.Value.Year.ShouldBe(2020); + file.CreationTime.Value.Month.ShouldBe(11); + file.CreationTime.Value.Day.ShouldBe(20); + } + class TestExtensibleObjectClass : ExtensibleObject { public string Name { get; set; } @@ -131,5 +171,60 @@ namespace Volo.Abp.Json Zip = 0, Exe = 2 } + + protected class FileWithDatetime + { + public string Name { get; set; } + + public DateTime CreationTime { get; set; } + } + + protected class FileWithNullableDatetime + { + public string Name { get; set; } + + public DateTime? CreationTime { get; set; } + } + } + + public class FormatAbpSystemTextJsonSerializerProvider_Tests : AbpSystemTextJsonSerializerProvider_Tests + { + protected override void AfterAddApplication(IServiceCollection services) + { + services.Configure(options => + { + options.DefaultDateTimeFormat = "yyyy*MM*dd"; + }); + } + + [Fact] + public void Serialize_Deserialize_With_Format_Datetime() + { + var json = "{\"name\":\"abp\",\"creationTime\":\"2020*11*20\"}"; + var file = JsonSerializer.Deserialize(json); + file.CreationTime.Year.ShouldBe(2020); + file.CreationTime.Month.ShouldBe(11); + file.CreationTime.Day.ShouldBe(20); + } + + [Fact] + public void Serialize_Deserialize_With_Nullable_Format_Datetime() + { + var json = "{\"name\":\"abp\",\"creationTime\":null}"; + var file = JsonSerializer.Deserialize(json); + file.CreationTime.ShouldBeNull(); + + json = "{\"name\":\"abp\"}"; + file = JsonSerializer.Deserialize(json); + file.CreationTime.ShouldBeNull(); + + json = "{\"name\":\"abp\",\"creationTime\":\"2020*11*20\"}"; + file = JsonSerializer.Deserialize(json); + file.CreationTime.ShouldNotBeNull(); + + file.CreationTime.Value.Year.ShouldBe(2020); + file.CreationTime.Value.Month.ShouldBe(11); + file.CreationTime.Value.Day.ShouldBe(20); + } } } 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 fbc222a46a..1e2c50a448 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 @@ -6,21 +6,28 @@ using Volo.Abp.Domain.Entities; namespace Volo.Abp.MemoryDb.JsonConverters { public class EntityJsonConverter : JsonConverter - where TEntity : IEntity + where TEntity : Entity { public override TEntity Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { var jsonDocument = JsonDocument.ParseValue(ref reader); - var entity = (TEntity)JsonSerializer.Deserialize(jsonDocument.RootElement.GetRawText(), typeToConvert); - var idJsonElement = jsonDocument.RootElement.GetProperty(nameof(IEntity.Id)); - - var id = JsonSerializer.Deserialize(idJsonElement.GetRawText()); - if (id != null) + if (jsonDocument.RootElement.ValueKind == JsonValueKind.Object) { - EntityHelper.TrySetId(entity, () => id); + var entity = (TEntity)JsonSerializer.Deserialize(jsonDocument.RootElement.GetRawText(), typeToConvert); + + var idJsonElement = jsonDocument.RootElement.GetProperty(nameof(Entity.Id)); + if (idJsonElement.ValueKind != JsonValueKind.Undefined) + { + var id = JsonSerializer.Deserialize(idJsonElement.GetRawText()); + if (id != null) + { + EntityHelper.TrySetId(entity, () => id); + } + } + return entity; } - return entity; + throw new JsonException("RootElement ValueKind is not Object!"); } public override void Write(Utf8JsonWriter writer, TEntity value, JsonSerializerOptions options) From 6404c02764647acc445dcec6395602d040e3cf70 Mon Sep 17 00:00:00 2001 From: maliming Date: Sat, 14 Nov 2020 13:04:14 +0800 Subject: [PATCH 2/5] Fix unit test. --- ...pSystemTextJsonSerializerProvider_Tests.cs | 115 +++++++++--------- 1 file changed, 59 insertions(+), 56 deletions(-) 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 3c32a99dec..9e166aa146 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 @@ -8,15 +8,71 @@ using Xunit; namespace Volo.Abp.Json { - public class AbpSystemTextJsonSerializerProvider_Tests : AbpJsonTestBase + public abstract class AbpSystemTextJsonSerializerProvider_TestBase : AbpJsonTestBase { protected AbpSystemTextJsonSerializerProvider JsonSerializer; - public AbpSystemTextJsonSerializerProvider_Tests() + public AbpSystemTextJsonSerializerProvider_TestBase() { JsonSerializer = GetRequiredService(); } + public class TestExtensibleObjectClass : ExtensibleObject + { + public string Name { get; set; } + } + + public class FileWithBoolean + { + public string Name { get; set; } + + public bool IsDeleted { get; set; } + } + + public class FileWithNullableBoolean + { + public string Name { get; set; } + + public bool? IsDeleted { get; set; } + } + + public class FileWithEnum + { + public string Name { get; set; } + + public FileType Type { get; set; } + } + + public class FileWithNullableEnum + { + public string Name { get; set; } + + public FileType? Type { get; set; } + } + + public enum FileType + { + Zip = 0, + Exe = 2 + } + + public class FileWithDatetime + { + public string Name { get; set; } + + public DateTime CreationTime { get; set; } + } + + public class FileWithNullableDatetime + { + public string Name { get; set; } + + public DateTime? CreationTime { get; set; } + } + } + + public class AbpSystemTextJsonSerializerProvider_Tests : AbpSystemTextJsonSerializerProvider_TestBase + { [Fact] public void Serialize_Deserialize_With_Boolean() { @@ -132,62 +188,9 @@ namespace Volo.Abp.Json file.CreationTime.Value.Month.ShouldBe(11); file.CreationTime.Value.Day.ShouldBe(20); } - - class TestExtensibleObjectClass : ExtensibleObject - { - public string Name { get; set; } - } - - class FileWithBoolean - { - public string Name { get; set; } - - public bool IsDeleted { get; set; } - } - - class FileWithNullableBoolean - { - public string Name { get; set; } - - public bool? IsDeleted { get; set; } - } - - class FileWithEnum - { - public string Name { get; set; } - - public FileType Type { get; set; } - } - - class FileWithNullableEnum - { - public string Name { get; set; } - - public FileType? Type { get; set; } - } - - enum FileType - { - Zip = 0, - Exe = 2 - } - - protected class FileWithDatetime - { - public string Name { get; set; } - - public DateTime CreationTime { get; set; } - } - - protected class FileWithNullableDatetime - { - public string Name { get; set; } - - public DateTime? CreationTime { get; set; } - } } - public class FormatAbpSystemTextJsonSerializerProvider_Tests : AbpSystemTextJsonSerializerProvider_Tests + public class AbpSystemTextJsonSerializerProviderDefaultDateTimeFormat_Tests : AbpSystemTextJsonSerializerProvider_TestBase { protected override void AfterAddApplication(IServiceCollection services) { From 0768f34776bc91855e68508fadc3b56a8d348d7c Mon Sep 17 00:00:00 2001 From: maliming Date: Sat, 14 Nov 2020 13:49:37 +0800 Subject: [PATCH 3/5] Add more datetime unit tests. --- ...pSystemTextJsonSerializerProvider_Tests.cs | 51 ++++++++++++++++++- 1 file changed, 50 insertions(+), 1 deletion(-) 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 9e166aa146..06e858c7c0 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 @@ -4,6 +4,7 @@ using Shouldly; using Volo.Abp.Data; using Volo.Abp.Json.SystemTextJson; using Volo.Abp.ObjectExtending; +using Volo.Abp.Timing; using Xunit; namespace Volo.Abp.Json @@ -167,6 +168,9 @@ namespace Volo.Abp.Json file.CreationTime.Year.ShouldBe(2020); file.CreationTime.Month.ShouldBe(11); file.CreationTime.Day.ShouldBe(20); + + var newJson = JsonSerializer.Serialize(file); + newJson.ShouldBe(json); } [Fact] @@ -187,10 +191,13 @@ namespace Volo.Abp.Json file.CreationTime.Value.Year.ShouldBe(2020); file.CreationTime.Value.Month.ShouldBe(11); file.CreationTime.Value.Day.ShouldBe(20); + + var newJson = JsonSerializer.Serialize(file); + newJson.ShouldBe(json); } } - public class AbpSystemTextJsonSerializerProviderDefaultDateTimeFormat_Tests : AbpSystemTextJsonSerializerProvider_TestBase + public class AbpSystemTextJsonSerializerProvider_DateTimeFormat_Tests : AbpSystemTextJsonSerializerProvider_TestBase { protected override void AfterAddApplication(IServiceCollection services) { @@ -208,6 +215,9 @@ namespace Volo.Abp.Json file.CreationTime.Year.ShouldBe(2020); file.CreationTime.Month.ShouldBe(11); file.CreationTime.Day.ShouldBe(20); + + var newJson = JsonSerializer.Serialize(file); + newJson.ShouldBe(json); } [Fact] @@ -228,6 +238,45 @@ namespace Volo.Abp.Json file.CreationTime.Value.Year.ShouldBe(2020); file.CreationTime.Value.Month.ShouldBe(11); file.CreationTime.Value.Day.ShouldBe(20); + + var newJson = JsonSerializer.Serialize(file); + newJson.ShouldBe(json); } } + + public abstract class AbpSystemTextJsonSerializerProvider_Datetime_Kind_Tests : AbpSystemTextJsonSerializerProvider_TestBase + { + protected DateTimeKind Kind { get; set; } = DateTimeKind.Unspecified; + + [Fact] + public void Serialize_Deserialize() + { + var json = "{\"name\":\"abp\",\"creationTime\":\"2020-11-20T00:00:00\"}"; + var file = JsonSerializer.Deserialize(json); + file.CreationTime.Kind.ShouldBe(Kind); + } + } + + public class AbpSystemTextJsonSerializerProvider_Datetime_Kind_UTC_Tests : AbpSystemTextJsonSerializerProvider_Datetime_Kind_Tests + { + protected override void AfterAddApplication(IServiceCollection services) + { + Kind = DateTimeKind.Utc; + services.Configure(x => x.Kind = Kind); + } + } + + public class AbpSystemTextJsonSerializerProvider_Datetime_Kind_Local_Tests : AbpSystemTextJsonSerializerProvider_Datetime_Kind_Tests + { + protected override void AfterAddApplication(IServiceCollection services) + { + Kind = DateTimeKind.Local; + services.Configure(x => x.Kind = Kind); + } + } + + public class AbpSystemTextJsonSerializerProvider_Datetime_Kind_Unspecified_Tests : AbpSystemTextJsonSerializerProvider_Datetime_Kind_Tests + { + + } } From 9e05025ad856b8c244168956419f09bf04aab7b3 Mon Sep 17 00:00:00 2001 From: maliming Date: Sat, 14 Nov 2020 14:42:58 +0800 Subject: [PATCH 4/5] Enhance JsonConverter in the Feature module. --- .../AbpHasExtraPropertiesJsonConverter.cs | 6 ++--- ...ctionStringValueItemSourceJsonConverter.cs | 21 +++++++++------ .../StringValueTypeJsonConverter.cs | 26 ++++++++++++------ .../ValueValidatorJsonConverter.cs | 27 ++++++++++++------- .../StringValueJsonConverter_Tests.cs | 11 ++++++++ 5 files changed, 63 insertions(+), 28 deletions(-) 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 index e5e3f5e187..466fee45f3 100644 --- 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 @@ -20,10 +20,10 @@ namespace Volo.Abp.Json.SystemTextJson.JsonConverters { var extensibleObject = JsonSerializer.Deserialize(rootElement.GetRawText(), newOptions); - var extraProperties = rootElement.EnumerateObject().FirstOrDefault(x => x.Name.Equals(nameof(IHasExtraProperties.ExtraProperties), StringComparison.OrdinalIgnoreCase)); - if (extraProperties.Value.ValueKind == JsonValueKind.Object) + var extraPropertiesJsonProperty = rootElement.EnumerateObject().FirstOrDefault(x => x.Name.Equals(nameof(IHasExtraProperties.ExtraProperties), StringComparison.OrdinalIgnoreCase)); + if (extraPropertiesJsonProperty.Value.ValueKind == JsonValueKind.Object) { - var extraPropertyDictionary = JsonSerializer.Deserialize(extraProperties.Value.GetRawText(), typeof(ExtraPropertyDictionary), newOptions); + var extraPropertyDictionary = JsonSerializer.Deserialize(extraPropertiesJsonProperty.Value.GetRawText(), typeof(ExtraPropertyDictionary), newOptions); ObjectHelper.TrySetProperty(extensibleObject, x => x.ExtraProperties, () => extraPropertyDictionary); } 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 3a05e3f63f..bae37dbff9 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 @@ -1,5 +1,4 @@ using System; -using System.Collections.Generic; using System.Linq; using System.Text.Json; using System.Text.Json.Serialization; @@ -11,16 +10,22 @@ namespace Volo.Abp.FeatureManagement.JsonConverters { public override ISelectionStringValueItemSource Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { - var newOptions = JsonSerializerOptionsHelper.Create(options, this); - var rootElement = JsonDocument.ParseValue(ref reader).RootElement; - var items = rootElement.EnumerateObject().FirstOrDefault(x => x.Name.Equals(nameof(ISelectionStringValueItemSource.Items), StringComparison.OrdinalIgnoreCase)).Value.GetRawText(); + var itemsJsonProperty = rootElement.EnumerateObject().FirstOrDefault(x => + x.Name.Equals(nameof(ISelectionStringValueItemSource.Items), StringComparison.OrdinalIgnoreCase)); + + if (itemsJsonProperty.Value.ValueKind == JsonValueKind.Array) + { + var newOptions = JsonSerializerOptionsHelper.Create(options, this); + + var selectionStringValueItem = + JsonSerializer.Deserialize(itemsJsonProperty.Value.GetRawText(), newOptions) ?? + Array.Empty(); - var selectionStringValueItem = - JsonSerializer.Deserialize>(items, newOptions) ?? - new List(); + return new StaticSelectionStringValueItemSource(selectionStringValueItem.As()); + } - return new StaticSelectionStringValueItemSource(selectionStringValueItem.ToArray().As()); + throw new JsonException($"Can't to get the {nameof(ISelectionStringValueItemSource.Items)} property of {nameof(ISelectionStringValueItemSource)}!"); } public override void Write(Utf8JsonWriter writer, ISelectionStringValueItemSource value, JsonSerializerOptions options) diff --git a/modules/feature-management/src/Volo.Abp.FeatureManagement.Application.Contracts/Volo/Abp/FeatureManagement/JsonConverters/StringValueTypeJsonConverter.cs b/modules/feature-management/src/Volo.Abp.FeatureManagement.Application.Contracts/Volo/Abp/FeatureManagement/JsonConverters/StringValueTypeJsonConverter.cs index 08ba6326b9..0d2196e911 100644 --- a/modules/feature-management/src/Volo.Abp.FeatureManagement.Application.Contracts/Volo/Abp/FeatureManagement/JsonConverters/StringValueTypeJsonConverter.cs +++ b/modules/feature-management/src/Volo.Abp.FeatureManagement.Application.Contracts/Volo/Abp/FeatureManagement/JsonConverters/StringValueTypeJsonConverter.cs @@ -11,15 +11,25 @@ namespace Volo.Abp.FeatureManagement.JsonConverters public override IStringValueType Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { var rootElement = JsonDocument.ParseValue(ref reader).RootElement; - var name = rootElement.EnumerateObject().FirstOrDefault(x => x.Name.Equals(nameof(IStringValueType.Name), StringComparison.OrdinalIgnoreCase)).Value.GetString(); - var newOptions = JsonSerializerOptionsHelper.Create(options, this, new ValueValidatorJsonConverter(), new SelectionStringValueItemSourceJsonConverter()); - return name switch + + var nameJsonProperty = rootElement.EnumerateObject().FirstOrDefault(x => x.Name.Equals(nameof(IStringValueType.Name), StringComparison.OrdinalIgnoreCase)); + if (nameJsonProperty.Value.ValueKind == JsonValueKind.String) { - "SelectionStringValueType" => JsonSerializer.Deserialize(rootElement.GetRawText(), newOptions), - "FreeTextStringValueType" => JsonSerializer.Deserialize(rootElement.GetRawText(), newOptions), - "ToggleStringValueType" => JsonSerializer.Deserialize(rootElement.GetRawText(), newOptions), - _ => throw new ArgumentException($"{nameof(IStringValueType)} named {name} was not found!") - }; + var name = nameJsonProperty.Value.GetString(); + + var newOptions = JsonSerializerOptionsHelper.Create(options, this, new ValueValidatorJsonConverter(), + new SelectionStringValueItemSourceJsonConverter()); + + return name switch + { + "SelectionStringValueType" => JsonSerializer.Deserialize(rootElement.GetRawText(), newOptions), + "FreeTextStringValueType" => JsonSerializer.Deserialize(rootElement.GetRawText(), newOptions), + "ToggleStringValueType" => JsonSerializer.Deserialize(rootElement.GetRawText(), newOptions), + _ => throw new ArgumentException($"{nameof(IStringValueType)} named {name} was not found!") + }; + } + + throw new JsonException($"Can't to get the {nameof(IStringValueType.Name)} property of {nameof(IStringValueType)}!"); } public override void Write(Utf8JsonWriter writer, IStringValueType value, JsonSerializerOptions options) 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 98c1013c12..25bb603c1a 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 @@ -13,21 +13,30 @@ namespace Volo.Abp.FeatureManagement.JsonConverters public override IValueValidator Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { var rootElement = JsonDocument.ParseValue(ref reader).RootElement; - var name = rootElement.EnumerateObject().FirstOrDefault(x => x.Name.Equals(nameof(IValueValidator.Name), StringComparison.OrdinalIgnoreCase)).Value.GetString(); - var valueValidator = CreateValueValidatorByName(name); - var propertiesRawText = rootElement.EnumerateObject().FirstOrDefault(x => x.Name.Equals(nameof(IValueValidator.Properties), StringComparison.OrdinalIgnoreCase)).Value.GetRawText(); - var newOptions = JsonSerializerOptionsHelper.Create(options, this, new ObjectToInferredTypesConverter()); - var properties = JsonSerializer.Deserialize>(propertiesRawText, newOptions); - if (properties != null && properties.Any()) + var nameJsonProperty = rootElement.EnumerateObject().FirstOrDefault(x => x.Name.Equals(nameof(IValueValidator.Name), StringComparison.OrdinalIgnoreCase)); + if (nameJsonProperty.Value.ValueKind == JsonValueKind.String) { - foreach (var property in properties) + var valueValidator = CreateValueValidatorByName(nameJsonProperty.Value.GetString()); + + var propertiesJsonProperty = rootElement.EnumerateObject().FirstOrDefault(x => x.Name.Equals(nameof(IValueValidator.Properties), StringComparison.OrdinalIgnoreCase)); + if (propertiesJsonProperty.Value.ValueKind == JsonValueKind.Object) { - valueValidator.Properties[property.Key] = property.Value; + var newOptions = JsonSerializerOptionsHelper.Create(options, this, new ObjectToInferredTypesConverter()); + var properties = JsonSerializer.Deserialize>(propertiesJsonProperty.Value.GetRawText(), newOptions); + if (properties != null && properties.Any()) + { + foreach (var property in properties) + { + valueValidator.Properties[property.Key] = property.Value; + } + } } + + return valueValidator; } - return valueValidator; + throw new JsonException($"Can't to get the {nameof(IValueValidator.Name)} property of {nameof(IValueValidator)}!"); } public override void Write(Utf8JsonWriter writer, IValueValidator value, JsonSerializerOptions options) diff --git a/modules/feature-management/test/Volo.Abp.FeatureManagement.Application.Tests/Volo/Abp/FeatureManagement/StringValueJsonConverter_Tests.cs b/modules/feature-management/test/Volo.Abp.FeatureManagement.Application.Tests/Volo/Abp/FeatureManagement/StringValueJsonConverter_Tests.cs index c72cbddffd..0a96027317 100644 --- a/modules/feature-management/test/Volo.Abp.FeatureManagement.Application.Tests/Volo/Abp/FeatureManagement/StringValueJsonConverter_Tests.cs +++ b/modules/feature-management/test/Volo.Abp.FeatureManagement.Application.Tests/Volo/Abp/FeatureManagement/StringValueJsonConverter_Tests.cs @@ -60,6 +60,14 @@ namespace Volo.Abp.FeatureManagement MinValue = 10 } } + }, + new FeatureDto + { + Provider = new FeatureProviderDto + { + Name = "FeatureName", + Key = "FeatureKey" + } } } } @@ -85,6 +93,9 @@ namespace Volo.Abp.FeatureManagement featureListDto2.Groups[0].Features[2].ValueType.Validator.ShouldBeOfType(); featureListDto2.Groups[0].Features[2].ValueType.Validator.As().MaxValue.ShouldBe(1000); featureListDto2.Groups[0].Features[2].ValueType.Validator.As().MinValue.ShouldBe(10); + + featureListDto2.Groups[0].Features[3].Provider.Name.ShouldBe("FeatureName"); + featureListDto2.Groups[0].Features[3].Provider.Key.ShouldBe("FeatureKey"); } } } From 71bfe2af8d1d051e42f676c43248210616d8813a Mon Sep 17 00:00:00 2001 From: maliming Date: Sun, 15 Nov 2020 17:39:22 +0800 Subject: [PATCH 5/5] Use CultureInfo.CurrentUICulture of DateTime JSON converter. --- .../JsonConverters/AbpDateTimeConverter.cs | 10 +++++----- .../AbpHasExtraPropertiesJsonConverter.cs | 2 +- .../AbpNullableDateTimeConverter.cs | 8 ++++---- ...pSystemTextJsonSerializerProvider_Tests.cs | 20 ++++++++++++++++++- .../JsonConverters/EntityJsonConverter.cs | 2 +- 5 files changed, 30 insertions(+), 12 deletions(-) diff --git a/framework/src/Volo.Abp.Json/Volo/Abp/Json/SystemTextJson/JsonConverters/AbpDateTimeConverter.cs b/framework/src/Volo.Abp.Json/Volo/Abp/Json/SystemTextJson/JsonConverters/AbpDateTimeConverter.cs index e35589830d..0d571aa0e8 100644 --- a/framework/src/Volo.Abp.Json/Volo/Abp/Json/SystemTextJson/JsonConverters/AbpDateTimeConverter.cs +++ b/framework/src/Volo.Abp.Json/Volo/Abp/Json/SystemTextJson/JsonConverters/AbpDateTimeConverter.cs @@ -26,15 +26,15 @@ namespace Volo.Abp.Json.SystemTextJson.JsonConverters if (reader.TokenType == JsonTokenType.String) { var s = reader.GetString(); - if (DateTime.TryParseExact(s, _options.DefaultDateTimeFormat, CultureInfo.InvariantCulture, DateTimeStyles.AdjustToUniversal, out var d1)) + if (DateTime.TryParseExact(s, _options.DefaultDateTimeFormat, CultureInfo.CurrentUICulture, DateTimeStyles.None, out var d1)) { return _clock.Normalize(d1); } - throw new JsonException($"{s} cannot parse to DateTime({_options.DefaultDateTimeFormat})!"); + throw new JsonException($"'{s}' can't parse to DateTime({_options.DefaultDateTimeFormat})!"); } - throw new JsonException("reader TokenType is not String!"); + throw new JsonException("Reader's TokenType is not String!"); } if (reader.TryGetDateTime(out var d2)) @@ -42,7 +42,7 @@ namespace Volo.Abp.Json.SystemTextJson.JsonConverters return _clock.Normalize(d2); } - throw new JsonException("reader can't get datetime!"); + throw new JsonException("Can't get datetime from the reader!"); } public override void Write(Utf8JsonWriter writer, DateTime value, JsonSerializerOptions options) @@ -53,7 +53,7 @@ namespace Volo.Abp.Json.SystemTextJson.JsonConverters } else { - writer.WriteStringValue(_clock.Normalize(value).ToString(_options.DefaultDateTimeFormat)); + writer.WriteStringValue(_clock.Normalize(value).ToString(_options.DefaultDateTimeFormat, CultureInfo.CurrentUICulture)); } } } 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 index 466fee45f3..47640ec306 100644 --- 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 @@ -30,7 +30,7 @@ namespace Volo.Abp.Json.SystemTextJson.JsonConverters return extensibleObject; } - throw new JsonException("RootElement ValueKind is not Object!"); + throw new JsonException("RootElement's ValueKind is not Object!"); } public override void Write(Utf8JsonWriter writer, T value, JsonSerializerOptions options) diff --git a/framework/src/Volo.Abp.Json/Volo/Abp/Json/SystemTextJson/JsonConverters/AbpNullableDateTimeConverter.cs b/framework/src/Volo.Abp.Json/Volo/Abp/Json/SystemTextJson/JsonConverters/AbpNullableDateTimeConverter.cs index 3a603af994..ad9e6cb52a 100644 --- a/framework/src/Volo.Abp.Json/Volo/Abp/Json/SystemTextJson/JsonConverters/AbpNullableDateTimeConverter.cs +++ b/framework/src/Volo.Abp.Json/Volo/Abp/Json/SystemTextJson/JsonConverters/AbpNullableDateTimeConverter.cs @@ -26,15 +26,15 @@ namespace Volo.Abp.Json.SystemTextJson.JsonConverters if (reader.TokenType == JsonTokenType.String) { var s = reader.GetString(); - if (DateTime.TryParseExact(s, _options.DefaultDateTimeFormat, CultureInfo.InvariantCulture, DateTimeStyles.AdjustToUniversal, out var d1)) + if (DateTime.TryParseExact(s, _options.DefaultDateTimeFormat, CultureInfo.CurrentUICulture, DateTimeStyles.None, out var d1)) { return _clock.Normalize(d1); } - throw new JsonException($"{s} cannot parse to DateTime({_options.DefaultDateTimeFormat})!"); + throw new JsonException($"'{s}' can't parse to DateTime({_options.DefaultDateTimeFormat})!"); } - throw new JsonException("reader TokenType is not String!"); + throw new JsonException("Reader's TokenType is not String!"); } if (reader.TryGetDateTime(out var d2)) @@ -59,7 +59,7 @@ namespace Volo.Abp.Json.SystemTextJson.JsonConverters } else { - writer.WriteStringValue(_clock.Normalize(value.Value).ToString(_options.DefaultDateTimeFormat)); + writer.WriteStringValue(_clock.Normalize(value.Value).ToString(_options.DefaultDateTimeFormat, CultureInfo.CurrentUICulture)); } } } 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 06e858c7c0..3f7ce5dc26 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 @@ -152,7 +152,7 @@ namespace Volo.Abp.Json } [Fact] - public void Serialize_Deserialize_ExtensibleObject_With_ExtraProperties_String() + public void Serialize_Deserialize_ExtensibleObject_Without_String() { var json = "{\"name\":\"test\"}"; var extensibleObject = JsonSerializer.Deserialize(json); @@ -160,6 +160,24 @@ namespace Volo.Abp.Json extensibleObject.ExtraProperties.ShouldBeEmpty(); } + [Fact] + public void Serialize_Deserialize_ExtensibleObject_Without_Empty() + { + var json = "{\"name\":\"test\",\"extraProperties\":{}}"; + var extensibleObject = JsonSerializer.Deserialize(json); + extensibleObject.ExtraProperties.ShouldNotBeNull(); + extensibleObject.ExtraProperties.ShouldBeEmpty(); + } + + [Fact] + public void Serialize_Deserialize_ExtensibleObject_Without_Null() + { + var json = "{\"name\":\"test\",\"extraProperties\":null}"; + var extensibleObject = JsonSerializer.Deserialize(json); + extensibleObject.ExtraProperties.ShouldNotBeNull(); + extensibleObject.ExtraProperties.ShouldBeEmpty(); + } + [Fact] public void Serialize_Deserialize_With_Datetime() { 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 1e2c50a448..6d21074e92 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 @@ -27,7 +27,7 @@ namespace Volo.Abp.MemoryDb.JsonConverters return entity; } - throw new JsonException("RootElement ValueKind is not Object!"); + throw new JsonException("RootElement's ValueKind is not Object!"); } public override void Write(Utf8JsonWriter writer, TEntity value, JsonSerializerOptions options)