You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
Connected.Framework/Connected.Interop/Serializer.cs

215 lines
5.2 KiB

using System.Collections;
using System.Reflection;
using System.Text;
using System.Text.Json;
using System.Text.Json.Nodes;
using Connected.Interop.Merging;
namespace Connected.Interop;
public static class Serializer
{
private static readonly JsonSerializerOptions _options;
static Serializer()
{
_options = new JsonSerializerOptions
{
AllowTrailingCommas = true,
IncludeFields = false,
IgnoreReadOnlyFields = false,
IgnoreReadOnlyProperties = true,
PropertyNameCaseInsensitive = true,
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
WriteIndented = true
};
}
internal static JsonSerializerOptions SerializerOptions => _options;
public static async Task<T?> Deserialize<T>(string value)
{
using var ms = new MemoryStream(Encoding.UTF8.GetBytes(value));
ms.Seek(0, SeekOrigin.Begin);
return await JsonSerializer.DeserializeAsync<T>(ms, _options);
}
public static async Task<string?> Serialize(object value)
{
using var ms = new MemoryStream();
using var writer = new Utf8JsonWriter(ms, new JsonWriterOptions { Indented = true, SkipValidation = false });
if (value is JsonNode json)
json.WriteTo(writer);
else
{
if (value.GetType().IsEnumerable())
SerializeArray(writer, null, value);
else if (value.GetType().IsTypePrimitive())
SerializePrimitive(writer, value);
else
SerializeObject(writer, null, value);
}
await writer.FlushAsync();
ms.Seek(0, SeekOrigin.Begin);
return Encoding.UTF8.GetString(ms.ToArray());
}
private static void SerializeObject(Utf8JsonWriter writer, PropertyInfo property, object value)
{
if (value is null)
return;
var properties = value.GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance);
if (property is not null)
writer.WriteStartObject(property.Name.ToCamelCase());
else
writer.WriteStartObject();
foreach (var p in properties)
SerializeProperty(writer, p, value);
writer.WriteEndObject();
}
private static void SerializeArray(Utf8JsonWriter writer, PropertyInfo property, object value)
{
if (value is null)
return;
var enumerable = property is not null ? property.GetValue(value) as IEnumerable : value as IEnumerable;
if (enumerable is null)
return;
if (property is not null)
writer.WriteStartArray(property.Name.ToCamelCase());
else
writer.WriteStartArray();
var enumerator = enumerable.GetEnumerator();
while (enumerator.MoveNext())
SerializeObject(writer, null, enumerator.Current);
writer.WriteEndArray();
}
private static void SerializeProperty(Utf8JsonWriter writer, PropertyInfo property, object value)
{
if (property.PropertyType.IsEnumerable())
SerializeArray(writer, property, value);
else if (!property.PropertyType.IsTypePrimitive())
SerializeObject(writer, property, value);
else
{
writer.WritePropertyName(property.Name.ToCamelCase());
SerializePrimitive(writer, property.GetValue(value));
}
}
private static void SerializePrimitive(Utf8JsonWriter writer, object? value)
{
if (value is null)
{
writer.WriteNullValue();
return;
}
if (value.GetType().IsNumber())
SerializeNumber(writer, value);
else if (value is string)
writer.WriteStringValue(value.ToString());
else if (value is DateTime date)
writer.WriteStringValue(date);
else if (value is DateTimeOffset offset)
writer.WriteStringValue(offset);
else if (value is Guid guid)
writer.WriteStringValue(guid);
else if (value is bool @bool)
writer.WriteBooleanValue(@bool);
else if (value is Enum en)
{
var type = Enum.GetUnderlyingType(en.GetType());
if (TypeConversion.TryConvert(value, out object? enumNumber, type))
SerializeNumber(writer, enumNumber);
}
}
private static void SerializeNumber(Utf8JsonWriter writer, object? value)
{
if (value is decimal @decimal)
writer.WriteNumberValue(@decimal);
else if (value is double @double)
writer.WriteNumberValue(@double);
else if (value is float @float)
writer.WriteNumberValue(@float);
else if (value is int @int)
writer.WriteNumberValue(@int);
else if (value is long @long)
writer.WriteNumberValue(@long);
else if (value is uint @uint)
writer.WriteNumberValue(@uint);
else if (value is ulong @ulong)
writer.WriteNumberValue(@ulong);
else if (value is byte @byte)
writer.WriteNumberValue(@byte);
else if (value is sbyte @sbyte)
writer.WriteNumberValue(@sbyte);
else if (value is short @short)
writer.WriteNumberValue(@short);
else if (value is ushort @ushort)
writer.WriteNumberValue(@ushort);
}
public static T Merge<T>(T destination, params object[] sources)
{
if (destination is null)
return default;
if (sources is null || !sources.Any())
return destination;
var hasJson = false;
foreach (var source in sources)
{
if (source is JsonNode)
{
hasJson = true;
break;
}
}
if (!hasJson)
new ObjectMerger().Merge(destination, sources);
else
{
foreach (var source in sources)
{
if (source is JsonNode node)
new JsonMerger().Merge(destination, node);
else
new ObjectMerger().Merge(destination, source);
}
}
return destination;
}
public static T Merge<T>(T destination, string value)
{
var node = Deserialize<JsonNode>(value);
return Merge(destination, node);
}
}