Сериализация десятичного числа для JSON, как округлить?
У меня есть класс
public class Money
{
public string Currency { get; set; }
public decimal Amount { get; set; }
}
и хотел бы сериализовать его в JSON. Если я использую JavaScriptSerializer
, я получаю
{"Currency":"USD","Amount":100.31000}
Из-за API я должен соответствовать потребностям JSON с максимальными двумя десятичными знаками, я считаю, что должно быть возможно каким-то образом изменить способ сериализации JavaScriptSerializer
десятичного поля, но я не могу узнать, как это сделать. Существует SimpleTypeResolver, который вы можете передать в конструкторе, но он работает только по типам, насколько я могу понять. JavaScriptConverter, который вы можете добавить через RegisterConverters (...), как представляется, для Dictionary
.
Я хотел бы получить
{"Currency":"USD","Amount":100.31}
после сериализации. Кроме того, о переходе к двойному вопросу не может быть и речи. И мне, вероятно, нужно сделать округление (100.311 должно стать 100.31).
Кто-нибудь знает, как это сделать? Возможно ли альтернатива JavaScriptSerializer
, которая позволяет более подробно управлять сериализацией?
Ответы
Ответ 1
В первом случае 000
не наносит вреда, значение остается тем же и будет десериализовано до того же самого значения.
Во втором случае JavascriptSerializer вам не поможет. JavacriptSerializer
не должен изменять данные, поскольку он сериализует его в хорошо известном формате, он не обеспечивает преобразование данных на уровне участника (но он предоставляет настраиваемые объектные преобразователи). Вы хотите преобразовать + сериализацию, это двухфазная задача.
Два предложения:
1) Используйте DataContractJsonSerializer
: добавьте другое свойство, которое округляет значение:
public class Money
{
public string Currency { get; set; }
[IgnoreDataMember]
public decimal Amount { get; set; }
[DataMember(Name = "Amount")]
public decimal RoundedAmount { get{ return Math.Round(Amount, 2); } }
}
2) Клонировать объект, округляющий значения:
public class Money
{
public string Currency { get; set; }
public decimal Amount { get; set; }
public Money CloneRounding() {
var obj = (Money)this.MemberwiseClone();
obj.Amount = Math.Round(obj.Amount, 2);
return obj;
}
}
var roundMoney = money.CloneRounding();
Я думаю, json.net тоже не может этого сделать, но я не уверен на 100%.
Ответ 2
Я до сих пор не был полностью доволен всеми техническими средствами для достижения этого. JsonConverterAttribute представляется наиболее перспективным, но я не мог жить с жестко заданными параметрами и распространением классов конвертеров для каждой комбинации опций.
Итак, я представил PR, который добавляет возможность передавать различные аргументы JsonConverter и JsonProperty. Он был принят вверх по течению, и я ожидаю, что он будет в следующем выпуске (независимо от того, что после 6.0.5)
Затем вы можете сделать это следующим образом:
public class Measurements
{
[JsonProperty(ItemConverterType = typeof(RoundingJsonConverter))]
public List<double> Positions { get; set; }
[JsonProperty(ItemConverterType = typeof(RoundingJsonConverter), ItemConverterParameters = new object[] { 0, MidpointRounding.ToEven })]
public List<double> Loads { get; set; }
[JsonConverter(typeof(RoundingJsonConverter), 4)]
public double Gain { get; set; }
}
Обратитесь к примеру CustomDoubleRounding() для примера.
Ответ 3
Я просто пережил те же проблемы, что и некоторые десятичные числа, которые были сериализованы с 1.00, а некоторые с 1.0000.
Это мое изменение:
Создайте JsonTextWriter, который может округлить значение до 4 десятичных знаков. Затем каждое десятичное число округляется до 4 десятичных знаков: 1,0 становится равным 1.0000 и 1.0000000 равно также 1.0000
private class JsonTextWriterOptimized : JsonTextWriter
{
public JsonTextWriterOptimized(TextWriter textWriter)
: base(textWriter)
{
}
public override void WriteValue(decimal value)
{
// we really really really want the value to be serialized as "0.0000" not "0.00" or "0.0000"!
value = Math.Round(value, 4);
// divide first to force the appearance of 4 decimals
value = Math.Round((((value+0.00001M)/10000)*10000)-0.00001M, 4);
base.WriteValue(value);
}
}
Используйте свой собственный сценарий вместо стандартного:
var jsonSerializer = Newtonsoft.Json.JsonSerializer.Create();
var sb = new StringBuilder(256);
var sw = new StringWriter(sb, CultureInfo.InvariantCulture);
using (var jsonWriter = new JsonTextWriterOptimized(sw))
{
jsonWriter.Formatting = Formatting.None;
jsonSerializer.Serialize(jsonWriter, instance);
}
Ответ 4
Для справки в будущем это может быть достигнуто в Json.net довольно элегантно, создав пользовательский JsonConverter
public class DecimalFormatJsonConverter : JsonConverter
{
private readonly int _numberOfDecimals;
public DecimalFormatJsonConverter(int numberOfDecimals)
{
_numberOfDecimals = numberOfDecimals;
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
var d = (decimal) value;
var rounded = Math.Round(d, _numberOfDecimals);
writer.WriteValue((decimal)rounded);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue,
JsonSerializer serializer)
{
throw new NotImplementedException("Unnecessary because CanRead is false. The type will skip the converter.");
}
public override bool CanRead
{
get { return false; }
}
public override bool CanConvert(Type objectType)
{
return objectType == typeof(decimal);
}
}
Если вы создаете сериализаторы в коде с использованием конструктора явно, это будет работать нормально, но я думаю, что лучше украсить соответствующие свойства с помощью JsonConverterAttribute
, в котором Если класс должен иметь открытый конструктор без параметров. Я решил это, создав подкласс, который специфичен для формата, который я хочу.
public class SomePropertyDecimalFormatConverter : DecimalFormatJsonConverter
{
public SomePropertyDecimalFormatConverter() : base(3)
{
}
}
public class Poco
{
[JsonConverter(typeof(SomePropertyDecimalFormatConverter))]
public decimal SomeProperty { get;set; }
}
Пользовательский конвертер был получен из документации Json.NET.