@@ -13,7 +13,7 @@ use crate::PydanticSerializationUnexpectedValue;
13
13
14
14
use super :: computed_fields:: ComputedFields ;
15
15
use super :: errors:: py_err_se_err;
16
- use super :: extra:: Extra ;
16
+ use super :: extra:: { Extra , SortKeysMode } ;
17
17
use super :: filter:: SchemaFilter ;
18
18
use super :: infer:: { infer_json_key, infer_serialize, infer_to_python, SerializeInfer } ;
19
19
use super :: shared:: PydanticSerializer ;
@@ -156,7 +156,7 @@ impl GeneralFieldsSerializer {
156
156
let output_dict = PyDict :: new ( py) ;
157
157
let mut used_req_fields: usize = 0 ;
158
158
159
- if ! extra. sort_keys {
159
+ if matches ! ( extra. sort_keys, SortKeysMode :: Unsorted ) {
160
160
for result in main_iter {
161
161
let ( key, value) = result?;
162
162
if let Some ( is_required) =
@@ -201,6 +201,23 @@ impl GeneralFieldsSerializer {
201
201
}
202
202
}
203
203
204
+ fn sort_dict_recursive < ' py > ( py : Python < ' py > , value : & Bound < ' py , PyAny > ) -> PyResult < Bound < ' py , PyDict > > {
205
+ let dict = value. downcast :: < PyDict > ( ) ?;
206
+ let mut items = dict_items ( dict) . collect :: < PyResult < Vec < _ > > > ( ) ?;
207
+ items. sort_by_cached_key ( |( key, _) | key_str ( key) . unwrap_or_default ( ) . to_string ( ) ) ;
208
+
209
+ let sorted_dict = PyDict :: new ( py) ;
210
+ for ( k, v) in items {
211
+ if v. downcast :: < PyDict > ( ) . is_ok ( ) {
212
+ let sorted_v = Self :: sort_dict_recursive ( py, & v) ?;
213
+ sorted_dict. set_item ( k, sorted_v) ?;
214
+ } else {
215
+ sorted_dict. set_item ( k, v) ?;
216
+ }
217
+ }
218
+ Ok ( sorted_dict)
219
+ }
220
+
204
221
fn process_field_entry_python < ' py > (
205
222
& self ,
206
223
key : & Bound < ' py , PyAny > ,
@@ -235,10 +252,21 @@ impl GeneralFieldsSerializer {
235
252
if let Some ( field) = op_field {
236
253
if let Some ( ref serializer) = field. serializer {
237
254
if !exclude_default ( value, & field_extra, serializer) ? {
238
- let value =
239
- serializer. to_python ( value, next_include. as_ref ( ) , next_exclude. as_ref ( ) , & field_extra) ?;
255
+ let processed_value = if matches ! ( extra. sort_keys, SortKeysMode :: Recursive )
256
+ && value. downcast :: < PyDict > ( ) . is_ok ( )
257
+ {
258
+ let sorted_dict = Self :: sort_dict_recursive ( value. py ( ) , value) ?;
259
+ serializer. to_python (
260
+ sorted_dict. as_ref ( ) ,
261
+ next_include. as_ref ( ) ,
262
+ next_exclude. as_ref ( ) ,
263
+ & field_extra,
264
+ ) ?
265
+ } else {
266
+ serializer. to_python ( value, next_include. as_ref ( ) , next_exclude. as_ref ( ) , & field_extra) ?
267
+ } ;
240
268
let output_key = field. get_key_py ( output_dict. py ( ) , & field_extra) ;
241
- output_dict. set_item ( output_key, value ) ?;
269
+ output_dict. set_item ( output_key, processed_value ) ?;
242
270
}
243
271
}
244
272
@@ -247,13 +275,33 @@ impl GeneralFieldsSerializer {
247
275
}
248
276
return Ok ( Some ( false ) ) ;
249
277
} else if self . mode == FieldsMode :: TypedDictAllow {
250
- let value = match & self . extra_serializer {
251
- Some ( serializer) => {
252
- serializer. to_python ( value, next_include. as_ref ( ) , next_exclude. as_ref ( ) , & field_extra) ?
278
+ let processed_value = if matches ! ( extra. sort_keys, SortKeysMode :: Recursive )
279
+ && value. downcast :: < PyDict > ( ) . is_ok ( )
280
+ {
281
+ let sorted_dict = Self :: sort_dict_recursive ( value. py ( ) , value) ?;
282
+ match & self . extra_serializer {
283
+ Some ( serializer) => serializer. to_python (
284
+ sorted_dict. as_ref ( ) ,
285
+ next_include. as_ref ( ) ,
286
+ next_exclude. as_ref ( ) ,
287
+ & field_extra,
288
+ ) ?,
289
+ None => infer_to_python (
290
+ sorted_dict. as_ref ( ) ,
291
+ next_include. as_ref ( ) ,
292
+ next_exclude. as_ref ( ) ,
293
+ & field_extra,
294
+ ) ?,
295
+ }
296
+ } else {
297
+ match & self . extra_serializer {
298
+ Some ( serializer) => {
299
+ serializer. to_python ( value, next_include. as_ref ( ) , next_exclude. as_ref ( ) , & field_extra) ?
300
+ }
301
+ None => infer_to_python ( value, next_include. as_ref ( ) , next_exclude. as_ref ( ) , & field_extra) ?,
253
302
}
254
- None => infer_to_python ( value, next_include. as_ref ( ) , next_exclude. as_ref ( ) , & field_extra) ?,
255
303
} ;
256
- output_dict. set_item ( key, value ) ?;
304
+ output_dict. set_item ( key, processed_value ) ?;
257
305
return Ok ( None ) ;
258
306
} else if field_extra. check == SerCheck :: Strict {
259
307
return Err ( PydanticSerializationUnexpectedValue :: new (
@@ -281,7 +329,7 @@ impl GeneralFieldsSerializer {
281
329
// we don't both with `used_fields` here because on unions, `to_python(..., mode='json')` is used
282
330
let mut map = serializer. serialize_map ( Some ( expected_len) ) ?;
283
331
284
- if ! extra. sort_keys {
332
+ if matches ! ( extra. sort_keys, SortKeysMode :: Unsorted ) {
285
333
for result in main_iter {
286
334
let ( key, value) = result. map_err ( py_err_se_err) ?;
287
335
self . process_field_entry :: < S > ( & key, & value, & mut map, include, exclude, & extra) ?;
@@ -308,32 +356,56 @@ impl GeneralFieldsSerializer {
308
356
if extra. exclude_none && value. is_none ( ) {
309
357
return Ok ( ( ) ) ;
310
358
}
311
- let key_str = key_str ( key) . map_err ( py_err_se_err) ?;
359
+ let field_key_str = key_str ( key) . map_err ( py_err_se_err) ?;
312
360
let field_extra = Extra {
313
- field_name : Some ( key_str ) ,
361
+ field_name : Some ( field_key_str ) ,
314
362
..* extra
315
363
} ;
316
364
317
365
let filter = self . filter . key_filter ( key, include, exclude) . map_err ( py_err_se_err) ?;
318
366
if let Some ( ( next_include, next_exclude) ) = filter {
319
- if let Some ( field) = self . fields . get ( key_str ) {
367
+ if let Some ( field) = self . fields . get ( field_key_str ) {
320
368
if let Some ( ref serializer) = field. serializer {
321
369
if !exclude_default ( value, & field_extra, serializer) . map_err ( py_err_se_err) ? {
322
- let s = PydanticSerializer :: new (
323
- value,
324
- serializer,
325
- next_include. as_ref ( ) ,
326
- next_exclude. as_ref ( ) ,
327
- & field_extra,
328
- ) ;
329
- let output_key = field. get_key_json ( key_str, & field_extra) ;
330
- map. serialize_entry ( & output_key, & s) ?;
370
+ if matches ! ( extra. sort_keys, SortKeysMode :: Recursive ) && value. downcast :: < PyDict > ( ) . is_ok ( ) {
371
+ let sorted_dict = Self :: sort_dict_recursive ( value. py ( ) , value) . map_err ( py_err_se_err) ?;
372
+ let s = PydanticSerializer :: new (
373
+ sorted_dict. as_ref ( ) ,
374
+ serializer,
375
+ next_include. as_ref ( ) ,
376
+ next_exclude. as_ref ( ) ,
377
+ & field_extra,
378
+ ) ;
379
+ let output_key = field. get_key_json ( field_key_str, & field_extra) ;
380
+ map. serialize_entry ( & output_key, & s) ?;
381
+ } else {
382
+ let s = PydanticSerializer :: new (
383
+ value,
384
+ serializer,
385
+ next_include. as_ref ( ) ,
386
+ next_exclude. as_ref ( ) ,
387
+ & field_extra,
388
+ ) ;
389
+ let output_key = field. get_key_json ( field_key_str, & field_extra) ;
390
+ map. serialize_entry ( & output_key, & s) ?;
391
+ }
331
392
}
332
393
}
333
394
} else if self . mode == FieldsMode :: TypedDictAllow {
334
395
let output_key = infer_json_key ( key, & field_extra) . map_err ( py_err_se_err) ?;
335
- let s = SerializeInfer :: new ( value, next_include. as_ref ( ) , next_exclude. as_ref ( ) , & field_extra) ;
336
- map. serialize_entry ( & output_key, & s) ?;
396
+ if matches ! ( extra. sort_keys, SortKeysMode :: Recursive ) && value. downcast :: < PyDict > ( ) . is_ok ( ) {
397
+ let sorted_dict = Self :: sort_dict_recursive ( value. py ( ) , value) . map_err ( py_err_se_err) ?;
398
+ let s = SerializeInfer :: new (
399
+ sorted_dict. as_ref ( ) ,
400
+ next_include. as_ref ( ) ,
401
+ next_exclude. as_ref ( ) ,
402
+ & field_extra,
403
+ ) ;
404
+ map. serialize_entry ( & output_key, & s) ?;
405
+ } else {
406
+ let s = SerializeInfer :: new ( value, next_include. as_ref ( ) , next_exclude. as_ref ( ) , & field_extra) ;
407
+ map. serialize_entry ( & output_key, & s) ?;
408
+ }
337
409
}
338
410
// no error case here since unions (which need the error case) use `to_python(..., mode='json')`
339
411
}
0 commit comments