14 KiB
The Null Value
colander.null
is a
sentinel value which may be passed to colander.SchemaNode.serialize
during serialization or
to colander.SchemaNode.deserialize
during
deserialization.
During serialization, the use of colander.null
indicates that the appstruct
value
corresponding to the node it's passed to is missing and the value of the
default
attribute of the corresponding node should be used
instead.
During deserialization, the use of colander.null
indicates that the cstruct
value corresponding
to the node it's passed to is missing, and if possible, the value of the
missing
attribute of the corresponding node should be used
instead.
Note that colander.null
has no relationship to the built-in
Python None
value. colander.null
is used
instead of None
because None
is a potentially
valid value for some serializations and deserializations, and using it
as a sentinel would prevent None
from being used in this
way.
Serializing The Null Value
A node will attempt to serialize its default value during
colander.SchemaNode.serialize
if the value it is
passed as an appstruct
argument is the colander.null
sentinel
value.
The default value of a node is specified during schema
creation as its default
attribute / argument. For example,
the hair_color
node below has a default value of
brown
:
import colander
class Person(colander.MappingSchema):
= colander.SchemaNode(colander.String())
name = colander.SchemaNode(colander.Int(),
age =colander.Range(0, 200))
validator= colander.SchemaNode(colander.String(), default='brown') hair_color
Because the hair_color
node is passed a
default
value, if the above schema is used to serialize a
mapping that does not have a hair_color
key, the default
will be serialized:
= Person()
schema = schema.serialize({'name':'Fred', 'age':20}) serialized
Even though we did not include the hair_color
attribute
in the appstruct we fed to serialize
, the value of
serialized
above will be
{'name':'Fred, 'age':'20', 'hair_color':'brown'}
. This is
because a default
value of brown
was provided
during schema node construction for hair_color
.
The same outcome would have been true had we fed the schema a mapping
for serialization which had the colander.null
sentinel as the hair_color
value:
import colander
= Person()
schema = schema.serialize({'name':'Fred', 'age':20,
serialized 'hair_color':colander.null})
When the above is run, the value of serialized
will be
{'name':'Fred, 'age':'20', 'hair_color':'brown'}
just as it
was in the example where hair_color
was not present in the
mapping.
As we can see, serializations may be done of partial data structures;
the colander.null
value is inserted into the serialization whenever a corresponding value
in the data structure being serialized is missing.
Note
The injection of the colander.null
value into a serialization when a
default doesn't exist for the corresponding node is not a behavior
shared during both serialization and deserialization. While a
serialization can be performed against a partial data structure
without corresponding node defaults, a deserialization cannot
be done to partial data without corresponding node missing
values. When a value is missing from a data structure being
deserialized, and no missing
value exists for the node
corresponding to the missing item in the data structure, a colander.Invalid
exception
will be the result.
If, during serialization, a value for the node is missing from the
cstruct and the node does not possess an explicit default
value, the colander.null
sentinel value is passed to the type's
serialize
method directly, instructing the type to
serialize a type-specific null value.
Serialization of a null value is completely type-specific, meaning
each type is free to serialize colander.null
to a value that makes sense for that
particular type. For example, the null serialization value of a colander.String
type is the
empty string.
For example:
import colander
class Person(colander.MappingSchema):
= colander.SchemaNode(colander.String())
name = colander.SchemaNode(colander.Int(),
age =colander.Range(0, 200))
validator= colander.SchemaNode(colander.String())
hair_color
= Person()
schema = schema.serialize({'name':'Fred', 'age':20}) serialized
In the above example, the hair_color
value is missing
and the schema does not name a default
value for
hair_color
. However, when we attempt to serialize the data
structure, an error is not raised. Instead, the value for
serialized
above will be
{'name':'Fred, 'age':'20', 'hair_color':colander.null}
.
Because we did not include the hair_color
attribute in
the data we fed to serialize
, and there was no
default
value associated with hair_color
to
fall back to, the colander.null
value is passed as the
appstruct
value to the serialize
method of the
underlying type (colander.String
). The return value of that type's
serialize
method when colander.null
is passed as the appstruct
is placed into the serialization. colander.String
happens to return colander.null
when it is
passed colander.null
as its appstruct argument, so this is what winds up in the resulting
cstruct.
The colander.null
value will be passed to a type either directly or indirectly:
- directly: because
colander.null
is passed directly to theserialize
method of a node. - indirectly: because every schema node uses a
colander.null
value as itsdefault
attribute when no explicit default is provided.
When a particular type cannot serialize the null value to anything
sensible, that type's serialize
method must return the null
object itself as a serialization. For example, when the colander.Boolean
type is
asked to serialize the colander.null
value, its serialize
method simply returns the colander.null
value (because null is conceptually
neither true nor false).
Therefore, when colander.null
is used as input to serialization, or
as the default value of a schema node, it is possible that the colander.null
value will
placed into the serialized data structure. The consumer of the
serialization must anticipate this and deal with the special colander.null
value in the
output however it sees fit.
Serialization Combinations
Within this table, the Value
column represents the value
passed to the colander.SchemaNode.serialize
method of a particular
schema node, the Default
column represents the
default
value of that schema node, and the
Result
column is a description of the result of invoking
the colander.SchemaNode.serialize
method of the schema
node with the effective value.
Value | Default | Result |
---|---|---|
colander.null | value | value serialized |
<missing> | value | value serialized |
colander.null | colander.null | null serialized |
<missing> | colander.null | null serialized |
value | <missing> | value serialized |
value_a | value_b | value_a serialized |
value | colander.null | value serialized |
colander.null | <missing> | null serialized |
colander.null | value | null serialized |
Note
<missing>
in the above table represents the
circumstance in which a key present in a colander.MappingSchema
is not present in a mapping
passed to its colander.SchemaNode.serialize
method. In reality,
<missing>
means exactly the same thing as colanderr.null
, because the
colander.Mapping
type
does the equivalent of mapping.get(keyname, colander.null)
to find a subvalue during serialization.
Deserializing The Null Value
The data structure passed to colander.SchemaNode.deserialize
may contain one or
more colander.null
sentinel markers.
When a colander.null
sentinel marker is passed to the colander.SchemaNode.deserialize
method of a
particular node in a schema, the node will take the following steps:
- The type object's
deserialize
method will be called with the null value to allow the type to convert the null value to a type-specific default. The resulting "appstruct" is used instead of the value passed directly tocolander.SchemaNode.deserialize
in subsequent operations. Most types, when they receive thenull
value will simply return it, however. - If the appstruct value computed by the type's
deserialize
method iscolander.null
and the schema node has an explicitmissing
attribute (the node's constructor was supplied with an explicitmissing
argument), themissing
value will be returned. Note that when this happens, themissing
value is not validated by any schema node validator: it is simply returned. - If the appstruct value computed by the type's
deserialize
method iscolander.null
and the schema node does not have an explicitly providedmissing
attribute (the node's constructor was not supplied with an explicitmissing
value), acolander.Invalid
exception will be raised with a message indicating that the field is required.
Note
There are differences between serialization and deserialization
involving the colander.null
value. During serialization, if an
colander.null
value is
encountered, and no valid default
attribute exists on the
node related to the value the null value for that node is
returned. Deserialization, however, doesn't use the default
attribute of the node to find a default deserialization value in the
same circumstance; instead it uses the missing
attribute
instead. Also, if, during deserialization, an colander.null
value is
encountered as the value passed to the deserialize method, and no
explicit missing
value exists for the node, a colander.Invalid
exception is
raised (colander.null
is not returned, as it is during serialization).
Here's an example of a deserialization which uses a
missing
value in the schema as a deserialization default
value:
import colander
class Person(colander.MappingSchema):
= colander.SchemaNode(colander.String())
name = colander.SchemaNode(colander.Int(), missing=None)
age
= Person()
schema = schema.deserialize({'name':'Fred', 'age':colander.null}) deserialized
The value for deserialized
above will be
{'name':'Fred, 'age':None}
.
Because the age
schema node is provided a
missing
value of None
, if that schema is used
to deserialize a mapping that has an an age
key of colander.null
, the
missing
value of None
is serialized into the
appstruct output for age
.
Note
Note that None
can be used for the missing
schema node value as required, as in the above example. It's no
different than any other value used as missing
. The empty
string can also be used as the missing
value if that is
helpful.
The colander.null
value is also the default, so it needn't be specified in the cstruct.
Therefore, the deserialized
value of the below is
equivalent to the above's:
import colander
class Person(colander.MappingSchema):
= colander.SchemaNode(colander.String())
name = colander.SchemaNode(colander.Int(), missing=None)
age
= Person()
schema = schema.deserialize({'name':'Fred'}) deserialized
Deserialization Combinations
Within this table, the Value
column represents the value
passed to the colander.SchemaNode.deserialize
method of a
particular schema node, the Missing
column represents the
missing
value of that schema node, and the
Result
column is a description of the result of invoking
the colander.SchemaNode.deserialize
method of the schema
node with the effective value.
Value | Missing | Result |
---|---|---|
colander.null | <missing> | Invalid exception raised |
<missing> | <missing> | Invalid exception raised |
colander.null | value | value used |
<missing> | value | value used |
<missing> | colander.null | colander.null used |
value | <missing> | value used |
value | colander.null | value used |
value_a | value_b | value_a used |
Note
<missing>
in the above table represents the
circumstance in which a key present in a colander.MappingSchema
is not present in a mapping
passed to its colander.SchemaNode.deserialize
method. In reality,
<missing>
means exactly the same thing as colander.null
, because the
colander.Mapping
type
does the equivalent of mapping.get(keyname, colander.null)
to find a subvalue during deserialization.