欢迎访问Python每天3分钟系列。

每天花3分钟时间,学习或温习一个Python知识点。

图片

今天是第026篇:

JSON序列化自定义对象

用Python做JSON的序列化和反序列化是很简单的,就用dumps和loads方法就够了。细节见昨天的3分钟。这是一个例子:

food = [
   {'name':'pizza', 'origin':'Italy'},
   {'name':'hamburger', 'origin':'UK'},
]

import json
# 把对象转成JSON字符串
food_json = json.dumps(food)
print(f'检查一下food_json的类型,是字符串:{type(food_json)}')
print('打印一下结果:')
print(food_json)

# 把JSON字符串转成对象
from_json = json.loads(food_json)
print(f'看一下类型,应该是列表:{type(from_json)}')

但是如果要转化自定义对象,就没那么简单了。看这个例子:

class Food:
  def __init__(self, name, origin, calories, price):
    self.name = name 
    self.origin = origin 
    self.calories = calories 
    self.price = price 

print('...此处省略2500行推荐算法代码...')
food1 = Food('胡辣汤', '山东', 25, 2)
food2 = Food('油条', '山东', 88, 1)
food3 = Food('豆腐脑', '山东', 35, 2.5)
food4 = Food('焖饼', '山东', 65, 10)

recommend = [food1, food2, food3, food4]

import json
recommend_json = json.dumps(recommend, indent=4, ensure_ascii=False)
print(recommend_json)

列表recommend中有4个Food对象。尝试序列化这个列表会出现下面的错误:

TypeError: Object of type Food is not JSON serializable

提示说Food不能序列化!

夸张的说,如何解决这个问题可以说是世纪难题。你看看stackoverflow上这个问题:已经提问了10年了,没有人给出很理想的解决方案。

https://stackoverflow.com/questions/10252010/serializing-class-instance-to-json

理想解决方案

作为一个pythoner,理想的解决方案应该是这样的:

json模块调用一组魔术方法,比如__jsonize____unjsonize__做JSON的序列化。

自定义对象只要实现了这两个方法,就可以使用dumps和loads做序列化:

class Food:
  def __init__(self, name, origin, calories, price):
    self.name = name 
    self.origin = origin 
    self.calories = calories 
    self.price = price 

  def __jsonize__(self):
    # 省略...
    pass

  def __unjsonize__(self):
    # 省略...
    pass

这是比较pythonic的,但是不知道为什么,json模块并没有这样的魔术方法。所以这个方案行不通。

json的设计者使用了更加Java化的设计思想,dumps函数可以接受一个自定义的序列器类,但这个方法夹里夹气的,这里就不细说了。另外,这种方法只能单独序列化当前对象,不能够序列化包含当前对象的字典,列表等

普通但常用的解决方案

在实际工作中,很多人就不用json库了,自己定义相应的函数to_json,比如这样:

class Food:
  def __init__(self, name, origin, calories, price):
    self.name = name 
    self.origin = origin 
    self.calories = calories 
    self.price = price 

  def to_json(self):
    return self.__dict__

print('...此处省略2500行推荐算法代码...')
food1 = Food('胡辣汤', '山东', 25, 2)
food2 = Food('油条', '山东', 88, 1)
food3 = Food('豆腐脑', '山东', 35, 2.5)
food4 = Food('焖饼', '山东', 65, 10)

recommend = [food1, food2, food3, food4]

print(food1.to_json())

这个函数自己定义了to_json函数,等于自己干了。

运行结果如下:

{'name': '胡辣汤', 'origin': '山东', 'calories': 25, 'price': 2}

没什么问题。这个方法在现实中应用也不少。

但是通过这个方法,并不能给recommend列表做序列化,还要自己再手工拼接列表中的数据。和上面的方法一样:这种方法只能单独序列化当前对象,不能够序列化包含当前对象的字典,列表等**。

比较理想的解决方案

下面说一个比较理想的解决方法,说它比较理想是因为:

  • 对象可以单独被序列化
  • 把对象放在列表,元组,字典中仍然可以直接被序列化
  • 把对象传给别的对象,仍然可以被序列化

后面两个优点是前面的方法都无法比拟的。

看例子:

class Food(dict):
  def __init__(self, name, origin, calories, price):
    dict.__init__(self, name=name, origin=origin, calories=calories, price=price)    
    self.name = name 
    self.origin = origin 
    self.calories = calories 
    self.price = price 

  def to_json(self):
    return self.__dict__

print('...此处省略2500行推荐算法代码...')
food1 = Food('胡辣汤', '山东', 25, 2)
food2 = Food('油条', '山东', 88, 1)
food3 = Food('豆腐脑', '山东', 35, 2.5)
food4 = Food('焖饼', '山东', 65, 10)

recommend = [food1, food2, food3, food4]

import json
recommend_json = json.dumps(recommend, indent=4, ensure_ascii=False)
print(recommend_json)

运行结果是这样的,完美:

[
    {
        "name": "胡辣汤",
        "origin": "山东",
        "calories": 25,
        "price": 2
    },
    {
        "name": "油条",
        "origin": "山东",
        "calories": 88,
        "price": 1
    },
    {
        "name": "豆腐脑",
        "origin": "山东",
        "calories": 35,
        "price": 2.5
    },
    {
        "name": "焖饼",
        "origin": "山东",
        "calories": 65,
        "price": 10
    }
]

注意代码中的要点:

  • 继承dict类,因为dict是可以被序列化的
  • __init__函数中调用dict的构造函数。

总的来说,我对json库的表现是不满意的。把简单的问题搞得复杂化,但是it is what it is。

就像人生一样,很多事情看起来并不合理,但我们心里默默说一句QNMD,还是选择接受它。

声明:本网站资源来源于网络收集,如有侵权,请联系站长进行删除处理。 分享目的仅供大家学习和交流,请不要用于商业用途,否则后果自负。本站提供的源码、模板、插件等等其他资源,都不包含技术服务请大家谅解。本站资源售价只是赞助,收取费用仅维持本站的日常运营所需。反馈邮箱:1159995880@qq.com