【问题标题】:jsonpickle with simplejson backend serializes Decimal as null带有 simplejson 后端的 jsonpickle 将 Decimal 序列化为 null
【发布时间】:2019-01-20 12:25:52
【问题描述】:

我正在尝试在 python 3.7 中使用 jsonpickle 将对象树序列化为 json。但是,所有Decimals 都被序列化为null。我使用 simplejson 作为后端,所以它应该能够序列化小数。

如何将(复杂)对象树序列化为 json,包括小数?

示例代码(需要安装 simplejson 和 jsonpickle): 预期的序列化 json 应该是 {"amount": 1.0},我不想使用 float,因为舍入错误。

import jsonpickle
from decimal import Decimal

jsonpickle.set_preferred_backend('simplejson')
jsonpickle.set_encoder_options('simplejson', use_decimal=True)

class MyClass():
    def __init__(self, amount):
        self.amount = amount

    def to_json(self):
        return jsonpickle.dumps(self, unpicklable=False)

if __name__ == '__main__':
    obj = MyClass(Decimal('1.0'))
    print(obj.to_json())  # prints '{"amount": null}'

PS 我不关心使用 jsonpickle。因此,也欢迎使用 jsonpickle 的替代方法将复杂的对象树序列化为 json(包括十进制字段)。

【问题讨论】:

    标签: python python-3.x simplejson jsonpickle


    【解决方案1】:

    更新答案:jsonpickle 的 master 分支现在有一个 use_decimal 模式,允许您在没有任何自定义处理程序的情况下实现此结果。

    import decimal
    import unittest
    
    import jsonpickle
    
    
    class Example(object):
        """Example class holding a Decimal"""
    
        def __init__(self, amount):
            self.amount = decimal.Decimal(amount)
    
    
    class UseDecimalTestCase(unittest.TestCase):
        """Demonstrate the new use_decimal mode"""
    
        def test_use_decimal(self):
    
            obj = Example(0.5)
    
            # Configure simplejson to use decimals.
            jsonpickle.set_encoder_options('simplejson', use_decimal=True, sort_keys=True)
            jsonpickle.set_preferred_backend('simplejson')
    
            as_json = jsonpickle.dumps(obj, unpicklable=False, use_decimal=True)
            print(as_json)
            # {"amount": 0.5}
    
            # Configure simplejson to get back Decimal when restoring from json.
            jsonpickle.set_decoder_options('simplejson', use_decimal=True)
            obj_clone = jsonpickle.loads(as_json)
    
            # NOTE: we get back a dict, not an Example instance.
            self.assertTrue(isinstance(obj_clone, dict))
            # But, the Decimal *is* preserved
            self.assertTrue(isinstance(obj_clone['amount'], decimal.Decimal))
            self.assertEqual(obj.amount, obj_clone['amount'])
    
            # Side-effect of simplejson decimal mode:
            # floats become Decimal when round-tripping
            obj.amount = 0.5  # float
            as_json = jsonpickle.dumps(obj, unpicklable=False)
            obj_clone = jsonpickle.loads(as_json)
            self.assertTrue(isinstance(obj_clone['amount'], decimal.Decimal))
    
    
    if __name__ == '__main__':
        unittest.main()
    
    

    相关问题:

    https://github.com/jsonpickle/jsonpickle/issues/244

    对于较旧的 jsonpickle 版本:

    这可以通过允许 simplejson 进行编码的自定义传递处理程序来完成。您必须同时配置编码器和解码器选项,以便获得小数。如果您不关心往返,那么用例会更简单。

    import decimal
    import unittest
    
    import jsonpickle
    from jsonpickle.handlers import BaseHandler
    
    
    class SimpleDecimalHandler(BaseHandler):
        """Simple pass-through handler so that simplejson can do the encoding"""
    
        def flatten(self, obj, data):
            return obj
    
        def restore(self, obj):
            return obj
    
    
    
    class Example(object):
        """Example class holding a Decimal"""
    
        def __init__(self, amount):
            self.amount = decimal.Decimal(amount)
    
    
    
    class DecimalTestCase(unittest.TestCase):
        """Test Decimal json serialization"""
    
        def test_custom_handler(self):
    
            obj = Example(0.5)
    
            # Enable the simplejson Decimal handler -- slightly simpler than jsonpickle's
            # default handler which does the right thing already.
            # If you don't care about the json representation then you don't
            # need to do anything -- jsonpickle preserves decimal by default
            # when using its default dumps() options.
            #
            # We use this decimal handler so that simplejson does the encoding
            # rather than jsonpickle.  Thus, we have to configure simplejson too,
            # which is not needed otherwise when using jsonpickle's defaults.
    
            jsonpickle.set_encoder_options('simplejson', use_decimal=True, sort_keys=True)
            jsonpickle.set_decoder_options('simplejson', use_decimal=True)
            jsonpickle.set_preferred_backend('simplejson')
    
            SimpleDecimalHandler.handles(decimal.Decimal)
            as_json = jsonpickle.dumps(obj)
    
            print(as_json)
            # {"amount": 0.5, "py/object": "__main__.Example"}
    
            # NOTE: this comes back as an Example instance
            clone = jsonpickle.loads(as_json)
    
            self.assertTrue(isinstance(clone, Example))
            self.assertTrue(isinstance(clone.amount, decimal.Decimal))
            self.assertEqual(obj.amount, clone.amount)
    
    
            # We can simplify the JSON representation a little further
            # by using unpickleable=False, but we lose the Example class.
            as_json = jsonpickle.dumps(obj, unpicklable=False)
    
            # Upside: this prints {"amount": 0.5}
            # Downside: this object cannot be reconstructed back into an
            # instance of the Example class.
            print(as_json)
    
            # NOTE: we get back a dict, not an Example instance.
            obj_clone = jsonpickle.loads(as_json)
            self.assertTrue(isinstance(obj_clone, dict))
    
            # But, the Decimal *is* preserved
            self.assertTrue(isinstance(obj_clone['amount'], decimal.Decimal))
            self.assertEqual(obj.amount, obj_clone['amount'])
    
    
    if __name__ == '__main__':
        unittest.main()
    

    【讨论】:

      【解决方案2】:

      您需要注册一个处理程序来处理 Decimal 类

      import jsonpickle
      from decimal import Decimal
      
      jsonpickle.set_preferred_backend('simplejson')
      jsonpickle.set_encoder_options('simplejson', use_decimal=True)
      
      class DecimalHandler(jsonpickle.handlers.BaseHandler):
      
          def flatten(self, obj, data):
      
              return obj.__str__() #Convert to json friendly format
      
      jsonpickle.handlers.registry.register(Decimal, DecimalHandler)
      
      class MyClass():
          def __init__(self, amount):
              self.amount = amount
      
          def to_json(self):
              return jsonpickle.dumps(self, unpicklable=False)
      
      if __name__ == '__main__':
          obj = MyClass(Decimal('1.0'))
          print(obj.to_json())
      

      【讨论】:

      • 这实际上打印了{"amount": "1.0"},我希望它是{"amount": 1.0}(小数点周围没有引号)。
      • @JessedeWit 你可以将 obj.__str__() 转换为 float(obj.__str__())
      • 我担心 float 会产生舍入错误。这就是我尝试使用 simplejson 的原因。 simplejson 能够序列化小数,但显然不能与 jsonpickle 一起使用。
      猜你喜欢
      • 1970-01-01
      • 2017-09-26
      • 2017-03-08
      • 2013-04-29
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-01-16
      相关资源
      最近更新 更多