Repository: Qiskit/qiskit-ibmq-provider (language: python)

Issue:
Fix issue with parameters in metadata
<!--
⚠️ If you do not respect this template, your pull request will be closed.
⚠️ Your pull request title should be short detailed and understandable for all.
⚠️ Also, please add a release note file using reno if the change needs to be
  documented in the release notes.
⚠️ If your pull request fixes an open issue, please link to the issue.

✅ I have added the tests to cover my changes.
✅ I have updated the documentation accordingly.
✅ I have read the CONTRIBUTING document.
-->

### Summary
When we have a Parameter in circuit metadata then during the run of the job it crashes. 
For example, the circuit below before the fix throw the error: "TypeError: keys must be str, int, float, bool or None, not Parameter"

qr = QuantumRegister(1)
cr = ClassicalRegister(1)
my_circ_str = 'test_metadata'
my_circ = QuantumCircuit(qr, cr, name=my_circ_str, metadata={Parameter('φ'): 0.2})
job = backend.run(my_circ, shots=1024) 

This issue was reported by @nbronn.

### Details and comments
The solution: in _submit_job method after converting the Qobj to the dictionary we check if in the dictionary are only the valid JSON key, if not the key is converted to a string.




Discussion / hints:
@rafal-pracht Thank you for reporting this issue. We already use the [IQXJsonEncoder](https://github.com/Qiskit/qiskit-ibmq-provider/blob/master/qiskit/providers/ibmq/utils/json_encoder.py#L23) to serialize the Qobj when sending it to the server, so that'd be a better place to do additional processing.

Note that while converting the `Parameter` to a string would allow the Qobj be serialized, the string will not be converted back to a `Parameter` when you retrieve it later.
 @jyu00 Thanks for your review and sorry for the delay. I've updated the IQXJsonEncoder, and now it encodes the Parameter properly.

Regarding losing the Parameter during encoding. I've discussed this issue with @nbronn and this will not be an issue (we use the Parameter in metadata only for description purposes).

But it will not be an issue for me to write a code that doesn't lose the Parameter during encoding if you want. If yes, could you give me an example or point to the python file where we save a custom object as JSON in qiskit
Hi @jyu00 
I've spent some time fixing the unit test issue. And because I've no idea what happened, I've rollback all my changes. So now there are no changes in my branch, but the unit tests still fail with "Exception: Unable to locate valid credentials.". Do you have any hints for me?

https://github.com/rafal-pracht/qiskit-ibmq-provider/actions/runs/1449448748
@rafal-pracht The tests require some setup to run because they need IBM Quantum credentials. You'll need to define these environment variables in Github actions:

```
        * QE_TOKEN: default token to use.
        * QE_URL: default url to use (set this to https://auth.quantum-computing.ibm.com/api)
        * QE_HGP: default hub/group/project to use.
        * QE_DEVICE: default device to use.
```

There are also some known failures that we are still working on, so don't spend too much time if you see failed tests. 

@rathishcholarajan we should add the env variables needed for testing to the contribution guide. 
Thanks @jyu00, I've pushed my changes once again. It would be great if you can look and give me feedback. 

In meanwhile I'll look at the GitHub environment setup.
@rafal-pracht can you add some test cases? 
@jyu00 I've added some test cases.
@jyu00  Thanks for your feedback, I've just done the changes.

Fix patch:
diff --git a/qiskit/providers/ibmq/utils/json_encoder.py b/qiskit/providers/ibmq/utils/json_encoder.py
index f8c518e62..553dd6fab 100644
--- a/qiskit/providers/ibmq/utils/json_encoder.py
+++ b/qiskit/providers/ibmq/utils/json_encoder.py
@@ -23,6 +23,33 @@
 class IQXJsonEncoder(json.JSONEncoder):
     """A json encoder for qobj"""
 
+    def __encode(self, param: Any) -> Any:
+        """
+        Convert dictionary to contain only JSON serializable types. For example,
+        if the key is a Parameter we convert it to a string.
+        """
+        if isinstance(param, dict):
+            param_bind_str = {}
+            for key in param.keys():
+                value = self.__encode(param[key])
+
+                if isinstance(key, (bool, float, int, str)) or key is None:
+                    param_bind_str[key] = value
+                else:
+                    param_bind_str[str(key)] = value
+            return param_bind_str
+        elif isinstance(param, list):
+            return [self.__encode(p) for p in param]
+        else:
+            return param
+
+    def encode(self, o: Any) -> str:
+        """
+        Return a JSON string representation of a Python data structure.
+        """
+        new_o = self.__encode(o)
+        return super().encode(new_o)
+
     def default(self, o: Any) -> Any:
         # Convert numpy arrays:
         if hasattr(o, 'tolist'):
diff --git a/releasenotes/notes/fix-metadata-parameter-ab6fb1ace79215d9.yaml b/releasenotes/notes/fix-metadata-parameter-ab6fb1ace79215d9.yaml
new file mode 100644
index 000000000..2fb33cc77
--- /dev/null
+++ b/releasenotes/notes/fix-metadata-parameter-ab6fb1ace79215d9.yaml
@@ -0,0 +1,6 @@
+---
+fixes:
+  - |
+    Fix delivered in `#1065 <https://github.com/Qiskit/qiskit-ibmq-provider/pull/1065>`__ for the
+    issue where job kept crashing when ``Parameter`` was passed in circuit metadata.
+


Test patch:
diff --git a/test/ibmq/test_serialization.py b/test/ibmq/test_serialization.py
index 85bd06729..e88d2dcb2 100644
--- a/test/ibmq/test_serialization.py
+++ b/test/ibmq/test_serialization.py
@@ -15,11 +15,13 @@
 from unittest import SkipTest, skipIf
 from typing import Any, Dict, Optional
 
+import json
 import dateutil.parser
 from qiskit.test.reference_circuits import ReferenceCircuits
 from qiskit.test import slow_test
 from qiskit.providers.ibmq import least_busy
-from qiskit import transpile, schedule, QuantumCircuit
+from qiskit import transpile, schedule, assemble
+from qiskit import QuantumCircuit, QuantumRegister, ClassicalRegister
 from qiskit.providers.ibmq.utils.json_encoder import IQXJsonEncoder
 from qiskit.circuit import Parameter
 from qiskit.version import VERSION as terra_version
@@ -178,6 +180,43 @@ def test_convert_complex(self):
         self.assertEqual(val[0], 0.2)
         self.assertEqual(val[1], 0.1)
 
+    def test_exception_message(self):
+        """Test executing job with Parameter in methadata."""
+        qr = QuantumRegister(1)
+        cr = ClassicalRegister(1)
+        my_circ_str = 'test_metadata'
+        my_circ = QuantumCircuit(qr, cr, name=my_circ_str, metadata={Parameter('φ'): 0.2})
+        qobj = assemble(my_circ)
+        qobj_dict = qobj.to_dict()
+        json.dumps(qobj_dict, cls=IQXJsonEncoder)
+        # There is no self.assert method because if we cannot pass Parameter as metadata
+        # the last line throw:
+        # "TypeError: keys must be str, int, float, bool or None, not Parameter"
+
+    def test_encode_no_replace(self):
+        """Test encode where there is no invalid key to replace."""
+        test_dir = {
+            't1': 1,
+            None: None,
+            'list': [1, 2, {'ld': 1, 2: 3}]
+        }
+
+        self.assertEqual('{"t1": 1, "null": null, "list": [1, 2, {"ld": 1, "2": 3}]}',
+                         IQXJsonEncoder().encode(test_dir))
+
+    def test_encode_replace(self):
+        """Test encode where there is no invalid key to replace."""
+        test_dir = {
+            't1': 1,
+            None: None,
+            Parameter('a'): 0.2,
+            'list': [1, 2, {'ld': 1, 2: 3, Parameter('alfa'): 0.1}]
+        }
+
+        self.assertEqual(
+            '{"t1": 1, "null": null, "a": 0.2, "list": [1, 2, {"ld": 1, "2": 3, "alfa": 0.1}]}',
+            IQXJsonEncoder().encode(test_dir))
+
 
 def _find_potential_encoded(data: Any, c_key: str, tally: set) -> None:
     """Find data that may be in JSON serialized format.