【问题标题】:How can I use Json.NET to deserialize objects by reference, when using a custom format for the reference?使用自定义格式作为引用时,如何使用 Json.NET 通过引用反序列化对象?
【发布时间】:2017-08-20 06:35:16
【问题描述】:

我有以下格式的 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" ] 数组就地标识。

FOOBAR 都是静态常量,ANIMAL-22/VET-1 标识符是半随机的)

不幸的是,这与 Json.NET 已经 serializes/deserializes referenced objects 和我可以实现的 IReferenceResolver 不匹配似乎无法让我足够地调整行为(it's fixed to use "$ref" 开始)。

我还尝试为受影响的属性编写自定义 JsonConverter,但我似乎无法获得对根对象的 BAR 属性的引用。

有什么方法可以覆盖Json.NET,将上面的 JSON 反序列化为这种 C# 类结构?

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: 虽然我在示例中只给出了 AnimalVet,但有大量以这种方式引用的类型,我认为我需要一个“通用”或与类型无关的解决方案,可以处理数组结构 [ "FOO", "..." ] 的任何此类事件,而无需单独为每个 C# 类型编写代码。

【问题讨论】:

  • 不,您不能让这一切自动发生。撇开 "$ref" 属性名称的硬编码不谈,Json.NET 是一个单通道序列化程序,但 "ANIMAL-22""VET-1" 看起来是对稍后出现在文件中的 JSON 对象的前向引用 ,因此在阅读参考文献时不会被阅读。您需要在反序列化后手动对对象进行后处理以修复此类引用,或者使用 LINQ to JSON 将 JSON 预处理为 Json.NET 所需的格式。

标签: c# json json.net deserialization json-deserialization


【解决方案1】:

正如@dbc 在 cmets 中所说,没有一种简单的方法可以让 Json.Net 自动处理您的自定义引用格式。也就是说,您可以使用 LINQ-to-JSON (JObjects) 来解析 JSON,并在 JsonConverter 和几个字典的帮助下,解析引用并填充您的类,同时仍然保留大部分Json.Net 的繁重工作。这是我将采取的方法:

  1. 创建一个自定义通用JsonConverter,它可以解码[ "FOO", "&lt;key&gt;" ] 引用格式并从提供的字典中返回相应的对象。这是转换器的代码:

    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();
        }
    }
    
  2. 将 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>());
    
  3. 从 JSON 的 BAR 部分构建 Animals 字典,使用 ReferenceConverter&lt;T&gt; 和第 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));
    
  4. 最后,从 JSON 中反序列化 users 列表,再次使用 ReferenceConverter&lt;T&gt; 加上两个字典(现在实际上是两个转换器实例,每个字典一个)来解析所有引用。

    serializer.Converters.Add(new ReferenceConverter<Animal>(animals));
    List<User> users = data["users"].ToObject<List<User>>(serializer);
    

完整演示:https://dotnetfiddle.net/uUuy7v

【讨论】:

  • 感谢您的回答和工作演示!我认为我使用“自动”是一个错误的词选择。由于我要反序列化的类种类繁多,我希望有一个“通用”或不可知论的解决方案,它可以处理数组结构[ "FOO", "..." ] 的任何此类事件,而无需单独为每个 C# 类型编码。在我的示例中,我只有VetAnimal,但实际上还有更多。我也不保证可以通过检查属性来确定被引用对象的类型(正如您在步骤#2 中使用vet_id 所做的那样)。
  • 我确实认为使用BAR 字典的内容填充某些内容的概念是可行的方法。我需要推迟每个引用的 value 的反序列化,直到为属性调用 JsonConverter 并且知道属性类型。但据我所知,JsonConverter 是由正在反序列化 to/from 的已知类型触发的,而不是 JSON 中存在数组结构 [ "FOO", "..." ]
  • 对不起,已经一年多了,我应该早点接受你的答案作为最有帮助的答案!
  • @BenJenkinson 别担心。
猜你喜欢
  • 2022-11-15
  • 1970-01-01
  • 2017-03-19
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-09-09
  • 2022-08-14
  • 1970-01-01
相关资源
最近更新 更多