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.