44
55- Use the marshmallow library to deserialize an input dictionary to a Python
66 class instance.
7+ - Filter fields as ` load_only ` or ` dump_only ` .
8+ - Define custom ` function ` and ` method ` fields to compute values during
9+ serialization.
710
811---
912
@@ -399,6 +402,176 @@ $ python lib/load_only_dump_only.py
399402'{"name": "Lua", "created_at": "2023-11-15T07:30:30.660156"}'
400403```
401404
405+ ---
406+
407+ ---
408+
409+ ## Custom Fields
410+
411+ You can create a custom field beyond the builtin field types. This is often done
412+ during serialization to compute a value from other fields. We will look at two
413+ options for creating a custom field (1) defining a ` function ` field, and (2)
414+ defining a ` method ` field.
415+
416+ Consider the code in ` lib/custom_field.py ` . A cat has fields ` name ` , ` dob ` , and
417+ ` favorite_toys ` .
418+
419+ ``` py
420+ # lib/custom_field.py
421+
422+ from marshmallow import Schema, fields, post_load
423+ from datetime import date
424+ from pprint import pprint
425+
426+ # model
427+
428+ class Cat :
429+ def __init__ (self , name , dob , favorite_toys = []):
430+ self .name = name
431+ self .dob = dob
432+ self .favorite_toys = favorite_toys
433+
434+ # schema
435+
436+ class CatSchema (Schema ):
437+ name = fields.Str(required = True , error_messages = {" required" : " Name is required." })
438+ dob = fields.Date(format = " %Y-%m-%d " )
439+ favorite_toys = fields.List(fields.Str())
440+
441+ @post_load
442+ def make_cat (self , data , ** kwargs ):
443+ return Cat(** data)
444+
445+ schema = CatSchema()
446+
447+ # deserialize
448+ cat_1 = schema.load({" name" : " Meowie" , " dob" : " 2020-11-28" , " favorite_toys" : [" ball" , " squeaky mouse" ]})
449+ cat_2 = schema.load({" name" : " Whiskers" , " dob" : " 2015-4-15" , " favorite_toys" : []})
450+
451+ # serialize
452+ pprint(schema.dump(cat_1))
453+ # => {'age': 2,
454+ # => 'dob': '2020-11-28',
455+ # => 'favorite_toys': ['ball', 'squeaky mouse']}
456+
457+ pprint(schema.dump(cat_2))
458+ # => {'age': 8,
459+ # => 'dob': '2015-04-15',
460+ # => 'favorite_toys': []}
461+ ```
462+
463+ Running the code produces the expected output:
464+
465+ ``` console
466+ {'dob': '2020-11-28',
467+ 'favorite_toys': ['ball', 'squeaky mouse'],
468+ 'name': 'Meowie'}
469+ {'dob': '2015-04-15', 'favorite_toys': [], 'name': 'Whiskers'}
470+ ```
471+
472+ Suppose we would like to include in the serialized output two more fields:
473+
474+ - ` likes_toys ` : a boolean that is true if the list of favorite toys is not
475+ empty
476+ - ` age ` : an integer calculated using the date of birth and the current date.
477+
478+ These fields will not be included during loading, since they can be calculated
479+ from the other fields. While we could update the model class to calculate them,
480+ we can also just update the schema to compute them during serialization.
481+
482+ We can use define ` likes_toys ` using the class ` fields.Function ` as shown below.
483+ We pass a callable from which to compute the value. The function must take a
484+ single argument ` obj ` which is the object to be serialized.
485+
486+ ``` py
487+ likes_toys = fields.Function(lambda obj : len (obj.favorite_toys) > 0 , dump_only = True )
488+ ```
489+
490+ The calculation for the ` age ` field doesn't necessarily work as a simple lambda
491+ expression, so we'll implement that field using the class ` fields.Method ` as
492+ shown:
493+
494+ ``` py
495+ age = fields.Method(" calculate_age" , dump_only = True )
496+
497+ def calculate_age (self , obj ):
498+ today = date.today()
499+ return today.year - obj.dob.year - ((today.month, today.day) < (obj.dob.month, obj.dob.day))
500+
501+ ```
502+
503+ Update the code to add the ` likes_toys ` function and ` age ` method fields to the
504+ schema:
505+
506+ ``` py
507+ # lib/custom_field.py
508+
509+ from marshmallow import Schema, fields, post_load
510+ from datetime import date
511+ from pprint import pprint
512+
513+ # model
514+
515+ class Cat :
516+ def __init__ (self , name , dob , favorite_toys = []):
517+ self .name = name
518+ self .dob = dob
519+ self .favorite_toys = favorite_toys
520+
521+ # schema
522+
523+ class CatSchema (Schema ):
524+ name = fields.Str(required = True , error_messages = {" required" : " Name is required." })
525+ dob = fields.Date(format = " %Y-%m-%d " )
526+ favorite_toys = fields.List(fields.Str())
527+ likes_toys = fields.Function(lambda obj : len (obj.favorite_toys) > 0 , dump_only = True )
528+ age = fields.Method(" calculate_age" , dump_only = True )
529+
530+ def calculate_age (self , obj ):
531+ today = date.today()
532+ return today.year - obj.dob.year - ((today.month, today.day) < (obj.dob.month, obj.dob.day))
533+
534+ @post_load
535+ def make_cat (self , data , ** kwargs ):
536+ return Cat(** data)
537+
538+ schema = CatSchema()
539+
540+ # deserialize
541+ cat_1 = schema.load({" name" : " Meowie" , " dob" : " 2020-11-28" , " favorite_toys" : [" ball" , " squeaky mouse" ]})
542+ cat_2 = schema.load({" name" : " Whiskers" , " dob" : " 2015-4-15" , " favorite_toys" : []})
543+
544+ # serialize
545+ pprint(schema.dump(cat_1))
546+ # => {'age': 2,
547+ # => 'dob': '2020-11-28',
548+ # => 'favorite_toys': ['ball', 'squeaky mouse'],
549+ # => 'likes_toys': True,
550+ # => 'name': 'Meowie'}
551+
552+ pprint(schema.dump(cat_2))
553+ # => {'age': 8,
554+ # => 'dob': '2015-04-15',
555+ # => 'favorite_toys': [],
556+ # => 'likes_toys': False,
557+ # => 'name': 'Whiskers'}
558+ ```
559+
560+ Now we see ` age ` and ` likes_toys ` included in the serialized output:
561+
562+ ``` console
563+ {'age': 2,
564+ 'dob': '2020-11-28',
565+ 'favorite_toys': ['ball', 'squeaky mouse'],
566+ 'likes_toys': True,
567+ 'name': 'Meowie'}
568+ {'age': 8,
569+ 'dob': '2015-04-15',
570+ 'favorite_toys': [],
571+ 'likes_toys': False,
572+ 'name': 'Whiskers'}
573+ ```
574+
402575## Conclusion
403576
404577This lesson has covered quite a few concepts involving deserialization:
@@ -417,6 +590,8 @@ This lesson has covered quite a few concepts involving deserialization:
417590 indicates the field should be serialized but not used during deserialization.
418591- By default, a field is defined as ` load_only=False ` and ` dump_only=False ` ,
419592 meaning it will be used for both serialization and deserialization.
593+ - Custom fields can be created using ` fields.Function ` and ` fields.Method `
594+ types.
420595
421596## Solution Code
422597
@@ -566,6 +741,60 @@ pprint(user_schema.dumps(user)) # password is load_only
566741# => '{"name": "Lua", "created_at": "2023-11-11T10:56:55.898190"}'
567742```
568743
744+ ``` py
745+ # lib/custom_field.py
746+
747+ from marshmallow import Schema, fields, post_load
748+ from datetime import date
749+ from pprint import pprint
750+
751+ # model
752+
753+ class Cat :
754+ def __init__ (self , name , dob , favorite_toys = []):
755+ self .name = name
756+ self .dob = dob
757+ self .favorite_toys = favorite_toys
758+
759+ # schema
760+
761+ class CatSchema (Schema ):
762+ name = fields.Str(required = True , error_messages = {" required" : " Name is required." })
763+ dob = fields.Date(format = " %Y-%m-%d " )
764+ favorite_toys = fields.List(fields.Str())
765+ likes_toys = fields.Function(lambda obj : len (obj.favorite_toys) > 0 , dump_only = True )
766+ age = fields.Method(" calculate_age" , dump_only = True )
767+
768+ def calculate_age (self , obj ):
769+ today = date.today()
770+ return today.year - obj.dob.year - ((today.month, today.day) < (obj.dob.month, obj.dob.day))
771+
772+ @post_load
773+ def make_cat (self , data , ** kwargs ):
774+ return Cat(** data)
775+
776+ schema = CatSchema()
777+
778+ # deserialize
779+ cat_1 = schema.load({" name" : " Meowie" , " dob" : " 2020-11-28" , " favorite_toys" : [" ball" , " squeaky mouse" ]})
780+ cat_2 = schema.load({" name" : " Whiskers" , " dob" : " 2015-4-15" , " favorite_toys" : []})
781+
782+ # serialize
783+ pprint(schema.dump(cat_1))
784+ # => {'age': 2,
785+ # => 'dob': '2020-11-28',
786+ # => 'favorite_toys': ['ball', 'squeaky mouse'],
787+ # => 'likes_toys': True,
788+ # => 'name': 'Meowie'}
789+
790+ pprint(schema.dump(cat_2))
791+ # => {'age': 8,
792+ # => 'dob': '2015-04-15',
793+ # => 'favorite_toys': [],
794+ # => 'likes_toys': False,
795+ # => 'name': 'Whiskers'}
796+ ```
797+
569798---
570799
571800## Resources
0 commit comments