solo - quitar decimales c#
Serializando un decimal a JSON, ¿cómo redondear? (4)
Tengo una clase
public class Money
{
public string Currency { get; set; }
public decimal Amount { get; set; }
}
y me gustaría serializarlo a JSON. Si uso el JavaScriptSerializer
obtengo
{"Currency":"USD","Amount":100.31000}
Debido a que la API tengo que ajustarme a las cantidades de JSON con un máximo de dos decimales, creo que debería ser posible alterar de alguna manera la forma en que el JavaScriptSerializer
serializa un campo decimal, pero no puedo descubrir cómo. Existe el SimpleTypeResolver que puede pasar en el constructor, pero solo funciona en los tipos que yo entiendo. El JavaScriptConverter , que puede agregar a través de RegisterConverters (...) parece estar hecho para Dictionary
.
Me gustaría conseguir
{"Currency":"USD","Amount":100.31}
después de que serialice. Además, cambiar al doble está fuera de discusión. Y probablemente necesito hacer un redondeo (100.311 debería ser 100.31).
¿Alguien sabe como hacer esto? ¿Existe quizás una alternativa al JavaScriptSerializer
que le permite controlar la serialización con más detalle?
Acabo de pasar por el mismo problema porque tuve algunos decimales que se serializaron con 1.00 y otros con 1.0000. Este es mi cambio:
Cree un JsonTextWriter que pueda redondear el valor a 4 decimales. Cada decimal se redondeará a 4 decimales: 1.0 se convierte en 1.0000 y 1.0000000 se convierte también en 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);
}
}
Use su propio escritor en lugar del estándar:
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);
}
En el primer caso, el 000
no hace daño, el valor sigue siendo el mismo y se deserializará al mismo valor exacto.
En el segundo caso el JavascriptSerializer no te ayudará. JavacriptSerializer
se supone que JavacriptSerializer
cambie los datos, ya que los serializa en un formato conocido que no proporciona conversión de datos a nivel de miembro (pero proporciona convertidores de objetos personalizados). Lo que quieres es una conversión + serialización, esta es una tarea de dos fases.
Dos sugerencias:
1) Use DataContractJsonSerializer
: agregue otra propiedad que redondee el valor:
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) Clone el objeto redondeando los valores:
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();
Supongo que json.net tampoco puede hacer esto, pero no estoy 100% seguro.
No estaba completamente satisfecho con todas las técnicas hasta ahora para lograr esto. El atributo JsonConverter parecía el más prometedor, pero no podía vivir con parámetros codificados y la proliferación de clases de convertidores para cada combinación de opciones.
Por lo tanto, envié un PR que agrega la capacidad de pasar varios argumentos a JsonConverter y JsonProperty. Se aceptó en sentido ascendente y espero que esté en la próxima versión (lo que sea después de 6.0.5)
Entonces puedes hacerlo así:
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; }
}
Consulte la prueba CustomDoubleRounding() para ver un ejemplo.
Para futuras referencias, esto se puede lograr en Json.net con bastante elegancia mediante la creación de un JsonConverter
personalizado 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);
}
}
Si está creando serializadores en el código utilizando el constructor explícitamente, esto funcionará bien, pero creo que es mejor decorar las propiedades relevantes con JsonConverterAttribute
, en cuyo caso la clase debe tener un constructor público sin parámetros. Resolví esto creando una subclase que es específica al formato que quiero.
public class SomePropertyDecimalFormatConverter : DecimalFormatJsonConverter
{
public SomePropertyDecimalFormatConverter() : base(3)
{
}
}
public class Poco
{
[JsonConverter(typeof(SomePropertyDecimalFormatConverter))]
public decimal SomeProperty { get;set; }
}
El convertidor personalizado se ha derivado de la documentación de Json.NET .