如何在 C# 中反序列化包含具有不同形状的对象的 JSON 数组?
How to deserialize a JSON array containing objects having different shape in C#?
考虑以下 JSON:
{
"Foo": "Whatever",
"Bar": [
{ "Name": "Enrico", "Age": 33, "Country": "Italy" }, { "Type": "Video", "Year": 2004 },
{ "Name": "Sam", "Age": 18, "Country": "USA" }, { "Type": "Book", "Year": 1980 }
]
}
注意 Items
数组是一个混合内容数组,它包含具有不同形状的对象。
可以使用以下 class 来描述其中一个形状:
class Person
{
public string Name { get; set; }
public int Age { get; set; }
public string Country { get; set; }
}
另一种形状可以使用以下 C# class 来描述:
class Item
{
public string Type { get; set; }
public int Year { get; set; }
}
我想通过使用 newtonsoft.json 或 [= 将此 JSON 反序列化为 C# class 38=]。
在这两种情况下,我都需要一个 class 用于反序列化,但我不知道如何处理 Bar
数组。
class ClassToDeserialize
{
public string Foo { get; set; }
public List<what should I put here ???> Bar { get; set; }
}
如何反序列化此 JSON?
对于那些熟悉 typescript 的人,我需要类似联合类型的东西(例如:将 Bar
属性 定义为 List<Person | Item>
),但基于我的知识联合C# 不支持类型。
创建一个 class 具有两者的属性,但具有可为 null 的选项。然后,您可以使用各个 classes 的属性创建两个接口。然后,一旦你将它接收到一个列表中,你就可以组织成两个不同的 IYourInterface
类型的列表
这可行,但比我预期的要复杂一些。首先创建一些接口:
public interface IPerson
{
string Name { get; }
int Age { get; }
string Country { get; }
}
public interface IItem
{
string Type { get; }
int Year { get; }
}
}
我们将使用它们来代表您的个人和物品。
然后创建另一个class:
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; }
}
它与您创建的 class 匹配,但我使用的是 dynamic
数组而不是 List<something>
。另请注意,我通过更改引号使您的 JSON 对 C# 更友好 - 它与 JSON.
相同
我们将继续向其中添加成员 class。
首先,我创建了两个实现 IPerson
和 IItem
的私有 classes。这些位于 JsonDynamicList
class:
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
:
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;
}
如果有人知道更好的方法来判断动态是否有特定成员,我很想知道。
然后我创建了一种方法来 cast (好吧,这不是真正的强制转换,但你可以这样想)数组中的一个项目转换为键入的内容.我用了前两个,我以为我要用后两个。
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]);
}
一些辅助测试的worker方法:
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}";
}
最后是一些测试代码:
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);
}
}
这导致:
Foo: Whatever
Name: Enrico - Age: 33 - Country: Italy
Type: Video - Year: 2004
Name: Sam - Age: 18 - Country: USA
Type: Book - Year: 1980
我会为列表项定义一个通用接口 IBar
,然后让您的 classes 实现这个接口。 IBar
可以只是一个空接口,或者您可以选择将 Type
属性 放入其中并添加合成 Type
属性 到 Person
class 匹配:
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 填充 classes,您可以像这样使用简单的 JsonConverter
:
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();
}
}
拼图的最后一块是用 [JsonConverter]
属性装饰 IBar
接口,以告诉序列化程序在处理 IBar
:[=25= 时使用转换器]
[JsonConverter(typeof(BarConverter))]
interface IBar
{
string Type { get; }
}
然后你可以像往常一样反序列化:
var root = JsonConvert.DeserializeObject<ClassToDeserialize>(json);
这是一个工作演示:https://dotnetfiddle.net/ENLgVx
考虑以下 JSON:
{
"Foo": "Whatever",
"Bar": [
{ "Name": "Enrico", "Age": 33, "Country": "Italy" }, { "Type": "Video", "Year": 2004 },
{ "Name": "Sam", "Age": 18, "Country": "USA" }, { "Type": "Book", "Year": 1980 }
]
}
注意 Items
数组是一个混合内容数组,它包含具有不同形状的对象。
可以使用以下 class 来描述其中一个形状:
class Person
{
public string Name { get; set; }
public int Age { get; set; }
public string Country { get; set; }
}
另一种形状可以使用以下 C# class 来描述:
class Item
{
public string Type { get; set; }
public int Year { get; set; }
}
我想通过使用 newtonsoft.json 或 [= 将此 JSON 反序列化为 C# class 38=]。
在这两种情况下,我都需要一个 class 用于反序列化,但我不知道如何处理 Bar
数组。
class ClassToDeserialize
{
public string Foo { get; set; }
public List<what should I put here ???> Bar { get; set; }
}
如何反序列化此 JSON?
对于那些熟悉 typescript 的人,我需要类似联合类型的东西(例如:将 Bar
属性 定义为 List<Person | Item>
),但基于我的知识联合C# 不支持类型。
创建一个 class 具有两者的属性,但具有可为 null 的选项。然后,您可以使用各个 classes 的属性创建两个接口。然后,一旦你将它接收到一个列表中,你就可以组织成两个不同的 IYourInterface
类型的列表这可行,但比我预期的要复杂一些。首先创建一些接口:
public interface IPerson
{
string Name { get; }
int Age { get; }
string Country { get; }
}
public interface IItem
{
string Type { get; }
int Year { get; }
}
}
我们将使用它们来代表您的个人和物品。
然后创建另一个class:
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; }
}
它与您创建的 class 匹配,但我使用的是 dynamic
数组而不是 List<something>
。另请注意,我通过更改引号使您的 JSON 对 C# 更友好 - 它与 JSON.
我们将继续向其中添加成员 class。
首先,我创建了两个实现 IPerson
和 IItem
的私有 classes。这些位于 JsonDynamicList
class:
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
:
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;
}
如果有人知道更好的方法来判断动态是否有特定成员,我很想知道。
然后我创建了一种方法来 cast (好吧,这不是真正的强制转换,但你可以这样想)数组中的一个项目转换为键入的内容.我用了前两个,我以为我要用后两个。
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]);
}
一些辅助测试的worker方法:
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}";
}
最后是一些测试代码:
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);
}
}
这导致:
Foo: Whatever
Name: Enrico - Age: 33 - Country: Italy
Type: Video - Year: 2004
Name: Sam - Age: 18 - Country: USA
Type: Book - Year: 1980
我会为列表项定义一个通用接口 IBar
,然后让您的 classes 实现这个接口。 IBar
可以只是一个空接口,或者您可以选择将 Type
属性 放入其中并添加合成 Type
属性 到 Person
class 匹配:
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 填充 classes,您可以像这样使用简单的 JsonConverter
:
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();
}
}
拼图的最后一块是用 [JsonConverter]
属性装饰 IBar
接口,以告诉序列化程序在处理 IBar
:[=25= 时使用转换器]
[JsonConverter(typeof(BarConverter))]
interface IBar
{
string Type { get; }
}
然后你可以像往常一样反序列化:
var root = JsonConvert.DeserializeObject<ClassToDeserialize>(json);
这是一个工作演示:https://dotnetfiddle.net/ENLgVx