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.
206 lines
5.0 KiB
206 lines
5.0 KiB
using Connected.Interop.Merging;
|
|
using System.Collections;
|
|
using System.Reflection;
|
|
using System.Text;
|
|
using System.Text.Json;
|
|
using System.Text.Json.Nodes;
|
|
|
|
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.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;
|
|
|
|
if (property is not null)
|
|
writer.WriteStartArray(property.Name.ToCamelCase());
|
|
else
|
|
writer.WriteStartArray();
|
|
|
|
if (value is IEnumerable enumerable)
|
|
{
|
|
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);
|
|
|
|
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);
|
|
}
|
|
}
|