反序列化具有不同结构和相同名称的 json 对象

Deserialize a json object with different structure and same name

我编写了一个应用程序,通过抓取电影页面源来获取 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/"
    }
}

我做了一个 "Movie" class 来反序列化 JSON 对象。有一个名为 "Director".

的 属性 Person class
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; }
    }

没关系。但问题是有些电影如 "The Matrix" 有不止一位导演。

{
  "@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<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 List<ImdbJsonPerson> Director { get; set; }
        //public string[] Creator { get; set; }
    }

另一个问题是如何反序列化由 Person class 和 Organization class.

创建的创建者 属性

所以问题是"How to deserialize this complex JSON object?"

谢谢

你试过了吗:https://app.quicktype.io/?l=csharp?它可以为您在 C# 中生成模型,这对于进一步的更改是非常好的开始(如果架构必须根据不同的 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();
    }
}

[更新]

至于有时是单一的,有时是数组的问题 --> 看这里:How to handle both a single item and an array for the same property using JSON.net

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

如你所说,正确答案在这个link。

所以我做了这个class。

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

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

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

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

谢谢