【问题标题】:Deserialize a json object with different structure and same name反序列化不同结构同名的json对象
【发布时间】:2020-08-21 18:03:54
【问题描述】:

我编写了一个应用程序,通过抓取电影页面源来获取 IMDb 电影信息。页面源中的一些电影数据是 JSON 格式,电影模式来自“Schema.org”。

{
  "@context": "http://schema.org",
  "@type": "Movie",
  "url": "/title/tt7131622/",
  "name": "Once Upon a Time... in Hollywood",
  "genre": [
    "Comedy",
    "Drama"
  ],
  "actor": [
    {
      "@type": "Person",
      "url": "/name/nm0000138/",
      "name": "Leonardo DiCaprio"
    },
    {
      "@type": "Person",
      "url": "/name/nm0000093/",
      "name": "Brad Pitt"
    },
    {
      "@type": "Person",
      "url": "/name/nm3053338/",
      "name": "Margot Robbie"
    },
    {
      "@type": "Person",
      "url": "/name/nm0386472/",
      "name": "Emile Hirsch"
    }
  ],
  "director": {
    "@type": "Person",
    "url": "/name/nm0000233/",
    "name": "Quentin Tarantino"
  },
  "creator": [
    {
      "@type": "Person",
      "url": "/name/nm0000233/",
      "name": "Quentin Tarantino"
    },
    {
      "@type": "Organization",
      "url": "/company/co0050868/"
    },
    {
      "@type": "Organization",
      "url": "/company/co0452101/"
    },
    {
      "@type": "Organization",
      "url": "/company/co0159772/"
    }
}

我创建了一个“电影”类来反序列化 JSON 对象。有一个名为“Director”的属性Person 类。

internal class ImdbJsonMovie
    {
        public string Url { get; set; }
        public string Name { get; set; }
        public string Image { get; set; }
        public List<string> Genre { get; set; }
        public List<ImdbJsonPerson> Actor { get; set; }
        public ImdbJsonPerson Director { get; set; }
        //public string[] Creator { get; set; }
    }

没关系。但问题是有些电影,比如《黑客帝国》,导演不止一位。

{
  "@context": "http://schema.org",
  "@type": "Movie",
  "url": "/title/tt0133093/",
  "name": "The Matrix",
  "genre": [
    "Action",
    "Sci-Fi"
  ],
  "actor": [
    {
      "@type": "Person",
      "url": "/name/nm0000206/",
      "name": "Keanu Reeves"
    },
    {
      "@type": "Person",
      "url": "/name/nm0000401/",
      "name": "Laurence Fishburne"
    },
    {
      "@type": "Person",
      "url": "/name/nm0005251/",
      "name": "Carrie-Anne Moss"
    },
    {
      "@type": "Person",
      "url": "/name/nm0915989/",
      "name": "Hugo Weaving"
    }
  ],
  "director": [
    {
      "@type": "Person",
      "url": "/name/nm0905154/",
      "name": "Lana Wachowski"
    },
    {
      "@type": "Person",
      "url": "/name/nm0905152/",
      "name": "Lilly Wachowski"
    }
  ],
  "creator": [
    {
      "@type": "Person",
      "url": "/name/nm0905152/",
      "name": "Lilly Wachowski"
    },
    {
      "@type": "Person",
      "url": "/name/nm0905154/",
      "name": "Lana Wachowski"
    },
    {
      "@type": "Organization",
      "url": "/company/co0002663/"
    },
    {
      "@type": "Organization",
      "url": "/company/co0108864/"
    },
    {
      "@type": "Organization",
      "url": "/company/co0060075/"
    },
    {
      "@type": "Organization",
      "url": "/company/co0019968/"
    },
    {
      "@type": "Organization",
      "url": "/company/co0070636/"
    }
}

所以一定是List&lt;Person&gt;

internal class ImdbJsonMovie
    {
        public string Url { get; set; }
        public string Name { get; set; }
        public string Image { get; set; }
        public List<string> Genre { get; set; }
        public List<ImdbJsonPerson> Actor { get; set; }
        public List<ImdbJsonPerson> Director { get; set; }
        //public string[] Creator { get; set; }
    }

另一个问题是如何反序列化由 Person 类和 Organization 类创建的 creator 属性。

所以问题是“如何反序列化这个复杂的 JSON 对象?”

谢谢

【问题讨论】:

  • 我猜,你应该为这些属性编写自己的转换器

标签: c# json deserialization imdb


【解决方案1】:

谢谢@Piotr。它完全奏效了。因为您的第一部分答案对我来说不正确,所以我将您的回复改写为答案。

正如你所说,正确答案在此链接中。

https://stackoverflow.com/questions/18994685/how-to-handle-both-a-single-item-and-an-array-for-the-same-property-using-json-n

所以我做了这门课。

class JsonConverter<T> : JsonConverter
{
    public override bool CanConvert(Type objectType)
    {
        return objectType == typeof(List<T>);
    }

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        JToken token = JToken.Load(reader);

        if (token.Type == JTokenType.Array)
        {
            return token.ToObject<List<T>>();
        }
        return new List<T> { token.ToObject<T>() };
    }

    public override bool CanWrite
    {
        get { return false; }
    }

    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        throw new NotImplementedException();
    }
}

并将我的电影类更改为此。

internal class ImdbJsonMovie
{
    public string Url { get; set; }

