Python JSON serialize a Decimal object

后端 未结 17 1161
星月不相逢
星月不相逢 2020-11-22 08:27

I have a Decimal(\'3.9\') as part of an object, and wish to encode this to a JSON string which should look like {\'x\': 3.9}. I don\'t care about p

相关标签:
17条回答
  • 2020-11-22 09:02

    This is what I have, extracted from our class

    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)
    

    Which passes 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)
    
    0 讨论(0)
  • 2020-11-22 09:10

    I tried switching from simplejson to builtin json for GAE 2.7, and had issues with the decimal. If default returned str(o) there were quotes (because _iterencode calls _iterencode on the results of default), and float(o) would remove trailing 0.

    If default returns an object of a class that inherits from float (or anything that calls repr without additional formatting) and has a custom __repr__ method, it seems to work like I want it to.

    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]'
    
    0 讨论(0)
  • 2020-11-22 09:10

    For Django users:

    Recently came across TypeError: Decimal('2337.00') is not JSON serializable while JSON encoding i.e. json.dumps(data)

    Solution:

    # converts Decimal, Datetime, UUIDs to str for Encoding
    from django.core.serializers.json import DjangoJSONEncoder  
    
    json.dumps(response.data, cls=DjangoJSONEncoder)
    

    But, now the Decimal value will be a string, now we can explicitly set the decimal/float value parser when decoding data, using parse_float option in json.loads:

    import decimal 
    
    data = json.loads(data, parse_float=decimal.Decimal) # default is float(num_str)
    
    0 讨论(0)
  • 2020-11-22 09:10

    You can create a custom JSON encoder as per your requirement.

    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)
    

    The Decoder can be called like this,

    import json
    from decimal import Decimal
    json.dumps({'x': Decimal('3.9')}, cls=CustomJSONEncoder)
    

    and the output will be:

    >>'{"x": 3.9}'
    
    0 讨论(0)
  • 2020-11-22 09:10

    For anybody that wants a quick solution here is how I removed Decimal from my queries in Django

    total_development_cost_var = process_assumption_objects.values('total_development_cost').aggregate(sum_dev = Sum('total_development_cost', output_field=FloatField()))
    total_development_cost_var = list(total_development_cost_var.values())
    
    • Step 1: use , output_field=FloatField() in you r query
    • Step 2: use list eg list(total_development_cost_var.values())

    Hope it helps

    0 讨论(0)
  • 2020-11-22 09:11

    Simplejson 2.1 and higher has native support for Decimal type:

    >>> json.dumps(Decimal('3.9'), use_decimal=True)
    '3.9'
    

    Note that use_decimal is True by default:

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

    So:

    >>> json.dumps(Decimal('3.9'))
    '3.9'
    

    Hopefully, this feature will be included in standard library.

    0 讨论(0)
提交回复
热议问题