using System.Collections; using System.Reflection; using System.Text.Json.Nodes; using System.Text.Json.Serialization; namespace Connected.Interop.Merging { internal sealed class JsonMerger : Merger { public void Merge(object destination, JsonNode? source) { if (source is null || destination is null) return; foreach (var property in Properties.GetImplementedProperties(destination)) MergeProperty(destination, source, property); } private void MergeProperty(object destination, JsonNode source, PropertyInfo property) { if (property.FindAttribute() is not null) return; if (property.PropertyType.IsTypePrimitive()) { if (!property.CanWrite || source is not JsonObject jo || ResolveJsonProperty(property, jo) is not JsonValue jprop) return; if (!TypeConversion.TryConvert(jprop.ToString(), out object? convertedValue, property.PropertyType)) return; property.SetValue(destination, convertedValue); } else if (IsArray(property)) MergeEnumerable(destination, source, property); else MergeObject(destination, source, property); } private static JsonValue? ResolveJsonProperty(PropertyInfo property, JsonObject json) { foreach (var prop in json) { if (string.Equals(prop.Key, property.Name, StringComparison.OrdinalIgnoreCase)) return prop.Value as JsonValue; } return null; } private void MergeEnumerable(object destination, JsonNode source, PropertyInfo property) { if (source is not JsonObject jobject) return; if (!jobject.ContainsKey(property.Name) || jobject[property.Name] is not JsonArray array || !array.Any()) return; var value = property.GetValue(destination); if (value is null && !property.CanWrite) return; var addMethod = property.PropertyType.GetMethod(nameof(IList.Add), BindingFlags.Public | BindingFlags.Instance | BindingFlags.NonPublic); var instance = addMethod is not null ? Activator.CreateInstance(property.PropertyType) : Array.CreateInstance(property.PropertyType, array.Count); var elementType = instance.GetType().GetElementType(); for (var i = 0; i < array.Count; i++) { var json = array[i]; var item = Activator.CreateInstance(elementType); Merge(item, json); if (addMethod is not null) addMethod.Invoke(instance, new object[] { item }); else ((Array)instance).SetValue(item, i); } property.SetValue(destination, instance); } private void MergeObject(object destination, JsonNode source, PropertyInfo property) { var value = property.GetValue(destination); if (value is null && property.CanWrite) property.SetValue(destination, property.PropertyType.CreateInstance()); value = property.GetValue(destination); if (value is null) return; foreach (var instanceProperty in Properties.GetImplementedProperties(value)) MergeProperty(value, source, instanceProperty); } } }