From 555558dfe8b10383db93e18870dc65e7ee24bd98 Mon Sep 17 00:00:00 2001 From: Sipke Schoorstra Date: Fri, 8 Mar 2024 14:27:32 +0100 Subject: [PATCH] Add Unicode encoding support to serializers (#5049) The update enriches the serializers with Unicode encoding to handle a wider range of characters. Classes such as ConfigurableSerializer, ObjectConverter, and JsonContentFactory have been modified to use JavaScriptEncoder with UnicodeRanges.All, supporting serialization and deserialization of all Unicode characters. Furthermore, any unreferenced code now carries a note for JSON serializer stating that it's incompatible with .NET's trimming feature. --- .../Serialization/ConfigurableSerializer.cs | 5 ++- .../Helpers/ObjectConverter.cs | 34 +++++++++++++------ .../ContentWriters/JsonContentFactory.cs | 4 +++ 3 files changed, 31 insertions(+), 12 deletions(-) diff --git a/src/modules/Elsa.Common/Serialization/ConfigurableSerializer.cs b/src/modules/Elsa.Common/Serialization/ConfigurableSerializer.cs index 37c622efd..2fcad8c68 100644 --- a/src/modules/Elsa.Common/Serialization/ConfigurableSerializer.cs +++ b/src/modules/Elsa.Common/Serialization/ConfigurableSerializer.cs @@ -1,6 +1,8 @@ +using System.Text.Encodings.Web; using System.Text.Json; using System.Text.Json.Serialization; using System.Text.Json.Serialization.Metadata; +using System.Text.Unicode; using Elsa.Common.Contracts; using Microsoft.Extensions.DependencyInjection; @@ -53,7 +55,8 @@ public abstract class ConfigurableSerializer { PropertyNamingPolicy = JsonNamingPolicy.CamelCase, PropertyNameCaseInsensitive = true, - DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull + DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull, + Encoder = JavaScriptEncoder.Create(UnicodeRanges.All) }; options.Converters.Add(new JsonStringEnumConverter()); diff --git a/src/modules/Elsa.Expressions/Helpers/ObjectConverter.cs b/src/modules/Elsa.Expressions/Helpers/ObjectConverter.cs index 677f09ac0..ba569ef1d 100644 --- a/src/modules/Elsa.Expressions/Helpers/ObjectConverter.cs +++ b/src/modules/Elsa.Expressions/Helpers/ObjectConverter.cs @@ -1,10 +1,13 @@ using System.Collections; using System.ComponentModel; +using System.Diagnostics.CodeAnalysis; using System.Dynamic; using System.Globalization; +using System.Text.Encodings.Web; using System.Text.Json; using System.Text.Json.Nodes; using System.Text.Json.Serialization; +using System.Text.Unicode; using Elsa.Expressions.Contracts; using Elsa.Expressions.Exceptions; using Elsa.Expressions.Extensions; @@ -31,6 +34,7 @@ public static class ObjectConverter /// /// Attempts to convert the source value into the destination type. /// + [RequiresUnreferencedCode("The JsonSerializer type is not trim-compatible.")] public static Result TryConvertTo(this object? value, Type targetType, ObjectConverterOptions? serializerOptions = null) { try @@ -47,11 +51,13 @@ public static class ObjectConverter /// /// Attempts to convert the source value into the destination type. /// + [RequiresUnreferencedCode("The JsonSerializer type is not trim-compatible.")] public static T? ConvertTo(this object? value, ObjectConverterOptions? serializerOptions = null) => value != null ? (T?)value.ConvertTo(typeof(T), serializerOptions) : default; /// /// Attempts to convert the source value into the destination type. /// + [RequiresUnreferencedCode("The JsonSerializer type is not trim-compatible.")] public static object? ConvertTo(this object? value, Type targetType, ObjectConverterOptions? converterOptions = null) { if (value == null) @@ -62,11 +68,17 @@ public static class ObjectConverter if (sourceType == targetType) return value; - var options = converterOptions?.SerializerOptions != null ? new JsonSerializerOptions(converterOptions.SerializerOptions) : new JsonSerializerOptions(); - options.PropertyNamingPolicy = JsonNamingPolicy.CamelCase; - options.ReferenceHandler = ReferenceHandler.Preserve; - options.PropertyNameCaseInsensitive = true; - options.Converters.Add(new JsonStringEnumConverter()); + var serializerOptions = converterOptions?.SerializerOptions != null ? new JsonSerializerOptions(converterOptions.SerializerOptions) : new JsonSerializerOptions(); + serializerOptions.PropertyNamingPolicy = JsonNamingPolicy.CamelCase; + serializerOptions.ReferenceHandler = ReferenceHandler.Preserve; + serializerOptions.PropertyNameCaseInsensitive = true; + serializerOptions.Converters.Add(new JsonStringEnumConverter()); + serializerOptions.Encoder = JavaScriptEncoder.Create(UnicodeRanges.All); + + var internalSerializerOptions = new JsonSerializerOptions + { + Encoder = JavaScriptEncoder.Create(UnicodeRanges.All), + }; var underlyingTargetType = Nullable.GetUnderlyingType(targetType) ?? targetType; var underlyingSourceType = Nullable.GetUnderlyingType(sourceType) ?? sourceType; @@ -79,7 +91,7 @@ public static class ObjectConverter if (jsonElement.ValueKind == JsonValueKind.String && underlyingTargetType != typeof(string)) return jsonElement.GetString().ConvertTo(underlyingTargetType); - return jsonElement.Deserialize(targetType, options); + return jsonElement.Deserialize(targetType, serializerOptions); } if (value is JsonObject jsonObject) @@ -87,7 +99,7 @@ public static class ObjectConverter return underlyingTargetType switch { { } t when t == typeof(string) => jsonObject.ToString(), - { } t when t != typeof(object) => jsonObject.Deserialize(targetType, options), + { } t when t != typeof(object) => jsonObject.Deserialize(targetType, serializerOptions), _ => jsonObject, }; } @@ -101,7 +113,7 @@ public static class ObjectConverter var firstChar = stringValue.TrimStart().FirstOrDefault(); if (firstChar is '{' or '[') - return JsonSerializer.Deserialize(stringValue, underlyingTargetType, options); + return JsonSerializer.Deserialize(stringValue, underlyingTargetType, serializerOptions); } catch (Exception e) { @@ -125,7 +137,7 @@ public static class ObjectConverter { if (typeof(ExpandoObject) == underlyingTargetType) { - var expandoJson = JsonSerializer.Serialize(value); + var expandoJson = JsonSerializer.Serialize(value, internalSerializerOptions); return ConvertTo(expandoJson, underlyingTargetType, converterOptions); } @@ -133,13 +145,13 @@ public static class ObjectConverter return new Dictionary((IDictionary)value); var sourceDictionary = (IDictionary)value; - var json = JsonSerializer.Serialize(sourceDictionary); + var json = JsonSerializer.Serialize(sourceDictionary, internalSerializerOptions); return ConvertTo(json, underlyingTargetType, converterOptions); } if (typeof(IEnumerable).IsAssignableFrom(underlyingSourceType)) if (underlyingTargetType == typeof(string)) - return JsonSerializer.Serialize(value); + return JsonSerializer.Serialize(value, internalSerializerOptions); var targetTypeConverter = TypeDescriptor.GetConverter(underlyingTargetType); diff --git a/src/modules/Elsa.Http/ContentWriters/JsonContentFactory.cs b/src/modules/Elsa.Http/ContentWriters/JsonContentFactory.cs index d9e57f7ed..ee0e64715 100644 --- a/src/modules/Elsa.Http/ContentWriters/JsonContentFactory.cs +++ b/src/modules/Elsa.Http/ContentWriters/JsonContentFactory.cs @@ -1,6 +1,9 @@ +using System.Diagnostics.CodeAnalysis; using System.Net.Mime; using System.Text; +using System.Text.Encodings.Web; using System.Text.Json; +using System.Text.Unicode; namespace Elsa.Http.ContentWriters; @@ -13,6 +16,7 @@ public class JsonContentFactory : IHttpContentFactory public IEnumerable SupportedContentTypes => new[] { MediaTypeNames.Application.Json, "text/json" }; /// + [RequiresUnreferencedCode("The JsonSerializer type is not trim-compatible.")] public HttpContent CreateHttpContent(object content, string contentType) { var text = content as string ?? JsonSerializer.Serialize(content);