Repository: quantumlib/Cirq

Issue:
Records-based ResultDict fails serialization
Example code:

```python
a = cirq.ResultDict(params={}, records={'a': np.array([[[1, 1], [1, 1]], [[1, 1], [1, 1]]])})
aj = cirq.to_json(a)
# ValueError: Cannot extract 2D measurements for repeated keys
```


Discussion:
CC @daxfohl @maffoo

I think `ResultDict` is new enough that we don't need backwards-compatibility tooling for this, but only if we can resolve it before the rapidly-approaching v0.14 release.
@95-martin-orion, I'm happy to take this if you're busy.
Thanks @maffoo, that would be great. Note that this needs to be wrapped up before the v0.14 release, which is slated for sometime next week.

Resolving patch:
diff --git a/cirq-core/cirq/study/result.py b/cirq-core/cirq/study/result.py
--- a/cirq-core/cirq/study/result.py
+++ b/cirq-core/cirq/study/result.py
@@ -176,10 +176,10 @@ def from_single_parameter_set(
 
     @property
     def repetitions(self) -> int:
-        if not self.measurements:
+        if not self.records:
             return 0
         # Get the length quickly from one of the keyed results.
-        return len(next(iter(self.measurements.values())))
+        return len(next(iter(self.records.values())))
 
     # Reason for 'type: ignore': https://github.com/python/mypy/issues/5273
     def multi_measurement_histogram(  # type: ignore
@@ -289,25 +289,29 @@ def histogram(  # type: ignore
     def __eq__(self, other: Any) -> bool:
         if not isinstance(other, Result):
             return NotImplemented
-        return self.data.equals(other.data) and self.params == other.params
-
-    def _measurement_shape(self) -> Tuple['cirq.ParamResolver', Dict[str, Tuple[int, ...]]]:
-        return self.params, {k: v.shape[1] for k, v in self.measurements.items()}
+        return (
+            self.records.keys() == other.records.keys()
+            and all(np.array_equal(self.records[k], other.records[k]) for k in self.records)
+            and self.params == other.params
+        )
 
     def __add__(self, other: 'cirq.Result') -> 'cirq.Result':
         if not isinstance(other, Result):
             return NotImplemented
-        if self._measurement_shape() != other._measurement_shape():
+        if self.params != other.params:
             raise ValueError(
-                'Results do not have the same parameters or do not have the '
-                'same measurement keys.'
+                f'Cannot add results with different parameters: {self.params} != {other.params}'
             )
-        all_measurements: Dict[str, np.ndarray] = {}
-        for key in other.measurements:
-            all_measurements[key] = np.append(
-                self.measurements[key], other.measurements[key], axis=0
+        shape = {k: v.shape[1:] for k, v in self.records.items()}
+        other_shape = {k: v.shape[1:] for k, v in other.records.items()}
+        if shape != other_shape:
+            raise ValueError(
+                f'Cannot add results with different measurement shapes: {shape} != {other_shape}'
             )
-        return ResultDict(params=self.params, measurements=all_measurements)
+        all_records: Dict[str, np.ndarray] = {}
+        for key in other.records:
+            all_records[key] = np.append(self.records[key], other.records[key], axis=0)
+        return ResultDict(params=self.params, records=all_records)
 
 
 class ResultDict(Result):
@@ -325,7 +329,7 @@ class ResultDict(Result):
     def __init__(
         self,
         *,  # Forces keyword args.
-        params: resolver.ParamResolver,
+        params: Optional[resolver.ParamResolver] = None,
         measurements: Optional[Mapping[str, np.ndarray]] = None,
         records: Optional[Mapping[str, np.ndarray]] = None,
     ) -> None:
@@ -345,6 +349,8 @@ def __init__(
                 the last index running over the qubits for the corresponding
                 measurements.
         """
+        if params is None:
+            params = resolver.ParamResolver({})
         if measurements is None and records is None:
             # For backwards compatibility, allow constructing with None.
             measurements = {}
@@ -399,10 +405,10 @@ def data(self) -> pd.DataFrame:
         return self._data
 
     def __repr__(self) -> str:
-        measurement_dict_repr = (
-            '{' + ', '.join(f'{k!r}: {proper_repr(v)}' for k, v in self.measurements.items()) + '}'
+        record_dict_repr = (
+            '{' + ', '.join(f'{k!r}: {proper_repr(v)}' for k, v in self.records.items()) + '}'
         )
-        return f'cirq.ResultDict(params={self.params!r}, measurements={measurement_dict_repr})'
+        return f'cirq.ResultDict(params={self.params!r}, records={record_dict_repr})'
 
     def _repr_pretty_(self, p: Any, cycle: bool) -> None:
         """Output to show in ipython and Jupyter notebooks."""
@@ -416,10 +422,10 @@ def __str__(self) -> str:
         return _keyed_repeated_bitstrings(self.measurements)
 
     def _json_dict_(self):
-        packed_measurements = {}
-        for key, digits in self.measurements.items():
+        packed_records = {}
+        for key, digits in self.records.items():
             packed_digits, binary = _pack_digits(digits)
-            packed_measurements[key] = {
+            packed_records[key] = {
                 'packed_digits': packed_digits,
                 'binary': binary,
                 'dtype': digits.dtype.name,
@@ -427,14 +433,21 @@ def _json_dict_(self):
             }
         return {
             'params': self.params,
-            'measurements': packed_measurements,
+            'records': packed_records,
         }
 
     @classmethod
-    def _from_json_dict_(cls, params, measurements, **kwargs):
+    def _from_json_dict_(cls, params, **kwargs):
+        if 'measurements' in kwargs:
+            measurements = kwargs['measurements']
+            return cls(
+                params=params,
+                measurements={key: _unpack_digits(**val) for key, val in measurements.items()},
+            )
+        records = kwargs['records']
         return cls(
             params=params,
-            measurements={key: _unpack_digits(**val) for key, val in measurements.items()},
+            records={key: _unpack_digits(**val) for key, val in records.items()},
         )
 
 


Test patch:
diff --git a/cirq-core/cirq/protocols/json_test_data/ResultDict.json b/cirq-core/cirq/protocols/json_test_data/ResultDict.json
--- a/cirq-core/cirq/protocols/json_test_data/ResultDict.json
+++ b/cirq-core/cirq/protocols/json_test_data/ResultDict.json
@@ -13,13 +13,14 @@
         ]
       ]
     },
-    "measurements": {
+    "records": {
       "m": {
         "packed_digits": "d32a",
         "binary": true,
         "dtype": "bool",
         "shape": [
           3,
+          1,
           5
         ]
       }
@@ -39,13 +40,14 @@
         ]
       ]
     },
-    "measurements": {
+    "records": {
       "m": {
         "packed_digits": "d32a",
         "binary": true,
         "dtype": "uint8",
         "shape": [
           3,
+          1,
           5
         ]
       }
@@ -65,13 +67,14 @@
         ]
       ]
     },
-    "measurements": {
+    "records": {
       "m": {
-        "packed_digits": "934e554d5059010076007b276465736372273a20277c6931272c2027666f727472616e5f6f72646572273a2046616c73652c20277368617065273a2028332c2033292c207d202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020200a000102030405060708",
+        "packed_digits": "934e554d5059010076007b276465736372273a20277c6931272c2027666f727472616e5f6f72646572273a2046616c73652c20277368617065273a2028332c20312c2033292c207d202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020200a000102030405060708",
         "binary": false,
         "dtype": "int8",
         "shape": [
           3,
+          1,
           3
         ]
       }
@@ -91,13 +94,14 @@
         ]
       ]
     },
-    "measurements": {
+    "records": {
       "m": {
-        "packed_digits": "934e554d5059010076007b276465736372273a20273c6932272c2027666f727472616e5f6f72646572273a2046616c73652c20277368617065273a2028332c2033292c207d202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020200a000001000200030004000500060007000800",
+        "packed_digits": "934e554d5059010076007b276465736372273a20273c6932272c2027666f727472616e5f6f72646572273a2046616c73652c20277368617065273a2028332c20312c2033292c207d202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020200a000001000200030004000500060007000800",
         "binary": false,
         "dtype": "int16",
         "shape": [
           3,
+          1,
           3
         ]
       }
@@ -117,13 +121,14 @@
         ]
       ]
     },
-    "measurements": {
+    "records": {
       "m": {
-        "packed_digits": "934e554d5059010076007b276465736372273a20273c6934272c2027666f727472616e5f6f72646572273a2046616c73652c20277368617065273a2028332c2033292c207d202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020200a000000000100000002000000030000000400000005000000060000000700000008000000",
+        "packed_digits": "934e554d5059010076007b276465736372273a20273c6934272c2027666f727472616e5f6f72646572273a2046616c73652c20277368617065273a2028332c20312c2033292c207d202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020200a000000000100000002000000030000000400000005000000060000000700000008000000",
         "binary": false,
         "dtype": "int32",
         "shape": [
           3,
+          1,
           3
         ]
       }
@@ -143,13 +148,14 @@
         ]
       ]
     },
-    "measurements": {
+    "records": {
       "m": {
-        "packed_digits": "934e554d5059010076007b276465736372273a20273c6938272c2027666f727472616e5f6f72646572273a2046616c73652c20277368617065273a2028332c2033292c207d202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020200a000000000000000001000000000000000200000000000000030000000000000004000000000000000500000000000000060000000000000007000000000000000800000000000000",
+        "packed_digits": "934e554d5059010076007b276465736372273a20273c6938272c2027666f727472616e5f6f72646572273a2046616c73652c20277368617065273a2028332c20312c2033292c207d202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020200a000000000000000001000000000000000200000000000000030000000000000004000000000000000500000000000000060000000000000007000000000000000800000000000000",
         "binary": false,
         "dtype": "int64",
         "shape": [
           3,
+          1,
           3
         ]
       }
@@ -169,13 +175,14 @@
         ]
       ]
     },
-    "measurements": {
+    "records": {
       "m": {
-        "packed_digits": "934e554d5059010076007b276465736372273a20277c7531272c2027666f727472616e5f6f72646572273a2046616c73652c20277368617065273a2028332c2033292c207d202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020200a000102030405060708",
+        "packed_digits": "934e554d5059010076007b276465736372273a20277c7531272c2027666f727472616e5f6f72646572273a2046616c73652c20277368617065273a2028332c20312c2033292c207d202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020200a000102030405060708",
         "binary": false,
         "dtype": "uint8",
         "shape": [
           3,
+          1,
           3
         ]
       }
@@ -195,13 +202,14 @@
         ]
       ]
     },
-    "measurements": {
+    "records": {
       "m": {
-        "packed_digits": "934e554d5059010076007b276465736372273a20273c7532272c2027666f727472616e5f6f72646572273a2046616c73652c20277368617065273a2028332c2033292c207d202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020200a000001000200030004000500060007000800",
+        "packed_digits": "934e554d5059010076007b276465736372273a20273c7532272c2027666f727472616e5f6f72646572273a2046616c73652c20277368617065273a2028332c20312c2033292c207d202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020200a000001000200030004000500060007000800",
         "binary": false,
         "dtype": "uint16",
         "shape": [
           3,
+          1,
           3
         ]
       }
@@ -221,13 +229,14 @@
         ]
       ]
     },
-    "measurements": {
+    "records": {
       "m": {
-        "packed_digits": "934e554d5059010076007b276465736372273a20273c7534272c2027666f727472616e5f6f72646572273a2046616c73652c20277368617065273a2028332c2033292c207d202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020200a000000000100000002000000030000000400000005000000060000000700000008000000",
+        "packed_digits": "934e554d5059010076007b276465736372273a20273c7534272c2027666f727472616e5f6f72646572273a2046616c73652c20277368617065273a2028332c20312c2033292c207d202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020200a000000000100000002000000030000000400000005000000060000000700000008000000",
         "binary": false,
         "dtype": "uint32",
         "shape": [
           3,
+          1,
           3
         ]
       }
@@ -247,13 +256,14 @@
         ]
       ]
     },
-    "measurements": {
+    "records": {
       "m": {
-        "packed_digits": "934e554d5059010076007b276465736372273a20273c7538272c2027666f727472616e5f6f72646572273a2046616c73652c20277368617065273a2028332c2033292c207d202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020200a000000000000000001000000000000000200000000000000030000000000000004000000000000000500000000000000060000000000000007000000000000000800000000000000",
+        "packed_digits": "934e554d5059010076007b276465736372273a20273c7538272c2027666f727472616e5f6f72646572273a2046616c73652c20277368617065273a2028332c20312c2033292c207d202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020200a000000000000000001000000000000000200000000000000030000000000000004000000000000000500000000000000060000000000000007000000000000000800000000000000",
         "binary": false,
         "dtype": "uint64",
         "shape": [
           3,
+          1,
           3
         ]
       }
@@ -273,22 +283,24 @@
         ]
       ]
     },
-    "measurements": {
+    "records": {
       "m": {
         "packed_digits": "d32a",
         "binary": true,
         "dtype": "uint8",
         "shape": [
           3,
+          1,
           5
         ]
       },
       "n": {
-        "packed_digits": "934e554d5059010076007b276465736372273a20273c6938272c2027666f727472616e5f6f72646572273a2046616c73652c20277368617065273a2028332c2033292c207d202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020200a000000000000000001000000000000000200000000000000030000000000000004000000000000000500000000000000060000000000000007000000000000000800000000000000",
+        "packed_digits": "934e554d5059010076007b276465736372273a20273c6938272c2027666f727472616e5f6f72646572273a2046616c73652c20277368617065273a2028332c20312c2033292c207d202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020200a000000000000000001000000000000000200000000000000030000000000000004000000000000000500000000000000060000000000000007000000000000000800000000000000",
         "binary": false,
         "dtype": "int64",
         "shape": [
           3,
+          1,
           3
         ]
       }
diff --git a/cirq-core/cirq/protocols/json_test_data/ResultDict.json_inward b/cirq-core/cirq/protocols/json_test_data/ResultDict.json_inward
new file mode 100644
--- /dev/null
+++ b/cirq-core/cirq/protocols/json_test_data/ResultDict.json_inward
@@ -0,0 +1,297 @@
+[
+  {
+    "cirq_type": "ResultDict",
+    "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": "ResultDict",
+    "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": "ResultDict",
+    "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": "ResultDict",
+    "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": "ResultDict",
+    "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": "ResultDict",
+    "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": "ResultDict",
+    "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": "ResultDict",
+    "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": "ResultDict",
+    "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": "ResultDict",
+    "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": "ResultDict",
+    "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-core/cirq/protocols/json_test_data/ResultDict.repr b/cirq-core/cirq/protocols/json_test_data/ResultDict.repr
--- a/cirq-core/cirq/protocols/json_test_data/ResultDict.repr
+++ b/cirq-core/cirq/protocols/json_test_data/ResultDict.repr
@@ -1 +1 @@
-[cirq.ResultDict(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=bool)}), cirq.ResultDict(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.ResultDict(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.ResultDict(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.ResultDict(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.ResultDict(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.ResultDict(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.ResultDict(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.ResultDict(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.ResultDict(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.ResultDict(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)})]
+[cirq.ResultDict(params=cirq.ParamResolver({sympy.Symbol('a'): 0.5}), records={'m': np.array([[[True, True, False, True, False]], [[False, True, True, False, False]], [[True, False, True, False, True]]], dtype=bool)}), cirq.ResultDict(params=cirq.ParamResolver({sympy.Symbol('a'): 0.5}), records={'m': np.array([[[1, 1, 0, 1, 0]], [[0, 1, 1, 0, 0]], [[1, 0, 1, 0, 1]]], dtype=np.uint8)}), cirq.ResultDict(params=cirq.ParamResolver({sympy.Symbol('a'): 0.5}), records={'m': np.array([[[0, 1, 2]], [[3, 4, 5]], [[6, 7, 8]]], dtype=np.int8)}), cirq.ResultDict(params=cirq.ParamResolver({sympy.Symbol('a'): 0.5}), records={'m': np.array([[[0, 1, 2]], [[3, 4, 5]], [[6, 7, 8]]], dtype=np.int16)}), cirq.ResultDict(params=cirq.ParamResolver({sympy.Symbol('a'): 0.5}), records={'m': np.array([[[0, 1, 2]], [[3, 4, 5]], [[6, 7, 8]]], dtype=np.int32)}), cirq.ResultDict(params=cirq.ParamResolver({sympy.Symbol('a'): 0.5}), records={'m': np.array([[[0, 1, 2]], [[3, 4, 5]], [[6, 7, 8]]], dtype=np.int64)}), cirq.ResultDict(params=cirq.ParamResolver({sympy.Symbol('a'): 0.5}), records={'m': np.array([[[0, 1, 2]], [[3, 4, 5]], [[6, 7, 8]]], dtype=np.uint8)}), cirq.ResultDict(params=cirq.ParamResolver({sympy.Symbol('a'): 0.5}), records={'m': np.array([[[0, 1, 2]], [[3, 4, 5]], [[6, 7, 8]]], dtype=np.uint16)}), cirq.ResultDict(params=cirq.ParamResolver({sympy.Symbol('a'): 0.5}), records={'m': np.array([[[0, 1, 2]], [[3, 4, 5]], [[6, 7, 8]]], dtype=np.uint32)}), cirq.ResultDict(params=cirq.ParamResolver({sympy.Symbol('a'): 0.5}), records={'m': np.array([[[0, 1, 2]], [[3, 4, 5]], [[6, 7, 8]]], dtype=np.uint64)}), cirq.ResultDict(params=cirq.ParamResolver({sympy.Symbol('a'): 0.5}), records={'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)})]
diff --git a/cirq-core/cirq/protocols/json_test_data/ResultDict.repr_inward b/cirq-core/cirq/protocols/json_test_data/ResultDict.repr_inward
new file mode 100644
--- /dev/null
+++ b/cirq-core/cirq/protocols/json_test_data/ResultDict.repr_inward
@@ -0,0 +1 @@
+[cirq.ResultDict(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=bool)}), cirq.ResultDict(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.ResultDict(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.ResultDict(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.ResultDict(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.ResultDict(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.ResultDict(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.ResultDict(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.ResultDict(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.ResultDict(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.ResultDict(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)})]
diff --git a/cirq-core/cirq/study/result_test.py b/cirq-core/cirq/study/result_test.py
--- a/cirq-core/cirq/study/result_test.py
+++ b/cirq-core/cirq/study/result_test.py
@@ -28,12 +28,43 @@ def test_result_init():
     assert cirq.ResultDict(params=cirq.ParamResolver({}), measurements={}).repetitions == 0
 
 
+def test_default_repetitions():
+    class MyResult(cirq.Result):
+        def __init__(self, records):
+            self._records = records
+
+        @property
+        def params(self):
+            raise NotImplementedError()
+
+        @property
+        def measurements(self):
+            raise NotImplementedError()
+
+        @property
+        def records(self):
+            return self._records
+
+        @property
+        def data(self):
+            raise NotImplementedError()
+
+    assert MyResult({}).repetitions == 0
+    assert MyResult({'a': np.zeros((5, 2, 3))}).repetitions == 5
+
+
 def test_repr():
     v = cirq.ResultDict(
         params=cirq.ParamResolver({'a': 2}), measurements={'xy': np.array([[1, 0], [0, 1]])}
     )
     cirq.testing.assert_equivalent_repr(v)
 
+    v = cirq.ResultDict(
+        params=cirq.ParamResolver({'a': 2}),
+        records={'xy': np.array([[[0, 0], [0, 1]], [[1, 0], [1, 1]]])},
+    )
+    cirq.testing.assert_equivalent_repr(v)
+
 
 def test_result_constructor_deprecation():
     with cirq.testing.assert_deprecated("Use the ResultDict constructor", deadline="v0.15"):
@@ -227,10 +258,11 @@ def test_multi_measurement_histogram():
     )
 
 
-def test_trial_result_equality():
+def test_result_equality():
     et = cirq.testing.EqualsTester()
     et.add_equality_group(
-        cirq.ResultDict(params=cirq.ParamResolver({}), measurements={'a': np.array([[0]] * 5)})
+        cirq.ResultDict(params=cirq.ParamResolver({}), measurements={'a': np.array([[0]] * 5)}),
+        cirq.ResultDict(params=cirq.ParamResolver({}), records={'a': np.array([[[0]]] * 5)}),
     )
     et.add_equality_group(
         cirq.ResultDict(params=cirq.ParamResolver({}), measurements={'a': np.array([[0]] * 6)})
@@ -238,9 +270,12 @@ def test_trial_result_equality():
     et.add_equality_group(
         cirq.ResultDict(params=cirq.ParamResolver({}), measurements={'a': np.array([[1]] * 5)})
     )
+    et.add_equality_group(
+        cirq.ResultDict(params=cirq.ParamResolver({}), records={'a': np.array([[[0], [1]]] * 5)})
+    )
 
 
-def test_trial_result_addition_valid():
+def test_result_addition_valid():
     a = cirq.ResultDict(
         params=cirq.ParamResolver({'ax': 1}),
         measurements={
@@ -260,8 +295,32 @@ def test_trial_result_addition_valid():
     np.testing.assert_array_equal(c.measurements['q0'], np.array([[0, 1], [1, 0], [0, 1], [0, 1]]))
     np.testing.assert_array_equal(c.measurements['q1'], np.array([[0], [0], [1], [0]]))
 
+    # Add results with repeated measurements.
+    a = cirq.ResultDict(
+        params=cirq.ParamResolver({'ax': 1}),
+        records={
+            'q0': np.array([[[0, 1]], [[1, 0]], [[0, 1]]], dtype=bool),
+            'q1': np.array([[[0], [0]], [[0], [1]], [[1], [0]]], dtype=bool),
+        },
+    )
+    b = cirq.ResultDict(
+        params=cirq.ParamResolver({'ax': 1}),
+        records={
+            'q0': np.array([[[0, 1]]], dtype=bool),
+            'q1': np.array([[[1], [1]]], dtype=bool),
+        },
+    )
+
+    c = a + b
+    np.testing.assert_array_equal(
+        c.records['q0'], np.array([[[0, 1]], [[1, 0]], [[0, 1]], [[0, 1]]])
+    )
+    np.testing.assert_array_equal(
+        c.records['q1'], np.array([[[0], [0]], [[0], [1]], [[1], [0]], [[1], [1]]])
+    )
+
 
-def test_trial_result_addition_invalid():
+def test_result_addition_invalid():
     a = cirq.ResultDict(
         params=cirq.ParamResolver({'ax': 1}),
         measurements={
@@ -290,13 +349,22 @@ def test_trial_result_addition_invalid():
             'q1': np.array([[0, 1], [0, 1], [1, 1]], dtype=bool),
         },
     )
+    e = cirq.ResultDict(
+        params=cirq.ParamResolver({'ax': 1}),
+        records={
+            'q0': np.array([[0, 1], [1, 0], [0, 1]], dtype=bool),
+            'q1': np.array([[[0], [0]], [[0], [1]], [[1], [0]]], dtype=bool),
+        },
+    )
 
-    with pytest.raises(ValueError, match='same parameters'):
+    with pytest.raises(ValueError, match='different parameters'):
         _ = a + b
-    with pytest.raises(ValueError, match='same measurement keys'):
+    with pytest.raises(ValueError, match='different measurement shapes'):
         _ = a + c
-    with pytest.raises(ValueError):
+    with pytest.raises(ValueError, match='different measurement shapes'):
         _ = a + d
+    with pytest.raises(ValueError, match='different measurement shapes'):
+        _ = a + e
     with pytest.raises(TypeError):
         _ = a + 'junk'
 
@@ -344,13 +412,21 @@ def text(self, to_print):
     assert p.text_pretty == 'ResultDict(...)'
 
 
-def test_json_bit_packing_and_dtype():
+@pytest.mark.parametrize('use_records', [False, True])
+def test_json_bit_packing_and_dtype(use_records: bool) -> None:
+    shape = (256, 3, 256) if use_records else (256, 256)
+
     prng = np.random.RandomState(1234)
-    bits = prng.randint(2, size=(256, 256)).astype(np.uint8)
-    digits = prng.randint(256, size=(256, 256)).astype(np.uint8)
+    bits = prng.randint(2, size=shape).astype(np.uint8)
+    digits = prng.randint(256, size=shape).astype(np.uint8)
 
-    bits_result = cirq.ResultDict(params=cirq.ParamResolver({}), measurements={'m': bits})
-    digits_result = cirq.ResultDict(params=cirq.ParamResolver({}), measurements={'m': digits})
+    params = cirq.ParamResolver({})
+    if use_records:
+        bits_result = cirq.ResultDict(params=params, records={'m': bits})
+        digits_result = cirq.ResultDict(params=params, records={'m': digits})
+    else:
+        bits_result = cirq.ResultDict(params=params, measurements={'m': bits})
+        digits_result = cirq.ResultDict(params=params, measurements={'m': digits})
 
     bits_json = cirq.to_json(bits_result)
     digits_json = cirq.to_json(digits_result)
@@ -358,8 +434,12 @@ def test_json_bit_packing_and_dtype():
     loaded_bits_result = cirq.read_json(json_text=bits_json)
     loaded_digits_result = cirq.read_json(json_text=digits_json)
 
-    assert loaded_bits_result.measurements['m'].dtype == np.uint8
-    assert loaded_digits_result.measurements['m'].dtype == np.uint8
+    if use_records:
+        assert loaded_bits_result.records['m'].dtype == np.uint8
+        assert loaded_digits_result.records['m'].dtype == np.uint8
+    else:
+        assert loaded_bits_result.measurements['m'].dtype == np.uint8
+        assert loaded_digits_result.measurements['m'].dtype == np.uint8
     np.testing.assert_allclose(len(bits_json), len(digits_json) / 8, rtol=0.02)
 
 
@@ -382,3 +462,41 @@ def test_json_bit_packing_force():
     assert _pack_digits(2 * np.ones(10, dtype=int), pack_bits='force') == _pack_digits(
         np.ones(10), pack_bits='auto'
     )
+
+
+def test_json_unpack_compat():
+    """Test reading old json with serialized measurements array."""
+    old_json = """
+        {
+            "cirq_type": "ResultDict",
+            "params": {
+                "cirq_type": "ParamResolver",
+                "param_dict": []
+            },
+            "measurements": {
+                "m": {
+                    "packed_digits": "d32a",
+                    "binary": true,
+                    "dtype": "bool",
+                    "shape": [
+                        3,
+                        5
+                    ]
+                }
+            }
+        }
+    """
+    result = cirq.read_json(json_text=old_json)
+    assert result == cirq.ResultDict(
+        params=cirq.ParamResolver({}),
+        measurements={
+            'm': np.array(
+                [
+                    [True, True, False, True, False],
+                    [False, True, True, False, False],
+                    [True, False, True, False, True],
+                ],
+                dtype=bool,
+            )
+        },
+    )
diff --git a/cirq-google/cirq_google/json_test_data/cirq.google.ExecutableGroupResult.json b/cirq-google/cirq_google/json_test_data/cirq.google.ExecutableGroupResult.json
--- a/cirq-google/cirq_google/json_test_data/cirq.google.ExecutableGroupResult.json
+++ b/cirq-google/cirq_google/json_test_data/cirq.google.ExecutableGroupResult.json
@@ -41,13 +41,14 @@
           "cirq_type": "ParamResolver",
           "param_dict": []
         },
-        "measurements": {
+        "records": {
           "z": {
             "packed_digits": "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
             "binary": true,
             "dtype": "float64",
             "shape": [
               1000,
+              1,
               4
             ]
           }
@@ -77,13 +78,14 @@
           "cirq_type": "ParamResolver",
           "param_dict": []
         },
-        "measurements": {
+        "records": {
           "z": {
             "packed_digits": "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
             "binary": true,
             "dtype": "float64",
             "shape": [
               1000,
+              1,
               4
             ]
           }
@@ -113,13 +115,14 @@
           "cirq_type": "ParamResolver",
           "param_dict": []
         },
-        "measurements": {
+        "records": {
           "z": {
             "packed_digits": "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
             "binary": true,
             "dtype": "float64",
             "shape": [
               1000,
+              1,
               4
             ]
           }
diff --git a/cirq-google/cirq_google/json_test_data/cirq.google.ExecutableResult.json b/cirq-google/cirq_google/json_test_data/cirq.google.ExecutableResult.json
--- a/cirq-google/cirq_google/json_test_data/cirq.google.ExecutableResult.json
+++ b/cirq-google/cirq_google/json_test_data/cirq.google.ExecutableResult.json
@@ -44,13 +44,14 @@
       "cirq_type": "ParamResolver",
       "param_dict": []
     },
-    "measurements": {
+    "records": {
       "z": {
         "packed_digits": "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
         "binary": true,
         "dtype": "float64",
         "shape": [
           1000,
+          1,
           4
         ]
       }
diff --git a/cirq-ionq/cirq_ionq/results_test.py b/cirq-ionq/cirq_ionq/results_test.py
--- a/cirq-ionq/cirq_ionq/results_test.py
+++ b/cirq-ionq/cirq_ionq/results_test.py
@@ -100,19 +100,19 @@ def test_qpu_result_bad_measurement_key():
 def test_qpu_result_to_cirq_result():
     result = ionq.QPUResult({0b00: 1, 0b01: 2}, num_qubits=2, measurement_dict={'x': [0, 1]})
     assert result.to_cirq_result() == cirq.ResultDict(
-        params=cirq.ParamResolver({}), measurements={'x': [[0, 0], [0, 1], [0, 1]]}
+        params=cirq.ParamResolver({}), measurements={'x': np.array([[0, 0], [0, 1], [0, 1]])}
     )
     params = cirq.ParamResolver({'a': 0.1})
     assert result.to_cirq_result(params) == cirq.ResultDict(
-        params=params, measurements={'x': [[0, 0], [0, 1], [0, 1]]}
+        params=params, measurements={'x': np.array([[0, 0], [0, 1], [0, 1]])}
     )
     result = ionq.QPUResult({0b00: 1, 0b01: 2}, num_qubits=2, measurement_dict={'x': [0]})
     assert result.to_cirq_result() == cirq.ResultDict(
-        params=cirq.ParamResolver({}), measurements={'x': [[0], [0], [0]]}
+        params=cirq.ParamResolver({}), measurements={'x': np.array([[0], [0], [0]])}
     )
     result = ionq.QPUResult({0b00: 1, 0b01: 2}, num_qubits=2, measurement_dict={'x': [1]})
     assert result.to_cirq_result() == cirq.ResultDict(
-        params=cirq.ParamResolver({}), measurements={'x': [[0], [1], [1]]}
+        params=cirq.ParamResolver({}), measurements={'x': np.array([[0], [1], [1]])}
     )
     # cirq.Result only compares pandas data frame, so possible to have supplied an list of
     # list instead of a numpy multidimensional array. Check this here.
@@ -124,7 +124,11 @@ def test_qpu_result_to_cirq_result():
     )
     assert result.to_cirq_result() == cirq.ResultDict(
         params=cirq.ParamResolver({}),
-        measurements={'x': [[0, 1], [0, 1], [1, 0]], 'y': [[0], [0], [1]], 'z': [[1], [1], [0]]},
+        measurements={
+            'x': np.array([[0, 1], [0, 1], [1, 0]]),
+            'y': np.array([[0], [0], [1]]),
+            'z': np.array([[1], [1], [0]]),
+        },
     )
 
 
@@ -135,8 +139,8 @@ def test_qpu_result_to_cirq_result_multiple_keys():
     assert result.to_cirq_result() == cirq.ResultDict(
         params=cirq.ParamResolver({}),
         measurements={
-            'x': [[0], [0], [0], [0], [0]],
-            'y': [[0, 0], [0, 0], [1, 1], [1, 1], [1, 1]],
+            'x': np.array([[0], [0], [0], [0], [0]]),
+            'y': np.array([[0, 0], [0, 0], [1, 1], [1, 1], [1, 1]]),
         },
     )
 
@@ -273,30 +277,30 @@ def test_simulator_result_to_cirq_result():
         {0b00: 0.25, 0b01: 0.75}, num_qubits=2, measurement_dict={'x': [0, 1]}, repetitions=3
     )
     assert result.to_cirq_result(seed=2) == cirq.ResultDict(
-        params=cirq.ParamResolver({}), measurements={'x': [[0, 1], [0, 0], [0, 1]]}
+        params=cirq.ParamResolver({}), measurements={'x': np.array([[0, 1], [0, 0], [0, 1]])}
     )
     assert result.to_cirq_result(seed=3) == cirq.ResultDict(
-        params=cirq.ParamResolver({}), measurements={'x': [[0, 1], [0, 1], [0, 1]]}
+        params=cirq.ParamResolver({}), measurements={'x': np.array([[0, 1], [0, 1], [0, 1]])}
     )
     params = cirq.ParamResolver({'a': 0.1})
     assert result.to_cirq_result(seed=3, params=params) == cirq.ResultDict(
-        params=params, measurements={'x': [[0, 1], [0, 1], [0, 1]]}
+        params=params, measurements={'x': np.array([[0, 1], [0, 1], [0, 1]])}
     )
     assert result.to_cirq_result(seed=2, override_repetitions=2) == cirq.ResultDict(
-        params=cirq.ParamResolver({}), measurements={'x': [[0, 1], [0, 0]]}
+        params=cirq.ParamResolver({}), measurements={'x': np.array([[0, 1], [0, 0]])}
     )
 
     result = ionq.SimulatorResult(
         {0b00: 0.25, 0b01: 0.75}, num_qubits=2, measurement_dict={'x': [0]}, repetitions=3
     )
     assert result.to_cirq_result(seed=2) == cirq.ResultDict(
-        params=cirq.ParamResolver({}), measurements={'x': [[0], [0], [0]]}
+        params=cirq.ParamResolver({}), measurements={'x': np.array([[0], [0], [0]])}
     )
     result = ionq.SimulatorResult(
         {0b00: 0.25, 0b01: 0.75}, num_qubits=2, measurement_dict={'x': [1]}, repetitions=3
     )
     assert result.to_cirq_result(seed=2) == cirq.ResultDict(
-        params=cirq.ParamResolver({}), measurements={'x': [[1], [0], [1]]}
+        params=cirq.ParamResolver({}), measurements={'x': np.array([[1], [0], [1]])}
     )
     # cirq.Result only compares pandas data frame, so possible to have supplied an list of
     # list instead of a numpy multidimensional array. Check this here.
@@ -312,7 +316,7 @@ def test_simulator_result_to_cirq_result_multiple_keys():
     )
     assert result.to_cirq_result(seed=2) == cirq.ResultDict(
         params=cirq.ParamResolver({}),
-        measurements={'x': [[1], [0], [1]], 'y': [[1, 0], [0, 0], [1, 0]]},
+        measurements={'x': np.array([[1], [0], [1]]), 'y': np.array([[1, 0], [0, 0], [1, 0]])},
     )
 
 
diff --git a/cirq-ionq/cirq_ionq/service_test.py b/cirq-ionq/cirq_ionq/service_test.py
--- a/cirq-ionq/cirq_ionq/service_test.py
+++ b/cirq-ionq/cirq_ionq/service_test.py
@@ -18,6 +18,7 @@
 
 import pytest
 
+import numpy as np
 import pandas as pd
 import sympy
 
@@ -58,7 +59,7 @@ def test_service_run(target, expected_results):
         param_resolver=params,
         seed=2,
     )
-    assert result == cirq.ResultDict(params=params, measurements={'a': expected_results})
+    assert result == cirq.ResultDict(params=params, measurements={'a': np.array(expected_results)})
 
     create_job_kwargs = mock_client.create_job.call_args[1]
     # Serialization induces a float, so we don't validate full circuit.