    [JsonProperty("name")]
    public string Name { get; set; }

    [JsonProperty("image")]
    public string Image { get; set; }

    [JsonProperty("genre")]
    [JsonConverter(typeof(JsonConverter<string>))]
    public List<string> Genre { get; set; }

    [JsonProperty("contentRating")]
    public string ContentRating { get; set; }

    [JsonProperty("actor")]
    [JsonConverter(typeof(JsonConverter<ImdbJsonTypeEnum>))]
    public List<ImdbJsonTypeEnum> Actor { get; set; }

    [JsonProperty("director")] 
    [JsonConverter(typeof(JsonConverter<ImdbJsonTypeEnum>))]
    public List<ImdbJsonTypeEnum> Director { get; set; }

    [JsonProperty("creator")]
    [JsonConverter(typeof(JsonConverter<ImdbJsonTypeEnum>))]
    public List<ImdbJsonTypeEnum> Creator { get; set; }
}

还有这个枚举

public class ImdbJsonTypeEnum
{
    [JsonProperty("@type")]
    public TypeEnum Type { get; set; }

    [JsonProperty("url")]
    public string Url { get; set; }

    [JsonProperty("name")]
    public string Name { get; set; }

    public enum TypeEnum
    {
        Organization,
        Person
    };
}

它适用于一位导演和多导演的电影。

谢谢

【讨论】:

    【解决方案2】:

    您尝试了吗:https://app.quicktype.io/?l=csharp?它可以为您生成C#模型,这是进一步更改的良好开端(如果Schema必须根据不同的json响应而有所不同)

    我确实输入了您的 JSON,创建的模型如下:

    namespace QuickType
    {
        using System;
        using System.Collections.Generic;
    
        using System.Globalization;
        using Newtonsoft.Json;
        using Newtonsoft.Json.Converters;
    
        public partial class Movies
        {
            [JsonProperty("@context")]
            public Uri Context { get; set; }
    
            [JsonProperty("@type")]
            public string Type { get; set; }
    
            [JsonProperty("url")]
            public string Url { get; set; }
    
            [JsonProperty("name")]
            public string Name { get; set; }
    
            [JsonProperty("genre")]
            public List<string> Genre { get; set; }
    
            [JsonProperty("actor")]
            public List<Tor> Actor { get; set; }
    
            [JsonProperty("director")]
            public List<Tor> Director { get; set; }
    
            [JsonProperty("creator")]
            public List<Tor> Creator { get; set; }
        }
    
        public partial class Tor
        {
            [JsonProperty("@type")]
            public TypeEnum Type { get; set; }
    
            [JsonProperty("url")]
            public string Url { get; set; }
    
            [JsonProperty("name", NullValueHandling = NullValueHandling.Ignore)]
            public string Name { get; set; }
        }
    
        public enum TypeEnum { Organization, Person };
    
        internal static class Converter
        {
            public static readonly JsonSerializerSettings Settings = new JsonSerializerSettings
            {
                MetadataPropertyHandling = MetadataPropertyHandling.Ignore,
                DateParseHandling = DateParseHandling.None,
                Converters =
                {
                    TypeEnumConverter.Singleton,
                    new IsoDateTimeConverter { DateTimeStyles = DateTimeStyles.AssumeUniversal }
                },
            };
        }
    
        internal class TypeEnumConverter : JsonConverter
        {
            public override bool CanConvert(Type t) => t == typeof(TypeEnum) || t == typeof(TypeEnum?);
    
            public override object ReadJson(JsonReader reader, Type t, object existingValue, JsonSerializer serializer)
            {
                if (reader.TokenType == JsonToken.Null) return null;
                var value = serializer.Deserialize<string>(reader);
                switch (value)
                {
                    case "Organization":
                        return TypeEnum.Organization;
                    case "Person":
                        return TypeEnum.Person;
                }
                throw new Exception("Cannot unmarshal type TypeEnum");
            }
    
            public override void WriteJson(JsonWriter writer, object untypedValue, JsonSerializer serializer)
            {
                if (untypedValue == null)
                {
                    serializer.Serialize(writer, null);
                    return;
                }
                var value = (TypeEnum)untypedValue;
                switch (value)
                {
                    case TypeEnum.Organization:
                        serializer.Serialize(writer, "Organization");
                        return;
                    case TypeEnum.Person:
                        serializer.Serialize(writer, "Person");
                        return;
                }
                throw new Exception("Cannot marshal type TypeEnum");
            }
    
            public static readonly TypeEnumConverter Singleton = new TypeEnumConverter();
        }
    }
    

    [更新]

    关于sometime single,sometime array的问题 --> 看这里:How to handle both a single item and an array for the same property using JSON.net

    【讨论】:

    • 我认为,OP 已经有了正确的结构,他清楚地说明了问题,director 属性可以是单个项目或数组
    • 感谢您的回复。是的。确切地。当我使用List&lt;Person&gt; 时,对于有多个导演的电影没问题,但对于有一个导演的电影,它会出错,反之亦然。并且 creator 属性由两个不同的类组成。
    • @Morteza - 我已经更新了响应以涵盖单个项目与数组项目的问题。
    • @Piotr - 这很有趣。让我试试。谢谢
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2014-11-02
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-12-24
    • 1970-01-01
    相关资源
    最近更新 更多