Для сериализации json с помощью json.net
:
double.NaN
) в null
.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.
@Клептус, какая твоя идея? Какой атрибут и где применять?
Нет необходимости создавать собственный конвертер. Я предлагал атрибуты JsonObject(MemberSerialization.OptIn)
и JsonProperty
согласно примеру в документации, на которую я дал ссылку.
Также для двухдесятичного форматирования вы можете использовать специальный конвертер для этих свойств. Некоторое время назад я ответил на аналогичный вопрос с преобразованием даты и времени
@Cleptus, вы предлагаете полностью исключить свойство из сериализации, даже если оно содержит допустимое, ненулевое значение? Это не мое желание. Во втором комментарии вы говорите, что мне нужен конвертер, как эти два комментария объединяются?
Не заметил требования «Не писать свойства с нулевыми значениями». Мой первый комментарий будет работать только в том случае, если одно и то же свойство всегда имеет нулевое значение (думаю, это не так). Второй комментарий — это быстрый и простой способ форматирования свойства (G3 или другой формат).
Для этого вам необходимо объединить собственную логику преобразования с обработкой нулевых значений. Проблема, с которой вы столкнулись, возникает из-за того, что нулевое значение, созданное вашим преобразователем, не вызывает поведение 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
Интересная идея запустить конвертер в резолвере. Это приведет к вызову конвертера дважды для каждого допустимого значения, отличного от нуля. Хм.. Конвертер vs резольвер . Не уверен, что мне нужен только преобразователь. У меня есть несколько преобразователей, но несколько резольверов могут быть вариантом.
Я воспользуюсь этим. С небольшим изменением: добавьте public static bool ShouldSerialize(object? value)
к самому конвертеру и верните его результат из предиката property.ShouldSerialize
.
Ознакомьтесь с документацией по атрибутам сериализации