Implementing a New Data Type

COMPAS data types are classes that are based on compas.data.Data.

Data types can be serialized to JSON with

  • compas.json_dump()

  • compas.json_dumps()

  • compas.json_dumpz()

and deserialized with the corresponding “load” functions

  • compas.json_load()

  • compas.json_loads()

  • compas.json_loadz()

All geometry objects and data structures, and also, for example, the visualization scene, are serializable data types.

Creating a new data type

In most cases, it is sufficient to implement the __data__ property when creating your custom Data class.

class SomeThing(Data):

    def __init__(self, a, b)
        super().__init__()
        # note that if the code needs to be compatible with IronPython
        # you should write the following:
        # super(SomeThing, self).__init__()
        self.a = a
        self.b = b

    @property
    def __data__(self):
        return {
            "a": self.a,
            "b": self.b,
        }
>>> custom = SomeThing(a=1, b=2)
>>> compas.json_dump(custom, "custom.json")
>>> result = compas.json_load("custom.json")
>>> isinstance(result, SomeThing)
True
>>> result.a
1
>>> result.b
2

If the attributes stored in the data dictionary defined by the __data__ property are different from the initialization parameters of the class, you must also customize the __from_data__ class method to compensate for the difference.

class SomeThing(Data):

    def __init__(self)
        super().__init__()
        # note that if the code needs to be compatible with IronPython
        # you should write the following:
        # super(SomeThing, self).__init__()
        self.items = []

    @property
    def __data__(self):
        return {
            "items": self.items,
        }

    @classmethod
    def __from_data__(cls, data):
        custom = cls()
        for item in data['items']:
            custom.add(item)
        return custom

    def add(self, item):
        self.items.append(item)
>>> custom = SomeThing()
>>> custom.add(1)
>>> custom.add(2)
>>> compas.json_dump(custom, "custom.json")
>>> result = compas.json_load("custom.json")
>>> isinstance(result, SomeThing)
True
>>> result.items
[1, 2]

Attribute types

Any attribute that is an instance of a Python base type or a serializable COMPAS data object can be included in the data dict created by the __data__ property without further processing. The serialization process will recursively serialize all these attributes.

class SomeThing(Data):

    def __init__(self, point, frame, mesh):
        super().__init__()
        # note that if the code needs to be compatible with IronPython
        # you should write the following:
        # super(SomeThing, self).__init__()
        self.point = point
        self.frame = frame
        self.mesh = mesh

    @property
    def __data__(self):
        return {
            "point": self.point,
            "frame": self.frame,
            "mesh": self.mesh,
        }
>>> import compas
>>> from compas.geometry import Point, Frame
>>> from compas.datastructures import Mesh
>>> point = Point(1, 2, 3)
>>> frame = Frame()
>>> mesh = Mesh.from_meshgrid(10, 10)
>>> custom = SomeThing(point, frame, mesh)
>>> compas.json_dump(custom, "custom.json")
>>> result = compas.json_load("custom.json")
>>> isinstance(result.point, Point)
True
>>> isinstance(result.frame, Frame)
True
>>> isinstance(result.mesh, Mesh)
True
>>> result.point == point
True
>>> result.point is point
False

Note that the the automatic serialization process will incur overhead information that increases the size of the resulting JSON file. The performance impact may be significant when many of these instances are serialized.

To avoid this, anticipated conversions can be included explicitly in __data__ and __from_data__.

class SomeThing(Data):

    def __init__(self, point, frame, mesh):
        super().__init__()
        # note that if the code needs to be compatible with IronPython
        # you should write the following:
        # super(SomeThing, self).__init__()
        self.point = point
        self.frame = frame
        self.mesh = mesh

    @property
    def __data__(self):
        return {
            "point": self.point.__data__,
            "frame": self.frame.__data__,
            "mesh": self.mesh.__data__,
        }

    @classmethod
    def __from_data__(cls, data):
        return cls(
            Point.__from_data__(data['point']),
            Frame.__from_data__(data['frame']),
            Mesh.__from_data__(data['mesh']),
        )