如何使用Newtonsoft Json.NET处理JSON文档中的对象引用? [英] How to handle object references in JSON document with Newtonsoft Json.NET?

查看:79
本文介绍了如何使用Newtonsoft Json.NET处理JSON文档中的对象引用?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有一个带有标准数据字段和参考字段的json数据集.看起来像这样:

I have a json dataset that comes with standard data fields and reference fields. It looks something like this:

[
    {
        "id":1,
        "name":"Book",
        "description":"Something you can read"
    },
    {
        "id":2,
        "name":"newspaper",
        "description": {
            "ref":"0.description"
        }
    }
]

这是我的数据模型:

public class PhysicalObject {
    [Newtonsoft.Json.JsonProperty("id", Required = Newtonsoft.Json.Required.Default)]
    public int id;

    [Newtonsoft.Json.JsonProperty("name", Required = Newtonsoft.Json.Required.Default)]
    public string name;

    [Newtonsoft.Json.JsonProperty("description", Required = Newtonsoft.Json.Required.Default)]  // FIXED should have been description not desc
    public string desc;
}

json文件中的每个属性都有特定的类型,例如idintdescriptionstring,但是每个属性也可以通过ref链接到另一个属性.在这种情况下,id = 2的descriptionid = 1

Every property in the json file has a specific type such as int for id, and string for description however every property can also link to another via a ref. In this case, the description of id= 2 is the same as id = 1

是否可以通过错误处理或创建某种可应用的后备反序列化来使我可以序列化ref?

Is there a way via Error handling or creating some kind of fallback deserialization that can be applied that can allow me to serialize the ref?

请注意,由于其他要求,我必须使用Newtonsoft Json.NET库来解决此问题.有关解决此问题的其他库或技术的信息可以提供信息,但可能无法解决问题.

Note that because of other requirements, I must use the Newtonsoft Json.NET library to resolve this. Information about other libraries or techniques to solve this are informative but probably won't resolve the issue.

推荐答案

您可以将JSON预加载到 JToken 层次结构,然后使用 LINQ JSON 替换为{"ref":"some.period-separated.path"}形式的对象,并将其替换为路径中指示的标记.然后,可以将JToken层次结构反序列化为最终模型.

You can preload your JSON into a JToken hierarchy, then use LINQ to JSON to replace objects of the form {"ref":"some.period-separated.path"} with the tokens indicated in the path. Then subsequently the JToken hierarchy can be deserialized to your final model.

以下扩展方法可以解决问题:

The following extension method does the trick:

public static partial class JsonExtensions
{
    const string refPropertyName = "ref";

    public static void ResolveRefererences(JToken root)
    {
        if (!(root is JContainer container))
            return;
        var refs = container.Descendants().OfType<JObject>().Where(o => IsRefObject(o)).ToList();
        Console.WriteLine(JsonConvert.SerializeObject(refs));
        foreach (var refObj in refs)
        {
            var path = GetRefObjectValue(refObj);
            var original = ResolveRef(root, path);
            if (original != null)
                refObj.Replace(original);
        }
    }

    static bool IsRefObject(JObject obj)
    {
        return GetRefObjectValue(obj) != null;
    }

    static string GetRefObjectValue(JObject obj)
    {
        if (obj.Count == 1)
        {
            var refValue = obj[refPropertyName];
            if (refValue != null && refValue.Type == JTokenType.String)
            {
                return (string)refValue;
            }
        }
        return null;
    }

    static JToken ResolveRef(JToken token, string path)
    {
        // TODO: determine whether it is possible for a property name to contain a '.' character, and if so, how the path will look.
        var components = path.Split('.'); 

        foreach (var component in components)
        {
            if (token is JObject obj)
                token = obj[component];
            else if (token is JArray array)
                token = token[int.Parse(component, NumberFormatInfo.InvariantInfo)];
            else
                // Or maybe just return null?
                throw new JsonException("Unexpected token type.");
        }
        return token;
    }
} 

然后您将按以下方式使用它:

Then you would use it as follows:

// Load into intermediate JToken hierarchy; do not perform DateTime recognition yet.
var root = JsonConvert.DeserializeObject<JToken>(jsonString, new JsonSerializerSettings { DateParseHandling = DateParseHandling.None });

// Replace {"ref": "...") objects with their references.
JsonExtensions.ResolveRefererences(root);

// Deserialize directly to final model.  DateTime recognition should get performed now.
var list = root.ToObject<List<PhysicalObject>>();

注意:

  1. 此解决方案不尝试保留引用,即使反序列化的{"ref":"some.period-separated.path"}引用与反序列化的原始实例相同的实例.尽管Json.NET确实具有通过保留对象引用的功能. >和"$id"属性,它有几个限制,包括:

  1. This solution does not attempt to preserve references, i.e. to make the deserialized {"ref":"some.period-separated.path"} refer to the same instance as the deserialized original. While Json.NET does have functionality to preserve object references via "$ref" and "$id" properties, it has several limitations including:

  • 它不处理对基元的引用,仅处理对象和数组.

  • It does not handle references to primitives, only objects and arrays.

不允许前向引用,仅允许向后引用.从这个问题尚不清楚,您的JSON中的"ref"属性是否可以引用文档后面的值.

It does not allow for forward-references, only backward references. It's not clear from the question whether the "ref" properties in your JSON might refer to values later in the document.


这些限制会使将问题中显示的参考语法转换为Json.NET的语法变得很复杂.


These limitations would complicate transforming the reference syntax shown in the question into Json.NET's syntax.

最好推迟 DateTime识别直到最终反序列化.如果您的模型具有string属性,其JSON值可能看起来像ISO 8601日期,那么过早的日期识别可能会导致字符串值被修改.

It's a good idea to defer DateTime recognition until final deserialization. If your model has string properties whose JSON values might happen to look like ISO 8601 dates then premature date recognition may cause the string values to get modified.

演示小提琴此处.

这篇关于如何使用Newtonsoft Json.NET处理JSON文档中的对象引用?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