且构网

分享程序员开发的那些事...
且构网 - 分享程序员编程开发的那些事

如何使用Newtonsoft Json.NET处理JSON文档中的对象引用?

更新时间:2023-01-17 18:37:06

您可以将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.

演示小提琴此处.