JSON.NET сериализовать JObject, игнорируя свойства null
у меня есть 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
});
к сожалению, это не сработало. Я наткнулся этот вопрос и понял, что a null
значение в объекте является фактическим JToken
тип и не совсем null
, который имеет смысл. Однако в этом конкретном случае мне нужно избавиться от этих" неиспользуемых " полей. Я попытался вручную перебирать узлы и удалять их, но это тоже не сработало. Обратите внимание, что единственный управляемый тип, который я использую, -JObject
; у меня нет модели для преобразования объекта или определения атрибутов, так как этот "шаблон" разрешается во время выполнения. Мне просто интересно, сталкивался ли кто-нибудь с такой проблемой и есть ли какие-либо идеи. Любая помощь очень ценится!
3 ответов
вы можете использовать рекурсивный вспомогательный метод, как показано ниже, чтобы удалить 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
}
}
}
Скрипка:https://dotnetfiddle.net/ZdYogJ
вы можете предотвратить создание нулевых токенов для начала, указав JsonSerializer
с NullValueHandler
значение NullValueHandler.Ignore
. Это передается в качестве параметра JObject.FromObject
как видно из ответа на тот же вопрос, который вы связали с:https://stackoverflow.com/a/29259032/263139.
ответ Брайана работает. Я также придумал другой (все еще рекурсивный) способ сделать это вскоре после публикации вопроса, если кто-то еще заинтересован.
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();
}
}
}
}