| File: Serialization\JsonStoredDataSerializer.cs | Web Access |
| Project: src\aspnetcore\src\Components\Endpoints\src\Microsoft.AspNetCore.Components.Endpoints.csproj (Microsoft.AspNetCore.Components.Endpoints) |
// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. using System.Diagnostics.CodeAnalysis; using System.Text.Json; using Microsoft.AspNetCore.Internal; namespace Microsoft.AspNetCore.Components.Endpoints; internal sealed class JsonStoredDataSerializer : IStoredDataSerializer { private static readonly JsonSerializerOptions _options = new(JsonSerializerDefaults.Web); private static readonly Type ObjectArrayType = typeof(object[]); private static readonly HashSet<Type> _supportedCollectionDefinitions = [ typeof(List<>), typeof(HashSet<>), typeof(System.Collections.ObjectModel.Collection<>), ]; public bool CanSerialize(Type type) { if (type == ObjectArrayType) { return true; } if (IsSupportedElement(type)) { return true; } if (type.IsSZArray) { return IsSupportedElement(type.GetElementType()!); } if (type.IsGenericType) { var definition = type.GetGenericTypeDefinition(); var arguments = type.GetGenericArguments(); if (definition == typeof(Dictionary<,>)) { return arguments[0] == typeof(string) && IsSupportedElement(arguments[1]); } if (_supportedCollectionDefinitions.Contains(definition)) { return IsSupportedElement(arguments[0]); } } return false; } private static bool IsSupportedElement(Type type) { type = Nullable.GetUnderlyingType(type) ?? type; return type == typeof(int) || type == typeof(bool) || type == typeof(string) || type == typeof(Guid) || type == typeof(DateTime) || (type.IsEnum && type.GetEnumUnderlyingType() == typeof(int)); } public IDictionary<string, object?> DeserializeData(IDictionary<string, JsonElement> data) { var result = new Dictionary<string, object?>(data.Count); foreach (var (key, element) in data) { result[key] = DeserializeEntry(element); } return result; } private object? DeserializeEntry(JsonElement element) { if (element.ValueKind is JsonValueKind.Null) { return null; } var typeName = element.GetProperty("type").GetString()!; var valueElement = element.GetProperty("value"); var type = ResolveType(typeName); if (type == ObjectArrayType) { var array = new object?[valueElement.GetArrayLength()]; var index = 0; foreach (var item in valueElement.EnumerateArray()) { array[index++] = DeserializeEntry(item); } return array; } return JsonSerializer.Deserialize(valueElement, type, _options); } [return: DynamicallyAccessedMembers(LinkerFlags.JsonSerialized)] private Type ResolveType(string typeName) { var type = Type.GetType(typeName, throwOnError: false); if (type is null || !CanSerialize(type)) { throw new InvalidOperationException($"Cannot deserialize type '{typeName}'."); } return type; } private static string GetStorageToken(Type type) { if (ContainsEnum(type)) { return type.AssemblyQualifiedName ?? throw new InvalidOperationException($"Cannot resolve the assembly-qualified name for type '{type}'."); } if (type.IsSZArray) { return GetStorageToken(type.GetElementType()!) + "[]"; } if (type.IsGenericType) { var arguments = Array.ConvertAll(type.GetGenericArguments(), GetStorageToken); return $"{type.GetGenericTypeDefinition().FullName}[{string.Join(',', arguments)}]"; } return type.ToString(); } private static bool ContainsEnum(Type type) { type = Nullable.GetUnderlyingType(type) ?? type; if (type.IsEnum) { return true; } if (type.IsSZArray) { return ContainsEnum(type.GetElementType()!); } return type.IsGenericType && Array.Exists(type.GetGenericArguments(), ContainsEnum); } public byte[] SerializeData(IDictionary<string, object?> data) { using var buffer = new MemoryStream(); using var writer = new Utf8JsonWriter(buffer); writer.WriteStartObject(); foreach (var (key, value) in data) { writer.WritePropertyName(key); WriteEntry(writer, value, value?.GetType()); } writer.WriteEndObject(); writer.Flush(); return buffer.ToArray(); } private void WriteEntry(Utf8JsonWriter writer, object? value, Type? type) { if (value is null) { writer.WriteNullValue(); return; } var valueType = type ?? value.GetType(); if (!CanSerialize(valueType)) { throw new InvalidOperationException($"Cannot serialize type '{valueType}'."); } writer.WriteStartObject(); writer.WriteString("type", GetStorageToken(valueType)); writer.WritePropertyName("value"); if (valueType == ObjectArrayType) { writer.WriteStartArray(); foreach (var item in (object?[])value) { WriteEntry(writer, item, item?.GetType()); } writer.WriteEndArray(); } else { JsonSerializer.Serialize(writer, value, valueType, _options); } writer.WriteEndObject(); } public byte[] SerializeValue(object value, Type type) { using var buffer = new MemoryStream(); using var writer = new Utf8JsonWriter(buffer); WriteEntry(writer, value, type); writer.Flush(); return buffer.ToArray(); } public object? DeserializeValue(ReadOnlySpan<byte> utf8Json, [DynamicallyAccessedMembers(LinkerFlags.JsonSerialized)] Type targetType) { var element = JsonSerializer.Deserialize<JsonElement>(utf8Json, _options); if (element.ValueKind is JsonValueKind.Null) { return null; } if (targetType != typeof(object) && targetType != ObjectArrayType && CanSerialize(targetType)) { var valueElement = element.GetProperty("value"); return JsonSerializer.Deserialize(valueElement, targetType, _options); } return DeserializeEntry(element); } }