Mapping flat JSON/Dictionary to model (containing sub classes)

后端 未结 1 703
梦毁少年i
梦毁少年i 2021-01-03 09:38

I want to turn a flat json string into a model, the destination class has subclasses, and the flat json has all of the sub class objects with prefix; like \"{classname}.{pro

相关标签:
1条回答
  • 2021-01-03 10:13

    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.

    0 讨论(0)
提交回复
热议问题