Skip to content

Commit

Permalink
feat: Add additional parameters to to_json() and to_dict() methods (
Browse files Browse the repository at this point in the history
#384)

* feat: Add additional parameters to `to_json()` and `to_dict()` methods

- These parameters are just passthroughs to [`MessageToJson`][1] and [`MessageToDict`][2]

[1]: https://googleapis.dev/python/protobuf/latest/google/protobuf/json_format.html#google.protobuf.json_format.MessageToJson

[2]: https://googleapis.dev/python/protobuf/latest/google/protobuf/json_format.html#google.protobuf.json_format.MessageToDict

* chore: Update min protobuf version to 3.20.0 to support `ensure_ascii`

* Removed `ensure_ascii` to prevent version compatibility errors

* Update proto/message.py

Co-authored-by: Anthonios Partheniou <partheniou@google.com>

* test: Add tests for `sort_keys` and `float_precision`

* Fix test checks

* Remove indent from float_precision test and add re.compile to sortkeys regex

* Add spaces to float precision check, add dotall to sortkeys check

* Remove indent from sort_keys test, add space to float precision test

* Changed match to search

* test: Add test for `to_dict()` with `float_precision`

* Added TODOs for `float_precision` issue

---------

Co-authored-by: Anthonios Partheniou <partheniou@google.com>
  • Loading branch information
holtskinner and parthea authored Sep 8, 2023
1 parent 9d52442 commit 8f13a46
Show file tree
Hide file tree
Showing 3 changed files with 48 additions and 2 deletions.
15 changes: 13 additions & 2 deletions proto/message.py
Original file line number Diff line number Diff line change
Expand Up @@ -376,7 +376,9 @@ def to_json(
use_integers_for_enums=True,
including_default_value_fields=True,
preserving_proto_field_name=False,
sort_keys=False,
indent=2,
float_precision=None,
) -> str:
"""Given a message instance, serialize it to json
Expand All @@ -389,10 +391,13 @@ def to_json(
preserving_proto_field_name (Optional(bool)): An option that
determines whether field name representations preserve
proto case (snake_case) or use lowerCamelCase. Default is False.
indent: The JSON object will be pretty-printed with this indent level.
sort_keys (Optional(bool)): If True, then the output will be sorted by field names.
Default is False.
indent (Optional(int)): The JSON object will be pretty-printed with this indent level.
An indent level of 0 or negative will only insert newlines.
Pass None for the most compact representation without newlines.
float_precision (Optional(int)): If set, use this to specify float field valid digits.
Default is None.
Returns:
str: The json string representation of the protocol buffer.
"""
Expand All @@ -401,7 +406,9 @@ def to_json(
use_integers_for_enums=use_integers_for_enums,
including_default_value_fields=including_default_value_fields,
preserving_proto_field_name=preserving_proto_field_name,
sort_keys=sort_keys,
indent=indent,
float_precision=float_precision,
)

def from_json(cls, payload, *, ignore_unknown_fields=False) -> "Message":
Expand All @@ -428,6 +435,7 @@ def to_dict(
use_integers_for_enums=True,
preserving_proto_field_name=True,
including_default_value_fields=True,
float_precision=None,
) -> "Message":
"""Given a message instance, return its representation as a python dict.
Expand All @@ -443,6 +451,8 @@ def to_dict(
including_default_value_fields (Optional(bool)): An option that
determines whether the default field values should be included in the results.
Default is True.
float_precision (Optional(int)): If set, use this to specify float field valid digits.
Default is None.
Returns:
dict: A representation of the protocol buffer using pythonic data structures.
Expand All @@ -454,6 +464,7 @@ def to_dict(
including_default_value_fields=including_default_value_fields,
preserving_proto_field_name=preserving_proto_field_name,
use_integers_for_enums=use_integers_for_enums,
float_precision=float_precision,
)

def copy_from(cls, instance, other):
Expand Down
24 changes: 24 additions & 0 deletions tests/test_json.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
# limitations under the License.

import pytest
import re

import proto
from google.protobuf.json_format import MessageToJson, Parse, ParseError
Expand Down Expand Up @@ -172,3 +173,26 @@ class Squid(proto.Message):
s_two = Squid.from_json(j)

assert s == s_two


def test_json_sort_keys():
class Squid(proto.Message):
name = proto.Field(proto.STRING, number=1)
mass_kg = proto.Field(proto.INT32, number=2)

s = Squid(name="Steve", mass_kg=20)
j = Squid.to_json(s, sort_keys=True, indent=None)

assert re.search(r"massKg.*name", j)


# TODO: https://github.com/googleapis/proto-plus-python/issues/390
def test_json_float_precision():
class Squid(proto.Message):
name = proto.Field(proto.STRING, number=1)
mass_kg = proto.Field(proto.FLOAT, number=2)

s = Squid(name="Steve", mass_kg=3.14159265)
j = Squid.to_json(s, float_precision=3, indent=None)

assert j == '{"name": "Steve", "massKg": 3.14}'
11 changes: 11 additions & 0 deletions tests/test_message.py
Original file line number Diff line number Diff line change
Expand Up @@ -271,6 +271,17 @@ class Color(proto.Enum):
assert new_s == s


# TODO: https://github.com/googleapis/proto-plus-python/issues/390
def test_serialize_to_dict_float_precision():
class Squid(proto.Message):
mass_kg = proto.Field(proto.FLOAT, number=1)

s = Squid(mass_kg=3.14159265)

s_dict = Squid.to_dict(s, float_precision=3)
assert s_dict["mass_kg"] == 3.14


def test_unknown_field_deserialize():
# This is a somewhat common setup: a client uses an older proto definition,
# while the server sends the newer definition. The client still needs to be
Expand Down

0 comments on commit 8f13a46

Please sign in to comment.