1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226
|
Map a JSON Key to a Field
=========================
.. note::
**Important:** The current *key transform* and "custom mappings" functionality is being phased out.
Please refer to the new docs for **V1 Opt-in** features, which introduces enhanced support for these use
cases. For more details, see the `Field Guide to V1 Opt‐in`_ and the `V1 Alias`_ documentation.
This change is part of the ongoing improvements in version ``v0.35.0+``, and the old functionality will no longer be maintained in future releases.
.. _Field Guide to V1 Opt‐in: https://github.com/rnag/dataclass-wizard/wiki/Field-Guide-to-V1-Opt%E2%80%90in
.. _V1 Alias: https://dataclass-wizard.readthedocs.io/en/latest/common_use_cases/v1_alias.html
The ``dataclass-wizard`` library provides a set of built-in *key transform* helper
functions that automatically transform the casing of keys in a JSON or Python
``dict`` object to and from dataclass field names. As mentioned in the
:doc:`Meta <meta>` section, this key transform only applies to dataclasses
at present, not to keys in ``dict`` objects or to sub-classes of
`NamedTuple`_ or `TypedDict`_, for example.
When converting a JSON key to a dataclass field name, the key transform function
defaults to :func:`to_snake_case`, which converts all JSON keys to -
*you guessed it!* - `snake case`_, which is the leading convention in Python. Therefore, a JSON key
appearing as *myField*, *MYField*, *MyField*, or *my-field* will all implicitly
be mapped to a dataclass field named ``my_field`` by default. When converting
the dataclass field back to JSON, the default key transform function is
:func:`to_camel_case`, which transforms it back to ``myField`` in this case.
It's also possible to update the key transform functions used, as explained in
the :doc:`Meta <meta>` section.
However, suppose you want to instead create a custom mapping of a JSON key to a
dataclass field name. For example, a key appears in the JSON object as
``myJSONKey`` (case-sensitive), and you want to map it to a dataclass
field that is declared as ``my_str``.
The below example demonstrates how to set up a custom mapping of a JSON key name
to a dataclass field. There a few different options available, so feel free to
choose whichever approach is most preferable. I am myself partial to the last
approach, as I find it to be the most explicit, and also one that plays well
with IDEs in general.
.. note:: The mapping of JSON key to field below is only in *addition* to the
default key transform as mentioned above. For example, ``myNewField`` is already
mapped to a ``my_new_field`` dataclass field, and the inverse is also true.
.. code:: python3
from dataclasses import dataclass, field
from typing import Annotated
from dataclass_wizard import JSONSerializable, json_field, json_key
@dataclass
class MyClass(JSONSerializable):
# 1-- Define a mapping for JSON key to dataclass field in the inner
# `Meta` subclass.
class Meta(JSONSerializable.Meta):
json_key_to_field = {
'myJSONKey': 'my_str'
}
# 2-- Using a sub-class of `Field`. This can be considered as an
# alias to the helper function `dataclasses.field`.
my_str: str = json_field(["myField", "myJSONKey"])
# 3-- Using `Annotated` with a `json_key` (or :class:`JSON`) argument.
my_str: Annotated[str, json_key('myField', 'myJSONKey')]
# 4-- Defining a value for `__remapping__` in the metadata stored
# within a `dataclasses.Field` class.
my_str: str = field(metadata={
'__remapping__': json_key('myField', 'myJSONKey')
})
One thing to note is that the mapping to each JSON key name is case-sensitive,
so passing *myfield* (all lowercase) will not match a *myField* key in a
JSON or Python ``dict`` object.
In either case, you can confirm that the custom key mapping works as expected:
.. code:: python3
def main():
string = """
{"myJSONKey": "hello world!"}
"""
c = MyClass.from_json(string)
print(repr(c))
# prints:
# MyClass(my_str='hello world!')
print(c)
# prints:
# {
# "myStr": "hello world!"
# }
if __name__ == '__main__':
main()
Map a Field Back to a JSON Key
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
By default, the reverse mapping (dataclass field to JSON key) will not
automatically be associated by default.
You can pass the ``all`` parameter (or an :attr:`__all__` key, in the case
of a dictionary) to also associate the inverse mapping, as shown below.
.. note:: If multiple JSON keys are specified for a dataclass field, only
the first one provided will be used to map a field name to a JSON key.
Using the :class:`Meta` approach
--------------------------------
.. code:: python3
from typing import Union
from dataclasses import dataclass
from dataclass_wizard import JSONSerializable
@dataclass
class MyClass(JSONSerializable):
class Meta(JSONSerializable.Meta):
json_key_to_field = {
# Pass `__all__` so the inverse mapping is also added.
'__all__': True,
# If there are multiple JSON keys for a field, the one that is
# first defined is used in the dataclass field to JSON key mapping.
'myJSONKey': 'my_str',
'myField': 'my_str',
'someBoolValue': 'my_bool',
}
my_str: str
my_bool: Union[bool, str]
Using a :func:`dataclasses.Field` subclass
------------------------------------------
.. code:: python3
from typing import Union
from dataclasses import dataclass
from dataclass_wizard import JSONSerializable, json_field
@dataclass
class MyClass(JSONSerializable):
my_str: str = json_field(
('myJSONKey',
'myField'),
# Pass `all` so the inverse mapping is also added.
all=True
)
my_bool: Union[bool, str] = json_field(
'someBoolValue', all=True
)
Using Annotated with a :func:`json_key` argument
------------------------------------------------
.. code:: python3
from dataclasses import dataclass
from typing import Annotated, Union
from dataclass_wizard import JSONSerializable, json_key
@dataclass
class MyClass(JSONSerializable):
my_str: Annotated[str,
# If there are multiple JSON keys listed for a
# dataclass field, the one that is defined first
# will be used.
json_key('myJSONKey', 'myField', all=True)]
my_bool: Annotated[Union[bool, str],
json_key('someBoolValue', all=True)]
In all the above cases, the custom key mappings apply for both the *load*
and *dump* process, so now the below behavior is observed:
.. code:: python3
def main():
string = """
{"myJSONKey": "hello world!", "someBoolValue": "TRUE"}
"""
c = MyClass.from_json(string)
print(repr(c))
# prints:
# MyClass(my_str='hello world!', my_bool='TRUE')
print(c)
# prints:
# {
# "myJSONKey": "hello world!",
# "someBoolValue": "TRUE"
# }
if __name__ == '__main__':
main()
.. _NamedTuple: https://docs.python.org/3.8/library/typing.html#typing.NamedTuple
.. _TypedDict: https://docs.python.org/3.8/library/typing.html#typing.TypedDict
.. _snake case: https://en.wikipedia.org/wiki/Snake_case
|