反序列化子类中的匿名顶级数组

Jer*_*vel 3 c# json json.net

我有一个JSON响应,包含在外部数组中,如下所示:

[
  {
    "type": "a"
  },
  {
    "type": "b",
    "id": 1
  },
  {
    "type": "c",
    "name": "somename"
  }
]
Run Code Online (Sandbox Code Playgroud)

我试图将此转换为对象,如下所示:

class LoginOptions
{
    public IList<ILoginOption> Options { get; set; }
}

interface ILoginOption
{
    [JsonProperty("type")]
    LoginType LoginType { get; }
}

class LoginOptionA : ILoginOption{
    public LoginType LoginType
    {
        get { return LoginType.A; }
    }
}

class LoginOptionB : ILoginOption{
    public LoginType LoginType
    {
        get { return LoginType.B; }
    }

    [JsonProperty("id")]
    public int Id { get; set; }
}

class LoginOptionC : ILoginOption{
    public LoginType LoginType
    {
        get { return LoginType.C; }
    }

    [JsonProperty("name")]
    public string Name { get; set; }
}
Run Code Online (Sandbox Code Playgroud)

导致此异常的原因是:

无法将当前JSON数组(例如[1,2,3])反序列化为类型"Library.Models.Domain.LoginOptions",因为该类型需要JSON对象(例如{"name":"value"})才能正确反序列化.要修复此错误,请将JSON更改为JSON对象(例如{"name":"value"})或将反序列化类型更改为数组或实现集合接口的类型(例如ICollection,IList),例如List从JSON数组反序列化.JsonArrayAttribute也可以添加到类型中以强制它从JSON数组反序列化.

我宁愿不在我的LoginOptions类中实现一个集合,因为当我应该能够将它存储在字段中时,这将是太多的开销.使用该[JsonArray]属性将返回"无法创建和填充"列表类型Library.Models.Domain.LoginOptions.

我发现大多数资源都处理{"name":"value"}顶部数组,而不是匿名数组.我该如何反序化呢?

我相应地改变了我的代码:

public sealed class LoginOptions
{    
    [JsonConverter(typeof(LoginOptionConverter))]
    public IList<ILoginOption> Options { get; set; }
}
Run Code Online (Sandbox Code Playgroud)

我的呼叫调度程序解析JSON的地方:

private List<ILoginOption> DeserializeObject<List<ILoginOption>>(Stream stream)
{
   using (StreamReader sr = new StreamReader(stream))
   using (JsonReader reader = new JsonTextReader(sr))
   {
       return new JsonSerializer().Deserialize<List<ILoginOption>>(reader);
   }
}
Run Code Online (Sandbox Code Playgroud)

和这样的自定义转换器:

internal class LoginOptionConverter : JsonConverter
{
    public override bool CanConvert(Type objectType)
    {
        return objectType == typeof(ILoginOption);
    }

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        var item = JObject.Load(reader);
        var data = item["type"].Value<string>();

        if (data == "UsernamePassword")
        {
            return item.ToObject<LoginOptionA>();
        }

        if (data == "Saml")
        {
            return item.ToObject<LoginOptionB>();
        }

        if (data == "Crm")
        {
            return item.ToObject<LoginOptionC>();
        }

        throw new ArgumentException("Invalid JSON response");
    }
}
Run Code Online (Sandbox Code Playgroud)

这会引发错误

无法创建Library.Models.Domain.ILoginOption类型的实例.Type是接口或抽象类,无法实例化.

运用

new JsonSerializerSettings
{
    TypeNameHandling = TypeNameHandling.Objects
};
Run Code Online (Sandbox Code Playgroud)

这里建议没有什么区别.

请注意,在将其放入自定义转换器之前JsonSerializer会抛出此错误:创建它时会抛出此错误.

实例化发生在这里:

var settings = new JsonSerializerSettings
{
     TypeNameHandling = TypeNameHandling.Objects
};

using (StreamReader sr = new StreamReader(stream))
using (JsonReader reader = new JsonTextReader(sr))
{
    return JsonSerializer.Create(settings).Deserialize<List<ILoginOption>>(reader);
}
Run Code Online (Sandbox Code Playgroud)

Bri*_*ers 7

由于JSON中的顶级是一个数组,因此您应该直接反序列化为一个列表.不需要包装类来保存列表.

List<ILoginOption> loginOptions = 
                   JsonConvert.DeserializeObject<List<ILoginOption>>(json);
Run Code Online (Sandbox Code Playgroud)

现在,因为您的列表将包含几种不同类型ILoginObjects,Json.Net将不知道要创建哪些,因为它反序列化列表.为此你需要一个JsonConverter. 这个答案显示了如何创建一个转换器来处理这个问题.