代码之家  ›  专栏  ›  技术社区  ›  Knio

python json序列化十进制对象

  •  179
  • Knio  · 技术社区  · 16 年前

    我有一个 Decimal('3.9') 作为对象的一部分,并希望将其编码为一个JSON字符串,该字符串应类似于 {'x': 3.9} . 我不在乎客户端的精确性,所以浮动是可以的。

    有没有一个好的方法来序列化这个?jsondecoder不接受decimal对象,并且预先转换为float会生成 {'x': 3.8999999999999999} 这是错误的,而且会浪费大量的带宽。

    14 回复  |  直到 7 年前
        1
  •  116
  •   Michał Marczyk    16 年前

    子类化怎么样 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)
    
        2
  •  186
  •   Jonah Bishop    9 年前

    Simplejson 2.1 更高版本支持十进制类型:

    >>> 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'
    

    希望这个特性将包含在标准库中。

        3
  •  138
  •   Elias Zamaria    16 年前

    我想让大家知道,我在运行python 2.6.5的Web服务器上尝试了Micha_但是,我升级到了python 2.7,它停止工作。我试着想出某种方法来编码十进制对象,这就是我想到的:

    class DecimalEncoder(json.JSONEncoder):
        def default(self, o):
            if isinstance(o, decimal.Decimal):
                return float(o)
            return super(DecimalEncoder, self).default(o)
    

    希望这能帮助任何有关于Python2.7问题的人。我测试过了,好像还可以。如果有人发现我的解决方案中有任何错误或想出更好的方法,请告诉我。

        4
  •  24
  •   tesdal    14 年前

    我尝试从simplejson切换到gae 2.7的内置json,但对十进制有问题。如果默认值返回str(o),则会有引号(因为在默认值的结果上调用了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]'
    
        5
  •  23
  •   ISONecroMAn    10 年前

    在使用python 2.7.11、flask炼金术(具有'db.decimal'类型)和flask棉花糖(用于'instant'序列化程序和反序列化程序)的flask应用程序中,每次执行get或post时都会出现此错误。序列化程序和反序列化程序未能将十进制类型转换为任何JSON可识别格式。

    我做了一个“pip安装simplejson”,然后 只是添加

    import simplejson as json
    

    序列化程序和反序列化程序再次开始发出呜呜声。我什么都没做… 小数显示为“234.00”浮点格式。

        6
  •  12
  •   Anurag Uniyal    12 年前

    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

    因此,如果不希望使用float,则只需将其作为字符串发送,并允许将十进制对象自动转换为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)
    
        7
  •  10
  •   Javier Buzzi    8 年前

    本地选项丢失了,所以我将为下一个查找它的人/gall添加它。

    从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)
    

    急板地!

        8
  •  9
  •   std''OrgnlDave    9 年前

    我的02美元!

    我扩展了一组JSON编码器,因为我正在为我的Web服务器序列化大量的数据。这是一些很好的代码。请注意,它可以很容易地扩展到几乎任何您喜欢的数据格式,并将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
    

    让我的生活更轻松…

        9
  •  6
  •   James Lin    11 年前

    这就是我从我们班上摘录的内容

    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)
    

    通过单元测试:

    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)
    
        10
  •  2
  •   Juanmi Taboada    8 年前

    基于 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()
    
        11
  •  2
  •   Hugo Mota    7 年前

    从 JSON Standard Document ,如链接在 json.org :

    JSON对数字的语义不可知。在任何编程语言中,都可以有 各种容量和补码的数字类型,固定或浮动,二进制或十进制。这可以使 不同编程语言之间的交换很困难。JSON只提供 人类使用的数字:数字序列。所有编程语言都知道如何理解数字 序列,即使它们在内部表示上不一致。这就足够允许交换了。

    所以在JSON中将小数表示为数字(而不是字符串)实际上是准确的。下面是解决这个问题的可能方法。

    定义自定义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)
    

    正如对其他答案的评论所指出的那样,在转换为float时,旧版本的python可能会弄乱表示,但现在已经不是这样了。

    要在python中返回小数点,请执行以下操作:

    Decimal(str(value))
    

    此解决方案暗示 Python 3.0 documentation on decimals :

    要从浮点创建小数,请首先将其转换为字符串。

        12
  •  0
  •   Max Malysh    8 年前

    如果要将包含小数的字典传递给 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')})
    

    问题的原因是 请求 使用 单工 只有当它存在并返回到内置 杰森 如果没有安装。

        13
  •  0
  •   sparrow    8 年前

    您可以根据需要创建一个定制的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}'
    
        14
  •  -5
  •   Knio    16 年前

    这可以通过添加

        elif isinstance(o, decimal.Decimal):
            yield str(o)
    

    在里面 \Lib\json\encoder.py:JSONEncoder._iterencode 但是我希望有更好的解决办法