Skip to content

Commit ea6e047

Browse files
Mizuchimeta-codesync[bot]
authored andcommitted
Expose key_order option on Json5ProtocolWriterOptions
Summary: Surfaces the C++ `Json5ProtocolWriter::Options::key_order` field through the thrift-python serializer. ___ Differential Revision: D106566885 fbshipit-source-id: 8c4c37d852c1e9758f6c6aff324fba42dbeecadc
1 parent b55aada commit ea6e047

5 files changed

Lines changed: 51 additions & 11 deletions

File tree

‎thrift/lib/python/Serializer.h‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
#include <memory>
2020

2121
#include <folly/io/IOBuf.h>
22+
#include <thrift/lib/cpp2/protocol/Protocol.h>
2223

2324
#ifdef THRIFT_HAS_JSON5_PROTOCOL
2425
#include <thrift/lib/cpp2/protocol/detail/Json5ProtocolWriter.h>
@@ -37,6 +38,8 @@ struct Json5ProtocolWriter {
3738
bool enumAsInteger = false;
3839
bool binaryAsBase64String = false;
3940
bool mapPrimitiveKeysAsMemberNames = false;
41+
::apache::thrift::KeyOrder keyOrder =
42+
::apache::thrift::KeyOrder::StableAscending;
4043
};
4144
};
4245
} // namespace apache::thrift::json5::detail

‎thrift/lib/python/serializer.pxd‎

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,13 @@ from thrift.python.types cimport cDynamicStructInfo
2121
from thrift.python.protocol cimport Protocol as cProtocol
2222

2323

24+
cdef extern from "<thrift/lib/cpp2/protocol/Protocol.h>" namespace "::apache::thrift":
25+
cpdef enum KeyOrder "::apache::thrift::KeyOrder":
26+
UNSPECIFIED "::apache::thrift::KeyOrder::Unspecified"
27+
NATIVE_ASCENDING "::apache::thrift::KeyOrder::NativeAscending"
28+
STABLE_ASCENDING "::apache::thrift::KeyOrder::StableAscending"
29+
30+
2431
cdef extern from "<thrift/lib/python/Serializer.h>" namespace "apache::thrift::json5::detail":
2532
cdef cppclass cJsonWriterOptions "apache::thrift::json5::detail::JsonWriterOptions":
2633
cbool listTrailingComma
@@ -36,6 +43,7 @@ cdef extern from "<thrift/lib/python/Serializer.h>" namespace "apache::thrift::j
3643
cbool enumAsInteger
3744
cbool binaryAsBase64String
3845
cbool mapPrimitiveKeysAsMemberNames
46+
KeyOrder keyOrder
3947

4048

4149
cdef extern from "<thrift/lib/python/Serializer.h>" namespace "::apache::thrift::python":

‎thrift/lib/python/serializer.pyi‎

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,18 +39,25 @@ class JsonWriterOptions:
3939

4040
JSON5_MODE: Json5ProtocolWriterOptions
4141

42+
class KeyOrder(Enum):
43+
UNSPECIFIED: KeyOrder = ...
44+
NATIVE_ASCENDING: KeyOrder = ...
45+
STABLE_ASCENDING: KeyOrder = ...
46+
4247
class Json5ProtocolWriterOptions:
4348
writer: JsonWriterOptions
4449
enum_as_integer: bool
4550
binary_as_base64_string: bool
4651
map_primitive_keys_as_member_names: bool
52+
key_order: KeyOrder
4753
def __init__(
4854
self,
4955
*,
5056
writer: JsonWriterOptions | None = None,
5157
enum_as_integer: bool = False,
5258
binary_as_base64_string: bool = False,
5359
map_primitive_keys_as_member_names: bool = False,
60+
key_order: KeyOrder = KeyOrder.STABLE_ASCENDING,
5461
) -> None: ...
5562

