问题描述
与JSON有关的问题是将 PreserveReferencesHandling 设置为 PreserveReferencesHandling.Objects 进行序列化的结果.我正在寻找一种聪明的方法来修改JSON树的一个分支(特别是删除或替换该分支),以使引用处理不会中断.
Question relates to JSON which is result of serialization with PreserveReferencesHandling set to PreserveReferencesHandling.Objects. I'm looking for clever way for modifying a branch of JSON tree (removing or replacing branch in particular) so that references handling is not broken.
考虑以下JSON:
{
"OuterGroup": {
"ElementA": {
"$id": "1",
"data": "A"
},
"ElementB": {
"$id": "2",
"data": "B"
}
},
"OuterElement": {
"$ref": "1"
}
}
我需要更换或卸下ElementA.如果我通过 JToken.Replace 这样做,OuterElement引用将被破坏.
I need to replace or remove ElementA. If I do it by JToken.Replace, OuterElement reference will be broken.
我想到的第一个解决方案就像遍历树并在进行任何修改之前用引用的零件替换引用一样简单.我正在寻找更优雅的方法.
First solution that came to my mind was as simple as traversing tree and replacing references with referenced part before any modifications. I am looking for more elegant approach.
我正在处理的系统中的某些数据保留在JSON中.由于某些原因,我必须在不进行反序列化的情况下迁移它(旧模型不可用).
Some data in system I work on is persisted in JSON. For some reason I have to migrate it without deserialization (old model is unavailable).
推荐答案
尝试直接修改JSON会遇到问题,尤其是当您的JSON嵌套很深或对同一对象有多个引用时,所以我不会尝试方法.
Attempting to modify the JSON directly is going to problematic, especially if your JSON is deeply nested or has multiple references to the same object, so I would not try that approach.
我认为最好的方法是将JSON反序列化为对象层次结构(当然保留引用),然后根据需要修改对象,最后使用PreserveReferencesHandling.Objects
设置将层次结构序列化回JSON.
I think best way to do this is to actually deserialize the JSON to an object hierarchy (preserving the references of course), then modify the objects as needed, and finally serialize the hierarchy back to JSON using the PreserveReferencesHandling.Objects
setting.
如果您拥有原始的对象模型,这将非常容易,因为Json.Net已经支持此功能.但是,由于您没有原始的对象模型,因此必须将JSON反序列化为通用的东西.通常,我会说使用JTokens
对此非常合适,但是事实证明JToken
不支持保留对象引用.
If you had the original object model this would be very easy, as Json.Net supports this functionality already. However, since you don't have the original object model, you'll have to deserialize the JSON into something generic. Normally I would say that using JTokens
would be perfect for this, but it turns out that JToken
does not support preserving object references.
因此,看起来唯一可行的选择是手动处理反序列化.幸运的是,这并不像听起来那样糟糕.您可以使用JsonTextReader
来读取JSON,同时以递归方式建立通用词典和列表的层次结构以保存数据.在此过程中,您可以使用另一个字典作为查找表来跟踪对象引用.
So, it looks like the only real option is to handle the deserialization manually. Fortunately, this is not as bad as it sounds. You can use a JsonTextReader
to read the JSON while recursively building up a hierarchy of generic dictionaries and lists to hold the data. During the process, you can keep track of object references using another dictionary as a lookup table.
以下是封装此逻辑的方法.请注意,此方法确实有一些假设:
Below is a method which encapsulates this logic. Note this method does make some assumptions:
- 您的JSON格式正确(当然)
- 对于具有
$id
的JSON对象,$id
是对象中的第一个属性,并且表示相对于JSON中所有其他对象的唯一ID - 对于具有
$ref
的JSON对象,$ref
是该对象中的唯一属性,它引用早已在JSON中出现的$id
.
- Your JSON is well-formed (of course)
- For JSON objects that have a
$id
, that$id
is the first property in the object and represents a unique ID relative to all other objects in the JSON - For JSON objects that have a
$ref
, the$ref
is the only property in that object and it refers to an$id
that already appeared earlier in the JSON
如果首先使用PreserveReferencesHandling.Objects
设置使用Json.Net创建JSON,则所有这些都应为真.
All of these should hold true if Json.Net was used to create the JSON in the first place using the PreserveReferencesHandling.Objects
setting.
public static object DeserializePreservingReferences(string json)
{
using (JsonTextReader reader = new JsonTextReader(new StringReader(json)))
{
return DeserializePreservingReferences(reader,
new Dictionary<string, Dictionary<string, object>>());
}
}
private static object DeserializePreservingReferences(JsonTextReader reader,
Dictionary<string, Dictionary<string, object>> lookup)
{
if (reader.TokenType == JsonToken.None)
{
reader.Read();
}
if (reader.TokenType == JsonToken.StartArray)
{
List<object> list = new List<object>();
while (reader.Read() && reader.TokenType != JsonToken.EndArray)
{
list.Add(DeserializePreservingReferences(reader, lookup));
}
return list;
}
if (reader.TokenType == JsonToken.StartObject)
{
Dictionary<string, object> dict = new Dictionary<string, object>();
while (reader.Read() && reader.TokenType != JsonToken.EndObject)
{
string propName = (string)reader.Value;
reader.Read();
if (propName == "$ref")
{
dict = lookup[reader.Value.ToString()];
}
else if (propName == "$id")
{
lookup[reader.Value.ToString()] = dict;
}
else
{
dict.Add(propName, DeserializePreservingReferences(reader, lookup));
}
}
return dict;
}
return new JValue(reader.Value).Value;
}
使用此方法,您可以完成您最初想要的工作.这是一个简短的演示:
Armed with this method, you can accomplish what you originally wanted. Here is a short demo:
class Program
{
static void Main(string[] args)
{
string json = @"
{
""OuterGroup"": {
""ElementA"": {
""$id"": ""1"",
""data"": ""A""
},
""ElementB"": {
""$id"": ""2"",
""data"": ""B""
}
},
""OuterElement"": {
""$ref"": ""1""
}
}";
var root = (Dictionary<string, object>)DeserializePreservingReferences(json);
var g = (Dictionary<string, object>)root["OuterGroup"];
g.Remove("ElementA");
JsonSerializerSettings settings = new JsonSerializerSettings();
settings.PreserveReferencesHandling = PreserveReferencesHandling.Objects;
settings.Formatting = Formatting.Indented;
Console.WriteLine(JsonConvert.SerializeObject(root, settings));
}
}
下面是演示程序的输出.尽管在新的JSON中引用可能具有不同的$id
值,但这些引用已保留.请注意,已删除的ElementA
中的data
已根据需要移至OuterElement
.
Below is the output from the demo program. Although the references may have different $id
values in the new JSON, the references have been preserved. Notice the data
from the removed ElementA
has moved to the OuterElement
as you wanted.
{
"$id": "1",
"OuterGroup": {
"$id": "2",
"ElementB": {
"$id": "3",
"data": "B"
}
},
"OuterElement": {
"$id": "4",
"data": "A"
}
}
这篇关于不会破坏"$ ref"的JSON树修改参考的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!