在使用自定义格式作为参考时,如何使用Json.NET通过引用反序列化对象?

Ben*_*son 6 c# json json.net deserialization json-deserialization

我有以下格式的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" ]
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

某些嵌套对象或多次引用的对象被序列化为引用.

然后,引用的对象包含在BARJSON对象末尾的数组中,并由[ "FOO", "ANIMAL-22" ]数组确定.

(这两个FOOBAR是静态常数,和ANIMAL-22/ VET-1标识符是半随机的)

不幸的是,这与Json.NET已经序列化/反序列化引用对象的方式不匹配,我可以实现的IReferenceResolver似乎不允许我对行为进行足够的调整(固定使用"$ 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; }
}
Run Code Online (Sandbox Code Playgroud)

编辑#1:虽然我只给出了Animal,但Vet在我的例子中,有很多类型以这种方式引用,我认为我需要一个"通用"或类型无关的解决方案来处理数组结构的任何这种情况[ "FOO", "..." ]而不需要单独编码每个C#类型.

Bri*_*ers 5

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

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

    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();
        }
    }
    
    Run Code Online (Sandbox Code Playgroud)
  2. 将JSON解析为a JObjectVetsBARJSON部分构建字典.

    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>());
    
    Run Code Online (Sandbox Code Playgroud)
  3. 构建的字典AnimalsBAR该JSON的部分,使用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));
    
    Run Code Online (Sandbox Code Playgroud)
  4. 最后,users从JSON 反序列化列表,再次使用ReferenceConverter<T>加两个字典(实际上现在有两个转换器实例,每个字典一个)来解析所有引用.

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

这里有完整的演示:https://dotnetfiddle.net/uUuy7v