web-dev-qa-db-ja.com

Newtonsoft.Jsonでの小数値の処理

編集:ほぼ5年が経ちましたが、これが進むべき道ではないと思います。クライアントは、正しい数値形式でデータを投稿する必要があります。 ReactまたはAngularのような現在のフレームワーク、または適切なアーキテクチャとエラー処理と検証を使用すると、これはほとんど問題ではないと思います。

ただし、Json.NETの筋肉を曲げたい場合は、お気軽に回答を確認してください。


MVCアプリケーションがあり、その中でJSONを処理します。簡単です。 ModelBinderには次の簡単なコードがあります。

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

そして、それは完璧に機能します。

まあ、ちょっと。

このクラスがあるとしましょう:

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

このJSONをデシリアライズしようとすると:

_"{\"MyProp\": 9888.77}"
_

もちろん、_9888.77_はJavaScriptのfloat値なので、動作します。おもう。

しかし、私は、JSONを次のように見せるために、ページにお金の入力をマスクしています(私の英語は申し訳ありません):

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

AAAND、失敗します。 _Could not convert string to decimal_と書かれています。

わかりました、それは公平です。これはJSフロートではありませんが、Convert.ToDecimal("9.888,77")は希望どおりに機能します。

私はインターネット上でカスタムデシリアライザーに関するチュートリアルをいくつか読みましたが、アプリケーションにあるすべてのクラスに対してカスタムデシリアライザーを定義することは私にとって不可欠です。

私が望むのは、JSON.Netが文字列を10進数のプロパティに変換する方法を、私がこれまでデシリアライズしたいクラスで簡単に再定義することです。現在のコンバータが機能しない場合、小数を変換するプロセスで_Convert.ToDecimal_関数を挿入します。

できる方法はありますか?

私はそれを行う方法があると思ったので、コードを少し変更しました。

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

そして、このクラスを作成しました:

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

しかし、それは非常にいです:クラッシュしたときにのみ、私が望むものを実行しますおよびbase.ReadAsDecimal() crashingに依存します。これ以上moreいことはありません。

そして機能しません:_Error converting value "1.231,23" to type 'System.Nullable1[System.Decimal]'. Path 'MyProp', line X, position Y._

値自体は変換されていますが、おそらく何らかの理由で、文字列「1.231,23」を10進数に変換しようとしています。

それで、それを適切に行う方法はありますか?

13
Ricardo Pieper

このようなカスタムJsonConverterクラスを使用して、両方の形式(JSON番号表現とマスクされた文字列形式)を処理できます。

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

これをバインダーにプラグインするには、コンバーターのインスタンスをConvertersオブジェクトの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

どうもありがとう!私は小数を常に同様の方法でシリアル化するソリューションを探していましたが、この投稿は正しい方向に私を送りました。これは私のコードです:

    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