From 0d930739c78b557db6cd48b38fe16eba93719c40 Mon Sep 17 00:00:00 2001 From: ericapetersson Date: Thu, 14 Dec 2023 17:48:14 +0100 Subject: [PATCH] fix: Deserializing JSON subfields within structs fails (#1742) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit … for deserializing json subfields from bigquery, this adds support for that. Thank you for opening a Pull Request! Before submitting your PR, there are a few things you can do to make sure it goes smoothly: - [x] Make sure to open an issue as a [bug/issue](https://togithub.com/googleapis/python-bigquery/issues/new/choose) before writing your code! That way we can discuss the change, evaluate designs, and agree on the general idea - [x] Ensure the tests and linter pass - [x] Code coverage does not decrease (if any source code was changed) - [x] Appropriate docs were updated (if necessary) Fixes #[1500](https://togithub.com/googleapis/python-bigquery/issues/1500) 🦕 --- google/cloud/bigquery/_helpers.py | 19 ++++++++++++------- tests/unit/test__helpers.py | 16 ++++++++++++++-- 2 files changed, 26 insertions(+), 9 deletions(-) diff --git a/google/cloud/bigquery/_helpers.py b/google/cloud/bigquery/_helpers.py index 13baea4ad..93b46341e 100644 --- a/google/cloud/bigquery/_helpers.py +++ b/google/cloud/bigquery/_helpers.py @@ -239,6 +239,15 @@ def _record_from_json(value, field): return record +def _json_from_json(value, field): + """Coerce 'value' to a Pythonic JSON representation.""" + if _not_null(value, field): + return json.loads(value) + else: + return None + + +# Parse BigQuery API response JSON into a Python representation. _CELLDATA_FROM_JSON = { "INTEGER": _int_from_json, "INT64": _int_from_json, @@ -257,6 +266,7 @@ def _record_from_json(value, field): "DATE": _date_from_json, "TIME": _time_from_json, "RECORD": _record_from_json, + "JSON": _json_from_json, } _QUERY_PARAMS_FROM_JSON = dict(_CELLDATA_FROM_JSON) @@ -413,13 +423,8 @@ def _time_to_json(value): return value -def _json_from_json(value, field): - """Coerce 'value' to a pythonic JSON representation, if set or not nullable.""" - if _not_null(value, field): - return json.loads(value) - - -# Converters used for scalar values marshalled as row data. +# Converters used for scalar values marshalled to the BigQuery API, such as in +# query parameters or the tabledata.insert API. _SCALAR_VALUE_TO_JSON_ROW = { "INTEGER": _int_to_json, "INT64": _int_to_json, diff --git a/tests/unit/test__helpers.py b/tests/unit/test__helpers.py index 3c425da5f..7bf55baeb 100644 --- a/tests/unit/test__helpers.py +++ b/tests/unit/test__helpers.py @@ -15,6 +15,7 @@ import base64 import datetime import decimal +import json import unittest import mock @@ -71,9 +72,20 @@ def test_w_none_required(self): with self.assertRaises(TypeError): self._call_fut(None, _Field("REQUIRED")) + def test_w_json_field(self): + data_field = _Field("REQUIRED", "data", "JSON") + + value = json.dumps( + {"v": {"key": "value"}}, + ) + + expected_output = {"v": {"key": "value"}} + coerced_output = self._call_fut(value, data_field) + self.assertEqual(coerced_output, expected_output) + def test_w_string_value(self): - coerced = self._call_fut('{"foo": true}', object()) - self.assertEqual(coerced, {"foo": True}) + coerced = self._call_fut('"foo"', object()) + self.assertEqual(coerced, "foo") class Test_float_from_json(unittest.TestCase):