问题描述
我有一个 JSON 字符串,我希望它包含我无法让 JSON.NET 满意的重复键.
I have a JSON string that I expect to contain duplicate keys that I am unable to make JSON.NET happy with.
我想知道是否有人知道最好的方法(也许使用 JsonConverter
?)让 JSON.NET 更改 JObject
的子 JObjects当看到重复的键名时,code> 进入到
JArrays
中?
I was wondering if anybody knows the best way (maybe using JsonConverter
? ) to get JSON.NET to change a JObject
's child JObjects
into to JArrays
when it sees duplicate key names ?
// For example: This gives me a JObject with a single "JPropertyJObject" child.
var obj = JsonConvert.DeserializeObject<object>("{ "HiThere":1}");
// This throws:
// System.ArgumentException : Can not add Newtonsoft.Json.Linq.JValue to Newtonsoft.Json.Linq.JObject.
obj = JsonConvert.DeserializeObject<object>("{ "HiThere":1, "HiThere":2, "HiThere":3 }");
我试图反序列化的实际 JSON 要复杂得多,并且重复项嵌套在多个级别.但是上面的代码说明了为什么它对我来说失败了.
The actual JSON I am trying to deserialize is much more complicated and the duplicates are nested at multiple levels. But the code above demonstrates why it fails for me.
我知道 JSON 不正确,这就是为什么我要问 JSON.NET 是否有办法解决这个问题.为了争论,假设我无法控制 JSON.实际上,我确实为父对象使用了特定类型,但有问题的特定属性将是字符串或另一个嵌套的 JSON 对象.由于这个原因,失败的属性类型是对象".
I understand that the JSON is not correct which is why I am asking if JSON.NET has a way to work around this. For argument's sake let's say I do not have control over the JSON. I actually do use a specific type for the parent object but the particular property that is having trouble will either be a string or another nested JSON object. The failing property type is "object" for this reason.
推荐答案
有趣的问题.我玩了一会儿,发现虽然 JObject
不能包含具有重复名称的属性,但用于在反序列化期间填充它的 JsonTextReader
没有这样的限制.(如果您考虑一下,这是有道理的:它是一个仅向前的阅读器;它不关心过去阅读的内容).有了这些知识,我开始编写一些代码来填充 JToken 的层次结构,如果在特定 JObject 中遇到重复的属性名称,则根据需要将属性值转换为 JArray.由于我不知道您的实际 JSON 和要求,您可能需要对其进行一些调整,但至少要从头开始.
Interesting question. I played around with this for a while and discovered that while a JObject
cannot contain properties with duplicate names, the JsonTextReader
used to populate it during deserialization does not have such a restriction. (This makes sense if you think about it: it's a forward-only reader; it is not concerned with what it has read in the past). Armed with this knowledge, I took a shot at writing some code that will populate a hierarchy of JTokens, converting property values to JArrays as necessary if a duplicate property name is encountered in a particular JObject. Since I don't know your actual JSON and requirements, you may need to make some adjustments to it, but it's something to start with at least.
代码如下:
public static JToken DeserializeAndCombineDuplicates(JsonTextReader reader)
{
if (reader.TokenType == JsonToken.None)
{
reader.Read();
}
if (reader.TokenType == JsonToken.StartObject)
{
reader.Read();
JObject obj = new JObject();
while (reader.TokenType != JsonToken.EndObject)
{
string propName = (string)reader.Value;
reader.Read();
JToken newValue = DeserializeAndCombineDuplicates(reader);
JToken existingValue = obj[propName];
if (existingValue == null)
{
obj.Add(new JProperty(propName, newValue));
}
else if (existingValue.Type == JTokenType.Array)
{
CombineWithArray((JArray)existingValue, newValue);
}
else // Convert existing non-array property value to an array
{
JProperty prop = (JProperty)existingValue.Parent;
JArray array = new JArray();
prop.Value = array;
array.Add(existingValue);
CombineWithArray(array, newValue);
}
reader.Read();
}
return obj;
}
if (reader.TokenType == JsonToken.StartArray)
{
reader.Read();
JArray array = new JArray();
while (reader.TokenType != JsonToken.EndArray)
{
array.Add(DeserializeAndCombineDuplicates(reader));
reader.Read();
}
return array;
}
return new JValue(reader.Value);
}
private static void CombineWithArray(JArray array, JToken value)
{
if (value.Type == JTokenType.Array)
{
foreach (JToken child in value.Children())
array.Add(child);
}
else
{
array.Add(value);
}
}
这是一个演示:
class Program
{
static void Main(string[] args)
{
string json = @"
{
""Foo"" : 1,
""Foo"" : [2],
""Foo"" : [3, 4],
""Bar"" : { ""X"" : [ ""A"", ""B"" ] },
""Bar"" : { ""X"" : ""C"", ""X"" : ""D"" },
}";
using (StringReader sr = new StringReader(json))
using (JsonTextReader reader = new JsonTextReader(sr))
{
JToken token = DeserializeAndCombineDuplicates(reader);
Dump(token, "");
}
}
private static void Dump(JToken token, string indent)
{
Console.Write(indent);
if (token == null)
{
Console.WriteLine("null");
return;
}
Console.Write(token.Type);
if (token is JProperty)
Console.Write(" (name=" + ((JProperty)token).Name + ")");
else if (token is JValue)
Console.Write(" (value=" + token.ToString() + ")");
Console.WriteLine();
if (token.HasValues)
foreach (JToken child in token.Children())
Dump(child, indent + " ");
}
}
输出:
Object
Property (name=Foo)
Array
Integer (value=1)
Integer (value=2)
Integer (value=3)
Integer (value=4)
Property (name=Bar)
Array
Object
Property (name=X)
Array
String (value=A)
String (value=B)
Object
Property (name=X)
Array
String (value=C)
String (value=D)
这篇关于如何在同一对象中反序列化具有重复属性名称的 JSON的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持编程学习网!