首页 > 解决方案 > 有没有一种优雅的方法可以用 django 序列化器扁平化嵌套的 JSON?

问题描述

我从 API 接收嵌套的 JSON(我无法影响结构)。我想在反序列化对象时使用 django rest framework serializer展平嵌套字段。我该如何优雅地做到这一点?

这是我目前的方法,它通过使用嵌套序列化程序并在以下内容中进行展平来工作.create()

from dataclasses import dataclass

from rest_framework import serializers

input_data = {
    "objectName": "Johnny",
    "geoInfo": {
        "latitude": 1.2,
        "longitude": 3.4,
    },
}

flattened_output = {
    "name": "Johnny",
    "lat": 1.2,
    "lon": 3.4,
}


@dataclass
class Thing:
    name: str
    lat: float
    lon: float


class TheFlattener(serializers.Serializer):
    class GeoInfoSerializer(serializers.Serializer):
        latitude = serializers.FloatField()
        longitude = serializers.FloatField()

    objectName = serializers.CharField(max_length=50, source="name")
    geoInfo = GeoInfoSerializer()

    def create(self, validated_data):
        geo_info = validated_data.pop("geoInfo")
        validated_data["lat"] = geo_info["latitude"]
        validated_data["lon"] = geo_info["longitude"]
        return Thing(**validated_data)


serializer = TheFlattener(data=input_data)
serializer.is_valid(raise_exception=True)
assert serializer.save() == Thing(**flattened_output)

我知道在将对象序列化为 JSON 时,您可以在source参数中引用嵌套/相关对象,例如

first_name = CharField(source="user.first_name")

这真的很好,但我无法找到类似的反序列化。

标签: pythonjsondjangonesteddeserialization

解决方案


对于您的情况,我认为您的解决方案非常优雅,因为您想更改字段的名称,而不仅仅是将其展平。

然而,对于较大的嵌套 Json,您可以定义一个展平函数(递归/迭代),或者使用 pandas 库中的一些有用工具,例如:

pandas.json_normalize & pandas.DataFrame。to_dict/to_json

https://pandas.pydata.org/pandas-docs/version/1.2.0/reference/api/pandas.json_normalize.html

https://pandas.pydata.org/docs/reference/api/pandas.DataFrame.to_dict.html

选项1

from pandas import json_normalize

input_data = {
    "objectName": "Johnny",
    "geoInfo": {
        "latitude": 1.2,
        "longitude": 3.4,
    },
}

d = json_normalize(input_data).to_dict(into=OrderedDict)
print(type(d))
print(d)

输出 1:

<class 'collections.OrderedDict'>
OrderedDict([('objectName', OrderedDict([(0, 'Johnny')])), ('geoInfo.latitude', OrderedDict([(0, 1.2)])), ('geoInfo.longitude', OrderedDict([(0, 3.4)]))])

选项 2

d = json_normalize(input_data).to_dict(into=OrderedDict, 'list')

输出 2

<class 'collections.OrderedDict'>
OrderedDict([('objectName', ['Johnny']), ('geoInfo.latitude', [1.2]), ('geoInfo.longitude', [3.4])])

选项 3

d = json_normalize(input_data).to_dict(into=OrderedDict, orient='records')

输出 3

<class 'list'>
[OrderedDict([('objectName', 'Johnny'), ('geoInfo.latitude', 1.2), ('geoInfo.longitude', 3.4)])]

选项 4

d = json_normalize(input_data).to_dict('index', into=OrderedDict)

输出 4

<class 'collections.OrderedDict'>
OrderedDict([(0, {'objectName': 'Johnny', 'geoInfo.latitude': 1.2, 'geoInfo.longitude': 3.4})])

对于这种情况,选项 3将是最好的。但由于它返回长度为 1 的列表,请添加 [0]

解决方案:

d = json_normalize(input_data).to_dict(orient='records')[0]

输出:

<class 'dict'>
{'objectName': 'Johnny', 'geoInfo.latitude': 1.2, 'geoInfo.longitude': 3.4}

对于重命名字段,需要更多的额外步骤。

from pandas import json_normalize
from collections import OrderedDict

input_data = {
    "objectName": "Johnny",
    "geoInfo": {
        "latitude": 1.2,
        "longitude": 3.4,
    },
}

d = json_normalize(input_data).to_dict(orient='records', into=OrderedDict)[0]
N = len(d)
key_map = {"objectName": "name", "geoInfo.latitude": "lat", "geoInfo.longitude":"lon"}
# or use list ["name", "lat", "lon"] and access by index.
# I used dict key_map for cases when normal dict is used, instead of ordereddict
# (when into=OrderedDict argument is not given to pandas to_dict function)
for _ in range(N):
    k, v = d.popitem(last=False)
d[key_map[k]] = v

print(d)

输出:

OrderedDict([('name', 'Johnny'), ('lat', 1.2), ('lon', 3.4)])

推荐阅读