首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
社区首页 >问答首页 >如何反序列化包含C#中形状不同的对象的JSON数组?

如何反序列化包含C#中形状不同的对象的JSON数组?
EN

Stack Overflow用户
提问于 2021-03-23 23:21:58
回答 3查看 496关注 0票数 1

考虑以下JSON:

代码语言:javascript
代码运行次数:0
运行
复制
{
  "Foo": "Whatever",
  "Bar": [
   { "Name": "Enrico", "Age": 33, "Country": "Italy" }, { "Type": "Video", "Year": 2004 },
   { "Name": "Sam", "Age": 18, "Country": "USA" }, { "Type": "Book", "Year": 1980 }
  ]
}

注意,Items数组是一个混合内容数组,它包含具有不同形状的对象。

这些形状之一可以使用以下类来描述:

代码语言:javascript
代码运行次数:0
运行
复制
class Person 
{
    public string Name { get; set; }
    public int Age { get; set; }
    public string Country { get; set; }
}

相反,可以使用以下C#类来描述另一个形状:

代码语言:javascript
代码运行次数:0
运行
复制
class Item 
{
    public string Type { get; set; }
    public int Year { get; set; }
}

我想通过使用C#或newtonsoft.json将这个JSON反序列化为一个System.Text.Json类。在这两种情况下,我都需要一个用于反序列化的类,但我不知道如何处理Bar数组。

代码语言:javascript
代码运行次数:0
运行
复制
class ClassToDeserialize
{
    public string Foo { get; set; }
    public List<what should I put here ???> Bar { get; set; }
}

如何反序列化这个JSON?

对于熟悉类型记录的人,我需要类似于union类型的东西(例如:将Bar属性定义为List<Person | Item>),但根据我的知识,C#中不支持联合类型。

EN

回答 3

Stack Overflow用户

回答已采纳

发布于 2021-03-24 16:39:29

我将为列表项定义一个公共接口IBar,然后让类实现这个接口。IBar可能只是一个空接口,也可以选择将Type属性放入其中并向Person类添加一个合成Type属性以匹配:

代码语言:javascript
代码运行次数:0
运行
复制
interface IBar
{
    string Type { get; }
}

class Person : IBar
{
    public string Type => "Person";
    public string Name { get; set; }
    public int Age { get; set; }
    public string Country { get; set; }
}

class Item : IBar
{
    public string Type { get; set; }
    public int Year { get; set; }
}

class ClassToDeserialize
{
    public string Foo { get; set; }
    public List<IBar> Bar { get; set; }
}

要从JSON填充类,可以使用以下简单的JsonConverter

代码语言:javascript
代码运行次数:0
运行
复制
public class BarConverter : JsonConverter
{
    public override bool CanConvert(Type objectType)
    {
        return typeof(IBar).IsAssignableFrom(objectType);
    }

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

        // If the "age" property is present in the JSON, it's a person, otherwise it's an item
        IBar bar;
        if (jo["age"] != null)
        {
            bar = new Person();
        }
        else
        {
            bar = new Item();
        }

        serializer.Populate(jo.CreateReader(), bar);

        return bar;
    }

    public override bool CanWrite => false;

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

最后一块拼图是用一个IBar属性装饰[JsonConverter]接口,告诉序列化程序在处理IBar时使用转换器。

代码语言:javascript
代码运行次数:0
运行
复制
[JsonConverter(typeof(BarConverter))]
interface IBar
{
    string Type { get; }
}

然后,您可以像通常那样反序列化:

代码语言:javascript
代码运行次数:0
运行
复制
var root = JsonConvert.DeserializeObject<ClassToDeserialize>(json);

下面是一个工作演示:https://dotnetfiddle.net/ENLgVx

票数 1
EN

Stack Overflow用户

发布于 2021-03-24 00:02:16

创建一个具有两个属性但具有可空选项的类。然后,可以使用各个类的属性创建两个接口。然后,一旦您收到它为一个列表,您可以组织成两个不同的列表类型IYourInterface

票数 3
EN

Stack Overflow用户

发布于 2021-03-24 00:56:52

这是可行的,但它比我预期的要复杂一些。首先创建一些接口:

代码语言:javascript
代码运行次数:0
运行
复制
public interface IPerson
{
    string Name { get; }
    int Age { get; }
    string Country { get; }
}

public interface IItem
{
    string Type { get; }
    int Year { get; }
}

}

我们会用它们来代表你的人和物品。

然后创建另一个类:

