Json.NET: удаление пустых объектов из массива при сериализации [duplicate]

Поместите ваш плавающий div(s) в div и в CSS дайте ему overflow:hidden;, он будет работать нормально.

3
задан Zoltán Tamási 27 April 2016 в 08:59
поделиться

3 ответа

В комментариях похоже, что вы решили прибегнуть к использованию 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<JProperty>())
            {
                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<bool>() == false) ||
               (token.Type == JTokenType.Integer && token.Value<int>() == 0) ||
               (token.Type == JTokenType.Float && token.Value<double>() == 0.0) || 
               (token.Type == JTokenType.Null);
    }

}

Затем вы можете сериализовать свой объект (ы) следующим образом:

var json = JsonHelper.SerializeToMinimalJson(obj);

Fiddle: https: // dotnetfiddle.net/awRPMR

EDIT

Если вы хотите почитать атрибут [DefaultValue] с помощью этого метода, вы можете сделать это, изменив метод SerializeToMinimalJson() на создайте экземпляр JsonSerializer, установив на него свойство 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(), чтобы он не удалял значения, которые являются стандартными по умолчанию. Вы можете уменьшить его до:

public static bool IsEmptyOrDefault(this JToken token)
{
    return (token.Type == JTokenType.Array && !token.HasValues) ||
           (token.Type == JTokenType.Object && !token.HasValues);
}

Fiddle: https://dotnetfiddle.net/0yVRI5

3
ответ дан Brian Rogers 19 August 2018 в 12:00
поделиться
  • 1
    Спасибо за отличный ответ, я не знал об этих манипуляциях. Один важный вопрос о вашем решении, возможно ли поддерживать DefaultValueAttribute при проверке, является ли значение токена «стандартным» ?? – Zoltán Tamási 28 April 2016 в 07:01
  • 2
    Кстати, я решил проблему рекурсивного пустого объекта, поставив решение Regex в цикл. Вид уродливой и грубой силы, но для моего нынешнего сценария это казалось приемлемым. – Zoltán Tamási 28 April 2016 в 07:02
  • 3
    @ ZoltánTamási Я обновил свой ответ, чтобы показать, как заставить его уважать DefaultValueAttribute. Кроме того, вы правы, я полагаю, что Regex будет работать, если вы будете применять его повторно в цикле до тех пор, пока не будет сделано никаких замен. Ну, вы, конечно, можете свободно использовать любой метод, который лучше всего подходит для вашей ситуации. Теперь у вас есть еще один инструмент в панели инструментов. – Brian Rogers 28 April 2016 в 15:10

Вы можете дать JsonSerializerSettings методу, используя NullValueHandling.Ignore:

var output = JsonConvert.SerializeObject(obj, new JsonSerializerSettings()
{
    NullValueHandling = NullValueHandling.Ignore
});

Если эти настройки не дают то, что вам нужно, проверьте: документацию. Здесь вы можете найти все свойства и описание.

Изменить: использование дочернего элемента (Sample) в качестве структуры, которое работает с DefaultValueHandling.Ignore. Но @ Zoltán Tamási будет использовать регулярное выражение из-за сложности класса.

0
ответ дан Oscar Vicente Perez 19 August 2018 в 12:00
поделиться
  • 1
    Спасибо за ответ, моя проблема в том, что дочерний объект не является нулевым, но рассматривается как пустой (поскольку все его свойства имеют значения по умолчанию). Я думаю, что вы указали на что-то другое. – Zoltán Tamási 27 April 2016 в 09:21
  • 2
    Вы получили DefaultValueHandling – Oscar Vicente Perez 27 April 2016 в 09:23
  • 3
    Да, я использую его для игнорирования значений по умолчанию, поэтому вложенный объект пуст в JSON. Но он не может использоваться для обработки значения Sample, как и по умолчанию (поскольку по умолчанию для ссылки является null). Надеюсь, вы понимаете, что я имею в виду – Zoltán Tamási 27 April 2016 в 09:25
  • 4
    Теперь да, проверьте обновление ответа и попробуйте его. – Oscar Vicente Perez 27 April 2016 в 09:26
  • 5
    Попробуй, пожалуйста. – Oscar Vicente Perez 27 April 2016 в 09:32

Я реализовал немного другое решение, которое использует общий метод, отражение и некоторую стандартную функциональность Newtonsoft.Json ShouldSerialize. Не элегантный, но концептуально простой для моей конкретной потребности. Ниже приведен фрагмент кода 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;
    }
}
0
ответ дан PsiMatrix 19 August 2018 в 12:00
поделиться
Другие вопросы по тегам:

Похожие вопросы: