Skip to content

Commit 0f0b212

Browse files
committed
feat: Add serde_skip_default field attribute
A shorthand of `skip_if=lambda v: v == <default_value>`.
1 parent e0c6039 commit 0f0b212

File tree

4 files changed

+51
-6
lines changed

4 files changed

+51
-6
lines changed

docs/features/conditional-skip.md

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,14 @@
11
# Conditional Skip
22

3-
If you conditionally skip some fields, you can pass function or lambda in `serde_skip_if`.
3+
## `skip_if`
4+
5+
If you conditionally skip some fields, you can pass function or lambda in `skip_if`.
46

57
```python
68
@serde
79
class World:
810
player: str
9-
buddy: str = field(default='', metadata={'serde_skip_if': lambda v: v == 'Pikachu'})
11+
buddy: str = field(default='', skip_if=lambda v: v == 'Pikachu')
1012

1113
world = World('satoshi', 'Pikachu')
1214
print(to_json(world))
@@ -22,4 +24,12 @@ As you can see below, field is skipped in serialization if `buddy` is "Pikachu".
2224
{"player": "green", "buddy": "Charmander"}
2325
```
2426

27+
## `skip_if_false`
28+
29+
`skip_if_false` is a shorthand of `skip_if=lambda v: not v`.
30+
31+
## `skip_if_default`
32+
33+
`skip_if_false` is a shorthand of `skip_if=lambda v: v == <default_value>`.
34+
2535
For complete example, please see [examples/skip.py](https://github.com/yukinarit/pyserde/blob/master/examples/skip.py)

examples/skip.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ class World:
2626
player: str
2727
enemies: List[str] = field(default_factory=list, skip_if_false=True)
2828
buddy: str = field(default='', skip_if=lambda v: v == 'Pikachu')
29+
town: str = field(default='Masara Town', skip_if_default=True)
2930

3031

3132
def main():
@@ -35,10 +36,12 @@ def main():
3536
]
3637
print(to_json(resources))
3738

39+
# "buddy" and "town" field will be omitted
3840
world = World('satoshi', ['Rattata', 'Pidgey'], 'Pikachu')
3941
print(to_json(world))
4042

41-
world = World('green', [], 'Charmander')
43+
# "enemies" field will be omitted
44+
world = World('green', [], 'Charmander', 'Black City')
4245
print(to_json(world))
4346

4447

serde/core.py

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import dataclasses
55
import datetime
66
import decimal
7+
import functools
78
import ipaddress
89
import logging
910
import pathlib
@@ -268,6 +269,10 @@ def skip_if_false(v):
268269
return not bool(v)
269270

270271

272+
def skip_if_default(v, default=None):
273+
return v == default
274+
275+
271276
@dataclass
272277
class FlattenOpts:
273278
"""
@@ -280,7 +285,8 @@ def field(
280285
rename: Optional[str] = None,
281286
skip: Optional[bool] = None,
282287
skip_if: Optional[Callable] = None,
283-
skip_if_false: Optional[Callable] = None,
288+
skip_if_false: Optional[bool] = None,
289+
skip_if_default: Optional[bool] = None,
284290
serializer=None,
285291
deserializer=None,
286292
flatten: Optional[FlattenOpts] = None,
@@ -301,6 +307,8 @@ def field(
301307
metadata["serde_skip_if"] = skip_if
302308
if skip_if_false is not None:
303309
metadata["serde_skip_if_false"] = skip_if_false
310+
if skip_if_default is not None:
311+
metadata["serde_skip_if_default"] = skip_if_default
304312
if serializer:
305313
metadata["serde_serializer"] = serializer
306314
if deserializer:
@@ -355,6 +363,9 @@ class Foo:
355363
* `skip_if_false` (Attribute name: `serde_skip_if_false`) skips (de)serialization if the field value evaluates
356364
to `False`. For example, this code skip (de)serialize `v` if `v` is empty.
357365
366+
* `skip_if_default` (Attribute name: `serde_skip_if_default`) skips (de)serialization if the field value is equal
367+
to the default value
368+
358369
```python
359370
@deserialize
360371
@serialize
@@ -387,6 +398,7 @@ class Foo:
387398
skip: Optional[bool] = None
388399
skip_if: Optional[Func] = None
389400
skip_if_false: Optional[bool] = None
401+
skip_if_default: Optional[bool] = None
390402
serializer: Optional[Func] = None # Custom field serializer.
391403
deserializer: Optional[Func] = None # Custom field deserializer.
392404
flatten: Optional[FlattenOpts] = None
@@ -398,7 +410,12 @@ def from_dataclass(cls, f: dataclasses.Field) -> 'Field':
398410
"""
399411
skip_if_false_func: Optional[Func] = None
400412
if f.metadata.get('serde_skip_if_false'):
401-
skip_if_false_func = Func(skip_if_false, cls.mangle(f, 'skip_if'))
413+
skip_if_false_func = Func(skip_if_false, cls.mangle(f, 'skip_if_false'))
414+
415+
skip_if_default_func: Optional[Func] = None
416+
if f.metadata.get('serde_skip_if_default'):
417+
skip_if_def = functools.partial(skip_if_default, default=f.default)
418+
skip_if_default_func = Func(skip_if_def, cls.mangle(f, 'skip_if_default'))
402419

403420
skip_if: Optional[Func] = None
404421
if f.metadata.get('serde_skip_if'):
@@ -432,7 +449,7 @@ def from_dataclass(cls, f: dataclasses.Field) -> 'Field':
432449
metadata=f.metadata,
433450
rename=f.metadata.get('serde_rename'),
434451
skip=f.metadata.get('serde_skip'),
435-
skip_if=skip_if or skip_if_false_func,
452+
skip_if=skip_if or skip_if_false_func or skip_if_default_func,
436453
serializer=serializer,
437454
deserializer=deserializer,
438455
flatten=flatten,

tests/test_basics.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -458,6 +458,21 @@ class Foo:
458458
assert ff.comments == []
459459

460460

461+
@pytest.mark.parametrize('se,de', all_formats)
462+
def test_skip_if_default(se, de):
463+
@serde.serde
464+
class Foo:
465+
a: str = serde.field(default='foo', skip_if_default=True)
466+
467+
f = Foo()
468+
assert f == de(Foo, se(f))
469+
470+
assert serde.to_dict(Foo()) == {}
471+
assert serde.to_dict(Foo('bar')) == {'a': 'bar'}
472+
assert serde.from_dict(Foo, {}) == Foo()
473+
assert serde.from_dict(Foo, {'a': 'bar'}) == Foo('bar')
474+
475+
461476
@pytest.mark.parametrize('se,de', format_msgpack)
462477
def test_inheritance(se, de):
463478
@serde.serde

0 commit comments

Comments
 (0)