Description
Especially for advanced Dash components, it would be useful if the Dash component author could do serialization/deserialization for the Dash app developer, in order to make an easy component API. Today the component authors are limited to accepting directly JSON-serializable Python objects, and can't e.g. accept instances of custom Python data objects / classes.
One way of achieving this feature could perhaps be roughly along these lines:
- In
dash.development.base_component.Component
add@classmethods
serialize
anddeserialize
:For all Dash components that don't need any other serialization/deserialization than what is offered today, no change is necessary since they will inherit the default serialization functions fromclass Component: @classmethod def serialize(prop_name, value): return to_json(value) @classmethod def deserialize(prop_name, value): return from_json(value)
Component
. For Dash component authors that want to support some other serialization function, they simply override them as usual: -
from dash.development.base_component import Component class SomeDashComponent(Component): @classmethod def serialize(prop_name, value): # component author defines serialization logic @classmethod def deserialize(prop_name, value): # component author defines deserialization logic
- In Dash core, call
SomeDashComponent.(de)serialize
when input/output data for a prop is to be serialized/deserialized.
Features possible with this:
- Advanced Dash components can have a simplified API, making it easier to be a Dash app developer (they can e.g. accept any Python class instance/data object making sense as input for a given component, and serialize from there).
- Components fine with default serialization don't need to do any changes.
- We keep non-default serialization logic definition together with the user of the serialized data (i.e. the React/Dash component). The component itself knows what is the optimal serialization format to send to the frontend.
- The same Python object (e.g.
pd.DataFrame
) could be serialized in different ways, depending on component. E.g. theDataTable
component, showing data row by row, would in general serialize thepd.DataFrame
in a different way than e.g. a graph component wanting the data serialized in another format (examples could be e.g. performance reasons frontend wanting the data serialized in some specific way, or maybe there for a given component are restrictions/assumptions on thepd.DataFrame
input that can be used to e.g. remove redundant data before sent to the client).
- The same Python object (e.g.
- It would be easy for Dash components to raise Python exceptions during serialization if input data is not on expected format (Dash app developers are used to Python traceback/exceptions/error messages, and would find this easier to relate to compared to error messages in browser console on the serialized data).
- It opens up some nice features on Python 3+ with
typing
(see below)*.
*The typing
module in Python 3+ opens up possibilities for libraries to accomplish all these at the same time:
- Document their code in the code, both for themselves and for users of the library.
- Utilize static type checks tools like
mypy
to check their code for type consistency. - Improve automatic documentation compilation (previously type has been written in e.g. docstrings, easily getting out of date when no tools like
mypy
can help detect inconsistencies).
Taken down to the context in this issue, type hint annotations could be introduced without breaking change at a later point following something along these lines:
from typing import overload, Literal, List
from dash.development.base_component import Component
import pandas as pd
# Using typing.overload and typing.Literal, two standard features in Python std.lib
# we can annotate the component with expected data input format for different
# props using standard conventions.
class SomeDashComponent(Component):
@overload
@classmethod
def serialize(prop_name: Literal["some_prop"], value: pd.DataFrame):
...
@overload
@classmethod
def serialize(prop_name: Literal["some_other_prop1", "some_other_prop2"], value: List[int]):
...
@classmethod
def serialize(prop_name, value):
if prop_name == "some_prop":
return some_serialization_logic(value)
# The other props are e.g. serialized using standard function
return to_json(value)
# @overload
# @classmethod
# def deserialize(...same setup as for serialize
The last point here on type hinting is related to #719 (comment) and #1748.