使用自定义格式作为引用时,如何使用 Json.NET 通过引用反序列化对象?
How can I use Json.NET to deserialize objects by reference, when using a custom format for the reference?
我有 JSON 格式如下:
{
"users": [
{
"first_name": "John",
"last_name": "Smith",
"vet": [ "FOO", "VET-1" ],
"animals": [ [ "FOO", "ANIMAL-22" ] ]
},
{
"first_name": "Susan",
"last_name": "Smith",
"vet": [ "FOO", "VET-1" ]
}
],
"BAR": {
"VET-1": {
"vet_name": "Acme, Inc",
"vet_id": 456
},
"ANIMAL-22": {
"animal_name": "Fido",
"species": "dog",
"animal_id": 789,
"vet": [ "FOO", "VET-1" ]
}
}
}
某些嵌套对象或多次引用的对象被序列化为引用。
然后引用的对象包含在 JSON 对象末尾的 BAR
数组中,并由 [ "FOO", "ANIMAL-22" ]
数组在适当的位置标识。
(FOO
和BAR
都是静态常量,ANIMAL-22
/VET-1
标识符是半随机的)
不幸的是,这与 Json.NET already serializes/deserializes referenced objects and the IReferenceResolver I could implement doesn't seem to allow me to adjust the behaviour enough (it's fixed to use "$ref" 一开始的方式不符。
我也尝试为受影响的属性编写自定义 JsonConverter,但我似乎无法获得对根对象的 BAR
属性 的引用。
有什么方法可以覆盖 Json.NET 以将上面的 JSON 反序列化为这种 C# class 结构?
public class User
{
[JsonProperty("first_name")]
public string FirstName { get; set; }
[JsonProperty("last_name")]
public string LastName { get; set; }
[JsonProperty("vet")]
public Vet Vet { get; set; }
[JsonProperty("animals")]
public List<Animal> Animals { get; set; }
}
public class Vet
{
[JsonProperty("vet_id")]
public int Id { get; set; }
[JsonProperty("vet_name")]
public string Name { get; set; }
}
public class Animal
{
[JsonProperty("animal_id")]
public int Id { get; set; }
[JsonProperty("animal_name")]
public string Name { get; set; }
[JsonProperty("vet")]
public Vet Vet { get; set; }
[JsonProperty("species")]
public string Species { get; set; }
}
编辑#1:虽然我在我的例子中只给出了Animal
和Vet
,但是有大量的类型以这种方式引用并且我想我需要一个 'generic' 或与类型无关的解决方案来处理任何此类数组结构 [ "FOO", "..." ]
的出现,而无需为每个 C# 类型单独编码。
正如@dbc 在评论中所说,没有一种简单的方法可以使 Json.Net 自动处理您的自定义参考格式。也就是说,您 可以 使用 LINQ-to-JSON (JObjects) 来解析 JSON,并借助 JsonConverter
和几个字典,解析引用并填充您的 类,同时仍然将大部分繁重的工作留给 Json.Net。这是我将采用的方法:
创建自定义泛型 JsonConverter
,它可以解码 [ "FOO", "<key>" ]
参考格式和 return 提供的字典中的相应对象。这是转换器的代码:
public class ReferenceConverter<T> : JsonConverter
{
private Dictionary<string, T> ReferenceDict { get; set; }
public ReferenceConverter(Dictionary<string, T> referenceDict)
{
ReferenceDict = referenceDict;
}
public override bool CanConvert(Type objectType)
{
return objectType == typeof(T);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
JArray array = JArray.Load(reader);
if (array.Count == 2 &&
array[0].Type == JTokenType.String &&
(string)array[0] == "FOO" &&
array[1].Type == JTokenType.String)
{
string key = (string)array[1];
T obj;
if (ReferenceDict.TryGetValue(key, out obj))
return obj;
throw new JsonSerializationException("No " + typeof(T).Name + " was found with the key \"" + key + "\".");
}
throw new JsonSerializationException("Reference had an invalid format: " + array.ToString());
}
public override bool CanWrite
{
get { return false; }
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
}
将 JSON 解析为 JObject
并从 JSON 的 BAR
部分构建 Vets
的字典。
JObject data = JObject.Parse(json);
Dictionary<string, Vet> vets = data["BAR"]
.Children<JProperty>()
.Where(jp => jp.Value["vet_id"] != null)
.ToDictionary(jp => jp.Name, jp => jp.Value.ToObject<Vet>());
从 JSON 的 BAR
部分构建 Animals
的字典,使用 ReferenceConverter<T>
和步骤 2 中的字典来解析Vet
参考。
JsonSerializer serializer = new JsonSerializer();
serializer.Converters.Add(new ReferenceConverter<Vet>(vets));
Dictionary<string, Animal> animals = data["BAR"]
.Children<JProperty>()
.Where(jp => jp.Value["animal_id"] != null)
.ToDictionary(jp => jp.Name, jp => jp.Value.ToObject<Animal>(serializer));
最后,反序列化 JSON 中的 users
列表,再次使用 ReferenceConverter<T>
加上两个字典(所以实际上现在有两个转换器实例,一个字典)解析所有引用。
serializer.Converters.Add(new ReferenceConverter<Animal>(animals));
List<User> users = data["users"].ToObject<List<User>>(serializer);
完整演示在这里:https://dotnetfiddle.net/uUuy7v
我有 JSON 格式如下:
{
"users": [
{
"first_name": "John",
"last_name": "Smith",
"vet": [ "FOO", "VET-1" ],
"animals": [ [ "FOO", "ANIMAL-22" ] ]
},
{
"first_name": "Susan",
"last_name": "Smith",
"vet": [ "FOO", "VET-1" ]
}
],
"BAR": {
"VET-1": {
"vet_name": "Acme, Inc",
"vet_id": 456
},
"ANIMAL-22": {
"animal_name": "Fido",
"species": "dog",
"animal_id": 789,
"vet": [ "FOO", "VET-1" ]
}
}
}
某些嵌套对象或多次引用的对象被序列化为引用。
然后引用的对象包含在 JSON 对象末尾的 BAR
数组中,并由 [ "FOO", "ANIMAL-22" ]
数组在适当的位置标识。
(FOO
和BAR
都是静态常量,ANIMAL-22
/VET-1
标识符是半随机的)
不幸的是,这与 Json.NET already serializes/deserializes referenced objects and the IReferenceResolver I could implement doesn't seem to allow me to adjust the behaviour enough (it's fixed to use "$ref" 一开始的方式不符。
我也尝试为受影响的属性编写自定义 JsonConverter,但我似乎无法获得对根对象的 BAR
属性 的引用。
有什么方法可以覆盖 Json.NET 以将上面的 JSON 反序列化为这种 C# class 结构?
public class User
{
[JsonProperty("first_name")]
public string FirstName { get; set; }
[JsonProperty("last_name")]
public string LastName { get; set; }
[JsonProperty("vet")]
public Vet Vet { get; set; }
[JsonProperty("animals")]
public List<Animal> Animals { get; set; }
}
public class Vet
{
[JsonProperty("vet_id")]
public int Id { get; set; }
[JsonProperty("vet_name")]
public string Name { get; set; }
}
public class Animal
{
[JsonProperty("animal_id")]
public int Id { get; set; }
[JsonProperty("animal_name")]
public string Name { get; set; }
[JsonProperty("vet")]
public Vet Vet { get; set; }
[JsonProperty("species")]
public string Species { get; set; }
}
编辑#1:虽然我在我的例子中只给出了Animal
和Vet
,但是有大量的类型以这种方式引用并且我想我需要一个 'generic' 或与类型无关的解决方案来处理任何此类数组结构 [ "FOO", "..." ]
的出现,而无需为每个 C# 类型单独编码。
正如@dbc 在评论中所说,没有一种简单的方法可以使 Json.Net 自动处理您的自定义参考格式。也就是说,您 可以 使用 LINQ-to-JSON (JObjects) 来解析 JSON,并借助 JsonConverter
和几个字典,解析引用并填充您的 类,同时仍然将大部分繁重的工作留给 Json.Net。这是我将采用的方法:
创建自定义泛型
JsonConverter
,它可以解码[ "FOO", "<key>" ]
参考格式和 return 提供的字典中的相应对象。这是转换器的代码:public class ReferenceConverter<T> : JsonConverter { private Dictionary<string, T> ReferenceDict { get; set; } public ReferenceConverter(Dictionary<string, T> referenceDict) { ReferenceDict = referenceDict; } public override bool CanConvert(Type objectType) { return objectType == typeof(T); } public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) { JArray array = JArray.Load(reader); if (array.Count == 2 && array[0].Type == JTokenType.String && (string)array[0] == "FOO" && array[1].Type == JTokenType.String) { string key = (string)array[1]; T obj; if (ReferenceDict.TryGetValue(key, out obj)) return obj; throw new JsonSerializationException("No " + typeof(T).Name + " was found with the key \"" + key + "\"."); } throw new JsonSerializationException("Reference had an invalid format: " + array.ToString()); } public override bool CanWrite { get { return false; } } public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) { throw new NotImplementedException(); } }
将 JSON 解析为
JObject
并从 JSON 的BAR
部分构建Vets
的字典。JObject data = JObject.Parse(json); Dictionary<string, Vet> vets = data["BAR"] .Children<JProperty>() .Where(jp => jp.Value["vet_id"] != null) .ToDictionary(jp => jp.Name, jp => jp.Value.ToObject<Vet>());
从 JSON 的
BAR
部分构建Animals
的字典,使用ReferenceConverter<T>
和步骤 2 中的字典来解析Vet
参考。JsonSerializer serializer = new JsonSerializer(); serializer.Converters.Add(new ReferenceConverter<Vet>(vets)); Dictionary<string, Animal> animals = data["BAR"] .Children<JProperty>() .Where(jp => jp.Value["animal_id"] != null) .ToDictionary(jp => jp.Name, jp => jp.Value.ToObject<Animal>(serializer));
最后,反序列化 JSON 中的
users
列表,再次使用ReferenceConverter<T>
加上两个字典(所以实际上现在有两个转换器实例,一个字典)解析所有引用。serializer.Converters.Add(new ReferenceConverter<Animal>(animals)); List<User> users = data["users"].ToObject<List<User>>(serializer);
完整演示在这里:https://dotnetfiddle.net/uUuy7v