【问题标题】:Deserialize JSON to multiple properties将 JSON 反序列化为多个属性
【发布时间】:2014-01-24 07:20:51
【问题描述】:

我正在针对返回 JSON 数据的第三方 API 进行编程,但格式可能有点奇怪。某些属性可以是对象(包含 Id 属性),也可以是字符串(即对象的 Id)。例如,以下两个都有效:

{
    ChildObject: 'childobjectkey1'
}

和

{
    ChildObject: {
        Id: 'childobjectkey1',
        // (other properties)
    }
}

我正在尝试使用 JSON.net 将其反序列化为强类型类,但到目前为止还没有多少运气。我最好的想法是将它序列化为两个属性,一个是字符串,另一个是对象,并为每个属性使用自定义 JsonConverter 以允许可变行为:

public abstract class BaseEntity
{
    public string Id { get; set; }
}

public class ChildObject : BaseEntity { }

public class MyObject
{
    [JsonProperty("ChildObject")]
    [JsonConverter(typeof(MyCustomIdConverter))]
    public string ChildObjectId { get; set; }

    [JsonProperty("ChildObject")]
    [JsonConverter(typeof(MyCustomObjectConverter))]
    public ChildObject ChildObject { get; set; }
}

但是,在具有相同 PropertyName 的两个属性上设置 JsonProperty 属性会导致异常:

Newtonsoft.Json.JsonSerializationException: 有名字的成员 'ChildObject' 已存在于 '.....' 上。 使用 JsonPropertyAttribute 指定另一个名称。

如果我能克服这个障碍,我相当肯定 JsonConverter 方法会起作用 - 我怀疑错误存在,因为 JsonProperty 属性用于序列化和反序列化。在这种情况下,我对序列化这个类没有兴趣 - 它只会用作反序列化的目标。

我无法控制远程端(它是第三方 API),但我希望能够实现这种序列化。我不介意它使用的是我已经开始使用的方法,还是我还没有想到的方法。

This question 也有关联,但没有答案。

【问题讨论】:

  • 我认为转换器应该用于整个对象而不是单个字段...(BTW 格式对于复杂的实体图似乎相当合理)
  • 希望避免整个对象的自定义转换器,因为它有多个实例,并且某些类型具有多个这样的属性。

标签: c# json json.net


【解决方案1】:

试试这个(如果您将在代码中使用它,请通过一些彻底的验证来扩展它):

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;
            }
        }
    }
}

【讨论】:

  • 有趣的想法;这里唯一的问题是 ChildObject 属性不再是强类型的,所以你必须在每次使用时适当地转换它。
  • 有趣,没想到直接收到JObject。虽然我通常不希望拆分逻辑以便将一些逻辑放在 DTO 上,但这最终是我的程序集外部最简洁的解决方案,因为对象类型的属性也可以在内部进行。谢谢!
【解决方案2】:

与其为每个字段创建两个单独的转换器,不如为“main”属性创建一个转换器并将另一个链接到它是明智的。 ChildObjectId 派生自 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; }
}

现在转换ChildObject 可能有点挑战。对象有两种可能的表示形式:字符串或对象。确定您拥有的表示形式并执行转换。

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,
        };
    }
}

【讨论】:

    【解决方案3】:

    这是我在这种情况下会做的事情。

    • 子对象在父类中只有一个属性,并使其类型为ChildObject
    • 创建一个自定义 JsonConverter,它可以检查 JSON 并且:
      • 如果数据存在,则反序列化子对象的完整实例,或者
      • 创建子对象的新实例并设置其 ID,将所有其他属性留空。 (或者您可以按照 Jeff Mercado 的建议进行操作,让转换器根据 ID 从数据库中加载对象,如果这适用于您的情况。)
    • (可选)在子对象上放置一个属性,指示它是否已完全填充。转换器可以在反序列化期间设置此属性。

    反序列化后,如果 JSON 中有 ChildObject 属性(带有 ID 或完整对象值),则保证您有一个 ChildObject 实例,您可以从中获取它的 ID;否则,如果 JSON 中没有 ChildObject 属性,则父类中的 ChildObject 属性将为空。

    下面是一个完整的工作示例来演示。在此示例中,我修改了父类以包含 ChildObject 的三个单独实例,以显示 JSON 中的不同可能性(仅字符串 ID、完整对象且都不存在)。他们都使用相同的转换器。我还在ChildObject 类中添加了Name 属性和IsFullyPopulated 属性。

    这里是 DTO 类:

    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; }
    }
    

    这是转换器:

    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);
        }
    }
    

    这是演示转换器操作的测试程序:

    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();
        }
    }
    

    这是上面的输出:

    ChildObject1
       Id: key1
       Name: Foo Bar Baz
       IsFullyPopulated: True
    
    ChildObject2
       Id: key2
       Name:
       IsFullyPopulated: False
    
    ChildObject3
       (null)
    

    【讨论】:

    • 我自己也一直在朝着这个方向前进,但试图避免“IsFullyLoaded”属性的想法。我将使用 Alex 的解决方案,因为我可以在一个程序集中隐藏所有凌乱的部分,并让它从外部显示为我想要的。
    • 此解决方案不依赖于 IsFullyPopulated 属性;如果你不需要它,你可以简单地删除它。我添加它只是为了表明它可以完成,因为它可以很容易地判断子对象是否具有它的所有数据或只有一个 ID。但是在父对象上拥有两个属性同样适用。归根结底,你希望你的代码如何工作是你自己的决定,所以如果另一个解决方案更适合你,我完全可以接受!干杯。
    猜你喜欢
    • 1970-01-01
    • 2012-08-19
    • 1970-01-01
    • 2013-04-09
    • 1970-01-01
    • 1970-01-01
    • 2020-01-25
    • 1970-01-01
    相关资源
    最近更新 更多