我有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
});
不幸的是,这不起作用。我遇到this question并意识到对象中的null
值是实际的JToken
类型而不是真正的null
,这是有道理的。但是,在这种非常特殊的情况下,我需要能够摆脱这些“未使用”的字段。我尝试手动迭代节点并删除它们,但这也不起作用。请注意,我使用的唯一托管类型是JObject
;我没有将对象转换为或定义属性的模型,因为此“模板”在运行时得到解析。我只是想知道是否有人遇到过这样的问题并且有任何见解。非常感谢任何帮助!
答案 0 :(得分:17)
您可以使用下面的递归帮助方法从序列化之前删除null
层次结构中的JToken
值。
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": [
{}
]
}
}
小提琴:https://dotnetfiddle.net/wzEOie
请注意,在删除所有空值后,FooArray
中将有一个您可能不需要的空对象。 (如果删除了该对象,那么您将有一个空FooArray
,这也是您可能不想要的。)如果您希望在删除帮助方法时更加积极,可以更改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
}
}
}
答案 1 :(得分:2)
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();
}
}
}
}
答案 2 :(得分:1)
这就是我能想到的。它删除仅包含空值的属性。这意味着它将处理该属性为标量值为null的情况,还将处理存在一个全为null值的数组的情况。它还会删除没有值的属性。处理该属性包含不具有子属性的对象的情况。请注意,我的使用的JObject
具有Descendents()
方法,这使实现变得容易。 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:\r\n{jo}");
jo.RemoveNullAndEmptyProperties();
Console.WriteLine($"AFTER:\r\n{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": ""
},
{}
]
}
答案 3 :(得分:0)
您可以通过指定JsonSerializer
并将NullValueHandler
设置为NullValueHandler.Ignore
来阻止创建空令牌。这将作为参数传递给JObject.FromObject
,如您对链接到的相同问题的回答中所示:https://stackoverflow.com/a/29259032/263139。