Snowman
Snowman

Reputation: 32061

Python StructuredProperty to dictionary

My models all have a method which converts the model to a dictionary:

def to_dict(model):
    output = {}
    SIMPLE_TYPES = (int, long, float, bool, dict, basestring, list)
    for key, prop in model._properties.iteritems():
        value = getattr(model, key)

        if value is None:
            continue
        if isinstance(value, SIMPLE_TYPES):
            output[key] = value
        elif isinstance(value, datetime.date):
            dateString = value.strftime('%Y-%m-%d %H:%M:%S.%f')[:-3]
            output[key] = dateString
        elif isinstance(value, ndb.Model):
            output[key] = to_dict(value)
        else:
            raise ValueError('cannot encode ' + repr(prop))
    return output

Now, one of my models, X, has a LocalStructuredProperty:

metaData = ndb.LocalStructuredProperty(MetaData, repeated=True)

So, repeated=True means this will be a list of MetaData objects. MetaData is another model, and it also has the same to_dict method.

However, when I call json.dumps(xInstance.to_dict()), I get an exception:

raise TypeError(repr(o) + " is not JSON serializable")
TypeError: MetaData(count=0, date=datetime.datetime(2012, 9, 19, 2, 46, 56, 660000), unique_id=u'8E2C3B07A06547C78AB00DD73B574B8C') is not JSON serializable

How can I handle this?

Upvotes: 1

Views: 621

Answers (3)

Mu Mind
Mu Mind

Reputation: 11194

If you want to handle this in to_dict() and before the level of serializing to JSON, you'll just need a few more cases in your to_dict(). Firstly, you said the to_dict definition above is a method. I would have it delegate to a function or staticmethod so you have something you can call on ints and such without checking the type first. The code will just come out better that way.

def coerce(value):
    SIMPLE_TYPES = (int, long, float, bool, basestring)
    if value is None or isinstance(value, SIMPLE_TYPES):
        return value
    elif isinstance(value, datetime.date):
        return value.strftime('%Y-%m-%d %H:%M:%S.%f')[:-3]
    elif hasattr(value, 'to_dict'):    # hooray for duck typing!
        return value.to_dict()
    elif isinstance(value, dict):
        return dict((coerce(k), coerce(v)) for (k, v) in value.items())
    elif hasattr(value, '__iter__'):    # iterable, not string
        return map(coerce, value)
    else:
        raise ValueError('cannot encode %r' % value)

Then just plug that into your to_dict method itself:

def to_dict(model):
    output = {}
    for key, prop in model._properties.iteritems():
        value = coerce(getattr(model, key))
        if value is not None:
            output[key] = value
    return output

Upvotes: 2

Snowman
Snowman

Reputation: 32061

I figured out how to solve the issue: in the X class, add this to the to_dict() method:

            ...         
            if value is None:
                continue
            if key == 'metaData':
                array = list()
                for data in value:
                    array.append(data.to_dict())
                output[key] = array
            elif isinstance(value, SIMPLE_TYPES):
                output[key] = value
            ...

Though I'm not really sure how to automate this case where it's not based off key, but rather whenever it encounters a list of custom objects, it first converts each object in the list to_dict() first.

Upvotes: 0

Mu Mind
Mu Mind

Reputation: 11194

All you need to do to serialize is to implement a function

def default_encode(obj):
    return obj.to_dict()

and then encode your JSON with

json.dumps(X.to_dict(), default=default_encode)

Upvotes: 1

Related Questions