Repository: quantumlib/Cirq

Issue:
TrialResult -> Result rename broke json parsing
**Description of the issue**

`cirq.TrialResult` was deprecated for `cirq.Result`, however the json parsing did not follow correctly the deprecation, the new code breaks on the old json format.

**How to reproduce the issue**

```
import cirq

print(cirq.read_json(json_text="""{
  "cirq_type": "TrialResult",
  "params": {
    "cirq_type": "ParamResolver",
    "param_dict": []
  },
  "measurements": {
    "0,1": {
      "packed_digits": "fcc0",
      "binary": true,
      "dtype": "uint8",
      "shape": [
        5,
        2
      ]
    }
  }
}
"""))

```

Prints:
```
Traceback (most recent call last):
  File "/Users/balintp/Library/Application Support/JetBrains/PyCharm2020.2/scratches/scratch_1.py", line 3, in <module>
    print(cirq.read_json(json_text="""{
  File "/Users/balintp/dev/proj/Cirq/cirq/protocols/json_serialization.py", line 526, in read_json
    return json.loads(json_text, object_hook=obj_hook)
  File "/Library/Frameworks/Python.framework/Versions/3.8/lib/python3.8/json/__init__.py", line 370, in loads
    return cls(**kw).decode(s)
  File "/Library/Frameworks/Python.framework/Versions/3.8/lib/python3.8/json/decoder.py", line 337, in decode
    obj, end = self.raw_decode(s, idx=_w(s, 0).end())
  File "/Library/Frameworks/Python.framework/Versions/3.8/lib/python3.8/json/decoder.py", line 353, in raw_decode
    obj, end = self.scan_once(s, idx)
  File "/Users/balintp/dev/proj/Cirq/cirq/protocols/json_serialization.py", line 523, in obj_hook
    return _cirq_object_hook(x, resolvers)
  File "/Users/balintp/dev/proj/Cirq/cirq/protocols/json_serialization.py", line 429, in _cirq_object_hook
    raise ValueError("Could not resolve type '{}' "
ValueError: Could not resolve type 'TrialResult' during deserialization
```

**Cirq version**
v0.9.0



Discussion:

Resolving patch:
diff --git a/cirq/_compat.py b/cirq/_compat.py
--- a/cirq/_compat.py
+++ b/cirq/_compat.py
@@ -15,7 +15,7 @@
 """Workarounds for compatibility issues between versions and libraries."""
 import functools
 import warnings
-from typing import Any, Callable, Optional, Dict, Tuple
+from typing import Any, Callable, Optional, Dict, Tuple, Type
 from types import ModuleType
 
 import numpy as np
@@ -122,6 +122,47 @@ def decorated_func(*args, **kwargs) -> Any:
     return decorator
 
 
