RESTful Webサービスを呼び出すためのテンプレートとして使用されるJObject
があります。この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
}
つまり、これら2つのフィールドのみを気にします。ここで、このテンプレート(JObject
)をシリアル化してJSON文字列に戻したいのですが、入力されたフィールドのみを表示したいと思います。だから私はこれを試しました:
string json = JsonConvert.SerializeObject(template,
new JsonSerializerSettings
{
NullValueHandling = NullValueHandling.Ignore
});
残念ながら、これは機能しませんでした。 この質問 に出くわし、オブジェクトのnull
値は実際のJToken
型であり、実際にはnull
ではないことに気付きました。これは理にかなっています。 。ただし、この非常に特殊なケースでは、これらの「未使用」フィールドを削除できる必要があります。ノードを手動で繰り返して削除しようとしましたが、それも機能しませんでした。私が使用しているマネージドタイプはJObject
のみであることに注意してください。この「テンプレート」は実行時に解決されるため、オブジェクトを変換したり、属性を定義したりするためのモデルがありません。誰かがこのような問題に遭遇し、何か洞察を持っているかどうか疑問に思っていました。どんな助けでも大歓迎です!
以下のような再帰ヘルパーメソッドを使用して、シリアル化する前に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
すべてのnull値を削除すると、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
}
}
}
ブライアンの答えはうまくいきます。また、他の誰かが興味を持っている場合に備えて、質問を投稿した直後に別の(まだ再帰的な)方法を考え出しました。
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();
}
}
}
}
これが私が思いついたものです。 null値のみを含むプロパティを削除します。これは、プロパティがnullのスカラー値である場合を処理し、すべてnull値である配列がある場合も処理することを意味します。また、値のないプロパティも削除されます。これは、プロパティに子プロパティを持たないオブジェクトが含まれている場合を処理します。 Descendents()
メソッドを持つJObject
を使用していることに注意してください。これにより、実装が簡単になりました。 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": ""
},
{}
]
}
JsonSerializer
をNullValueHandler.Ignore
に設定して、NullValueHandler
を指定することにより、最初からnullトークンが作成されないようにすることができます。これは、リンクした同じ質問への回答に見られるように、パラメータとしてJObject.FromObject
に渡されます: https://stackoverflow.com/a/29259032/263139 。