Mapping flat JSON/Dictionary to model (containing sub classes)
You can make a JsonConverter
that does this in a generic way, using a ContractResolver
to group and populate properties in the class being deserialized or its contained classes as appropriate.
You didn't ask for serialization, only deserialization, so that's what this does:
public class JsonFlatteningConverter : JsonConverter{ readonly IContractResolver resolver; public JsonFlatteningConverter(IContractResolver resolver) { if (resolver == null) throw new ArgumentNullException(); this.resolver = resolver; } public override bool CanConvert(Type objectType) { return resolver.ResolveContract(objectType) is JsonObjectContract; } public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) { if (reader.TokenType == JsonToken.Null) return null; JObject jObject = JObject.Load(reader); var contract = (JsonObjectContract)resolver.ResolveContract(objectType); // Throw an InvalidCastException if this object does not map to a JObject. existingValue = existingValue ?? contract.DefaultCreator(); if (jObject.Count == 0) return existingValue; var groups = jObject.Properties().GroupBy(p => p.Name.Contains('.') ? p.Name.Split('.').FirstOrDefault() : null).ToArray(); foreach (var group in groups) { if (string.IsNullOrEmpty(group.Key)) { var subObj = new JObject(group); using (var subReader = subObj.CreateReader()) serializer.Populate(subReader, existingValue); } else { var jsonProperty = contract.Properties[group.Key]; if (jsonProperty == null || !jsonProperty.Writable) continue; if (jsonProperty != null) { var subObj = new JObject(group.Select(p => new JProperty(p.Name.Substring(group.Key.Length + 1), p.Value))); using (var subReader = subObj.CreateReader()) { var propertyValue = serializer.Deserialize(subReader, jsonProperty.PropertyType); jsonProperty.ValueProvider.SetValue(existingValue, propertyValue); } } } } return existingValue; } public override bool CanWrite { get { return false; } } public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) { throw new NotImplementedException(); }}
And then use it thusly:
var resolver = new DefaultContractResolver(); var settings = new JsonSerializerSettings { ContractResolver = resolver, Converters = new JsonConverter[] { new JsonFlatteningConverter(resolver) } }; var person = JsonConvert.DeserializeObject<Person>(json, settings);
Prototype fiddle.