如何根据路径向 JSON 添加新的 JProperty?

use*_*207 6 c# json json.net

有一个很大的 JSON 文件(大约一千行)。任务是更新现有的 JProperties,或在结构中的特定位置添加新的 JProperties。新文本的位置基于 JToken.Path 属性。例如,这是 JSON 的开头:

"JonSnow": {
    "Direwolf": {
        "Name": "Ghost",
        "Color": "White",
    }
}
"DanaerysTargaryen": {
    "Dragons": {
        "Dragon1": {
            "Name": "Drogon",
        }
    }
    "Hair": {
        "Color": "White"
    }
}
Run Code Online (Sandbox Code Playgroud)

现在必须使用给定的 JToken 路径列表和相应的值更新 JSON。

第一种可能是,路径对应的 JProperty 可能已经存在,这种情况下需要更新值。我已经成功地使用JToken.Replace().

第二种可能是,JProperty 还不存在,需要添加。例如,我需要添加"DanaerysTargaryen.Dragons.Dragon1.Color"value "Black"

我知道我可以使用 JSON.NetAdd()方法,但是要使用这个方法,JSON 中只能缺少路径的最终子令牌。例如,我可以使用

JObject ObjToUpdate= JObject.Parse(jsonText);
JObject Dragon = ObjToUpdate["DanaerysTargaryen"]["Dragons"]["Dragon1"] as JObject;
Dragon.Add("Color", "Black"));
Run Code Online (Sandbox Code Playgroud)

但是如果我需要添加"JonSnow.Weapon.Type""Longsword"呢?因为"Weapon"还没有作为 JProperty 存在,它需要与"Type" : "Longsword". 对于每个路径,知道 JSON 中已经存在多少路径。这如何参数化?

// from outside source: Dictionary<string, string> PathBasedDict 
// key: Jtoken.Path (example: "JonSnow.Weapon.Type")
// value: new text to be added (example: "Longsword")

foreach(KeyValuePair entry in PathBasedDict)
{
    string path = entry.Key;
    string newText = entry.Value;

    if (ObjToUpdate.SelectToken(path) != null)  
        { ObjToUpdate.SelectToken(path).Replace(newText); }

    else AddToJson(path, newText);
}
Run Code Online (Sandbox Code Playgroud)

应该是什么AddToJson()样子?遍历整个路径并检查每个可能的 JProperty 以查看它是否存在,然后在下面添加其余部分,似乎非常麻烦。有一个更好的方法吗?任何我不知道的 Json.NET 技巧?我什至不确定如何对迭代进行参数化。

Her*_*key 5

有几种方法可以解决这个问题。这里有两个。

  1. 要使用您现有的代码,请将路径拆分为'.',然后遍历它们。如果路径不存在,请使用Add. 否则,如果我们在路径的最后一部分,只需添加值。

    var json = JObject.Parse(@"{""DanaerysTargaryen"":{""Dragons"":{""Dragon1"":{""Name"": ""Drogon""}},""Hair"": {""Color"": ""White""}}}");
    var toAdd = "DanaerysTargaryen.Dragons.Dragon1.Color";
    var valueToAdd = "Black";
    var pathParts = toAdd.Split('.');
    JToken node = json;
    for (int i = 0; i < pathParts.Length; i++)
    {
        var pathPart = pathParts[i];
        var partNode = node.SelectToken(pathPart);
        if (partNode == null && i < pathParts.Length - 1)
        {
            ((JObject)node).Add(pathPart, new JObject());
            partNode = node.SelectToken(pathPart);
        }
        else if (partNode == null && i == pathParts.Length - 1)
        {
            ((JObject)node).Add(pathPart, valueToAdd);
            partNode = node.SelectToken(pathPart);
        }
        node = partNode;
    }
    
    Console.WriteLine(json.ToString());
    
    Run Code Online (Sandbox Code Playgroud)

(dotnetfiddle.net 上的示例)

  1. 否则,您可以创建一个单独的JObject代表要添加的节点,然后合并它们。

     var json = JObject.Parse(@"{""DanaerysTargaryen"":{""Dragons"":{""Dragon1"":{""Name"": ""Drogon""}},""Hair"": {""Color"": ""White""}}}");
     var toMerge = @"{""DanaerysTargaryen"":{""Dragons"":{""Dragon1"":{""Color"":""Black""}}}}";
     var jsonToMerge = JObject.Parse(toMerge);
     json.Merge(jsonToMerge);
     Console.WriteLine(json.ToString());
    
    Run Code Online (Sandbox Code Playgroud)

(dotnetfiddle.net 上的示例)


yur*_*lav 5

基于来自异端猴子的回答的第一种方法,这是一个扩展方法:

public static class JObjectExtensions
{
    /// <summary>
    /// Replaces value based on path. New object tokens are created for missing parts of the given path.
    /// </summary>
    /// <param name="self">Instance to update</param>
    /// <param name="path">Dot delimited path of the new value. E.g. 'foo.bar'</param>
    /// <param name="value">Value to set.</param>
    public static void ReplaceNested(this JObject self, string path, JToken value)
    {
        if (self is null)
            throw new ArgumentNullException(nameof(self));
        
        if (string.IsNullOrEmpty(path))
            throw new ArgumentException("Path cannot be null or empty", nameof(path));

        var pathParts = path.Split('.');
        JToken currentNode = self;
        
        for (int i = 0; i < pathParts.Length; i++)
        {
            var pathPart = pathParts[i];
            var isLast = i == pathParts.Length - 1;
            var partNode = currentNode.SelectToken(pathPart);
            
            if (partNode is null)
            {
                var nodeToAdd = isLast ? value : new JObject();
                ((JObject)currentNode).Add(pathPart, nodeToAdd);
                currentNode = currentNode.SelectToken(pathPart);
            }
            else
            {
                currentNode = partNode;

                if (isLast)
                    currentNode.Replace(value);
            }
        }
    }
}
Run Code Online (Sandbox Code Playgroud)