私が持っているDecimal('3.9')
オブジェクトの一部として、とのようになりますJSON文字列にこれをエンコードしたいです{'x': 3.9}
。クライアント側の精度は気にしないので、フロートは問題ありません。
これをシリアル化する良い方法はありますか?JSONDecoderはDecimalオブジェクトを受け入れず、事前に浮動小数点数に変換するとエラーが{'x': 3.8999999999999999}
発生し、帯域幅の大きな浪費になります。
私が持っているDecimal('3.9')
オブジェクトの一部として、とのようになりますJSON文字列にこれをエンコードしたいです{'x': 3.9}
。クライアント側の精度は気にしないので、フロートは問題ありません。
これをシリアル化する良い方法はありますか?JSONDecoderはDecimalオブジェクトを受け入れず、事前に浮動小数点数に変換するとエラーが{'x': 3.8999999999999999}
発生し、帯域幅の大きな浪費になります。
回答:
サブクラス化はjson.JSONEncoder
どうですか?
class DecimalEncoder(json.JSONEncoder):
def _iterencode(self, o, markers=None):
if isinstance(o, decimal.Decimal):
# wanted a simple yield str(o) in the next line,
# but that would mean a yield on the line with super(...),
# which wouldn't work (see my comment below), so...
return (str(o) for o in [o])
return super(DecimalEncoder, self)._iterencode(o, markers)
次に、次のように使用します。
json.dumps({'x': decimal.Decimal('5.5')}, cls=DecimalEncoder)
DecimalEncoder()._iterencode(decimal.Decimal('3.9')).next()
、正しい'3.9'
ものがDecimalEncoder()._iterencode(3.9).next()
返された'3.899...'
が、別のオブジェクトを重ねたときにのみ返されるジェネレーターオブジェクトが返されたことでした.next()
。ジェネレータ面白いビジネス。まあ...今はうまくいくはずです。
return (str(o),)
代わりにできませんか?[o]
要素が1つしかないリストですが、なぜループするのですか?
return (str(o),)
長さ1のタプルを返しますが、回答のコードは長さ1のジェネレータを返します。iterencode()のドキュメントを
Simplejson 2.1以降では、Decimalタイプがネイティブでサポートされています。
>>> json.dumps(Decimal('3.9'), use_decimal=True)
'3.9'
注意use_decimal
されTrue
、デフォルトでは:
def dumps(obj, skipkeys=False, ensure_ascii=True, check_circular=True,
allow_nan=True, cls=None, indent=None, separators=None,
encoding='utf-8', default=None, use_decimal=True,
namedtuple_as_object=True, tuple_as_array=True,
bigint_as_string=False, sort_keys=False, item_sort_key=None,
for_json=False, ignore_nan=False, **kw):
そう:
>>> json.dumps(Decimal('3.9'))
'3.9'
うまくいけば、この機能は標準ライブラリに含まれるでしょう。
json.loads(s, use_decimal=True)
と、小数点が返されます。プロセス全体でフロートはありません。上記の回答を編集しました。オリジナルのポスターで問題ないことを願っています。
use_decimal=True
も荷物を使っていなかったと思います。
json.dumps({'a' : Decimal('3.9')}, use_decimal=True)
与え'{"a": 3.9}'
ます。目標はありませんでした'{"a": "3.9"}'
か?
simplejson.dumps(decimal.Decimal('2.2'))
また、動作します:明示的ではありませんuse_decimal
(simplejson / 3.6.0でテスト済み)。それを再度ロードする別の方法はjson.loads(s, parse_float=Decimal)
、次のとおりです。つまり、stdlibを使用して読み取ることができますjson
(古いsimplejson
バージョンもサポートされています)。
Python 2.6.5を実行しているWebサーバーでMichałMarczykの回答を試したところ、問題なく動作したことを皆さんに知らせたいと思います。しかし、Python 2.7にアップグレードすると機能しなくなりました。Decimalオブジェクトをエンコードするなんらかの方法を考えてみましたが、これが私が思いついたものです。
import decimal
class DecimalEncoder(json.JSONEncoder):
def default(self, o):
if isinstance(o, decimal.Decimal):
return float(o)
return super(DecimalEncoder, self).default(o)
これはうまくいけば、Python 2.7で問題が発生しているすべての人を助けるはずです。私はそれをテストしました、そしてそれはうまくいくようです。誰かが私のソリューションのバグに気づいたり、より良い方法を思いついたりした場合は、私に知らせてください。
unicode
またはのstr
代わりにfloat
を使用して、精度を確保します。
"54.4"
、二重引用符で囲まれたストリングとしてシリアル化されるため、はるかに不正確です。数。
Python 2.7.11、フラスコの錬金術(「db.decimal」タイプ)、およびフラスコマシュマロ(「インスタント」シリアライザーおよびデシリアライザー)を使用する私のフラスコアプリでは、GETまたはPOSTを実行するたびにこのエラーが発生しました。シリアライザーとデシリアライザーは、DecimalタイプをJSON識別可能な形式に変換できませんでした。
「pip install simplejson」を実行しました。
import simplejson as json
シリアライザとデシリアライザが再び鳴り始めます。私は他に何もしませんでした... DEciamlsは '234.00' float形式で表示されます。
Decimal('0.00') is not JSON serializable
いきません、そしてそれをpip経由でインストールした後もそれを得ました。この状況は、マシュマロとグラフェンの両方を使用している場合です。クエリがREST APIで呼び出されると、マシュマロは10進数フィールドで正常に機能します。ただし、graphqlで呼び出されると、is not JSON serializable
エラーが発生しました。
GAE 2.7でsimplejsonから組み込みjsonに切り替えてみましたが、小数点に問題がありました。defaultがstr(o)を返した場合、引用符があり(_iterencodeがdefaultの結果に対して_iterencodeを呼び出すため)、float(o)は末尾の0を削除します。
defaultがfloatから継承するクラスのオブジェクト(または追加のフォーマットなしでreprを呼び出すもの)を返し、カスタム__repr__メソッドがある場合、それは期待どおりに機能するようです。
import json
from decimal import Decimal
class fakefloat(float):
def __init__(self, value):
self._value = value
def __repr__(self):
return str(self._value)
def defaultencode(o):
if isinstance(o, Decimal):
# Subclass float with custom repr?
return fakefloat(o)
raise TypeError(repr(o) + " is not JSON serializable")
json.dumps([10.20, "10.20", Decimal('10.20')], default=defaultencode)
'[10.2, "10.20", 10.20]'
float.__repr__
(つまり、精度を失っ)ハードコード、およびfakefloat.__repr__
すべてで呼び出されません。上記のソリューションは、fakefloatに追加のメソッドがある場合、3.5.1までのpython3で正しく機能しますdef __float__(self): return self
。
ネイティブオプションが欠落しているので、それを探す次のガイ/ゴール用に追加します。
Django 1.7.xからは、DjangoJSONEncoder
から取得できる組み込み機能がありますdjango.core.serializers.json
。
import json
from django.core.serializers.json import DjangoJSONEncoder
from django.forms.models import model_to_dict
model_instance = YourModel.object.first()
model_dict = model_to_dict(model_instance)
json.dumps(model_dict, cls=DjangoJSONEncoder)
プレスト!
私の$ .02!
Webサーバーの大量のデータをシリアル化しているので、JSONエンコーダーの束を拡張します。これがいいコードです。それはあなたが好きなほとんどすべてのデータフォーマットに簡単に拡張でき、3.9として再現することに注意してください"thing": 3.9
JSONEncoder_olddefault = json.JSONEncoder.default
def JSONEncoder_newdefault(self, o):
if isinstance(o, UUID): return str(o)
if isinstance(o, datetime): return str(o)
if isinstance(o, time.struct_time): return datetime.fromtimestamp(time.mktime(o))
if isinstance(o, decimal.Decimal): return str(o)
return JSONEncoder_olddefault(self, o)
json.JSONEncoder.default = JSONEncoder_newdefault
私の人生をとても簡単にします...
"thing": "3.9"
ます。
3.9
IEEE floatで正確に表すことはできません。常に次のようになります3.8999999999999999
。たとえば、試してみてください print repr(3.9)
。詳しくはこちらをご覧ください。
http://en.wikipedia.org/wiki/Floating_point
http://docs.sun.com/source/806-3568/ncg_goldberg.html
したがって、フロートが必要ない場合は、オプションを文字列として送信する必要があり、10進数オブジェクトをJSONに自動変換できるようにするには、次のようにします。
import decimal
from django.utils import simplejson
def json_encode_decimal(obj):
if isinstance(obj, decimal.Decimal):
return str(obj)
raise TypeError(repr(obj) + " is not JSON serializable")
d = decimal.Decimal('3.5')
print simplejson.dumps([d], default=json_encode_decimal)
json.loads("3.9")
ます。私はそれをこれにしたいと思います
Djangoユーザーの場合:
最近TypeError: Decimal('2337.00') is not JSON serializable
、JSONエンコーディングIE中に遭遇しましたjson.dumps(data)
ソリューション:
# converts Decimal, Datetime, UUIDs to str for Encoding
from django.core.serializers.json import DjangoJSONEncoder
json.dumps(response.data, cls=DjangoJSONEncoder)
しかし、Decimal値は文字列になり、データをデコードするときにparse_float
、json.loads
次のオプションを使用して、decimal / float値のパーサーを明示的に設定できるようになりました。
import decimal
data = json.loads(data, parse_float=decimal.Decimal) # default is float(num_str)
json.orgでリンクされているJSON標準ドキュメントから:
JSONは数値のセマンティクスにとらわれません。どのプログラミング言語でも、固定または浮動、2進数または10進数など、さまざまな容量と補数のさまざまな数値タイプが存在する可能性があります。これにより、異なるプログラミング言語間の交換が困難になる可能性があります。JSONは代わりに、人間が使用する数値の表現、つまり一連の数字のみを提供します。すべてのプログラミング言語は、内部表現に同意しない場合でも、数字列を理解する方法を知っています。それは交換を可能にするのに十分です。
したがって、JSONでDecimalsを(文字列ではなく)数値として表すことは実際には正確です。ベローは、問題の可能な解決策です。
カスタムJSONエンコーダーを定義します。
import json
class CustomJsonEncoder(json.JSONEncoder):
def default(self, obj):
if isinstance(obj, Decimal):
return float(obj)
return super(CustomJsonEncoder, self).default(obj)
次に、データをシリアル化するときに使用します。
json.dumps(data, cls=CustomJsonEncoder)
他の回答についてのコメントから指摘されているように、古いバージョンのpythonは、浮動小数点に変換するときに表現を台無しにする可能性がありますが、それはもう当てはまりません。
Pythonで小数を戻すには:
Decimal(str(value))
この解決策は、小数に関するPython 3.0のドキュメントに示唆されています。
floatからDecimalを作成するには、まずそれを文字列に変換します。
float
必ずしもあなたが小数表現を失う、となります矛盾につながります。Decimal
使用することが重要な場合は、文字列を使用する方が良いと思います。
これが私たちのクラスから抜粋したものです
class CommonJSONEncoder(json.JSONEncoder):
"""
Common JSON Encoder
json.dumps(myString, cls=CommonJSONEncoder)
"""
def default(self, obj):
if isinstance(obj, decimal.Decimal):
return {'type{decimal}': str(obj)}
class CommonJSONDecoder(json.JSONDecoder):
"""
Common JSON Encoder
json.loads(myString, cls=CommonJSONEncoder)
"""
@classmethod
def object_hook(cls, obj):
for key in obj:
if isinstance(key, six.string_types):
if 'type{decimal}' == key:
try:
return decimal.Decimal(obj[key])
except:
pass
def __init__(self, **kwargs):
kwargs['object_hook'] = self.object_hook
super(CommonJSONDecoder, self).__init__(**kwargs)
unittestに合格するもの:
def test_encode_and_decode_decimal(self):
obj = Decimal('1.11')
result = json.dumps(obj, cls=CommonJSONEncoder)
self.assertTrue('type{decimal}' in result)
new_obj = json.loads(result, cls=CommonJSONDecoder)
self.assertEqual(new_obj, obj)
obj = {'test': Decimal('1.11')}
result = json.dumps(obj, cls=CommonJSONEncoder)
self.assertTrue('type{decimal}' in result)
new_obj = json.loads(result, cls=CommonJSONDecoder)
self.assertEqual(new_obj, obj)
obj = {'test': {'abc': Decimal('1.11')}}
result = json.dumps(obj, cls=CommonJSONEncoder)
self.assertTrue('type{decimal}' in result)
new_obj = json.loads(result, cls=CommonJSONDecoder)
self.assertEqual(new_obj, obj)
json.loads(myString, cls=CommonJSONEncoder)
コメントはjson.loads(myString, cls=CommonJSONDecoder)
要件に応じて、カスタムJSONエンコーダーを作成できます。
import json
from datetime import datetime, date
from time import time, struct_time, mktime
import decimal
class CustomJSONEncoder(json.JSONEncoder):
def default(self, o):
if isinstance(o, datetime):
return str(o)
if isinstance(o, date):
return str(o)
if isinstance(o, decimal.Decimal):
return float(o)
if isinstance(o, struct_time):
return datetime.fromtimestamp(mktime(o))
# Any other serializer if needed
return super(CustomJSONEncoder, self).default(o)
デコーダーは次のように呼び出すことができます、
import json
from decimal import Decimal
json.dumps({'x': Decimal('3.9')}, cls=CustomJSONEncoder)
出力は次のようになります。
>>'{"x": 3.9}'
サードパーティのライブラリを使用したくない人のために...エリアス・ザマリアの答えの問題は、それが浮動小数点に変換され、問題が発生する可能性があることです。例えば:
>>> json.dumps({'x': Decimal('0.0000001')}, cls=DecimalEncoder)
'{"x": 1e-07}'
>>> json.dumps({'x': Decimal('100000000000.01734')}, cls=DecimalEncoder)
'{"x": 100000000000.01733}'
このJSONEncoder.encode()
メソッドJSONEncoder.default()
では、json互換の型(floatなど)を返して通常の方法でエンコードするのとは異なり、jsonのリテラルコンテンツを返すことができます。の問題encode()
は、(通常)トップレベルでしか機能しないことです。しかし、それはまだ使用可能で、少し余分な作業が必要です(python 3.x):
import json
from collections.abc import Mapping, Iterable
from decimal import Decimal
class DecimalEncoder(json.JSONEncoder):
def encode(self, obj):
if isinstance(obj, Mapping):
return '{' + ', '.join(f'{self.encode(k)}: {self.encode(v)}' for (k, v) in obj.items()) + '}'
if isinstance(obj, Iterable) and (not isinstance(obj, str)):
return '[' + ', '.join(map(self.encode, obj)) + ']'
if isinstance(obj, Decimal):
return f'{obj.normalize():f}' # using normalize() gets rid of trailing 0s, using ':f' prevents scientific notation
return super().encode(obj)
それはあなたに与える:
>>> json.dumps({'x': Decimal('0.0000001')}, cls=DecimalEncoder)
'{"x": 0.0000001}'
>>> json.dumps({'x': Decimal('100000000000.01734')}, cls=DecimalEncoder)
'{"x": 100000000000.01734}'
stdOrgnlDaveの回答に基づいて、オプションの種類で呼び出せるようにこのラッパーを定義しました。これにより、エンコーダーはプロジェクト内の特定の種類でのみ機能します。私はコード内で作業を行う必要があると信じています。「暗黙的より明示的」であるため、この「デフォルト」エンコーダーを使用しないでください。ただし、これを使用すると時間の節約になることを理解しています。:-)
import time
import json
import decimal
from uuid import UUID
from datetime import datetime
def JSONEncoder_newdefault(kind=['uuid', 'datetime', 'time', 'decimal']):
'''
JSON Encoder newdfeault is a wrapper capable of encoding several kinds
Use it anywhere on your code to make the full system to work with this defaults:
JSONEncoder_newdefault() # for everything
JSONEncoder_newdefault(['decimal']) # only for Decimal
'''
JSONEncoder_olddefault = json.JSONEncoder.default
def JSONEncoder_wrapped(self, o):
'''
json.JSONEncoder.default = JSONEncoder_newdefault
'''
if ('uuid' in kind) and isinstance(o, uuid.UUID):
return str(o)
if ('datetime' in kind) and isinstance(o, datetime):
return str(o)
if ('time' in kind) and isinstance(o, time.struct_time):
return datetime.fromtimestamp(time.mktime(o))
if ('decimal' in kind) and isinstance(o, decimal.Decimal):
return str(o)
return JSONEncoder_olddefault(self, o)
json.JSONEncoder.default = JSONEncoder_wrapped
# Example
if __name__ == '__main__':
JSONEncoder_newdefault()
requests
(json
キーワード引数を使用して)小数を含むディクショナリをライブラリに渡したい場合は、単にインストールする必要がありますsimplejson
:
$ pip3 install simplejson
$ python3
>>> import requests
>>> from decimal import Decimal
>>> # This won't error out:
>>> requests.post('https://www.google.com', json={'foo': Decimal('1.23')})
問題の理由は、それが存在する場合にのみrequests
使用simplejson
し、json
インストールされていない場合は組み込みにフォールバックするためです。
これは、
elif isinstance(o, decimal.Decimal):
yield str(o)
で\Lib\json\encoder.py:JSONEncoder._iterencode
、しかし私はより良い解決策を望んでいました