0

I have an array of custom objects (lightning records) that I serialize without any problems with my custom encoder:

class LightningJSONEncoder(json.JSONEncoder):
    def default(self, obj):
        if isinstance(obj, Lightning):
            dct = dict()
            dct['type'] = 'Feature'
            ...
            return dct
        return json.JSONEncoder.default(self, obj)  # pragma: no cover

This serializes an array of Lightnings as:

[{"type": "Feature", ...}, ..., {...}]

But I need to add more data, the number of Lightnings and another type, such as:

{
  "type": "FeatureCollection",
  "number_of_features": 3,
  "features": [{"type": "Feature", ...}, ..., {...}]
}

So, if I try to serialize:

json.dumps([lightning, lightning, lightning], cls=LightningJSONEncoder)

It gives me the first output and I have to manually add a new dict get the second output.

lights = [lightning1, lightning2, lightning3]
dct = {
    "type": "FeatureCollection",
    "number_of_features": len(lights),
    "features": lights
}
json.dumps(dct, cls=LightningJSONEncoder)

How can I get this behaviour but inside a custom JSONEncoder class? I've read about the iterencode but I don't get it.

Thank you.

Jaume Figueras
  • 968
  • 1
  • 6
  • 6
  • No you can't because you cannot change how the `json.JSONEncoder` handles Python `list`s. You can create your own container class and then customize how *it* is serialized. I do something like that (for different reasons) in this [answer of mine](https://stackoverflow.com/questions/13249415/how-to-implement-custom-indentation-when-pretty-printing-with-the-json-module/13252112#13252112) to another question (i.e. `NoIndent` is the special container class). – martineau Mar 23 '22 at 23:34

1 Answers1

0

You can't do what you directly because the json module will only call the default() method of your custom JSONEncoder when it encounters and object it doesn't already know how to handle.

You may be able to workaround that by defining your own "container" class and handling it in the method, too. Here's an example of what I am suggesting:

import json

class Lightning:
    def __init__(self, name):
        self.name = name
    def __repr__(self):
        return f'{type(self).__name__}({self.name!r})'


class FeatureCollection:
    def __init__(self, *features):
        self.features = features
    def __repr__(self):
        return f'{type(self).__name__}({self.features!r})'


class LightningJSONEncoder(json.JSONEncoder):
    def default(self, obj):
        if isinstance(obj, Lightning):
            return dict(type='Feature', name=obj.name)
        elif isinstance(obj, FeatureCollection):
            return dict(type='FeatureCollection',
                        number_of_features=len(obj.features),
                        features=obj.features)
        return json.JSONEncoder.default(self, obj)


lightenings = [Lightning('Cloud-to-Ground'), Lightning('Cloud-to-Air'),
               Lightning('Intracloud')]

print(json.dumps(FeatureCollection(lightenings), indent=4, cls=LightningJSONEncoder))

Output:

{
    "type": "FeatureCollection",
    "number_of_features": 1,
    "features": [
        [
            {
                "type": "Feature",
                "name": "Cloud-to-Ground"
            },
            {
                "type": "Feature",
                "name": "Cloud-to-Air"
            },
            {
                "type": "Feature",
                "name": "Intracloud"
            }
        ]
    ]
}
martineau
  • 119,623
  • 25
  • 170
  • 301