1# Serialization guidelines
2
3This developer document explains how Cirq serializes objects into (and out of)
4JSON.
5It also explains how to add a new serializable object,
6how to remove a serializable object from cirq while maintaining backwards
7compatibility with old serialized files, and various related guidelines.
8
9## Exposed API
10
11Most Cirq objects can be converted into json using the `cirq.to_json` method.
12This is useful to users who want to keep track of the experiments that they have
13run, or who want a simple way to communicate information between computers.
14
15Here is an example of serializing an object:
16
17```python
18import cirq
19import sympy
20
21obj = cirq.X**sympy.Symbol('t')
22text = cirq.to_json(obj)
23
24print(text)
25# prints:
26# {
27#   "cirq_type": "XPowGate",
28#   "exponent": {
29#     "cirq_type": "sympy.Symbol",
30#     "name": "t"
31#   },
32#   "global_shift": 0.0
33# }
34```
35
36The JSON can also be written to a file:
37
38```
39cirq.to_json(obj, filepath)
40```
41
42Or read back in from a file:
43
44```
45obj = cirq.read_json(filepath)
46```
47
48Or read back in from a string:
49
50```python
51deserialized_obj = cirq.read_json(json_text=text)
52print(deserialized_obj)
53# prints:
54# X**t
55```
56
57## Mechanism
58
59When writing JSON, Cirq checks if the given object has a `_json_dict_` method.
60If it does, the object is replaced by the output of that method.
61Otherwise, there are a series of several hardcoded cases for complex numbers,
62numpy arrays, sympy expressions, and a few others.
63The process of replacing an object by JSON proceeds recursively.
64For example, the `_json_dict_` method may return a dictionary that contains a
65value that is not JSON.
66This value will be noticed, and converted into JSON using the same mechanism
67(checking `_json_dict_`, checking hardcoded cases, etc).
68
69When reading JSON, Cirq gives
70[an object hook to json.loads](https://docs.python.org/3/library/json.html#encoders-and-decoders)
71.
72This hook checks if the object being parsed is a dictionary containing the key
73"cirq_type".
74If it is, Cirq looks up the associated value (the type string) in a hardcoded
75dictionary in `cirq/protocols/json.py`.
76That dictionary returns a callable object, usually a class, that maps the
77dictionary into a parsed value.
78If the returned object has a `_from_json_dict_` attribute, it is called instead.
79
80## Adding a new serializable value
81
82All of Cirq's public classes should be serializable. Public classes are the ones that can be found in the Cirq module top level
83namespaces, i.e. `cirq.*`, `cirq_google.*`, `cirq_aqt.*`, etc, (see [Cirq modules](./modules.md) for setting up JSON serialization for a module).
84This is enforced by the `test_json_test_data_coverage` test in
85`cirq-core/cirq/protocols/json_serialization_test.py`, which iterates over cirq's API
86looking for types with no associated json test data.
87
88There are several steps needed to support an object's serialization and deserialization,
89and pass `cirq-core/cirq/protocols/json_serialization_test.py`:
90
911. The object should have a `_json_dict_` method that returns a dictionary
92containing a `"cirq_type"` key as well as keys for each of the value's
93attributes. If these keys do not match the names of the class' initializer
94arguments, a `_from_json_dict_` class method must also be defined.
95Typically the `"cirq_type"` will be the name of your class.
96
972. In `class_resolver_dictionary` within the packages's `json_resolver_cache.py` file,
98for each serializable class, the `cirq_type` of the class should be mapped to the imported class
99within the package. The key may also be mapped to a helper method that
100returns the class (important for backwards compatibility if e.g. a class is later replaced
101by another one). After doing this, `cirq.to_json` and `cirq.read_json` should start
102working for your object.
103
1043. Add test data files to the package's `json_test_data` directory.
105These are to ensure that the class remains deserializable in future versions.
106There should be two files: `your_class_name.repr` and `your_class_name.json`.
107`your_class_name.repr` should contain a python expression that evaluates to an
108instances of your class, or a list of instances of your class.
109The expression must eval correctly when only `cirq`, `pandas as pd`,
110`numpy as np` and `sympy` have been imported.
111Ideally, the contents of the `.repr` file are exactly the output of
112`repr(your_obj)`.
113`your_class_name.json` should contain the expected JSON output when serializing
114the test value from `your_class_name.repr`.
115
116## Deprecating a serializable value
117When a serializable value is marked deprecated, but is not yet removed, the
118`.json` and `.repr` files continue to exist but `json_serialization_test.py`
119will start complaining that deprecated values cannot be used in tests.
120In order to fix this, one should add an entry corresponding to deprecated value to the `deprecated` dict in
121`cirq-<module>/cirq/protocols/json_test_data/spec.py`, of the form:
122```python
123deprecated={
124 'DeprecatedClass': 'deprecation_deadline',
125}
126```
127
128## Removing a serializable value
129
130When a serializable value is removed from cirq, old serialized instances
131**must still work**.
132They may deserialize to something different (but equivalent), but it is crucial
133that they not fail to parse.
134As such, "removing" a serializable value is more akin to removing it
135*from the public API* as opposed to completely deleting it.
136
137There are several steps:
138
1391. Find the object's test files in relevant package's `json_test_data`
140directory. Change the file name extensions from `.json` to `.json_inward` and `.repr` to
141`.repr_inward`. This indicates that only deserialization needs to be tested, not deserialization
142and serialization. If `_inward` files already exist, merge into them (e.g. by
143ensuring they encode lists and then appending into those lists).
144
1452. Define a parsing method to stand in for the object.
146This parsing method must return an object with the same basic behavior as the
147object being removed, but does not have to return an exactly identical object.
148For example, an X could be replaced by a PhasedX with no phasing.
149Edit the entry in the in `cirq-<module>/json_test_data/spec.py` or in the
150 relevant package's `class_resolver_dictionary` (`cirq-<module>/cirq_module/json_resolver_cache.py`) to
151 point at this method instead of the object being removed.
152(There will likely be debate about exactly how to do this, on a case by case
153basis.)
154
155
156## Marking a public object as non-serializable
157
158Some public objects will be exceptional and should not be serialized ever. These could be marked in the
159given top level package's spec.py (`<module>/<top level package>/json_test_data/spec.py`) by adding its
160name to `should_not_serialize`.
161
162We allow for incremental introduction of new objects to serializability - if an object should be
163serialized but is not yet serializable, it should be added to the `not_yet_serializable` list in the `spec.py` file.