Как отменить запись свойства из конвертера json?

Требование.

Для сериализации json с помощью json.net:

  1. Преобразуйте недопустимые двойные значения (например, double.NaN) в null.
  2. Сделайте вдвое короче (G3).
  3. Не записывайте свойства со значениями null.

Звучит просто?

Очевидное решение.

Первое и второе делается путем создания конвертера json, верно?

Третий делается с помощью JsonSerializerSettings с NullValueHandling = NullValueHandling.Ignore, верно?

Ну, есть проблема, они не работают вместе. Если значение null, то конвертер даже не вызывается, хорошо. Но если преобразователь выдает null, игнорирования нулевого значения не происходит.

Пытаться.

Ниже приведен код или попробуйте воспроизвести напрямую. Что я должен делать?


    static void Main(string[] args)
    {
        var test = new A(
            d: 1.23456789,
            nd1: 1.23456789,
            nd2: null,
            nd3: double.NaN,
            da: [1.23456789],
            nda: [1.23456789, null, double.NaN]);

        var json = JsonConvert.SerializeObject(test, new JsonSerializerSettings
        {
            NullValueHandling = NullValueHandling.Ignore,
            Converters = { new MyJsonConverter() }
        });

        Console.WriteLine(json);
    }


public class MyJsonConverter : JsonConverter
{
    public override bool CanConvert(Type objectType) => objectType == typeof(double) || objectType == typeof(double?);

    public override void WriteJson(JsonWriter writer, object? value, JsonSerializer serializer)
    {
        if (value is double input && double.IsFinite(input))
            writer.WriteRawValue(input.ToString("G3", NumberFormatInfo.InvariantInfo));
        else
            writer.WriteNull();
    }

    public override object? ReadJson(JsonReader reader, Type objectType, object? existingValue, JsonSerializer serializer) => throw new NotImplementedException();
}

public record A(
    double d,
    double? nd1,
    double? nd2,
    double? nd3,
    double[] da,
    double?[] nda)
{ }

Выход:

{"d":1.23, "nd1":1.23, "nd3":null,"da":[1.23],"nda":[1.23,null,null]}

Ожидаемый результат без nd3:

{"d":1.23, "nd1":1.23, "da":[1.23],"nda":[1.23,null,null]}

P.S.: Я не мог придумать хорошее название, поэтому спросил, как решить мою попытку решения (проблема XY). Я думаю, что такое название лучше всего описывает намерение, и кто-то может столкнуться с аналогичной проблемой, когда значение, создаваемое преобразователем, должно влиять на свойство json.

Ознакомьтесь с документацией по атрибутам сериализации

Cleptus 30.08.2024 12:12

@Клептус, какая твоя идея? Какой атрибут и где применять?

Sinatr 30.08.2024 12:15

Нет необходимости создавать собственный конвертер. Я предлагал атрибуты JsonObject(MemberSerialization.OptIn) и JsonProperty согласно примеру в документации, на которую я дал ссылку.

Cleptus 30.08.2024 12:35

Также для двухдесятичного форматирования вы можете использовать специальный конвертер для этих свойств. Некоторое время назад я ответил на аналогичный вопрос с преобразованием даты и времени

Cleptus 30.08.2024 12:55

@Cleptus, вы предлагаете полностью исключить свойство из сериализации, даже если оно содержит допустимое, ненулевое значение? Это не мое желание. Во втором комментарии вы говорите, что мне нужен конвертер, как эти два комментария объединяются?

Sinatr 30.08.2024 13:16

Не заметил требования «Не писать свойства с нулевыми значениями». Мой первый комментарий будет работать только в том случае, если одно и то же свойство всегда имеет нулевое значение (думаю, это не так). Второй комментарий — это быстрый и простой способ форматирования свойства (G3 или другой формат).

Cleptus 30.08.2024 13:27
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
0
6
50
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Для этого вам необходимо объединить собственную логику преобразования с обработкой нулевых значений. Проблема, с которой вы столкнулись, возникает из-за того, что нулевое значение, созданное вашим преобразователем, не вызывает поведение NullValueHandling.Ignore. Эту проблему можно решить, изменив способ сериализации объектов.

