Skip to content

Commit 1c62bf1

Browse files
committed
feat: allow sort_keys on to_json and to_python
1 parent d9dacb0 commit 1c62bf1

File tree

10 files changed

+674
-121
lines changed

10 files changed

+674
-121
lines changed

python/pydantic_core/_pydantic_core.pyi

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -305,6 +305,7 @@ class SchemaSerializer:
305305
exclude_defaults: bool = False,
306306
exclude_none: bool = False,
307307
round_trip: bool = False,
308+
sort_keys: bool = False,
308309
warnings: bool | Literal['none', 'warn', 'error'] = True,
309310
fallback: Callable[[Any], Any] | None = None,
310311
serialize_as_any: bool = False,
@@ -325,6 +326,7 @@ class SchemaSerializer:
325326
exclude_defaults: Whether to exclude fields that are equal to their default value.
326327
exclude_none: Whether to exclude fields that have a value of `None`.
327328
round_trip: Whether to enable serialization and validation round-trip support.
329+
sort_keys: Whether to sort dictionary keys. If True, all dictionary keys will be sorted alphabetically, including nested dictionaries.
328330
warnings: How to handle invalid fields. False/"none" ignores them, True/"warn" logs errors,
329331
"error" raises a [`PydanticSerializationError`][pydantic_core.PydanticSerializationError].
330332
fallback: A function to call when an unknown value is encountered,
@@ -352,6 +354,7 @@ class SchemaSerializer:
352354
exclude_defaults: bool = False,
353355
exclude_none: bool = False,
354356
round_trip: bool = False,
357+
sort_keys: bool = False,
355358
warnings: bool | Literal['none', 'warn', 'error'] = True,
356359
fallback: Callable[[Any], Any] | None = None,
357360
serialize_as_any: bool = False,
@@ -373,6 +376,7 @@ class SchemaSerializer:
373376
exclude_defaults: Whether to exclude fields that are equal to their default value.
374377
exclude_none: Whether to exclude fields that have a value of `None`.
375378
round_trip: Whether to enable serialization and validation round-trip support.
379+
sort_keys: Whether to sort dictionary keys. If True, all dictionary keys will be sorted alphabetically, including nested dictionaries.
376380
warnings: How to handle invalid fields. False/"none" ignores them, True/"warn" logs errors,
377381
"error" raises a [`PydanticSerializationError`][pydantic_core.PydanticSerializationError].
378382
fallback: A function to call when an unknown value is encountered,
@@ -401,6 +405,7 @@ def to_json(
401405
by_alias: bool = True,
402406
exclude_none: bool = False,
403407
round_trip: bool = False,
408+
sort_keys: bool = False,
404409
timedelta_mode: Literal['iso8601', 'float'] = 'iso8601',
405410
bytes_mode: Literal['utf8', 'base64', 'hex'] = 'utf8',
406411
inf_nan_mode: Literal['null', 'constants', 'strings'] = 'constants',
@@ -424,6 +429,7 @@ def to_json(
424429
by_alias: Whether to use the alias names of fields.
425430
exclude_none: Whether to exclude fields that have a value of `None`.
426431
round_trip: Whether to enable serialization and validation round-trip support.
432+
sort_keys: Whether to sort dictionary keys. If True, all dictionary keys will be sorted alphabetically, including nested dictionaries.
427433
timedelta_mode: How to serialize `timedelta` objects, either `'iso8601'` or `'float'`.
428434
bytes_mode: How to serialize `bytes` objects, either `'utf8'`, `'base64'`, or `'hex'`.
429435
inf_nan_mode: How to serialize `Infinity`, `-Infinity` and `NaN` values, either `'null'`, `'constants'`, or `'strings'`.
@@ -482,6 +488,7 @@ def to_jsonable_python(
482488
by_alias: bool = True,
483489
exclude_none: bool = False,
484490
round_trip: bool = False,
491+
sort_keys: bool = False,
485492
timedelta_mode: Literal['iso8601', 'float'] = 'iso8601',
486493
bytes_mode: Literal['utf8', 'base64', 'hex'] = 'utf8',
487494
inf_nan_mode: Literal['null', 'constants', 'strings'] = 'constants',
@@ -503,6 +510,7 @@ def to_jsonable_python(
503510
by_alias: Whether to use the alias names of fields.
504511
exclude_none: Whether to exclude fields that have a value of `None`.
505512
round_trip: Whether to enable serialization and validation round-trip support.
513+
sort_keys: Whether to sort dictionary keys. If True, all dictionary keys will be sorted alphabetically, including nested dictionaries.
506514
timedelta_mode: How to serialize `timedelta` objects, either `'iso8601'` or `'float'`.
507515
bytes_mode: How to serialize `bytes` objects, either `'utf8'`, `'base64'`, or `'hex'`.
508516
inf_nan_mode: How to serialize `Infinity`, `-Infinity` and `NaN` values, either `'null'`, `'constants'`, or `'strings'`.

python/pydantic_core/core_schema.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,9 @@ def serialize_as_any(self) -> bool: ...
154154
@property
155155
def round_trip(self) -> bool: ...
156156

157+
@property
158+
def sort_keys(self) -> bool: ...
159+
157160
def mode_is_json(self) -> bool: ...
158161

159162
def __str__(self) -> str: ...

src/errors/validation_exception.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -341,7 +341,7 @@ impl ValidationError {
341341
include_input: bool,
342342
) -> PyResult<Bound<'py, PyString>> {
343343
let state = SerializationState::new("iso8601", "utf8", "constants")?;
344-
let extra = state.extra(py, &SerMode::Json, None, false, false, true, None, false, None);
344+
let extra = state.extra(py, &SerMode::Json, None, false, false, false, true, None, false, None);
345345
let serializer = ValidationErrorSerializer {
346346
py,
347347
line_errors: &self.line_errors,

src/serializers/extra.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ impl SerializationState {
4747
by_alias: Option<bool>,
4848
exclude_none: bool,
4949
round_trip: bool,
50+
sort_keys: bool,
5051
serialize_unknown: bool,
5152
fallback: Option<&'py Bound<'_, PyAny>>,
5253
serialize_as_any: bool,
@@ -61,6 +62,7 @@ impl SerializationState {
6162
false,
6263
exclude_none,
6364
round_trip,
65+
sort_keys,
6466
&self.config,
6567
&self.rec_guard,
6668
serialize_unknown,
@@ -87,6 +89,7 @@ pub(crate) struct Extra<'a> {
8789
pub exclude_defaults: bool,
8890
pub exclude_none: bool,
8991
pub round_trip: bool,
92+
pub sort_keys: bool,
9093
pub config: &'a SerializationConfig,
9194
pub rec_guard: &'a SerRecursionState,
9295
// the next two are used for union logic
@@ -113,6 +116,7 @@ impl<'a> Extra<'a> {
113116
exclude_defaults: bool,
114117
exclude_none: bool,
115118
round_trip: bool,
119+
sort_keys: bool,
116120
config: &'a SerializationConfig,
117121
rec_guard: &'a SerRecursionState,
118122
serialize_unknown: bool,
@@ -129,6 +133,7 @@ impl<'a> Extra<'a> {
129133
exclude_defaults,
130134
exclude_none,
131135
round_trip,
136+
sort_keys,
132137
config,
133138
rec_guard,
134139
check: SerCheck::None,
@@ -197,6 +202,7 @@ pub(crate) struct ExtraOwned {
197202
exclude_defaults: bool,
198203
exclude_none: bool,
199204
round_trip: bool,
205+
sort_keys: bool,
200206
config: SerializationConfig,
201207
rec_guard: SerRecursionState,
202208
check: SerCheck,
@@ -218,6 +224,7 @@ impl ExtraOwned {
218224
exclude_defaults: extra.exclude_defaults,
219225
exclude_none: extra.exclude_none,
220226
round_trip: extra.round_trip,
227+
sort_keys: extra.sort_keys,
221228
config: extra.config.clone(),
222229
rec_guard: extra.rec_guard.clone(),
223230
check: extra.check,
@@ -240,6 +247,7 @@ impl ExtraOwned {
240247
exclude_defaults: self.exclude_defaults,
241248
exclude_none: self.exclude_none,
242249
round_trip: self.round_trip,
250+
sort_keys: self.sort_keys,
243251
config: &self.config,
244252
rec_guard: &self.rec_guard,
245253
check: self.check,

0 commit comments

Comments
 (0)