+def deprecated_class(*, deadline: str, fix: str,
+                     name: Optional[str] = None) -> Callable[[Type], Type]:
+    """Marks a class as deprecated.
+
+    Args:
+        deadline: The version where the function will be deleted (e.g. "v0.7").
+        fix: A complete sentence describing what the user should be using
+            instead of this particular function (e.g. "Use cos instead.")
+        name: How to refer to the class.
+            Defaults to `class.__qualname__`.
+
+    Returns:
+        A decorator that decorates classes with a deprecation warning.
+    """
+
+    def decorator(clazz: Type) -> Type:
+        clazz_new = clazz.__new__
+
+        def patched_new(cls, *args, **kwargs):
+            qualname = (clazz.__qualname__ if name is None else name)
+            print(f"HELLO {qualname}")
+            warnings.warn(
+                f'{qualname} was used but is deprecated.\n'
+                f'It will be removed in cirq {deadline}.\n'
+                f'{fix}\n',
+                DeprecationWarning,
+                stacklevel=2)
+
+            return clazz_new(cls)
+
+        setattr(clazz, '__new__', patched_new)
+        clazz.__doc__ = (f'THIS CLASS IS DEPRECATED.\n\n'
+                         f'IT WILL BE REMOVED IN `cirq {deadline}`.\n\n'
+                         f'{fix}\n\n'
+                         f'{clazz.__doc__ or ""}')
+
+        return clazz
+
+    return decorator
+
+
 def deprecated_parameter(
         *,
         deadline: str,
diff --git a/cirq/protocols/json_serialization.py b/cirq/protocols/json_serialization.py
--- a/cirq/protocols/json_serialization.py
+++ b/cirq/protocols/json_serialization.py
@@ -160,6 +160,7 @@ def two_qubit_matrix_gate(matrix):
                 'TaggedOperation': cirq.TaggedOperation,
                 'ThreeDQubit': cirq.pasqal.ThreeDQubit,
                 'Result': cirq.Result,
+                'TrialResult': cirq.TrialResult,
                 'TwoDQubit': cirq.pasqal.TwoDQubit,
                 'TwoQubitMatrixGate': two_qubit_matrix_gate,
                 'TwoQubitDiagonalGate': cirq.TwoQubitDiagonalGate,
diff --git a/cirq/study/result.py b/cirq/study/result.py
--- a/cirq/study/result.py
+++ b/cirq/study/result.py
@@ -22,7 +22,7 @@
 import pandas as pd
 
 from cirq import value, ops
-from cirq._compat import deprecated, proper_repr
+from cirq._compat import proper_repr, deprecated_class
 from cirq.study import resolver
 
 if TYPE_CHECKING:
@@ -323,7 +323,9 @@ def _from_json_dict_(cls, params, measurements, **kwargs):
             })
 
 
-@deprecated(deadline='v0.11', fix='Use cirq.Result instead.')
+@deprecated_class(deadline='v0.11',
+                  fix='Use cirq.Result instead.',
+                  name="cirq.TrialResult")
 class TrialResult(Result):
     pass
 


Test patch:
diff --git a/cirq/_compat_test.py b/cirq/_compat_test.py
--- a/cirq/_compat_test.py
+++ b/cirq/_compat_test.py
@@ -20,7 +20,7 @@
 
 import cirq.testing
 from cirq._compat import (proper_repr, deprecated, deprecated_parameter,
-                          proper_eq, wrap_module)
+                          proper_eq, wrap_module, deprecated_class)
 
 
 def test_proper_repr():
@@ -152,3 +152,35 @@ def test_wrap_module():
 
     with cirq.testing.assert_logs(count=0):
         _ = wrapped.bar
