我有一个JObject
,它被用作调用RESTful web服务的模板。这个JObject
是通过解析器创建的,因为它被用作告诉用户端点模式是什么样子的模板,所以我必须找到一种方法来保留所有属性,这就是为什么我将它们的值默认为null
。例如,这就是对象最初的样子:
{
"Foo":{
"P1":null,
"P2":null,
"P3":null,
"P4":{
"P1":null,
"P2":null,
"P3":null,
},
"FooArray":[
{
"F1":null,
"F2":null,
"F3":null,
}
]
},
"Bar":null
}
然后,用户可以根据需要填写各个字段,例如Foo.P2
和Foo.P4.P1
:
{
"Foo":{
"P1":null,
"P2":"hello world",
"P3":null,
"P4":{
"P1":1,
"P2":null,
"P3":null,
},
"FooArray":[
{
"F1":null,
"F2":null,
"F3":null,
}
]
},
"Bar":null
}
这意味着他们只关心这两个领域。现在,我想将这个模板(JObject
(序列化回JSON字符串,但只想显示那些填充的字段。所以我尝试了这个:
string json = JsonConvert.SerializeObject(template,
new JsonSerializerSettings
{
NullValueHandling = NullValueHandling.Ignore
});
不幸的是,这并没有奏效。我遇到了这个问题,并意识到对象中的null
值是实际的JToken
类型,而不是真正的null
,这是有道理的。然而,在这种非常特殊的情况下,我需要能够去掉这些"未使用"的字段。我尝试手动迭代节点并删除它们,但这也不起作用。请注意,我使用的唯一托管类型是JObject
;我没有一个模型可以将对象转换为或在其上定义属性,因为这个"模板"在运行时得到解析。我只是想知道是否有人遇到过这样的问题,并有任何见解。非常感谢您的帮助!
在序列化JToken
层次结构之前,您可以使用类似下面这样的递归助手方法从CCD_12层级结构中删除null
值。
using System;
using Newtonsoft.Json.Linq;
public static class JsonHelper
{
public static JToken RemoveEmptyChildren(JToken token)
{
if (token.Type == JTokenType.Object)
{
JObject copy = new JObject();
foreach (JProperty prop in token.Children<JProperty>())
{
JToken child = prop.Value;
if (child.HasValues)
{
child = RemoveEmptyChildren(child);
}
if (!IsEmpty(child))
{
copy.Add(prop.Name, child);
}
}
return copy;
}
else if (token.Type == JTokenType.Array)
{
JArray copy = new JArray();
foreach (JToken item in token.Children())
{
JToken child = item;
if (child.HasValues)
{
child = RemoveEmptyChildren(child);
}
if (!IsEmpty(child))
{
copy.Add(child);
}
}
return copy;
}
return token;
}
public static bool IsEmpty(JToken token)
{
return (token.Type == JTokenType.Null);
}
}
演示:
string json = @"
{
""Foo"": {
""P1"": null,
""P2"": ""hello world"",
""P3"": null,
""P4"": {
""P1"": 1,
""P2"": null,
""P3"": null
},
""FooArray"": [
{
""F1"": null,
""F2"": null,
""F3"": null
}
]
},
""Bar"": null
}";
JToken token = JsonHelper.RemoveEmptyChildren(JToken.Parse(json));
Console.WriteLine(token.ToString(Formatting.Indented));
输出:
{
"Foo": {
"P2": "hello world",
"P4": {
"P1": 1
},
"FooArray": [
{}
]
}
}
Fiddle:https://dotnetfiddle.net/wzEOie
请注意,在删除所有null值后,FooArray
中会有一个空对象,这可能是您不想要的。(如果该对象被删除,那么你会有一个空的FooArray
,你可能也不想要。(如果你想让helper方法在删除时更积极,你可以将IsEmpty函数更改为:
public static bool IsEmpty(JToken token)
{
return (token.Type == JTokenType.Null) ||
(token.Type == JTokenType.Array && !token.HasValues) ||
(token.Type == JTokenType.Object && !token.HasValues);
}
有了这个改变,你的输出会变成这样:
{
"Foo": {
"P2": "hello world",
"P4": {
"P1": 1
}
}
}
Fiddle:https://dotnetfiddle.net/ZdYogJ
您可以通过指定JsonSerializer
并将其NullValueHandler
设置为NullValueHandler.Ignore
来防止从一开始就创建空令牌。这作为参数传递给JObject.FromObject
,如您链接到的同一问题的答案所示:https://stackoverflow.com/a/29259032/263139.
Brian的答案很有效。在发布问题后不久,我还想出了另一种(但仍然是递归的(方法,以防其他人感兴趣。
private void RemoveNullNodes(JToken root)
{
if (root is JValue)
{
if (((JValue)root).Value == null)
{
((JValue)root).Parent.Remove();
}
}
else if (root is JArray)
{
((JArray)root).ToList().ForEach(n => RemoveNullNodes(n));
if (!(((JArray)root)).HasValues)
{
root.Parent.Remove();
}
}
else if (root is JProperty)
{
RemoveNullNodes(((JProperty)root).Value);
}
else
{
var children = ((JObject)root).Properties().ToList();
children.ForEach(n => RemoveNullNodes(n));
if (!((JObject)root).HasValues)
{
if (((JObject)root).Parent is JArray)
{
((JArray)root.Parent).Where(x => !x.HasValues).ToList().ForEach(n => n.Remove());
}
else
{
var propertyParent = ((JObject)root).Parent;
while (!(propertyParent is JProperty))
{
propertyParent = propertyParent.Parent;
}
propertyParent.Remove();
}
}
}
}
使用JsonPath
,我们可以获得更优雅的解决方案:
jObject.SelectTokens("$..*")
.OfType<JValue>()
.Where(x=>x.Type == JTokenType.Null)
.Select(a => a.Parent)
.ToList()
.ForEach(a => a.Remove());
这里有一个工作示例:https://dotnetfiddle.net/zVgXOq
以下是我能想到的。它删除只包含null值的属性。这意味着它将处理属性为null的标量值的情况,还将处理数组全部为null的情况。它还会删除没有值的特性。这将处理属性包含没有子属性的对象的情况。注意,我使用了一个JObject
,它有一个CCD21方法,这使得实现变得容易。JToken
没有。我的实现改变了JObject
本身,而不是创建它的副本。此外,它会继续删除属性,直到不再出现为止。它比其他实现更简洁一些。我不知道它与性能相比如何。
using Newtonsoft.Json.Linq;
using System;
using System.IO;
using System.Linq;
namespace JsonConsoleApplication
{
class Program
{
static void Main(string[] args)
{
var jo = JObject.Parse(File.ReadAllText(@"test.json"));
Console.WriteLine($"BEFORE:rn{jo}");
jo.RemoveNullAndEmptyProperties();
Console.WriteLine($"AFTER:rn{jo}");
}
}
public static class JObjectExtensions
{
public static JObject RemoveNullAndEmptyProperties(this JObject jObject)
{
while (jObject.Descendants().Any(jt => jt.Type == JTokenType.Property && (jt.Values().All(a => a.Type == JTokenType.Null) || !jt.Values().Any())))
foreach (var jt in jObject.Descendants().Where(jt => jt.Type == JTokenType.Property && (jt.Values().All(a => a.Type == JTokenType.Null) || !jt.Values().Any())).ToArray())
jt.Remove();
return jObject;
}
}
}
以下是程序输出:
BEFORE:
{
"propertyWithValue": "",
"propertyWithObjectWithProperties": {
"nestedPropertyWithValue": "",
"nestedPropertyWithNull": null
},
"propertyWithEmptyObject": {},
"propertyWithObjectWithPropertyWithNull": {
"nestedPropertyWithNull": null
},
"propertyWithNull": null,
"emptyArray": [],
"arrayWithNulls": [
null,
null
],
"arrayWithObjects": [
{
"propertyWithValue": ""
},
{
"propertyWithNull": null
}
]
}
AFTER:
{
"propertyWithValue": "",
"propertyWithObjectWithProperties": {
"nestedPropertyWithValue": ""
},
"arrayWithObjects": [
{
"propertyWithValue": ""
},
{}
]
}