Пользовательский конвертер: ваш MyJsonConverter должен обрабатывать double.NaN, преобразовывая его в значение null и форматируя действительные двойные значения с использованием формата G3, как вы уже сделали.

Пользовательский преобразователь контрактов. Чтобы пропустить свойства с нулевыми значениями, созданными преобразователем, вы можете использовать собственный преобразователь контрактов. Этот преобразователь исключит свойства с нулевыми значениями из процесса сериализации.

Код:

using Newtonsoft.Json;
using Newtonsoft.Json.Serialization;
using System;
using System.Globalization;

public class MyJsonConverter : JsonConverter
{
    public override bool CanConvert(Type objectType) => objectType == typeof(double) || objectType == typeof(double?);

    public override void WriteJson(JsonWriter writer, object? value, JsonSerializer serializer)
    {
        if (value is double input && double.IsFinite(input))
            writer.WriteRawValue(input.ToString("G3", NumberFormatInfo.InvariantInfo));
        else
            writer.WriteNull();
    }

    public override object? ReadJson(JsonReader reader, Type objectType, object? existingValue, JsonSerializer serializer) => throw new NotImplementedException();
}

public class SkipNullPropertyContractResolver : DefaultContractResolver
{
    protected override JsonProperty CreateProperty(System.Reflection.MemberInfo member, MemberSerialization memberSerialization)
    {
        var property = base.CreateProperty(member, memberSerialization);

        // Override the ShouldSerialize method to check if the value is null after applying the converter
        property.ShouldSerialize = instance =>
        {
            var value = property.ValueProvider.GetValue(instance);
            if (value == null)
                return false;

            // Use a dummy JsonWriter to determine if the converter will output null
            using (var writer = new System.IO.StringWriter())
            {
                var jsonWriter = new JsonTextWriter(writer);
                var serializer = new JsonSerializer();
                serializer.Converters.Add(new MyJsonConverter());

                serializer.Serialize(jsonWriter, value);
                var serializedValue = writer.ToString();

                return serializedValue != "null";
            }
        };

        return property;
    }
}

public record A(
    double d,
    double? nd1,
    double? nd2,
    double? nd3,
    double[] da,
    double?[] nda)
{ }

class Program
{
    static void Main(string[] args)
    {
        var test = new A(
            d: 1.23456789,
            nd1: 1.23456789,
            nd2: null,
            nd3: double.NaN,
            da: new[] { 1.23456789 },
            nda: new double?[] { 1.23456789, null, double.NaN });

        var settings = new JsonSerializerSettings
        {
            NullValueHandling = NullValueHandling.Ignore,
            Converters = { new MyJsonConverter() },
            ContractResolver = new SkipNullPropertyContractResolver()
        };

        var json = JsonConvert.SerializeObject(test, settings);
        Console.WriteLine(json);
    }
}

Ожидаемый результат:

{"d":1.23,"nd1":1.23,"da":[1.23],"nda":[1.23,null,null]}

Таким образом, гарантируется, что свойства со значениями NULL, независимо от того, имеют ли они изначально значение NULL или преобразованы в значение NULL пользовательским преобразователем, исключаются из выходных данных JSON.

Попробуйте это. dotnetfiddle.net/GwPPy7

Kaveesha Methruwan 30.08.2024 12:36

Интересная идея запустить конвертер в резолвере. Это приведет к вызову конвертера дважды для каждого допустимого значения, отличного от нуля. Хм.. Конвертер vs резольвер . Не уверен, что мне нужен только преобразователь. У меня есть несколько преобразователей, но несколько резольверов могут быть вариантом.

Sinatr 30.08.2024 13:40

Я воспользуюсь этим. С небольшим изменением: добавьте public static bool ShouldSerialize(object? value) к самому конвертеру и верните его результат из предиката property.ShouldSerialize.

Sinatr 30.08.2024 16:49

Другие вопросы по теме