Skip to content

Commit 16abd12

Browse files
committed
feat: add bigframes.bigquery.to_json
1 parent 1c6fe85 commit 16abd12

File tree

6 files changed

+88
-1
lines changed

6 files changed

+88
-1
lines changed

bigframes/bigquery/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@
5050
json_value,
5151
json_value_array,
5252
parse_json,
53+
to_json,
5354
to_json_string,
5455
)
5556
from bigframes.bigquery._operations.search import create_vector_index, vector_search
@@ -88,6 +89,7 @@
8889
json_value,
8990
json_value_array,
9091
parse_json,
92+
to_json,
9193
to_json_string,
9294
# search ops
9395
create_vector_index,

bigframes/bigquery/_operations/json.py

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -430,6 +430,40 @@ def json_value_array(
430430
return input._apply_unary_op(ops.JSONValueArray(json_path=json_path))
431431

432432

433+
def to_json(
434+
input: series.Series,
435+
) -> series.Series:
436+
"""Converts a series with a JSON value to a JSON-formatted STRING value.
437+
438+
**Examples:**
439+
440+
>>> import bigframes.pandas as bpd
441+
>>> import bigframes.bigquery as bbq
442+
>>> bpd.options.display.progress_bar = None
443+
444+
>>> s = bpd.Series([1, 2, 3])
445+
>>> bbq.to_json(s)
446+
0 1
447+
1 2
448+
2 3
449+
dtype: extension<dbjson<JSONArrowType>>[pyarrow]
450+
451+
>>> s = bpd.Series([{"int": 1, "str": "pandas"}, {"int": 2, "str": "numpy"}])
452+
>>> bbq.to_json(s)
453+
0 {"int":1,"str":"pandas"}
454+
1 {"int":2,"str":"numpy"}
455+
dtype: extension<dbjson<JSONArrowType>>[pyarrow]
456+
457+
Args:
458+
input (bigframes.series.Series):
459+
The Series containing JSON or JSON-formatted string values.
460+
461+
Returns:
462+
bigframes.series.Series: A new Series with the JSON value.
463+
"""
464+
return input._apply_unary_op(ops.ToJSON())
465+
466+
433467
def to_json_string(
434468
input: series.Series,
435469
) -> series.Series:

bigframes/core/compile/ibis_compiler/scalar_op_registry.py

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1300,6 +1300,11 @@ def parse_json_op_impl(x: ibis_types.Value, op: ops.ParseJSON):
13001300
return parse_json(json_str=x)
13011301

13021302

1303+
@scalar_op_compiler.register_unary_op(ops.ToJSON)
1304+
def to_json_op_impl(json_obj: ibis_types.Value):
1305+
return to_json(json_obj=json_obj)
1306+
1307+
13031308
@scalar_op_compiler.register_unary_op(ops.ToJSONString)
13041309
def to_json_string_op_impl(json_obj: ibis_types.Value):
13051310
return to_json_string(json_obj=json_obj)
@@ -2067,9 +2072,14 @@ def json_extract_string_array( # type: ignore[empty-body]
20672072
"""Extracts a JSON array and converts it to a SQL ARRAY of STRINGs."""
20682073

20692074

2075+
@ibis_udf.scalar.builtin(name="to_json")
2076+
def to_json(json_obj) -> ibis_dtypes.JSON: # type: ignore[empty-body]
2077+
"""Convert to JSON."""
2078+
2079+
20702080
@ibis_udf.scalar.builtin(name="to_json_string")
20712081
def to_json_string(json_obj) -> ibis_dtypes.String: # type: ignore[empty-body]
2072-
"""Convert JSON to STRING."""
2082+
"""Convert to JSON-formatted STRING."""
20732083

20742084

20752085
@ibis_udf.scalar.builtin(name="json_value")

bigframes/operations/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,7 @@
123123
JSONValue,
124124
JSONValueArray,
125125
ParseJSON,
126+
ToJSON,
126127
ToJSONString,
127128
)
128129
from bigframes.operations.numeric_ops import (
@@ -375,6 +376,7 @@
375376
"JSONValue",
376377
"JSONValueArray",
377378
"ParseJSON",
379+
"ToJSON",
378380
"ToJSONString",
379381
# Bool ops
380382
"and_op",

bigframes/operations/json_ops.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,20 @@ def output_type(self, *input_types):
102102
return dtypes.JSON_DTYPE
103103

104104

105+
@dataclasses.dataclass(frozen=True)
106+
class ToJSON(base_ops.UnaryOp):
107+
name: typing.ClassVar[str] = "to_json"
108+
109+
def output_type(self, *input_types):
110+
input_type = input_types[0]
111+
if not dtypes.is_json_encoding_type(input_type):
112+
raise TypeError(
113+
"The value to be assigned must be a type that can be encoded as JSON."
114+
+ f"Received type: {input_type}"
115+
)
116+
return dtypes.JSON_DTYPE
117+
118+
105119
@dataclasses.dataclass(frozen=True)
106120
class ToJSONString(base_ops.UnaryOp):
107121
name: typing.ClassVar[str] = "to_json_string"

tests/system/small/bigquery/test_json.py

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -409,3 +409,28 @@ def test_to_json_string_from_struct():
409409
)
410410

411411
pd.testing.assert_series_equal(actual.to_pandas(), expected.to_pandas())
412+
413+
414+
def test_to_json_from_int():
415+
s = bpd.Series([1, 2, None, 3])
416+
actual = bbq.to_json(s)
417+
expected = bpd.Series(["1", "2", "null", "3"], dtype=dtypes.STRING_DTYPE)
418+
pd.testing.assert_series_equal(actual.to_pandas(), expected.to_pandas())
419+
420+
421+
def test_to_json_from_struct():
422+
s = bpd.Series(
423+
[
424+
{"version": 1, "project": "pandas"},
425+
{"version": 2, "project": "numpy"},
426+
]
427+
)
428+
assert dtypes.is_struct_like(s.dtype)
429+
430+
actual = bbq.to_json(s)
431+
expected = bpd.Series(
432+
['{"project":"pandas","version":1}', '{"project":"numpy","version":2}'],
433+
dtype=dtypes.JSON_DTYPE,
434+
)
435+
436+
pd.testing.assert_series_equal(actual.to_pandas(), expected.to_pandas())

0 commit comments

Comments
 (0)