La sérialisation d'un décimal en JSON, comment faire pour arrondir?

j'ai une classe

public class Money
{
    public string Currency { get; set; }
    public decimal Amount { get; set; }
}

et j'aimerais le sérialiser à JSON. Si j'utilise le JavaScriptSerializer je

{"Currency":"USD","Amount":100.31000}

en raison de L'API que je dois me conformer aux quantités JSON nécessaires avec un maximum de deux décimales, je pense qu'il devrait être possible de modifier la façon dont le JavaScriptSerializer sérialise un champ décimal, mais je ne sais pas comment. Il y a le SimpleTypeResolver vous pouvez passer dans le constructeur, mais il ne fonctionne que sur les types d'aussi loin que je peux comprendre. JavaScriptConverter, que vous pouvez ajouter via RegisterConverters(...) semble être faite pour les Dictionary.

je voudrais obtenir

{"Currency":"USD","Amount":100.31}

après je sérialiser. De plus, il est hors de question de passer au double. Et je dois probablement faire un peu d'arrondissement (100.311 devrait devenir 100.31).

est ce que quelqu'un sait comment faire cela? Y a-t-il peut-être une alternative à la JavaScriptSerializer qui vous permet de contrôler la sérialisation plus en détail?

18
demandé sur Halvard 2012-09-05 17:53:54

4 réponses

Dans le premier cas, l' 000 ne pas nuire, la valeur est toujours la même et sera désérialisé exactement la même valeur.

dans le second cas, le JavascriptSerializer ne vous aidera pas. JavacriptSerializer n'est pas censé changer les données, puisqu'il sérialise un format connu, il ne fournit pas la conversion des données au niveau des membres (mais il fournit Objet personnalisé convertisseurs). Ce que vous voulez est une conversion + sérialisation, c'est une tâche en deux phases.

deux suggestions:

1) utiliser DataContractJsonSerializer: ajouter un autre bien qui arrondit la valeur:

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) Cloner l'objet arrondissant les valeurs:

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();

I guess json.net Je ne peux pas le faire non plus, mais je ne suis pas sûr à 100%.

3
répondu Marcelo De Zen 2012-09-05 14:36:04

je n'étais pas complètement satisfait de toutes les techniques pour atteindre cet objectif. JsonConverterAttribute semblait le plus prometteur, mais je ne pouvais pas vivre avec des paramètres codés et la prolifération de classes de convertisseur pour chaque combinaison d'options.

alors, j'ai soumis un PR cela ajoute la capacité de passer divers arguments à JsonConverter et JsonProperty. Il a été accepté en amont et je m'attends à être dans la prochaine version (peu importe ce qui est prochain après 6.0.5)--2-->

Vous pouvez ensuite faire comme ceci:

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; }
}

voir la CustomDoubleRounding () test pour un exemple.

13
répondu BrandonLWhite 2014-09-23 15:26:46

pour référence future, ceci peut être réalisé en Json.net assez élégamment en créant une coutume 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 vous créez des sérialiseurs dans le code en utilisant explicitement le constructeur, cela fonctionnera très bien mais je pense qu'il est plus agréable de décorer les propriétés pertinentes avec JsonConverterAttribute, auquel cas la classe doit avoir un public, constructeur sans paramètre. J'ai résolu ce problème en créant une sous-classe spécifique au format I vouloir.

public class SomePropertyDecimalFormatConverter : DecimalFormatJsonConverter
{
    public SomePropertyDecimalFormatConverter() : base(3)
    {
    }
}

public class Poco 
{
    [JsonConverter(typeof(SomePropertyDecimalFormatConverter))]
    public decimal SomeProperty { get;set; }
}

le convertisseur personnalisé a été dérivé de Json.NET documentation--6-->.

9
répondu htuomola 2014-06-12 08:51:19

je viens de passer par la même difficulté que j'ai eu quelques décimales étant sérialisé avec 1.00 et certains avec 1.0000. C'est mon changement:

créer un JsonTextWriter qui peut arrondir la valeur à 4 décimales. Chaque décimale sera alors arrondie à 4 décimales: 1.0 devient 1.0000 et 1.0000000 devient aussi 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);
    }
}

utilisez votre propre écrivain au lieu de celui standard:

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);
}
8
répondu Corneliu 2013-08-11 22:58:22