在marshmallow中反序列化嵌套字段

12

我正在使用一个返回类似以下内容的API:

{'name': 'foo', 'start': {'date': '2016-06-19', 'time': '18:00'}}

我想使用marshmallow对其进行反序列化,仅获取名称和开始日期,因此期望的结果如下:

{'name': 'foo', 'date': '2016-06-19'}

但我还没有找到获取日期的方法,这是我所尝试过的:

from marshmallow import Schema, fields, pprint

event = {'name': 'foo', 'start': {'date': '2016-06-19', 'time': '18:00'}}
class EventSchema(Schema):
    name = fields.Str()
    date = fields.Str(load_from='start.date')


schema = EventSchema()
result = schema.load(event)
pprint(result.data)
4个回答

21

你所描述的可以通过在预处理步骤中转换输入数据来实现。尽管已被接受的答案似乎会这样做,但Marshmallow具有内置装饰器,可以让您以更加清晰的方式实现此目的:

from marshmallow import Schema, pre_load, fields, pprint

event = {'name': 'foo', 'start': {'date': '2016-06-19', 'time': '18:00'}}
expected = {'name': 'foo', 'date': '2016-06-19'}


class EventSchema(Schema):
    name = fields.Str()
    # Marshmallow 2
    date = fields.Str(load_from='date')
    # Marshmallow 3
    date = fields.Str(data_key='date')

    @pre_load
    def move_date(self, data):
        """This will alter the data passed to ``load()`` before Marshmallow
        attempts deserialization.
        """
        start = data.pop('start')
        data['date'] = start['date']
        return data

schema = EventSchema()
result = schema.load(event)
pprint(result.data)

assert result.data == expected

* transformpre-process 是物体建模和数据处理领域里的专业术语。我将它们加粗是因为知道这些术语可能有助于阅读者成功地通过谷歌搜索相关问题的答案。


3
我认为在这里使用 load_from='start.date' 是不正确的。 - Ivan Virabyan
优秀的回答。这可能会受益于最新文档的审查,因为对于不熟悉Python装饰器语法的开发人员来说,这可能并不明显。 - dreftymac
3
提醒一下,从版本3.0.0b8开始,load_fromdump_to被替换为data_key参数:https://marshmallow.readthedocs.io/en/dev/api_reference.html?highlight=load_from#module-marshmallow.fields - jket

7
你需要为嵌套字典创建一个名为NestedSchema的模式,并重写父模式的load方法以将嵌套字段添加到父模式中。指定一个only属性,以便Nested字段不获取所有项目:
class DateTimeSchema(Schema):
    date = fields.Str()
    time = fields.Str()


class EventSchema(Schema):
    name = fields.Str()
    date = fields.Nested(DateTimeSchema, load_from='start', only='date')

    def load(self, *args, special=None):
        _partial = super(EventSchema, self).load(*args)

        # Move special field from Nest to Parent
        if special is not None and special in _partial.data:
            _partial.data[special]  = _partial.data[special].get(special)
        return _partial

然后,您可以这样设置模式实例:

event = {'name': 'foo', 'start': {'date': '2016-06-19', 'time': '18:00'}}

schema, special_field = EventSchema(), 'date'
result = schema.load(event, special=special_field)
pprint(result.data)
# {'name': 'foo', 'date': '2016-06-19'}

您可以根据自己的喜好进行微调。


你也可以使用lambda表达式来简化代码,http://marshmallow.readthedocs.io/en/latest/custom_fields.html#function-fields,像这样:`date = fields.Function(load_from='start', load_only=True, deserialize=lambda start: start['date'])`。 - Luka Žitnik
@LukaŽitnik 但是这样你就失去了验证。 - user916367

3

Marshmallow 3 has Pluck:

class DateTimeSchema(Schema):
    date = fields.Str()
    time = fields.Str()

class EventSchema(Schema):
    name = fields.Str()
    date = fields.Pluck(DateTimeSchema, 'date')

fields.Pluck()的文档

将Original Answer翻译为"最初的回答"。

3
“pluck”不是@camaya所要求的相反吗? 我觉得@camaya需要在反序列化过程中“pluck”一个字段,而Pluck序列化过程中提取一个字段,如果我理解文档正确的话? - Dale

1

看到之前的答案很复杂,我想提供一个非常简单的解决方案(使用 fields.Function):

from marshmallow import Schema, fields

class EventSchema(Schema):
    name = fields.Str()
    date = fields.Function(data_key='start',
                           deserialize=lambda start: start['date'])

schema = EventSchema()
event = {'name': 'foo', 'start': {'date': '2016-06-19', 'time': '18:00'}}
result = schema.load(event)
print(result)

结果符合预期:{'name': 'foo', 'date': '2016-06-19'}
这适用于marshmallow的第3版本。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接