Search code examples
pythonmarshmallow

Loading from nested keys in marshmallow


I am trying to using Marshmallow to load nested keys from a dictionary, but I cannot figure out how to do so.

For example, I have a field 'price' which maps to the incoming dictionary's value dictionary['price']['value']. I cannot find a built-in way that Marshmallow supports this, so I tried using a pre_load processor to flatten the dictionary. However, I am unable to get this to work. My example unit test fails with this error for all nested keys:

Is not a valid string

Any ideas?

import unittest


class ResultSchema(Schema):
    title = fields.String()
    description = fields.String()
    brand = fields.String(load_from='brand__name')
    price = fields.String(load_from='price__value')
    url = fields.String(load_from='url__value')

    @pre_load
    def flatten_fields(self, data):
        keys = [('brand', 'name'), ('price', 'value'), ('url', 'value')]

        for outer, inner in keys:
            try:
                data[outer + '__' + inner] = data[outer][inner]
            except (KeyError, TypeError):
                pass

        return data

class SchemaTests(unittest.TestCase):

    def setUp(self):
        self.resultSchema = ResultSchema()
        self.expected = {
            'title': 'fake title',
            'description': 'fake description',
            'brand': {'name': 'fake brand name'},
            'price': {'value': '$82.99', 'integral': 8299},
            'url': {'value': 'fake url'},
            'images': [
                {'value': 'small url', 'xsize': 60, 'ysize': 60},
                {'value': 'small-medium url', 'xsize': 100, 'ysize': 100},
                {'value': 'medium-large url', 'xsize': 160, 'ysize': 160},
                {'value': 'large url', 'xsize': 400, 'ysize': 400}
            ]
        }

    def test_schema_load(self):
        loaded, err = self.resultSchema.load(self.expected)
        if err:
            self.fail(err)

Solution

  • Key name assigned with load_from argument kicks in when the field’s name is not found on the input, your method will work if original key get removed:

    data[outer + '__' + inner] = data[outer][inner]
    del data[outer]
    

    but, why not change the value of the nested field directly, then load_from=... become unnecessary:

    data[outer] = data[outer][inner]