Deserialize JSON to multiple properties

后端 未结 3 2102
刺人心
刺人心 2020-12-06 06:00

I am programming against a third party API which returns JSON data, but the format can be a little strange. Certain properties can either be an object (which contains an Id

相关标签:
3条回答
  • 2020-12-06 06:14

    Rather than creating two separate converters for each of the fields, it would be wise to create a single converter for the "main" property and link the other one to it. ChildObjectId is derived from the ChildObject.

    public class MyObject
    {
        [JsonIgnore]
        public string ChildObjectId
        {
            get { return ChildObject.Id; }
    
            // I would advise against having a setter here
            // you should only allow changes through the object only
            set { ChildObject.Id = value; }
        }
    
        [JsonConverter(typeof(MyObjectChildObjectConverter))]
        public ChildObject ChildObject { get; set; }
    }
    

    Now to convert the ChildObject can be a bit of a challenge. There are two possible representations of the object: a string or an object. Determine what representation you have and perform the conversion.

    public class MyObjectChildObjectConverter : JsonConverter
    {
        public override bool CanConvert(Type objectType)
        {
            return objectType == typeof(ChildObject);
        }
    
        public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
        {
            serializer.Serialize(writer, value);
        }
    
        public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
        {
            var obj = serializer.Deserialize<JToken>(reader);
            switch (obj.Type)
            {
            case JTokenType.Object:
                return ReadAsObject(obj as JObject);
            case JTokenType.String:
                return ReadAsString((string)(JValue)obj);
            default:
                throw new JsonSerializationException("Unexpected token type");
            }
        }
    
        private object ReadAsObject(JObject obj)
        {
            return obj.ToObject<ChildObject>();
        }
    
        private object ReadAsString(string str)
        {
            // do a lookup for the actual object or whatever here
            return new ChildObject
            {
                Id = str,
            };
        }
    }
    
    0 讨论(0)
  • 2020-12-06 06:22

    Try this (extend it with some thorough validation if you'll be using it in your code):

    public class MyObject
    {
        public ChildObject MyChildObject;
        public string MyChildObjectId;
    
        [JsonProperty("ChildObject")]
        public object ChildObject
        {
            get
            {
                return MyChildObject;
            }
            set
            {
                if (value is JObject)
                {
                    MyChildObject = ((JToken)value).ToObject<ChildObject>();
                    MyChildObjectId = MyChildObject.Id;
                }
                else
                {
                    MyChildObjectId = value.ToString();
                    MyChildObject = null;
                }
            }
        }
    }
    
    0 讨论(0)
  • 2020-12-06 06:40

    Here is what I would do in this situation.

    • Only have a single property in your parent class for the child object, and make it of type ChildObject
    • Create a custom JsonConverter which can inspect the JSON and either:
      • deserialize the full instance of the child object if the data is present, or
      • create a new instance of the child object and set its ID, leaving all other properties blank. (Or you could do as Jeff Mercado suggested and have the converter load the object from a database based on the ID, if that applies to your situation.)
    • Optionally, put a property on the child object indicating whether it is fully populated. The converter can set this property during deserialization.

    After deserialization, if there was a ChildObject property in the JSON (with either an ID or a full object value), you are guaranteed to have a ChildObject instance and you can get its ID from it; otherwise, if there was no ChildObject property in the JSON, the ChildObject property in the parent class will be null.

    Below is a full working example to demonstrate. In this example, I modified the parent class to include three separate instances of the ChildObject to show the different possibilities in the JSON (string ID only, full object and neither present). They all use the same converter. I also added a Name property and an IsFullyPopulated property to the ChildObject class.

    Here are the DTO classes:

    public abstract class BaseEntity
    {
        public string Id { get; set; }
    }
    
    public class ChildObject : BaseEntity 
    {
        public string Name { get; set; }
        public bool IsFullyPopulated { get; set; }
    }
    
    public class MyObject
    {
        [JsonProperty("ChildObject1")]
        [JsonConverter(typeof(MyCustomObjectConverter))]
        public ChildObject ChildObject1 { get; set; }
    
        [JsonProperty("ChildObject2")]
        [JsonConverter(typeof(MyCustomObjectConverter))]
        public ChildObject ChildObject2 { get; set; }
    
        [JsonProperty("ChildObject3")]
        [JsonConverter(typeof(MyCustomObjectConverter))]
        public ChildObject ChildObject3 { get; set; }
    }
    

    Here is the converter:

    class MyCustomObjectConverter : JsonConverter
    {
        public override bool CanConvert(Type objectType)
        {
            return (objectType == typeof(ChildObject));
        }
    
        public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
        {
            JToken token = JToken.Load(reader);
            ChildObject child = null;
            if (token.Type == JTokenType.String)
            {
                child = new ChildObject();
                child.Id = token.ToString();
                child.IsFullyPopulated = false;
            }
            else if (token.Type == JTokenType.Object)
            {
                child = token.ToObject<ChildObject>();
                child.IsFullyPopulated = true;
            }
            else if (token.Type != JTokenType.Null)
            {
                throw new JsonSerializationException("Unexpected token: " + token.Type);
            }
            return child;
        }
    
        public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
        {
            serializer.Serialize(writer, value);
        }
    }
    

    Here is the test program to demonstrate the operation of the converter:

    class Program
    {
        static void Main(string[] args)
        {
            string json = @"
            {
                ""ChildObject1"": 
                {
                    ""Id"": ""key1"",
                    ""Name"": ""Foo Bar Baz""
                },
                ""ChildObject2"": ""key2""
            }";
    
            MyObject obj = JsonConvert.DeserializeObject<MyObject>(json);
    
            DumpChildObject("ChildObject1", obj.ChildObject1);
            DumpChildObject("ChildObject2", obj.ChildObject2);
            DumpChildObject("ChildObject3", obj.ChildObject3);
        }
    
        static void DumpChildObject(string prop, ChildObject obj)
        {
            Console.WriteLine(prop);
            if (obj != null)
            {
                Console.WriteLine("   Id: " + obj.Id);
                Console.WriteLine("   Name: " + obj.Name);
                Console.WriteLine("   IsFullyPopulated: " + obj.IsFullyPopulated);
            }
            else
            {
                Console.WriteLine("   (null)");
            }
            Console.WriteLine();
        }
    }
    

    And here is the output of the above:

    ChildObject1
       Id: key1
       Name: Foo Bar Baz
       IsFullyPopulated: True
    
    ChildObject2
       Id: key2
       Name:
       IsFullyPopulated: False
    
    ChildObject3
       (null)
    
    0 讨论(0)
提交回复
热议问题