Как пропустить / игнорировать / пропускать пустые литералы объектов в выпуске JSON?
Я использую Json.NET
для преобразования сложного графа объектов C#
в JSON. Из-за игнорирования свойств, которые имеют значения по умолчанию в объекте, я обычно получаю пустые литералы объектов на выходе, которые я бы хотел опустить.
Например:
public class Sample { public int Value { get; set; } public string Name { get; set; } } public class ParentSample { // this property should never be null, hence the initializer public Sample Sample { get; } = new Sample(); } .. var obj = new ParentSample(); // settings for indentation and excluding default values omitted for clarity var output = JsonConvert.SerializeObject(obj, ... ); // output will be // { // Sample: {} // } // // I'd like it to be // {}
Я знаю некоторые конкретные решения, такие как добавление булевского метода ShouldSerializeSample
к типу ParentSample
и проверка наличия по умолчанию всех свойств. Однако я хотел бы получить общее решение в виде пользовательского адаптера контракта, например.
- Сериализация типа Json.Net с полиморфным дочерним объектом
- Как реализовать пользовательский JsonConverter в JSON.NET для десериализации списка объектов базового classа?
- Deserialize json с известными и неизвестными полями
- Что такое эквивалент JSON.NET XPath, SelectNodes XML, SelectSingleNode?
- Как обрабатывать как отдельный элемент, так и массив для одного и того же свойства с помощью JSON.net
- Как установить Json.Net в качестве сериализатора по умолчанию для службы WCF REST
- JSON.NET как сериализатор OAP для WebAPI 2 и ODataMediaTypeFormatter
- Как десериализовать объект JSON с недопустимым именем поля в нем
- Как я могу разбирать JSON с C #?
- JsonValueProviderFactory выбрасывает «запрос слишком большой»
- Как изменить имена свойств при сериализации с помощью Json.net?
- Указание пользовательского формата DateTime при сериализации с помощью Json.Net
- Сериализовать контейнер перечислений в виде строк с помощью JSON.net
В комментариях похоже, что вы решили прибегнуть к использованию Regex, чтобы избавиться от пустых объектов. Одна из проблем с этой идеей заключается в том, что она, вероятно, не будет обрабатывать ситуацию, когда у вас есть то, что я назову «рекурсивные пустые объекты». Другими словами, что-то вроде этого:
{ "foo": { "bar": {}, "baz": {} } }
Если вам удастся выровнять bar
с пустым объектом с самым высоким уровнем и baz
с помощью Regex (хотя вы также понимаете, что вам нужно удалить запятую между ними, чтобы JSON был действителен), вы все равно оставите пустой объект: foo
.
{ "foo": { } }
Я считаю, что лучшим решением является загрузка ваших данных в иерархию JToken
а затем использование рекурсивного метода для удаления всех пустых детей, прежде чем записывать их в JSON. Что-то вроде этого должно работать для ваших нужд:
using System; using Newtonsoft.Json.Linq; public static class JsonHelper { public static string SerializeToMinimalJson(object obj) { return JToken.FromObject(obj).RemoveEmptyChildren().ToString(); } public static JToken RemoveEmptyChildren(this JToken token) { if (token.Type == JTokenType.Object) { JObject copy = new JObject(); foreach (JProperty prop in token.Children()) { JToken child = prop.Value; if (child.HasValues) { child = child.RemoveEmptyChildren(); } if (!child.IsEmptyOrDefault()) { 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 = child.RemoveEmptyChildren(); } if (!child.IsEmptyOrDefault()) { copy.Add(child); } } return copy; } return token; } public static bool IsEmptyOrDefault(this JToken token) { return (token.Type == JTokenType.Array && !token.HasValues) || (token.Type == JTokenType.Object && !token.HasValues) || (token.Type == JTokenType.String && token.ToString() == String.Empty) || (token.Type == JTokenType.Boolean && token.Value() == false) || (token.Type == JTokenType.Integer && token.Value() == 0) || (token.Type == JTokenType.Float && token.Value() == 0.0) || (token.Type == JTokenType.Null); } }
Затем вы можете сериализовать свой объект (ы) следующим образом:
var json = JsonHelper.SerializeToMinimalJson(obj);
Сценарий: https://dotnetfiddle.net/awRPMR
РЕДАКТИРОВАТЬ
Если вы хотите почитать [DefaultValue]
этим методом, вы можете сделать это, изменив метод SerializeToMinimalJson()
чтобы создать экземпляр JsonSerializer
, установив на DefaultValueHandling
свойство DefaultValueHandling
, а затем передав его в JToken.FromObject()
как показано ниже. (Это нужно сделать так, потому что у JTokens
нет ссылок на исходные объекты, из которых они были созданы с помощью FromObject()
, поэтому после этого невозможно получить значения атрибутов [DefaultValue]
.)
public static string SerializeToMinimalJson(object obj) { var serializer = new JsonSerializer(); serializer.NullValueHandling = NullValueHandling.Ignore; serializer.DefaultValueHandling = DefaultValueHandling.Ignore; return JToken.FromObject(obj, serializer).RemoveEmptyChildren().ToString(); }
Если вы это сделаете, вы также можете изменить метод IsEmptyOrDefault()
чтобы он не IsEmptyOrDefault()
значения, которые являются стандартными по умолчанию. Вы можете уменьшить его до:
public static bool IsEmptyOrDefault(this JToken token) { return (token.Type == JTokenType.Array && !token.HasValues) || (token.Type == JTokenType.Object && !token.HasValues); }
Сценарий: https://dotnetfiddle.net/0yVRI5
Вы можете дать JsonSerializerSettings
для метода, используя NullValueHandling.Ignore
:
var output = JsonConvert.SerializeObject(obj, new JsonSerializerSettings() { NullValueHandling = NullValueHandling.Ignore });
Если эти настройки не дают то, что вам нужно, проверьте: документацию. Там вы найдете все свойства и описание.
Изменить: использование дочернего элемента (Sample) в качестве структуры работает с DefaultValueHandling.Ignore. Но @ Zoltán Tamási будет использовать регулярное выражение из-за сложности classа.
Я внедрил немного другое решение, которое использует общий метод, reflection и некоторую стандартную функциональность Newtonsoft.Json ShouldSerialize. Не элегантный, но концептуально простой для моей конкретной потребности. Ниже приведен fragment кода LinqPad.
void Main() { Person person = new Person(); person.MyAddress = new Address(); var ret = person.ShouldSerializeMyAddress(); var json = JsonConvert.SerializeObject(person, Newtonsoft.Json.Formatting.Indented, new JsonSerializerSettings { NullValueHandling = NullValueHandling.Ignore }); json.Dump(); } public static class JsonExtensions { public static bool ShouldSerialize(this object self) { if (self == null) return false; var methods = self.GetType().GetMethods().Where(p => p.Name.StartsWith("ShouldSerialize")); return methods.Any(p => p.Invoke(self, null) is bool value && value); } } public class Person { public Address MyAddress { get; set; } public bool ShouldSerializeMyAddress() { return MyAddress.ShouldSerialize(); } } public class Address { public string Street { get; set; } public bool ShouldSerializeStreet() { return false; // or whatever your property serialization criteria should be } public string City { get; set; } public bool ShouldSerializeCity() { return false; } public string State { get; set; } public bool ShouldSerializeState() { return false; } public string Zip { get; set; } public bool ShouldSerializeZip() { return false; } }