Map a JSON Key to a Field

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 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 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 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 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.

from dataclasses import dataclass, field
from typing_extensions 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:

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 __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 Meta approach

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 dataclasses.Field() subclass

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 json_key() argument

from dataclasses import dataclass
from typing import Union
from typing_extensions import Annotated

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:

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()