2015-10-08 4 views
8

У меня есть JObject, который используется как шаблон для вызова веб-сервисов RESTful. Этот JObject создается через синтаксический анализатор, и поскольку он используется в качестве шаблона, указывающего пользователю, как выглядит схема конечных точек, мне нужно было выяснить способ сохранения всех свойств, поэтому я по умолчанию делю их значения на null. В качестве примера, это то, что объект изначально выглядит следующим образом:JSON.NET сериализует JObject при игнорировании нулевых свойств

{ 
    "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 
    }); 

К сожалению, это не сработало. Я наткнулся на this question и понял, что значение null в объекте - это фактический тип JToken и не действительно null, что имеет смысл. Однако в этом конкретном случае мне нужно избавиться от этих «неиспользуемых» полей. Я пробовал вручную перебирать узлы и удалять их, но это тоже не сработало. Обратите внимание, что единственный управляемый тип, который я использую, - JObject; У меня нет модели для преобразования объекта или определения атрибутов, поскольку этот «шаблон» разрешается во время выполнения. Мне просто было интересно, если кто-нибудь столкнулся с такой проблемой, и у нее есть какие-то идеи. Любая помощь очень ценится!

ответ

11

Вы можете использовать метод рекурсивного помощника, как показано ниже, чтобы удалить значения 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": [ 
     {} 
    ] 
    } 
} 

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

+0

Спасибо! Я придумал почти аналогичный алгоритм вскоре после размещения вопроса, но ваш ответ по-прежнему действителен :) – PoweredByOrange

+0

Нет проблем; Рад, что смог помочь. –

+0

Я застрял, пытаясь добиться этого, спасибо за ваш вклад, он работает! –

2

ответ Брайан работает. Я также придумал еще один (но все же рекурсивный) способ сделать это вскоре после публикации вопроса, в случае, если кто-то еще заинтересован.

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

Вы можете предотвратить нулевые токены создается, чтобы начать с, указав JsonSerializer с NullValueHandler набором для NullValueHandler.Ignore. Это передается как параметр в JObject.FromObject, как показано в ответе на тот же вопрос, который вы связали с: https://stackoverflow.com/a/29259032/263139.

Смежные вопросы