代码语言:javascript
代码运行次数:0
运行
复制
public class JsonDynamicList
{
    private const string JsonData =
        @"{
            'Foo': 'Whatever',
            'Bar': [
                { 'Name': 'Enrico', 'Age': 33, 'Country': 'Italy' }, { 'Type': 'Video', 'Year': 2004 },
                { 'Name': 'Sam', 'Age': 18, 'Country': 'USA' }, { 'Type': 'Book', 'Year': 1980 }
                ]
        }";

    public string Foo { get; set; }
    public dynamic[] Bar { get; set; }
}

它与您创建的类相匹配,但我使用的是dynamic数组,而不是List<something>。还请注意,通过更改引号使您的JSON更加友好--它是同一个C#。

我们会继续增加那个班的成员。

首先,我创建了两个实现IPersonIItem的私有类。它们位于JsonDynamicList类中:

代码语言:javascript
代码运行次数:0
运行
复制
private class PersonImpl :IPerson
{
    private readonly dynamic _person;
    public PersonImpl(dynamic person)
    {
        _person = person;
    }

    public IPerson AsPerson()
    {
        if (!IsPerson(_person))
        {
            return null;
        }
        //otherwise
        Name = _person.Name;
        Age = _person.Age;
        Country = _person.Country;
        return this;
    }

    public string Name { get; private set; } = default;
    public int Age { get; private set; } = default;
    public string Country { get; private set; } = default;
}

private class ItemImpl : IItem
{
    private readonly dynamic _item;
    public ItemImpl(dynamic item)
    {
        _item = item;
    }

    public IItem AsItem()
    {
        if (!IsItem(_item))
        {
            return null;
        }
        //otherwise
        Type = _item.Type;
        Year = _item.Year;
        return this;
    }

    public string Type { get; private set; } = default;
    public int Year { get; private set; } = default;
}

我使用了一些Newtonsoft魔术来实现JsonDynamicList的下两个成员。他们可以决定一个项目是IItem还是IPerson

代码语言:javascript
代码运行次数:0
运行
复制
public static bool IsPerson(dynamic person)
{
    var jObjectPerson = ((JObject) person).ToObject<Dictionary<string, object>>();
    return jObjectPerson?.ContainsKey("Age") ?? false;
}

public static bool IsItem(dynamic item)
{
    var jObjectItem = ((JObject)item).ToObject<Dictionary<string, object>>();
    return jObjectItem?.ContainsKey("Year") ?? false;
}

如果有人知道一个更好的方法来判断一个动态是否有一个特定的成员,我很想知道。

然后,我创建了一种方法来将数组中的一个项转换为输入项(嗯,它实际上不是一个强制转换,但您可以这样想)。我用了前两个,我想我要用第二个。

代码语言:javascript
代码运行次数:0
运行
复制
public static IPerson AsPerson(dynamic person)
{
    var personImpl = new PersonImpl(person);
    return personImpl.AsPerson();
}

public static IItem AsItem(dynamic item)
{
    var itemImpl = new ItemImpl(item);
    return itemImpl.AsItem();
}

public IItem AsItem(int index)
{
    if (index < 0 || index >= Bar.Length)
    {
        throw new IndexOutOfRangeException();
    }
    return AsItem(Bar[index]);
}

public IPerson AsPerson(int index)
{
    if (index < 0 || index >= Bar.Length)
    {
        throw new IndexOutOfRangeException();
    }
    return AsPerson(Bar[index]);
}

一些便于测试的工作人员方法:

代码语言:javascript
代码运行次数:0
运行
复制
public static string ItemToString(IItem item)
{
    return $"Type: {item.Type} - Year: {item.Year}";
}

public static string PersonToString(IPerson person)
{
    return $"Name: {person.Name} - Age: {person.Age} - Country: {person.Country}";
}

最后,一些测试代码:

代码语言:javascript
代码运行次数:0
运行
复制
var data = JsonConvert.DeserializeObject<JsonDynamicList>(JsonData);
Debug.WriteLine($"Foo: {data.Foo}");
foreach (dynamic obj in data.Bar)
{
    if (IsItem(obj))
    {
        string itemString = ItemToString(AsItem(obj));
        Debug.WriteLine(itemString);
    }
    else
    {
        string personString = PersonToString(AsPerson(obj));
        Debug.WriteLine(personString);
    }
}

这导致:

代码语言:javascript
代码运行次数:0
运行
复制
Foo: Whatever
Name: Enrico - Age: 33 - Country: Italy
Type: Video - Year: 2004
Name: Sam - Age: 18 - Country: USA
Type: Book - Year: 1980
票数 1
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/66772647

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档