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();
            }
        }
    }
}