Skip to content

Commit

Permalink
Nextgen Proto Pythonic API: “Add-on” proto for JSON serialize/parse
Browse files Browse the repository at this point in the history
- add google.protobuf.proto_json module
- wrap json_format's MessageToDict/ParseDict to the new module:

def serialize(
    message: Message,
    always_print_fields_with_no_presence: bool=False,
    preserving_proto_field_name: bool=False,
    use_integers_for_enums: bool=False,
    descriptor_pool: Optional[DescriptorPool]=None,
    float_precision: int=None,
) -> dict

def parse(
    message_class: Type[Message],
    js_dict: dict,
    ignore_unknown_fields: bool=False,
    descriptor_pool: Optional[DescriptorPool]=None,
    max_recursion_depth: int=100
) -> Message

PiperOrigin-RevId: 634490919
  • Loading branch information
anandolee authored and copybara-github committed May 16, 2024
1 parent 69a26b2 commit 6c91de9
Show file tree
Hide file tree
Showing 3 changed files with 121 additions and 0 deletions.
5 changes: 5 additions & 0 deletions python/build_targets.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -421,6 +421,11 @@ def build_targets(name):
srcs = ["google/protobuf/internal/proto_test.py"],
)

internal_py_test(
name = "proto_json_test",
srcs = ["google/protobuf/internal/proto_json_test.py"],
)

native.cc_library(
name = "proto_api",
hdrs = ["google/protobuf/proto_api.h"],
Expand Down
33 changes: 33 additions & 0 deletions python/google/protobuf/internal/proto_json_test.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# -*- coding: utf-8 -*-
# Protocol Buffers - Google's data interchange format
# Copyright 2008 Google Inc. All rights reserved.
#
# Use of this source code is governed by a BSD-style
# license that can be found in the LICENSE file or at
# https://developers.google.com/open-source/licenses/bsd

"""Tests Nextgen Pythonic protobuf APIs."""

import unittest

from google.protobuf import proto_json
from google.protobuf.util import json_format_proto3_pb2


class ProtoJsonTest(unittest.TestCase):

def testSimpleSerialize(self):
message = json_format_proto3_pb2.TestMessage()
message.int32_value = 12345
expected = {'int32Value': 12345}
self.assertEqual(expected, proto_json.serialize(message))

def testSimpleParse(self):
expected = 12345
js_dict = {'int32Value': expected}
message = proto_json.parse(json_format_proto3_pb2.TestMessage,
js_dict)
self.assertEqual(expected, message.int32_value)

if __name__ == "__main__":
unittest.main()
83 changes: 83 additions & 0 deletions python/google/protobuf/proto_json.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
# Protocol Buffers - Google's data interchange format
# Copyright 2008 Google Inc. All rights reserved.
#
# Use of this source code is governed by a BSD-style
# license that can be found in the LICENSE file or at
# https://developers.google.com/open-source/licenses/bsd

"""Contains the Nextgen Pythonic Protobuf JSON APIs."""

from typing import Optional, Type

from google.protobuf.message import Message
from google.protobuf.descriptor_pool import DescriptorPool
from google.protobuf import json_format

def serialize(
message: Message,
always_print_fields_with_no_presence: bool=False,
preserving_proto_field_name: bool=False,
use_integers_for_enums: bool=False,
descriptor_pool: Optional[DescriptorPool]=None,
float_precision: int=None,
) -> dict:
"""Converts protobuf message to a dictionary.
When the dictionary is encoded to JSON, it conforms to proto3 JSON spec.
Args:
message: The protocol buffers message instance to serialize.
always_print_fields_with_no_presence: If True, fields without
presence (implicit presence scalars, repeated fields, and map fields) will
always be serialized. Any field that supports presence is not affected by
this option (including singular message fields and oneof fields).
preserving_proto_field_name: If True, use the original proto field names as
defined in the .proto file. If False, convert the field names to
lowerCamelCase.
use_integers_for_enums: If true, print integers instead of enum names.
descriptor_pool: A Descriptor Pool for resolving types. If None use the
default.
float_precision: If set, use this to specify float field valid digits.
Returns:
A dict representation of the protocol buffer message.
"""
return json_format.MessageToDict(
message,
always_print_fields_with_no_presence=always_print_fields_with_no_presence,
preserving_proto_field_name=preserving_proto_field_name,
use_integers_for_enums=use_integers_for_enums,
float_precision=float_precision,
)

def parse(
message_class: Type[Message],
js_dict: dict,
ignore_unknown_fields: bool=False,
descriptor_pool: Optional[DescriptorPool]=None,
max_recursion_depth: int=100
) -> Message:
"""Parses a JSON dictionary representation into a message.
Args:
message_class: The message meta class.
js_dict: Dict representation of a JSON message.
ignore_unknown_fields: If True, do not raise errors for unknown fields.
descriptor_pool: A Descriptor Pool for resolving types. If None use the
default.
max_recursion_depth: max recursion depth of JSON message to be deserialized.
JSON messages over this depth will fail to be deserialized. Default value
is 100.
Returns:
A new message passed from json_dict.
"""
new_message = message_class()
json_format.ParseDict(
js_dict=js_dict,
message=new_message,
ignore_unknown_fields=ignore_unknown_fields,
descriptor_pool=descriptor_pool,
max_recursion_depth=max_recursion_depth,
)
return new_message

0 comments on commit 6c91de9

Please sign in to comment.