private/runtime/Serialization/JsonSerializer.cs
using System; using System.Collections; using System.Collections.Generic; namespace Carbon.Json { public class JsonSerializer { private int depth = 0; private SerializationOptions options = new SerializationOptions(); #region Deserialization public T Deseralize<T>(JsonObject json) where T : new() { var contract = JsonModelCache.Get(typeof(T)); return (T)DeserializeObject(contract, json); } internal object DeserializeObject(JsonModel contract, JsonObject json) { var instance = Activator.CreateInstance(contract.Type); depth++; // Ensure we don't recurse forever if (depth > 5) throw new Exception("Depth greater than 5"); foreach (var field in json) { var member = contract[field.Key]; if (member != null) { var value = DeserializeValue(member, field.Value); member.SetValue(instance, value); } } depth--; return instance; } private object DeserializeValue(JsonMember member, JsonNode value) { if (value.Type == JsonType.Null) return null; var type = member.Type; if (member.IsStringLike && value.Type != JsonType.String) { // Take the long path... return DeserializeObject(JsonModelCache.Get(type), (JsonObject)value); } else if (member.Converter != null) { return member.Converter.FromJson(value); } else if (type.IsArray) { return DeserializeArray(type, (JsonArray)value); } else if (member.IsList) { return DeserializeList(type, (JsonArray)value); } else { var contract = JsonModelCache.Get(type); return DeserializeObject(contract, (JsonObject)value); } } private object DeserializeValue(Type type, JsonNode value) { if (type == null) throw new ArgumentNullException(nameof(type)); if (value.Type == JsonType.Null) return null; var typeDetails = TypeDetails.Get(type); if (typeDetails.JsonConverter != null) { return typeDetails.JsonConverter.FromJson(value); } else if (typeDetails.IsEnum) { return Enum.Parse(type, value.ToString(), ignoreCase: true); } else if (type.IsArray) { return DeserializeArray(type, (JsonArray)value); } else if (typeDetails.IsList) { return DeserializeList(type, (JsonArray)value); } else { var contract = JsonModelCache.Get(type); return DeserializeObject(contract, (JsonObject)value); } } public Array DeserializeArray(Type type, JsonArray elements) { var elementType = type.GetElementType(); var elementTypeDetails = TypeDetails.Get(elementType); var array = Array.CreateInstance(elementType, elements.Count); int i = 0; if (elementTypeDetails.JsonConverter != null) { foreach (var value in elements) { array.SetValue(elementTypeDetails.JsonConverter.FromJson(value), i); i++; } } else { foreach (var value in elements) { array.SetValue(DeserializeValue(elementType, value), i); i++; } } return array; } public IList DeserializeList(Type type, JsonArray jsonArray) { // TODO: Handle non-generic types if (!type.IsGenericType) throw new ArgumentException("Must be a generic type", nameof(type)); var elementType = type.GetGenericArguments()[0]; IList list; if (type.IsInterface) { // Create a concrete generic list list = (IList)Activator.CreateInstance(typeof(List<>).MakeGenericType(elementType)); } else { list = (IList)Activator.CreateInstance(type); } foreach (var value in jsonArray) { list.Add(DeserializeValue(elementType, value)); } return list; } #endregion #region Serialization public JsonNode Serialize(object instance) => Serialize(instance, SerializationOptions.Default); public JsonNode Serialize(object instance, string[] include) => Serialize(instance, new SerializationOptions { Include = include }); public JsonNode Serialize(object instance, SerializationOptions options) { this.options = options; if (instance == null) { return XNull.Instance; } return ReadValue(instance.GetType(), instance); } #region Readers public JsonArray ReadArray(IEnumerable collection) { var array = new XNodeArray(); foreach (var item in collection) { array.Add(ReadValue(item.GetType(), item)); } return array; } public IEnumerable<KeyValuePair<string, JsonNode>> ReadProperties(object instance) { var contract = JsonModelCache.Get(instance.GetType()); foreach (var member in contract.Members) { string name = member.Name; if (options.PropertyNameTransformer != null) { name = options.PropertyNameTransformer.Invoke(name); } // Skip the field if it's not included if ((depth == 1 && !options.IsIncluded(name))) { continue; } var value = member.GetValue(instance); if (!member.EmitDefaultValue && (value == null || (member.IsList && ((IList)value).Count == 0) || value.Equals(member.DefaultValue))) { continue; } else if (options.IgnoreNullValues && value == null) // Ignore null values { continue; } // Transform the value if there is one if (options.Transformations != null) { var transform = options.GetTransformation(name); if (transform != null) { value = transform.Transformer(value); } } yield return new KeyValuePair<string, JsonNode>(name, ReadValue(member.TypeDetails, value)); } } private JsonObject ReadObject(object instance) { depth++; // TODO: Guard against a self referencing graph if (depth > options.MaxDepth) { depth--; return new JsonObject(); } var node = new JsonObject(ReadProperties(instance)); depth--; return node; } private JsonNode ReadValue(Type type, object value) { if (value == null) { return XNull.Instance; } var member = TypeDetails.Get(type); return ReadValue(member, value); } private JsonNode ReadValue(TypeDetails type, object value) { if (value == null) { return XNull.Instance; } if (type.JsonConverter != null) { return type.JsonConverter.ToJson(value); } else if (type.IsArray) { switch (Type.GetTypeCode(type.ElementType)) { case TypeCode.String: return CreateArray((string[])value); case TypeCode.UInt16: return CreateArray((ushort[])value); case TypeCode.UInt32: return CreateArray((uint[])value); case TypeCode.UInt64: return CreateArray((ulong[])value); case TypeCode.Int16: return CreateArray((short[])value); case TypeCode.Int32: return CreateArray((int[])value); case TypeCode.Int64: return CreateArray((long[])value); case TypeCode.Single: return CreateArray((float[])value); case TypeCode.Double: return CreateArray((double[])value); default: return ReadArray((IEnumerable)value); } } else if (value is IEnumerable) { if (type.IsList && type.ElementType != null) { switch (Type.GetTypeCode(type.ElementType)) { case TypeCode.String: return CreateList<string>(value); case TypeCode.UInt16: return CreateList<ushort>(value); case TypeCode.UInt32: return CreateList<uint>(value); case TypeCode.UInt64: return CreateList<ulong>(value); case TypeCode.Int16: return CreateList<short>(value); case TypeCode.Int32: return CreateList<int>(value); case TypeCode.Int64: return CreateList<long>(value); case TypeCode.Single: return CreateList<float>(value); case TypeCode.Double: return CreateList<double>(value); } } return ReadArray((IEnumerable)value); } else { // Complex object return ReadObject(value); } } private XList<T> CreateList<T>(object value) => new XList<T>((IList<T>)value); private XImmutableArray<T> CreateArray<T>(T[] array) => new XImmutableArray<T>(array); #endregion #endregion } } |