Deserialize two values into the same property

后端 未结 2 1820
被撕碎了的回忆
被撕碎了的回忆 2021-01-25 08:55

I have a client which can call two different versions of a service.

One service only sends a single value:

{
  \"value\" : { ... }
}

T

相关标签:
2条回答
  • 2021-01-25 09:30

    To make a custom JsonConverter that has special processing for a few properties of a type but uses default processing for the remainder, you can load the JSON into a JObject, detach and process the custom properties, then populate the remainder from the JObject with JsonSerializer.Populate(), like so:

    class MyValuesConverter : CustomPropertyConverterBase<MyValues>
    {
        protected override void ProcessCustomProperties(JObject obj, MyValues value, JsonSerializer serializer)
        {
            // Remove the value property for manual deserialization, and deserialize
            var jValue = obj.GetValue("value", StringComparison.OrdinalIgnoreCase).RemoveFromLowestPossibleParent();
            if (jValue != null)
            {
                (value.Values = value.Values ?? new List<Stuff>()).Clear();
                value.Values.Add(jValue.ToObject<Stuff>(serializer));
            }
        }
    }
    
    public abstract class CustomPropertyConverterBase<T> : JsonConverter where T : class
    {
        public override bool CanConvert(Type objectType)
        {
            return typeof(T).IsAssignableFrom(objectType);
        }
    
        public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
        {
            if (reader.TokenType == JsonToken.Null)
                return null;
            var jObj = JObject.Load(reader);
            var contract = (JsonObjectContract)serializer.ContractResolver.ResolveContract(objectType);
            var value = existingValue as T ?? (T)contract.DefaultCreator();
    
            ProcessCustomProperties(jObj, value, serializer);
    
            // Populate the remaining properties.
            using (var subReader = jObj.CreateReader())
            {
                serializer.Populate(subReader, value);
            }
    
            return value;
        }
    
        protected abstract void ProcessCustomProperties(JObject obj, T value, JsonSerializer serializer);
    
        public override bool CanWrite { get { return false; } }
    
        public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
        {
            throw new NotImplementedException();
        }
    }
    
    public static class JsonExtensions
    {
        public static JToken RemoveFromLowestPossibleParent(this JToken node)
        {
            if (node == null)
                return null;
            var contained = node.AncestorsAndSelf().Where(t => t.Parent is JContainer && t.Parent.Type != JTokenType.Property).FirstOrDefault();
            if (contained != null)
                contained.Remove();
            // Also detach the node from its immediate containing property -- Remove() does not do this even though it seems like it should
            if (node.Parent is JProperty)
                ((JProperty)node.Parent).Value = null;
            return node;
        }
    }
    
    0 讨论(0)
  • 2021-01-25 09:31

    Rather than writing a JsonConverter, you could make a set-only property Value on your MyValues, like so:

    public class MyValues
    {
        [JsonProperty]
        Stuff Value
        {
            set
            {
                (Values = Values ?? new List<Stuff>(1)).Clear();
                Values.Add(value);
            }
        }
    
        public List<Stuff> Values { get; set; }
        public Thing Other { get; set; }
    }
    

    It could be public or private if marked with [JsonProperty]. In this case Json.NET will call the Value setter if the singleton "value" property is encountered in the JSON, and call the Values setter if the array "values" property is encountered. Since the property is set-only only the array property will be re-serialized.

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