web-dev-qa-db-fra.com

Sérialiser une décimale en JSON, comment arrondir?

J'ai un cours

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

et souhaite le sérialiser en JSON. Si j'utilise le JavaScriptSerializer j'obtiens

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

En raison de l'API à laquelle je dois me conformer, a besoin de montants JSON avec au maximum deux décimales, je pense qu'il devrait être possible de modifier la façon dont JavaScriptSerializer sérialise un champ décimal, mais je ne peux pas savoir comment. Il y a le SimpleTypeResolver que vous pouvez passer dans le constructeur, mais cela ne fonctionne que sur les types pour autant que je puisse comprendre. Le JavaScriptConverter , que vous pouvez ajouter via RegisterConverters (...) semble être fait pour Dictionary.

Je voudrais obtenir

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

après avoir sérialisé. De plus, passer au double est hors de question. Et j'ai probablement besoin d'arrondir (100.311 devrait devenir 100.31).

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

24
Halvard

Dans le premier cas, le 000 ne fait aucun mal, la valeur est toujours la même et sera désérialisée à la même valeur exacte.

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

Deux suggestions:

1) Utilisez DataContractJsonSerializer: ajoutez une autre propriété 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) Clonez l'objet en 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();

Je suppose que json.net ne peut pas faire cela non plus, mais je ne suis pas sûr à 100%.

4
Marcelo De Zen

Jusqu'à présent, je n'étais pas entièrement satisfait de toutes les techniques pour y parvenir. JsonConverterAttribute semblait le plus prometteur, mais je ne pouvais pas vivre avec des paramètres codés en dur et une prolifération de classes de convertisseur pour chaque combinaison d'options.

J'ai donc soumis un PR qui ajoute la possibilité de passer divers arguments à JsonConverter et JsonProperty. Il a été accepté en amont et je pense qu'il sera dans la prochaine version (quelle que soit la prochaine après 6.0.5)

Vous pouvez ensuite le 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; }
}

Reportez-vous au test CustomDoubleRounding () pour un exemple.

18
BrandonLWhite

Pour référence future, cela peut être réalisé dans Json.net assez élégamment en créant un personnalisé 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 du code en utilisant explicitement le constructeur, cela fonctionnera 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 cela en créant une sous-classe qui est spécifique au format que je veux.

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

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

Le convertisseur personnalisé est dérivé de documentation Json.NET .

10
htuomola

Je viens de traverser le même problème car j'avais des décimales sérialisées avec 1,00 et d'autres avec 1,0000. C'est mon changement:

Créez 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 également 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
Corneliu