Marshmallow: Flatten lists

Created on 13 May 2020  ·  6Comments  ·  Source: marshmallow-code/marshmallow

Hey,

From the dictionary:

data = {
    'id': 1337,
    'profiles': [
        {'email': '[email protected]'},
        {'email': '[email protected]'},
    ]
}

Which schema could I write to dump the following result?

{
  "id": 1337,
  "current_email": "[email protected]"
}

Assuming the latest email in the first entry from profiles.

I imagine it should be possible with fields.Pluck, but I can't make it work.

Thanks!

question

Most helpful comment

Indeed, it is also working. Full working example:

from marshmallow import decorators, fields, Schema

data = {
    'id': 1337,
    'profiles': [
        {'email': '[email protected]'},
        {'email': '[email protected]'},
    ]
}


class ProfileSchema(Schema):
    email = fields.String()


class UserSchema(Schema):
    id = fields.Int()

    @decorators.post_dump(pass_original=True)
    def add_email(self, data, original_data, many=False):
        data['email'] = original_data['profiles'][0]['email']
        return data


print (UserSchema().dump(data))

pass_original is important here. By default, original data is not given to post_dump.

Thanks!

All 6 comments

I found a hack.

Schema.dump() calls get_attribute, which calls marshmallow.utils.get_value to handle cases when the field attribute is a dot-separated string. For example with fields.String(attribute='obj.value') the serialized value is retrieved from the field value of obj.

Unfortunately utils.get_value doesn't accept array accessors, so attribute=profiles.0.email is not valid.

It is, however, possible to override get_attribute such as:

from marshmallow import fields, Schema

data = {
    'id': 1337,
    'profiles': [
        {'email': '[email protected]'},
        {'email': '[email protected]'},
    ]
}

class ProfileSchema(Schema):
    email = fields.String()


class UserSchema(Schema):
    id = fields.Int()
    email = fields.String(attribute=lambda obj: obj['profiles'][0]['email'])

    def get_attribute(self, obj, attr, default):
        if callable(attr):
            return attr(obj)
        return super().get_attribute(obj, attr, default)


print (UserSchema().dump(data))

This example does not check for errors and assumes profiles is always a list of at least one element with an email field.

Also, this method breaks schema loading, and there is no obvious way to fix it (but I don't need it anyway).

You could also use a custom field and implement _serialize.

Sidenote: Are you sure you want to be dump-ing a dictionary? Typically you load dictionaries into your application objects.

Closing for now, but feel free to continue discussion

How can I create this custom field? With the following data and schema:

data = {
    'id': 1337,
    'profiles': [
        {'email': '[email protected]'},
        {'email': '[email protected]'},
    ]
}

class MyField(fields.Field):
    def _serialize(self, value, attr, obj, **kwargs):
        # never called
        print(self, value, attr, obj, kwargs)

class UserSchema(Schema):
    id = fields.Int()
    email = MyField()

print (UserSchema().dump(data))

MyField._serialize is never called since email doesn't exist in the payload.

Oh right, custom field isn't what you want. You could use a post_dump method instead.

Indeed, it is also working. Full working example:

from marshmallow import decorators, fields, Schema

data = {
    'id': 1337,
    'profiles': [
        {'email': '[email protected]'},
        {'email': '[email protected]'},
    ]
}


class ProfileSchema(Schema):
    email = fields.String()


class UserSchema(Schema):
    id = fields.Int()

    @decorators.post_dump(pass_original=True)
    def add_email(self, data, original_data, many=False):
        data['email'] = original_data['profiles'][0]['email']
        return data


print (UserSchema().dump(data))

pass_original is important here. By default, original data is not given to post_dump.

Thanks!

Was this page helpful?
0 / 5 - 0 ratings

Related issues

ambye85 picture ambye85  ·  4Comments

imhoffd picture imhoffd  ·  3Comments

nickretallack picture nickretallack  ·  4Comments

zohuchneg picture zohuchneg  ·  3Comments

tadams42 picture tadams42  ·  3Comments