5663
def serialize_iobuf(

‎thrift/lib/python/serializer.pyx‎

Lines changed: 16 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ from libcpp.utility cimport move as cmove
1919
from thrift.python.exceptions cimport Error, GeneratedError
2020
from thrift.python.types cimport Struct, StructOrUnion, StructInfo, Union, UnionInfo
2121
from thrift.python.protocol import Protocol
22-
from thrift.python.serializer cimport cJsonWriterOptions, cJson5ProtocolWriterOptions
22+
from thrift.python.serializer cimport cJsonWriterOptions, cJson5ProtocolWriterOptions, KeyOrder
2323

2424
cdef extern from *:
2525
"""
@@ -49,23 +49,14 @@ cdef class JsonWriterOptions:
4949
self._writer_options.allowNanInf = allow_nan_inf
5050
self._writer_options.indentWidth = indent_width
5151

52-
JSON5_MODE = Json5ProtocolWriterOptions(
53-
writer=JsonWriterOptions(
54-
list_trailing_comma=True,
55-
object_trailing_comma=True,
56-
unquote_object_name=True,
57-
allow_nan_inf=True,
58-
indent_width=2,
59-
)
60-
)
61-
6252

6353
cdef class Json5ProtocolWriterOptions:
6454
"""Options for the JSON5 protocol writer that are passed to C++."""
6555
cdef public JsonWriterOptions writer
6656
cdef public bint enum_as_integer
6757
cdef public bint binary_as_base64_string
6858
cdef public bint map_primitive_keys_as_member_names
59+
cdef public object key_order
6960

7061
def __init__(
7162
self,
@@ -74,11 +65,24 @@ cdef class Json5ProtocolWriterOptions:
7465
enum_as_integer=False,
7566
binary_as_base64_string=False,
7667
map_primitive_keys_as_member_names=False,
68+
key_order=KeyOrder.STABLE_ASCENDING,
7769
):
7870
self.writer = writer if writer is not None else JsonWriterOptions()
7971
self.enum_as_integer = enum_as_integer
8072
self.binary_as_base64_string = binary_as_base64_string
8173
self.map_primitive_keys_as_member_names = map_primitive_keys_as_member_names
74+
self.key_order = KeyOrder(key_order)
75+
76+
77+
JSON5_MODE = Json5ProtocolWriterOptions(
78+
writer=JsonWriterOptions(
79+
list_trailing_comma=True,
80+
object_trailing_comma=True,
81+
unquote_object_name=True,
82+
allow_nan_inf=True,
83+
indent_width=2,
84+
)
85+
)
8286

8387

8488
cdef cJson5ProtocolWriterOptions _to_c_options(Json5ProtocolWriterOptions options):
@@ -87,6 +91,7 @@ cdef cJson5ProtocolWriterOptions _to_c_options(Json5ProtocolWriterOptions option
8791
c_options.enumAsInteger = options.enum_as_integer
8892
c_options.binaryAsBase64String = options.binary_as_base64_string
8993
c_options.mapPrimitiveKeysAsMemberNames = options.map_primitive_keys_as_member_names
94+
c_options.keyOrder = <KeyOrder>(<int>options.key_order)
9095
return cmove(c_options)
9196

9297

‎thrift/lib/python/test/json5_serializer_test.py‎

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
JSON5_MODE,
3131
Json5ProtocolWriterOptions,
3232
JsonWriterOptions,
33+
KeyOrder,
3334
Protocol,
3435
serialize,
3536
serialize_iobuf,
@@ -295,3 +296,19 @@ def test_json5_options_invalid_protocol(self) -> None:
295296
Protocol.COMPACT,
296297
options=Json5ProtocolWriterOptions(),
297298
)
299+
300+
def test_serialize_unspecified_preserves_iteration_order(self) -> None:
301+
"""With key_order=UNSPECIFIED the C++ writer must not sort: keys
302+
appear in the container's iteration order. Python dicts preserve
303+
insertion order, so the emitted order must match the Python
304+
insertion order.
305+
"""
306+
keys = [3, 1, 2] # intentionally unsorted insertion order
307+
example = test_types.Example(i32AsKey={k: k * 10 for k in keys})
308+
options = Json5ProtocolWriterOptions(key_order=KeyOrder.UNSPECIFIED)
309+
result = serialize(example, Protocol.JSON5, options=options).decode()
310+
311+
parsed = json.loads(result)
312+
emitted_keys = [entry["key"] for entry in parsed["i32AsKey"]]
313+
# No sorting: C++ order must match Python's insertion order.
314+
self.assertEqual(emitted_keys, keys)

0 commit comments

Comments
 (0)