JSON.NET 序列化 JObject,同时忽略空属性
JSON.NET serialize JObject while ignoring null properties
我有一个 JObject
用作调用 RESTful 网络服务的 模板 。这个 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
,这是有道理的。但是,在这种非常特殊的情况下,我需要能够摆脱这些 "unused" 字段。我尝试手动遍历节点并删除它们,但这也不起作用。请注意,我使用的唯一托管类型是 JObject
;我没有模型可以将对象转换为对象或在其上定义属性,因为这个 "template" 在运行时得到解析。我只是想知道是否有人遇到过这样的问题并且有任何见解。非常感谢任何帮助!
您可以使用如下所示的递归辅助方法在序列化之前从 JToken
层次结构中删除 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
请注意,删除所有空值后,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
}
}
}
Fiddle: https://dotnetfiddle.net/ZdYogJ
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();
}
}
}
}
您可以通过指定 JsonSerializer
并将其 NullValueHandler
设置为 NullValueHandler.Ignore
来防止创建空标记。这是作为参数传递给 JObject.FromObject
的,如您链接到的同一问题的答案所示:.
以下是我能够想到的。它删除仅包含空值的属性。这意味着它将处理 属性 是一个标量值为 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": ""
},
{}
]
}
我有一个 JObject
用作调用 RESTful 网络服务的 模板 。这个 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
,这是有道理的。但是,在这种非常特殊的情况下,我需要能够摆脱这些 "unused" 字段。我尝试手动遍历节点并删除它们,但这也不起作用。请注意,我使用的唯一托管类型是 JObject
;我没有模型可以将对象转换为对象或在其上定义属性,因为这个 "template" 在运行时得到解析。我只是想知道是否有人遇到过这样的问题并且有任何见解。非常感谢任何帮助!
您可以使用如下所示的递归辅助方法在序列化之前从 JToken
层次结构中删除 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
请注意,删除所有空值后,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
}
}
}
Fiddle: https://dotnetfiddle.net/ZdYogJ
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();
}
}
}
}
您可以通过指定 JsonSerializer
并将其 NullValueHandler
设置为 NullValueHandler.Ignore
来防止创建空标记。这是作为参数传递给 JObject.FromObject
的,如您链接到的同一问题的答案所示:
以下是我能够想到的。它删除仅包含空值的属性。这意味着它将处理 属性 是一个标量值为 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": ""
},
{}
]
}