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 Deserialize(string value) { using var ms = new MemoryStream(Encoding.UTF8.GetBytes(value)); ms.Seek(0, SeekOrigin.Begin); return await JsonSerializer.DeserializeAsync(ms, _options); } public static async Task 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 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 destination, string value) { var node = Deserialize(value); return Merge(destination, node); } }