+
+
+def test_deprecated_class():
+
+    class NewClass:
+
+        def __init__(self, a):
+            self._a = a
+
+        @property
+        def a(self):
+            return self._a
+
+        def __repr__(self):
+            return f"NewClass: {self.a}"
+
+        @classmethod
+        def hello(cls):
+            return f"hello {cls}"
+
+    @deprecated_class(deadline="deadline", fix="theFix", name="foo")
+    class OldClass(NewClass):
+        """The OldClass docs"""
+
+    assert OldClass.__doc__.startswith("THIS CLASS IS DEPRECATED")
+    assert "OldClass docs" in OldClass.__doc__
+
+    with cirq.testing.assert_logs('foo was used but is deprecated',
+                                  'will be removed in cirq deadline', 'theFix'):
+        old_obj = OldClass("1")
+        assert repr(old_obj) == "NewClass: 1"
+        assert "OldClass" in old_obj.hello()
diff --git a/cirq/protocols/json_test_data/TrialResult.json b/cirq/protocols/json_test_data/TrialResult.json
new file mode 100644
--- /dev/null
+++ b/cirq/protocols/json_test_data/TrialResult.json
@@ -0,0 +1,297 @@
+[
+  {
+    "cirq_type": "TrialResult",
+    "params": {
+      "cirq_type": "ParamResolver",
+      "param_dict": [
+        [
+          {
+            "cirq_type": "sympy.Symbol",
+            "name": "a"
+          },
+          0.5
+        ]
+      ]
+    },
+    "measurements": {
+      "m": {
+        "packed_digits": "d32a",
+        "binary": true,
+        "dtype": "bool",
+        "shape": [
+          3,
+          5
+        ]
+      }
+    }
+  },
+  {
+    "cirq_type": "TrialResult",
+    "params": {
+      "cirq_type": "ParamResolver",
+      "param_dict": [
+        [
+          {
+            "cirq_type": "sympy.Symbol",
+            "name": "a"
+          },
+          0.5
+        ]
+      ]
+    },
+    "measurements": {
+      "m": {
+        "packed_digits": "d32a",
+        "binary": true,
+        "dtype": "uint8",
+        "shape": [
+          3,
+          5
+        ]
+      }
+    }
+  },
+  {
+    "cirq_type": "TrialResult",
+    "params": {
+      "cirq_type": "ParamResolver",
+      "param_dict": [
+        [
+          {
+            "cirq_type": "sympy.Symbol",
+            "name": "a"
+          },
+          0.5
+        ]
+      ]
+    },
+    "measurements": {
+      "m": {
+        "packed_digits": "934e554d5059010076007b276465736372273a20277c6931272c2027666f727472616e5f6f72646572273a2046616c73652c20277368617065273a2028332c2033292c207d202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020200a000102030405060708",
+        "binary": false,
+        "dtype": "int8",
+        "shape": [
+          3,
+          3
+        ]
+      }
+    }
+  },
+  {
+    "cirq_type": "TrialResult",
+    "params": {
+      "cirq_type": "ParamResolver",
+      "param_dict": [
+        [
+          {
+            "cirq_type": "sympy.Symbol",
+            "name": "a"
+          },
+          0.5
+        ]
+      ]
+    },
+    "measurements": {
+      "m": {
+        "packed_digits": "934e554d5059010076007b276465736372273a20273c6932272c2027666f727472616e5f6f72646572273a2046616c73652c20277368617065273a2028332c2033292c207d202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020200a000001000200030004000500060007000800",
+        "binary": false,
+        "dtype": "int16",
+        "shape": [
+          3,
+          3
+        ]
+      }
+    }
+  },
+  {
+    "cirq_type": "TrialResult",
+    "params": {
+      "cirq_type": "ParamResolver",
+      "param_dict": [
+        [
+          {
+            "cirq_type": "sympy.Symbol",
+            "name": "a"
+          },
+          0.5
+        ]
+      ]
+    },
+    "measurements": {
+      "m": {
+        "packed_digits": "934e554d5059010076007b276465736372273a20273c6934272c2027666f727472616e5f6f72646572273a2046616c73652c20277368617065273a2028332c2033292c207d202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020200a000000000100000002000000030000000400000005000000060000000700000008000000",
+        "binary": false,
+        "dtype": "int32",
+        "shape": [
+          3,
+          3
+        ]
+      }
+    }
+  },
+  {
+    "cirq_type": "TrialResult",
+    "params": {
+      "cirq_type": "ParamResolver",
+      "param_dict": [
+        [
+          {
+            "cirq_type": "sympy.Symbol",
+            "name": "a"
+          },
+          0.5
+        ]
+      ]
+    },
+    "measurements": {
+      "m": {
+        "packed_digits": "934e554d5059010076007b276465736372273a20273c6938272c2027666f727472616e5f6f72646572273a2046616c73652c20277368617065273a2028332c2033292c207d202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020200a000000000000000001000000000000000200000000000000030000000000000004000000000000000500000000000000060000000000000007000000000000000800000000000000",
+        "binary": false,
+        "dtype": "int64",
+        "shape": [
+          3,
+          3
+        ]
+      }
+    }
+  },
+  {
+    "cirq_type": "TrialResult",
+    "params": {
+      "cirq_type": "ParamResolver",
+      "param_dict": [
+        [
+          {
+            "cirq_type": "sympy.Symbol",
+            "name": "a"
+          },
+          0.5
+        ]
+      ]
+    },
+    "measurements": {
+      "m": {
+        "packed_digits": "934e554d5059010076007b276465736372273a20277c7531272c2027666f727472616e5f6f72646572273a2046616c73652c20277368617065273a2028332c2033292c207d202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020200a000102030405060708",
+        "binary": false,
+        "dtype": "uint8",
+        "shape": [
+          3,
+          3
+        ]
+      }
+    }
+  },
+  {
+    "cirq_type": "TrialResult",
+    "params": {
+      "cirq_type": "ParamResolver",
+      "param_dict": [
+        [
+          {
+            "cirq_type": "sympy.Symbol",
+            "name": "a"
+          },
+          0.5
+        ]
+      ]
+    },
+    "measurements": {
+      "m": {
+        "packed_digits": "934e554d5059010076007b276465736372273a20273c7532272c2027666f727472616e5f6f72646572273a2046616c73652c20277368617065273a2028332c2033292c207d202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020200a000001000200030004000500060007000800",
+        "binary": false,
+        "dtype": "uint16",
+        "shape": [
+          3,
+          3
+        ]
+      }
+    }
+  },
+  {
+    "cirq_type": "TrialResult",
+    "params": {
+      "cirq_type": "ParamResolver",
+      "param_dict": [
+        [
+          {
+            "cirq_type": "sympy.Symbol",
+            "name": "a"
+          },
+          0.5
+        ]
+      ]
+    },
+    "measurements": {
+      "m": {
+        "packed_digits": "934e554d5059010076007b276465736372273a20273c7534272c2027666f727472616e5f6f72646572273a2046616c73652c20277368617065273a2028332c2033292c207d202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020200a000000000100000002000000030000000400000005000000060000000700000008000000",
+        "binary": false,
+        "dtype": "uint32",
+        "shape": [
+          3,
+          3
+        ]
+      }
+    }
+  },
+  {
+    "cirq_type": "TrialResult",
+    "params": {
+      "cirq_type": "ParamResolver",
+      "param_dict": [
+        [
+          {
+            "cirq_type": "sympy.Symbol",
+            "name": "a"
+          },
+          0.5
+        ]
+      ]
+    },
+    "measurements": {
+      "m": {
+        "packed_digits": "934e554d5059010076007b276465736372273a20273c7538272c2027666f727472616e5f6f72646572273a2046616c73652c20277368617065273a2028332c2033292c207d202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020200a000000000000000001000000000000000200000000000000030000000000000004000000000000000500000000000000060000000000000007000000000000000800000000000000",
+        "binary": false,
+        "dtype": "uint64",
+        "shape": [
+          3,
+          3
+        ]
+      }
+    }
+  },
+  {
+    "cirq_type": "TrialResult",
+    "params": {
+      "cirq_type": "ParamResolver",
+      "param_dict": [
+        [
+          {
+            "cirq_type": "sympy.Symbol",
+            "name": "a"
+          },
+          0.5
+        ]
+      ]
+    },
+    "measurements": {
+      "m": {
+        "packed_digits": "d32a",
+        "binary": true,
+        "dtype": "uint8",
+        "shape": [
+          3,
+          5
+        ]
+      },
+      "n": {
+        "packed_digits": "934e554d5059010076007b276465736372273a20273c6938272c2027666f727472616e5f6f72646572273a2046616c73652c20277368617065273a2028332c2033292c207d202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020200a000000000000000001000000000000000200000000000000030000000000000004000000000000000500000000000000060000000000000007000000000000000800000000000000",
+        "binary": false,
+        "dtype": "int64",
+        "shape": [
+          3,
+          3
+        ]
+      }
+    }
+  }
+]
\ No newline at end of file
diff --git a/cirq/protocols/json_test_data/TrialResult.repr b/cirq/protocols/json_test_data/TrialResult.repr
new file mode 100644
--- /dev/null
+++ b/cirq/protocols/json_test_data/TrialResult.repr
@@ -0,0 +1 @@
+[cirq.TrialResult(params=cirq.ParamResolver({sympy.Symbol('a'): 0.5}), measurements={'m': np.array([[True, True, False, True, False], [False, True, True, False, False], [True, False, True, False, True]], dtype=np.bool)}), cirq.TrialResult(params=cirq.ParamResolver({sympy.Symbol('a'): 0.5}), measurements={'m': np.array([[1, 1, 0, 1, 0], [0, 1, 1, 0, 0], [1, 0, 1, 0, 1]], dtype=np.uint8)}), cirq.TrialResult(params=cirq.ParamResolver({sympy.Symbol('a'): 0.5}), measurements={'m': np.array([[0, 1, 2], [3, 4, 5], [6, 7, 8]], dtype=np.int8)}), cirq.TrialResult(params=cirq.ParamResolver({sympy.Symbol('a'): 0.5}), measurements={'m': np.array([[0, 1, 2], [3, 4, 5], [6, 7, 8]], dtype=np.int16)}), cirq.TrialResult(params=cirq.ParamResolver({sympy.Symbol('a'): 0.5}), measurements={'m': np.array([[0, 1, 2], [3, 4, 5], [6, 7, 8]], dtype=np.int32)}), cirq.TrialResult(params=cirq.ParamResolver({sympy.Symbol('a'): 0.5}), measurements={'m': np.array([[0, 1, 2], [3, 4, 5], [6, 7, 8]], dtype=np.int64)}), cirq.TrialResult(params=cirq.ParamResolver({sympy.Symbol('a'): 0.5}), measurements={'m': np.array([[0, 1, 2], [3, 4, 5], [6, 7, 8]], dtype=np.uint8)}), cirq.TrialResult(params=cirq.ParamResolver({sympy.Symbol('a'): 0.5}), measurements={'m': np.array([[0, 1, 2], [3, 4, 5], [6, 7, 8]], dtype=np.uint16)}), cirq.TrialResult(params=cirq.ParamResolver({sympy.Symbol('a'): 0.5}), measurements={'m': np.array([[0, 1, 2], [3, 4, 5], [6, 7, 8]], dtype=np.uint32)}), cirq.TrialResult(params=cirq.ParamResolver({sympy.Symbol('a'): 0.5}), measurements={'m': np.array([[0, 1, 2], [3, 4, 5], [6, 7, 8]], dtype=np.uint64)}), cirq.TrialResult(params=cirq.ParamResolver({sympy.Symbol('a'): 0.5}), measurements={'m': np.array([[1, 1, 0, 1, 0], [0, 1, 1, 0, 0], [1, 0, 1, 0, 1]], dtype=np.uint8), 'n': np.array([[0, 1, 2], [3, 4, 5], [6, 7, 8]], dtype=np.int64)})]
\ No newline at end of file
diff --git a/cirq/study/result_test.py b/cirq/study/result_test.py
--- a/cirq/study/result_test.py
+++ b/cirq/study/result_test.py
@@ -13,6 +13,7 @@
 # limitations under the License.
 
 import collections
+
 import numpy as np
 import pytest
 import pandas as pd
@@ -307,3 +308,34 @@ def test_json_bit_packing_and_dtype():
 def test_deprecation_log():
     with cirq.testing.assert_logs('TrialResult was used but is deprecated'):
         cirq.TrialResult(params=cirq.ParamResolver({}), measurements={})
+
+
+def test_deprecated_json():
+    with cirq.testing.assert_logs('TrialResult was used but is deprecated'):
+        result = cirq.read_json(json_text="""{
+          "cirq_type": "TrialResult",
+          "params": {
+            "cirq_type": "ParamResolver",
+            "param_dict": []
+          },
+          "measurements": {
+            "0,1": {
+              "packed_digits": "fcc0",
+              "binary": true,
+              "dtype": "uint8",
+              "shape": [
+                5,
+                2
+              ]
+            }
+          }
+        }
+        """)
+
+        assert result == cirq.Result(
+            params=cirq.ParamResolver({}),
+            measurements={
+                '0,1':
+                np.array([[1, 1], [1, 1], [1, 1], [0, 0], [1, 1]],
+                         dtype=np.uint8)
+            })