web-dev-qa-db-ja.com

JSON.NETは、nullプロパティを無視してJObjectをシリアル化します

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.P2Foo.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のみであることに注意してください。この「テンプレート」は実行時に解決されるため、オブジェクトを変換したり、属性を定義したりするためのモデルがありません。誰かがこのような問題に遭遇し、何か洞察を持っているかどうか疑問に思っていました。どんな助けでも大歓迎です!

13
PoweredByOrange

以下のような再帰ヘルパーメソッドを使用して、シリアル化する前に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
    }
  }
}

フィドル: https://dotnetfiddle.net/ZdYogJ

20
Brian Rogers

ブライアンの答えはうまくいきます。また、他の誰かが興味を持っている場合に備えて、質問を投稿した直後に別の(まだ再帰的な)方法を考え出しました。

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
PoweredByOrange

これが私が思いついたものです。 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": ""
    },
    {}
  ]
}
0
Jon Miller

JsonSerializerNullValueHandler.Ignoreに設定して、NullValueHandlerを指定することにより、最初からnullトークンが作成されないようにすることができます。これは、リンクした同じ質問への回答に見られるように、パラメータとしてJObject.FromObjectに渡されます: https://stackoverflow.com/a/29259032/263139

0
jrupe