web-dev-qa-db-fra.com

Gestion des valeurs décimales dans Newtonsoft.Json

Edit: Cela fait presque 5 ans et je ne pense pas que ce soit la voie à suivre. Le client doit publier les données dans le format numérique correct. Avec les frameworks actuels comme React ou Angular, ou avec une architecture appropriée et une gestion et une validation des erreurs, je pense que ce n'est presque pas un problème.

Mais si quelqu'un souhaite fléchir ses muscles Json.NET, n'hésitez pas à vérifier les réponses.


J'ai une application MVC et j'y gère du JSON. C'est simple. J'ai ce simple morceau de code dans mon ModelBinder:

return JsonConvert.DeserializeObject(jsonString, bindingContext.ModelType, new JsonSerializerSettings
{
    NullValueHandling = NullValueHandling.Ignore,
    MissingMemberHandling = MissingMemberHandling.Ignore,
    Formatting = Formatting.None,
    DateFormatHandling = DateFormatHandling.IsoDateFormat,
    FloatParseHandling = FloatParseHandling.Decimal
});

Et cela fonctionne parfaitement.

Eh bien, en quelque sorte.

Disons que j'ai cette classe:

public class MyClass
{
    public decimal MyProp { get; set; }
}

Si j'essaie de désérialiser ce json:

"{\"MyProp\": 9888.77}"

Bien sûr, cela fonctionne, car 9888.77 Est une valeur flottante Javascript. Je pense.

Mais j'ai une entrée masquée pour l'argent dans ma page qui fait ressembler le JSON à ceci (désolé pour mon anglais):

"{\"MyProp\": \"9.888,77\" }"

AAAND, ça échoue. Il dit que c'est Could not convert string to decimal.

Ok, c'est juste. Ce n'est pas un flottant JS, mais Convert.ToDecimal("9.888,77") fonctionne comme je le souhaite.

J'ai lu quelques tutoriels sur Internet sur les désérialiseurs personnalisés, mais il est inviable pour moi de définir un désérialiseur personnalisé pour chaque classe que j'ai dans mon application.

Ce que je veux, c'est redéfinir simplement la façon dont JSON.Net convertit une chaîne en une propriété décimale, dans n'importe quelle classe que je voudrai désérialiser. Je veux injecter la fonction Convert.ToDecimal Dans le processus de conversion des décimales, lorsque le convertisseur actuel ne fonctionne pas.

Existe-t-il un moyen de le faire?

Je pensais qu'il y avait un moyen de le faire, alors j'ai changé un peu mon code.

JsonSerializer serializer = new JsonSerializer
{
    NullValueHandling = NullValueHandling.Ignore,
    MissingMemberHandling = MissingMemberHandling.Ignore,
    Formatting = Formatting.None,
    DateFormatHandling = DateFormatHandling.IsoDateFormat,
    FloatParseHandling = FloatParseHandling.Decimal,
};



return serializer.Deserialize(new DecimalReader(jsonStr), bindingContext.ModelType);

Et créé cette classe:

public class DecimalReader : JsonTextReader
{
    public DecimalReader(string s)
        : base(new StringReader(s))
    {
    }

    public override decimal? ReadAsDecimal()
    {
        try
        {
            return base.ReadAsDecimal();
        }
        catch (Exception)
        {
            if (this.TokenType == JsonToken.String)
            {
                decimal value = 0;

                bool convertible = Decimal.TryParse(this.Value.ToString(), out value);

                if (convertible)
                {
                    return new Nullable<decimal>(value);
                }
                else { throw; }
            }
            else
            {
                throw;
            }
        }
    }
}

Mais c'est très moche: il exécute ce que je veux seulement quand il plante, et dépend de base.ReadAsDecimal() crashing. Ça ne pourrait pas être plus moche.

Et ça ne marche pas: Error converting value "1.231,23" to type 'System.Nullable1[System.Decimal]'. Path 'MyProp', line X, position Y.

La valeur elle-même est en cours de conversion, mais peut-être pour une raison quelconque, elle essaie toujours de mettre la chaîne "1.231,23" en décimal.

Alors, existe-t-il un moyen de le faire correctement?

13
Ricardo Pieper

Vous pouvez gérer les deux formats (la représentation numérique JSON et le format de chaîne masquée) à l'aide d'une classe JsonConverter personnalisée comme celle-ci.

class DecimalConverter : JsonConverter
{
    public override bool CanConvert(Type objectType)
    {
        return (objectType == typeof(decimal) || objectType == typeof(decimal?));
    }

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        JToken token = JToken.Load(reader);
        if (token.Type == JTokenType.Float || token.Type == JTokenType.Integer)
        {
            return token.ToObject<decimal>();
        }
        if (token.Type == JTokenType.String)
        {
            // customize this to suit your needs
            return Decimal.Parse(token.ToString(), 
                   System.Globalization.CultureInfo.GetCultureInfo("es-ES"));
        }
        if (token.Type == JTokenType.Null && objectType == typeof(decimal?))
        {
            return null;
        }
        throw new JsonSerializationException("Unexpected token type: " + 
                                              token.Type.ToString());
    }

    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        throw new NotImplementedException();
    }
}

Pour le brancher dans votre classeur, ajoutez simplement une instance du convertisseur à la liste Converters dans l'objet JsonSerializerSettings:

JsonSerializerSettings settings = new JsonSerializerSettings
{
    NullValueHandling = NullValueHandling.Ignore,
    MissingMemberHandling = MissingMemberHandling.Ignore,
    Formatting = Formatting.None,
    DateFormatHandling = DateFormatHandling.IsoDateFormat,
    Converters = new List<JsonConverter> { new DecimalConverter() }
};
28
Brian Rogers

Merci beaucoup! Je cherchais une solution pour que les décimales soient toujours sérialisées de manière similaire et ce message m'a envoyé dans la bonne direction. Voici mon code:

    internal class DecimalConverter : JsonConverter
    {
        public override bool CanConvert(Type objectType)
        {
            return (objectType == typeof(decimal) || objectType == typeof(decimal?));
        }

        public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
        {
            throw new NotImplementedException();
        }

        public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
        {
            Decimal? d = default(Decimal?);
            if (value != null)
            {
                d = value as Decimal?;
                if (d.HasValue) // If value was a decimal?, then this is possible
                {
                    d = new Decimal?(new Decimal(Decimal.ToDouble(d.Value))); // The ToDouble-conversion removes all unnessecary precision
                }
            }
            JToken.FromObject(d).WriteTo(writer);
        }
    }
4
Kwaazaar