如何使用System.Text.Json向JsonElement添加新属性?

3

假设我有一个Dictionary<string, object> metas。该对象可以是JsonElement(System.Text.Json)

并且如果该对象是一个元素数组,我需要遍历每个元素,并向每个元素添加新属性。

我可以这样遍历元素:

var metaValue = metas["key-1"];

if (metaValue is JsonElement elementValue && elementValue.ValueKind == JsonValueKind.Array)
 {
    // Iterate over the elements in the array
    foreach (var element in elementValue.EnumerateArray())
    {
        // add new property to element
        // like element["newproperty"] = "test value"
    }
 }


我的问题是,如何向JsonElement添加新属性?

你的 Dictionary<string, object> metas 是如何构建的?是通过将 JSON 反序列化为 Dictionary<string, object> 来构建的吗?例如:var metas = JsonSerializer.Deserialize<Dictionary<string, object>>(json) - dbc
还有,你使用的是哪个版本的.NET?是.NET 6、更新的版本还是早期的版本? - dbc
我正在使用 .net 6。dictionary<string, object> 是一个类 MyType 的属性,它是通过 JsonSerializer.Deserialize<MyType>(..) 方法从 JSON 反序列化而来的。 - Youxu
然后,如我在答案中建议的那样,我会使用选项JsonSerializerOptions.UnknownTypeHandling = JsonUnknownTypeHandling.JsonNode进行反序列化。这样,你的字典值将变为可变的JsonNode对象,而不是不可变的JsonElement对象。 - dbc
然后,如我的回答所建议的那样,我会使用选项JsonSerializerOptions.UnknownTypeHandling = JsonUnknownTypeHandling.JsonNode进行反序列化。这样,你的字典值将变为可变的JsonNode对象,而不是不可变的JsonElement对象。 - undefined
显示剩余3条评论
1个回答

1
您无法修改JsonElement,它是完全不可变的。相反,您必须将其反序列化为某个可变类型,例如JsonNode,对可变类型进行修改,然后重新序列化回JsonElement。以下方法可以实现这一点:
public static class JsonExtensions
{
    public static JsonNode TryAddPropertyToArrayElements<TProperty>(this JsonNode node, string name, TProperty value)
    {
        if (node is JsonArray array)
            foreach (var obj in array.OfType<JsonObject>())
                obj[name] = JsonSerializer.SerializeToNode(value);
        return node;
    }

    public static JsonElement TryAddPropertyToArrayElements<TProperty>(this JsonElement element, string name, TProperty value) =>
        element.ValueKind == JsonValueKind.Array
        ? JsonSerializer.SerializeToElement(JsonSerializer.Deserialize<JsonNode>(element)!.TryAddPropertyToArrayElements(name, value))
        : element;

    public static object? TryAddPropertyToArrayElements<TProperty>(this object? obj, string name, TProperty value) =>
        obj switch
        {
            JsonElement e => e.TryAddPropertyToArrayElements(name, value),
            JsonNode n => n.TryAddPropertyToArrayElements(name, value),
            null => null, // JSON values that are null are deserialized to the c# null value, not some element or node of type null
            _ => throw new ArgumentException("Unexpected type ${obj}"),
        };
}

然后你可以按照以下方式使用:

metas["key-1"] = metas["key-1"]?
    .TryAddPropertyToArrayElements("newproperty", "test value");

示例演示 #1 here">这里。

显然,所有这些序列化都有些低效。为了避免额外的序列化,假设您通过反序列化生成了字典,您可以使用JsonSerializerOptions.UnknownTypeHandling = JsonUnknownTypeHandling.JsonNode">这个设置进行反序列化。此设置强制将声明为object的类型反序列化为JsonNode而不是JsonElement

var inputOptions = new JsonSerializerOptions
{
    UnknownTypeHandling = JsonUnknownTypeHandling.JsonNode,
};
var metas = JsonSerializer.Deserialize<Dictionary<string, object?>>(json, inputOptions)!;

如果你这样做,你将能够直接使用上面包含的方法JsonExtensions.TryAddPropertyToArrayElements<TProperty>(this JsonNode node, string name, TProperty value)来直接改变它的值。
注意事项:
  • JsonNodeJsonSerializer.SerializeToElement()是在.NET 6中引入的,所以如果你使用的是早期版本,你需要采用不同的方法。
演示fiddle #2 here

谢谢 dbc!你的答案完美地解决了问题!只有一个更多的问题,在某些情况下,我只想将新添加的属性赋值为现有属性的值,使用这段代码 jsonObject["newproperty"] = JsonObject["existingProperty"]; // jsonObject是使用你的方法反序列化的JsonArray中的项。然后我得到了这个错误:System.InvalidOperationException: '该节点已经有一个父级。这是什么意思? - Youxu
@Youxu - 不客气。The node already has a parent 错误是因为 JsonNode 对象形成了双向连接的图:父节点知道它们的子节点,子节点也知道它们的父节点。所以当你将一个已经有父节点的子节点分配给另一个父节点时,库需要做一些事情,比如从旧的父节点中移除它,或者克隆它。由于这一功能未实现,所以会产生错误。请参考 在 .NET 6 中克隆 JsonNode 并将其附加到另一个 JsonNode - dbc
感谢 @dbc!已将答案标记为已回答。 - Youxu
谢谢 @dbc!我已将答案标记为已回答。 - undefined

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接