# Source code for dataclass_wizard.loaders

    import collections.abc as abc
    from collections import defaultdict, deque, namedtuple
    from dataclasses import is_dataclass, MISSING
    from datetime import datetime, time, date, timedelta
    from decimal import Decimal
    from enum import Enum
    from pathlib import Path
    # noinspection PyUnresolvedReferences,PyProtectedMember
    from typing import (
        Any, Type, Dict, List, Tuple, Iterable, Sequence, Union,
        NamedTupleMeta,
        SupportsFloat, AnyStr, Text, Callable, Optional
    )
    from uuid import UUID

    from .abstractions import AbstractLoader, AbstractParser
    from .bases import BaseLoadHook, AbstractMeta, META
    from .class_helper import (
        dataclass_field_to_load_parser, json_field_to_dataclass_field,
        CLASS_TO_LOAD_FUNC, dataclass_fields, get_meta, is_subclass_safe, dataclass_field_to_json_path,
        dataclass_init_fields, dataclass_field_to_default,
    )
    from .constants import SINGLE_ARG_ALIAS, IDENTITY, CATCH_ALL
    from .decorators import _alias, _single_arg_alias, resolve_alias_func, _identity
    from .errors import (ParseError, MissingFields, UnknownKeysError,
                         MissingData, RecursiveClassError)
    from .loader_selection import fromdict, get_loader
    from .log import LOG
    from .models import Extras, PatternedDT
    from .parsers import *
    from .type_def import (
        ExplicitNull, FrozenKeys, DefFactory, NoneType, JSONObject,
        PyRequired, PyNotRequired,
        M, N, T, E, U, DD, LSQ, NT
    )
    # noinspection PyProtectedMember
    from .utils.dataclass_compat import _set_new_attribute
    from .utils.function_builder import FunctionBuilder
    from .utils.object_path import safe_get
    from .utils.string_conv import to_snake_case
    from .utils.type_conv import (
        as_bool, as_str, as_datetime, as_date, as_time, as_int, as_timedelta
    )
    from .utils.typing_compat import (
        is_literal, is_typed_dict, get_origin, get_args, is_annotated,
        eval_forward_ref_if_needed
    )

    class LoadMixin(AbstractLoader, BaseLoadHook):
        """
        This Mixin class derives its name from the eponymous `json.loads`
        function. Essentially it contains helper methods to convert JSON strings
        (or a Python dictionary object) to a `dataclass` which can often contain
        complex types such as lists, dicts, or even other dataclasses nested
        within it.

        Refer to the :class:`AbstractLoader` class for documentation on any of the
        implemented methods.

        """
        __slots__ = ()

        HOOK_ARITY = 2

        def __init_subclass__(cls, **kwargs):
            super().__init_subclass__()
            setup_default_loader(cls)

        @staticmethod
        @_alias(to_snake_case)
        def transform_json_field(string: str) -> str:
            # alias: to_snake_case
            ...

        @staticmethod
        @_identity
        def default_load_to(o: T, _: Any) -> T:
            # identity: o
            ...

        @staticmethod
        def load_after_type_check(o: Any, base_type: Type[T]) -> T:

            if isinstance(o, base_type):
                return o

            e = ValueError(f'data type is not a {base_type!s}')
            raise ParseError(e, o, base_type, 'load')

        @staticmethod
        @_alias(as_str)
        def load_to_str(o: Union[Text, N, None], base_type: Type[str]) -> str:
            # alias: as_str
            ...

        @staticmethod
        @_alias(as_int)
        def load_to_int(o: Union[str, int, bool, None], base_type: Type[N]) -> N:
            # alias: as_int
            ...

        @staticmethod
        @_single_arg_alias('base_type')
        def load_to_float(o: Union[SupportsFloat, str], base_type: Type[N]) -> N:
            # alias: base_type(o)
            ...

        @staticmethod
        @_single_arg_alias(as_bool)
        def load_to_bool(o: Union[str, bool, N], _: Type[bool]) -> bool:
            # alias: as_bool(o)
            ...

        @staticmethod
        @_single_arg_alias('base_type')
        def load_to_enum(o: Union[AnyStr, N], base_type: Type[E]) -> E:
            # alias: base_type(o)
            ...

        @staticmethod
        @_single_arg_alias('base_type')
        def load_to_uuid(o: Union[AnyStr, U], base_type: Type[U]) -> U:
            # alias: base_type(o)
            ...

        @staticmethod
        def load_to_iterable(
                o: Iterable, base_type: Type[LSQ],
                elem_parser: AbstractParser) -> LSQ:

            return base_type([elem_parser(elem) for elem in o])

        @staticmethod
        def load_to_tuple(
                o: Union[List, Tuple], base_type: Type[Tuple],
                elem_parsers: Sequence[AbstractParser]) -> Tuple:

            try:
                zipped = zip(elem_parsers, o)
            except TypeError:
                return base_type([e for e in o])
            else:
                return base_type([parser(e) for parser, e in zipped])

        @staticmethod
        def load_to_named_tuple(
                o: Union[Dict, List, Tuple], base_type: Type[NT],
                field_to_parser: 'FieldToParser',
                field_parsers: List[AbstractParser]) -> NT:

            if isinstance(o, dict):
                # Convert the values of all fields in the NamedTuple, using
                # their type annotations. The keys in a dictionary object
                # (assuming it was loaded from JSON) are required to be
                # strings, so we don't need to convert them.
                return base_type(
                    **{k: field_to_parser[k](o[k]) for k in o})
            # We're passed in a list or a tuple.
            return base_type(
                *[parser(elem) for parser, elem in zip(field_parsers, o)])

        @staticmethod
        def load_to_named_tuple_untyped(
                o: Union[Dict, List, Tuple], base_type: Type[NT],
                dict_parser: AbstractParser, list_parser: AbstractParser) -> NT:

            if isinstance(o, dict):
                return base_type(**dict_parser(o))
            # We're passed in a list or a tuple.
            return base_type(*list_parser(o))

        @staticmethod
        def load_to_dict(
                o: Dict, base_type: Type[M],
                key_parser: AbstractParser,
                val_parser: AbstractParser) -> M:

            return base_type(
                (key_parser(k), val_parser(v))
                for k, v in o.items()
            )

        @staticmethod
        def load_to_defaultdict(
                o: Dict, base_type: Type[DD],
                default_factory: DefFactory,
                key_parser: AbstractParser,
                val_parser: AbstractParser) -> DD:

            return base_type(
                default_factory,
                {key_parser(k): val_parser(v)
                 for k, v in o.items()}
            )

        @staticmethod
        def load_to_typed_dict(
                o: Dict, base_type: Type[M],
                key_to_parser: 'FieldToParser',
                required_keys: FrozenKeys,
                optional_keys: FrozenKeys) -> M:

            kwargs = {}

            # Set required keys for the `TypedDict`
            for k in required_keys:
                kwargs[k] = key_to_parser[k](o[k])

            # Set optional keys for the `TypedDict` (if they exist)
            for k in optional_keys:
                if k in o:
                    kwargs[k] = key_to_parser[k](o[k])

            return base_type(**kwargs)

        @staticmethod
        def load_to_decimal(o: N, base_type: Type[Decimal]) -> Decimal:

            return base_type(str(o))

        @staticmethod
        def load_to_path(o: N, base_type: Type[Path]) -> Path:

            return base_type(str(o))

        @staticmethod
        @_alias(as_datetime)
        def load_to_datetime(
                o: Union[str, N], base_type: Type[datetime]) -> datetime:
            # alias: as_datetime
            ...

        @staticmethod
        @_alias(as_time)
        def load_to_time(o: str, base_type: Type[time]) -> time:
            # alias: as_time
            ...

        @staticmethod
        @_alias(as_date)
        def load_to_date(o: Union[str, N], base_type: Type[date]) -> date:
            # alias: as_date
            ...

        @staticmethod
        @_alias(as_timedelta)
        def load_to_timedelta(
                o: Union[str, N], base_type: Type[timedelta]) -> timedelta:
            # alias: as_timedelta
            ...

        @staticmethod
        def load_func_for_dataclass(
            cls: Type[T],
            config: Optional[META],
        ) -> Callable[[JSONObject], T]:

            return load_func_for_dataclass(
                cls, is_main_class=False, config=config)

        @classmethod
        def get_parser_for_annotation(cls, ann_type: Type[T],
                                      base_cls: Type = None,
                                      extras: Extras = None) -> 'AbstractParser | Callable[[dict[str, Any]], T]':
            """Returns the Parser (dispatcher) for a given annotation type."""
            hooks = cls.__LOAD_HOOKS__
            ann_type = eval_forward_ref_if_needed(ann_type, base_cls)
            load_hook = hooks.get(ann_type)
            base_type = ann_type

            # TODO: I'll need to refactor the code below to remove the nested `if`
            #   statements, when time allows. Right now the branching logic is
            #   unseemly and there's really no need for that, as any such
            #   performance gains (if they do exist) are minimal at best.

            if 'pattern' in extras and is_subclass_safe(
                    ann_type, (date, time, datetime)):
                # Check for a field that was initially annotated like:
                #   Annotated[List[time], Pattern('%H:%M:%S')]
                return PatternedDTParser(base_cls, extras, base_type)

            if load_hook is None:
                # Need to check this first, because the `Literal` type in Python
                # 3.6 behaves a bit differently (doesn't have an `__origin__`
                # attribute for example)
                if is_literal(ann_type):
                    return LiteralParser(base_cls, extras, ann_type)

                if is_annotated(ann_type):
                    # Given `Annotated[T, MaxValue(10), ...]`, we only need `T`
                    ann_type = get_args(ann_type)[0]
                    return cls.get_parser_for_annotation(
                        ann_type, base_cls, extras)

                # This property will be available for most generic types in the
                # `typing` library.
                try:
                    base_type = get_origin(ann_type, raise_=True)

                # If we can't access this property, it's likely a non-generic
                # class or a non-generic sub-type.
                except AttributeError:

                    # https://stackoverflow.com/questions/76520264/dataclasswizard-after-upgrading-to-python3-11-is-not-working-as-expected
                    if base_type is Any:
                        load_hook = cls.default_load_to

                    elif isinstance(base_type, type):

                        if is_dataclass(base_type):
                            config: META = extras.get('config')

                            # enable support for cyclic / self-referential dataclasses
                            # see https://github.com/rnag/dataclass-wizard/issues/62
                            if AbstractMeta.recursive_classes or (config and config.recursive_classes):
                                # noinspection PyTypeChecker
                                return RecursionSafeParser(
                                    base_cls, extras, base_type, hook=None
                                )
                            else:  # else, logic is same as normal
                                base_type: 'type[T]'
                                # return a dynamically generated `fromdict`
                                # for the `cls` (base_type)
                                return cls.load_func_for_dataclass(
                                    base_type,
                                    config=extras['config']
                                )

                        elif issubclass(base_type, Enum):
                            load_hook = hooks.get(Enum)

                        elif issubclass(base_type, UUID):
                            load_hook = hooks.get(UUID)

                        elif issubclass(base_type, tuple) \
                                and hasattr(base_type, '_fields'):

                            if getattr(base_type, '__annotations__', None):
                                # Annotated as a `typing.NamedTuple` subtype
                                load_hook = hooks.get(NamedTupleMeta)
                                return NamedTupleParser(
                                    base_cls, extras, base_type, load_hook,
                                    cls.get_parser_for_annotation
                                )
                            else:
                                # Annotated as a `collections.namedtuple` subtype
                                load_hook = hooks.get(namedtuple)
                                return NamedTupleUntypedParser(
                                    base_cls, extras, base_type, load_hook,
                                    cls.get_parser_for_annotation
                                )

                        elif is_typed_dict(base_type):
                            load_hook = cls.load_to_typed_dict
                            return TypedDictParser(
                                base_cls, extras, base_type, load_hook,
                                cls.get_parser_for_annotation
                            )

                    elif isinstance(base_type, PatternedDT):
                        # Check for a field that was initially annotated like:
                        #   DateTimePattern('%m/%d/%y %H:%M:%S')]
                        return PatternedDTParser(base_cls, extras, base_type)

                    elif base_type is Ellipsis:
                        load_hook = cls.default_load_to

                    # If we can't find the underlying type of the object, we
                    # should emit a warning for awareness.
                    else:
                        load_hook = cls.default_load_to
                        LOG.warning('Using default loader, type=%r', ann_type)

                # Else, it's annotated with a generic type like Union or List -
                # basically anything that's subscriptable.
                else:
                    if base_type is Union:
                        # Get the subscripted values
                        #   ex. `Union[int, str]` -> (int, str)
                        base_types = get_args(ann_type)

                        if not base_types:
                            # Annotated as just `Union` (no subscripted types)
                            load_hook = cls.default_load_to

                        elif NoneType in base_types and len(base_types) == 2:
                            # Special case for Optional[x], which is actually Union[x, None]
                            return OptionalParser(
                                base_cls, extras, base_types[0],
                                cls.get_parser_for_annotation
                            )

                        else:
                            return UnionParser(
                                base_cls, extras, base_types,
                                cls.get_parser_for_annotation
                            )

                    elif base_type in (PyRequired, PyNotRequired):
                        # Given `Required[T]` or `NotRequired[T]`, we only need `T`
                        ann_type = get_args(ann_type)[0]
                        return cls.get_parser_for_annotation(
                            ann_type, base_cls, extras)

                    elif issubclass(base_type, defaultdict):
                        load_hook = hooks[defaultdict]
                        return DefaultDictParser(
                            base_cls, extras, ann_type, load_hook,
                            cls.get_parser_for_annotation
                        )

                    elif issubclass(base_type, dict):
                        load_hook = hooks[dict]
                        return MappingParser(
                            base_cls, extras, ann_type, load_hook,
                            cls.get_parser_for_annotation
                        )

                    elif issubclass(base_type, LSQ.__constraints__):
                        load_hook = cls.load_to_iterable
                        return IterableParser(
                            base_cls, extras, ann_type, load_hook,
                            cls.get_parser_for_annotation
                        )

                    elif issubclass(base_type, tuple):
                        load_hook = hooks[tuple]
                        # Check if the `Tuple` appears in the variadic form
                        #   i.e. Tuple[str, ...]
                        args = get_args(ann_type)
                        is_variadic = args and args[-1] is ...
                        # Determine the parser for the annotation
                        parser: Type[AbstractParser] = TupleParser
                        if is_variadic:
                            parser = VariadicTupleParser

                        return parser(
                            base_cls, extras, ann_type, load_hook,
                            cls.get_parser_for_annotation
                        )

                    elif base_type in (abc.Sequence, abc.MutableSequence, abc.Collection):
                        load_hook = cls.load_to_iterable
                        # desired (non-generic) origin type
                        desired_type = tuple if base_type is abc.Sequence else list
                        # Re-map to desired type, e.g. `Sequence[int]` -> `tuple[int]`
                        ann_type = desired_type[ann_type] if (
                            ann_type := get_args(ann_type)[0]) else desired_type

                        return IterableParser(
                            base_cls, extras, ann_type, load_hook,
                            cls.get_parser_for_annotation
                        )

                    else:
                        load_hook = hooks.get(base_type)

            # TODO i'll need to refactor this to remove duplicate lines above -
            # maybe merge them together.
            elif issubclass(base_type, dict):
                load_hook = hooks[dict]
                return MappingParser(
                    base_cls, extras, ann_type, load_hook,
                    cls.get_parser_for_annotation)

            elif issubclass(base_type, LSQ.__constraints__):
                load_hook = cls.load_to_iterable
                return IterableParser(
                    base_cls, extras, ann_type, load_hook,
                    cls.get_parser_for_annotation)

            elif issubclass(base_type, tuple):
                load_hook = hooks[tuple]
                return TupleParser(
                    base_cls, extras, ann_type, load_hook,
                    cls.get_parser_for_annotation)

            if load_hook is None:
                # If load hook is still not resolved at this point, it's possible
                # the type is a subclass of a known type.
                for typ in hooks:
                    # TODO use a `is_subclass_safe` helper function instead
                    try:
                        if issubclass(base_type, typ):
                            load_hook = hooks[typ]
                            break
                    except TypeError:
                        continue

                else:
                    # No matching hook is found for the type.
                    err = TypeError('Provided type is not currently supported.')
                    raise ParseError(
                        err, None, base_type, 'load',
                        unsupported_type=base_type
                    )

            if hasattr(load_hook, SINGLE_ARG_ALIAS):
                load_hook = resolve_alias_func(load_hook, locals())
                return SingleArgParser(base_cls, extras, base_type, load_hook)

            if hasattr(load_hook, IDENTITY):
                return IdentityParser(base_type, extras, base_type)

            return Parser(base_cls, extras, base_type, load_hook)

    def setup_default_loader(cls=LoadMixin):
        """
        Setup the default type hooks to use when converting `str` (json) or a
        Python `dict` object to a `dataclass` instance.

        Note: `cls` must be :class:`LoadMixIn` or a sub-class of it.
        """
        # Simple types
        cls.register_load_hook(str, cls.load_to_str)
        cls.register_load_hook(int, cls.load_to_int)
        cls.register_load_hook(float, cls.load_to_float)
        cls.register_load_hook(bool, cls.load_to_bool)
        cls.register_load_hook(bytes, cls.load_after_type_check)
        cls.register_load_hook(bytearray, cls.load_after_type_check)
        cls.register_load_hook(NoneType, cls.default_load_to)
        # Complex types
        cls.register_load_hook(Enum, cls.load_to_enum)
        cls.register_load_hook(UUID, cls.load_to_uuid)
        cls.register_load_hook(set, cls.load_to_iterable)
        cls.register_load_hook(frozenset, cls.load_to_iterable)
        cls.register_load_hook(deque, cls.load_to_iterable)
        cls.register_load_hook(list, cls.load_to_iterable)
        cls.register_load_hook(tuple, cls.load_to_tuple)
        # noinspection PyTypeChecker
        cls.register_load_hook(namedtuple, cls.load_to_named_tuple_untyped)
        cls.register_load_hook(NamedTupleMeta, cls.load_to_named_tuple)
        cls.register_load_hook(defaultdict, cls.load_to_defaultdict)
        cls.register_load_hook(dict, cls.load_to_dict)
        cls.register_load_hook(Decimal, cls.load_to_decimal)
        cls.register_load_hook(Path, cls.load_to_path)
        # Dates and times
        cls.register_load_hook(datetime, cls.load_to_datetime)
        cls.register_load_hook(time, cls.load_to_time)
        cls.register_load_hook(date, cls.load_to_date)
        cls.register_load_hook(timedelta, cls.load_to_timedelta)

    def load_func_for_dataclass(
            cls: Type[T],
            is_main_class: bool = True,
            config: Optional[META] = None,
            loader_cls=LoadMixin,
    ) -> Callable[[JSONObject], T]:

        # TODO dynamically generate for multiple nested classes at once

        # Tuple describing the fields of this dataclass.
        cls_fields = dataclass_fields(cls)

        # Get the loader for the class, or create a new one as needed.
        cls_loader = get_loader(cls, base_cls=loader_cls, v1=False)

        # Get the meta config for the class, or the default config otherwise.
        meta = get_meta(cls)

        if is_main_class:  # we are being run for the main dataclass
            # If the `recursive` flag is enabled and a Meta config is provided,
            # apply the Meta recursively to any nested classes.
            if meta.recursive and meta is not AbstractMeta:
                config = meta

        # we are being run for a nested dataclass
        elif config:
            # we want to apply the meta config from the main dataclass
            # recursively.
            meta = meta | config
            meta.bind_to(cls, is_default=False)

        # This contains a mapping of the original field name to the parser for its
        # annotated type; the item lookup *can* be case-insensitive.
        try:
            field_to_parser = dataclass_field_to_load_parser(cls_loader, cls, config)
        except RecursionError:
            if meta.recursive_classes:
                # recursion-safe loader is already in use; something else must have gone wrong
                raise
            else:
                raise RecursiveClassError(cls) from None

        # A cached mapping of each key in a JSON or dictionary object to the
        # resolved dataclass field name; useful so we don't need to do a case
        # transformation (via regex) each time.
        json_to_field = json_field_to_dataclass_field(cls)

        field_to_path = dataclass_field_to_json_path(cls)
        num_paths = len(field_to_path)
        has_json_paths = True if num_paths else False

        catch_all_field = json_to_field.get(CATCH_ALL)
        has_catch_all = catch_all_field is not None

        # Fix for using `auto_assign_tags` and `raise_on_unknown_json_key` together
        # See https://github.com/rnag/dataclass-wizard/issues/137
        has_tag_assigned = meta.tag is not None
        if (has_tag_assigned and
                # Ensure `tag_key` isn't a dataclass field before assigning an
                # `ExplicitNull`, as assigning it directly can cause issues.
                # See https://github.com/rnag/dataclass-wizard/issues/148
                meta.tag_key not in field_to_parser):
            json_to_field[meta.tag_key] = ExplicitNull

        _locals = {
            'cls': cls,
            'py_case': cls_loader.transform_json_field,
            'field_to_parser': field_to_parser,
            'json_to_field': json_to_field,
            'ExplicitNull': ExplicitNull,
        }

        _globals = {
            'cls_fields': cls_fields,
            'LOG': LOG,
            'MissingData': MissingData,
            'MissingFields': MissingFields,
        }

        # Initialize the FuncBuilder
        fn_gen = FunctionBuilder()

        if has_json_paths:
            loop_over_o = num_paths != len(dataclass_init_fields(cls))
            _locals['safe_get'] = safe_get
        else:
            loop_over_o = True

        with fn_gen.function('cls_fromdict', ['o'], MISSING, _locals):

            _pre_from_dict_method = getattr(cls, '_pre_from_dict', None)
            if _pre_from_dict_method is not None:
                _locals['__pre_from_dict__'] = _pre_from_dict_method
                fn_gen.add_line('o = __pre_from_dict__(o)')

            # Need to create a separate dictionary to copy over the constructor
            # args, as we don't want to mutate the original dictionary object.
            fn_gen.add_line('init_kwargs = {}')
            if has_catch_all:
                fn_gen.add_line('catch_all = {}')

            if has_json_paths:

                with fn_gen.try_():
                    field_to_default = dataclass_field_to_default(cls)
                    for field, path in field_to_path.items():
                        if field in field_to_default:
                            default_value = f'_default_{field}'
                            _locals[default_value] = field_to_default[field]
                            extra_args = f', {default_value}'
                        else:
                            extra_args = ''
                        fn_gen.add_line(f'field={field!r}; init_kwargs[field] = field_to_parser[field](safe_get(o, {path!r}{extra_args}))')

                with fn_gen.except_(ParseError, 'e'):
                    # We run into a parsing error while loading the field value;
                    # Add additional info on the Exception object before re-raising it.
                    fn_gen.add_line("e.class_name, e.field_name, e.json_object, e.fields = cls, field, o, cls_fields")
                    fn_gen.add_line("raise")

            if loop_over_o:
                # This try-block is here in case the object `o` is None.
                with fn_gen.try_():
                    # Loop over the dictionary object
                    with fn_gen.for_('json_key in o'):

                        with fn_gen.try_():
                            # Get the resolved dataclass field name
                            fn_gen.add_line("field = json_to_field[json_key]")

                        with fn_gen.except_(KeyError):
                            fn_gen.add_line('# Lookup Field for JSON Key')
                            # Determines the dataclass field which a JSON key should map to.
                            # Note this logic only runs the initial time, i.e. the first time
                            # we encounter the key in a JSON object.
                            #
                            # :raises UnknownKeysError: If there is no resolved field name for the
                            #   JSON key, and`raise_on_unknown_json_key` is enabled in the Meta
                            #   config for the class.

                            # Short path: an identical-cased field name exists for the JSON key
                            with fn_gen.if_('json_key in field_to_parser'):
                                fn_gen.add_line("field = json_to_field[json_key] = json_key")

                            with fn_gen.else_():
                                # Transform JSON field name (typically camel-cased) to the
                                # snake-cased variant which is convention in Python.
                                fn_gen.add_line("py_field = py_case(json_key)")

                                with fn_gen.try_():
                                    # Do a case-insensitive lookup of the dataclass field, and
                                    # cache the mapping, so we have it for next time
                                    fn_gen.add_line("field "
                                                    "= json_to_field[json_key] "
                                                    "= field_to_parser.get_key(py_field)")

                                with fn_gen.except_(KeyError):
                                    # Else, we see an unknown field in the dictionary object
                                    fn_gen.add_line("field = json_to_field[json_key] = ExplicitNull")
                                    fn_gen.add_line("LOG.warning('JSON field %r missing from dataclass schema, "
                                                    "class=%r, parsed field=%r',json_key,cls,py_field)")

                                    # Raise an error here (if needed)
                                    if meta.raise_on_unknown_json_key:
                                        _globals['UnknownKeysError'] = UnknownKeysError
                                        fn_gen.add_line("raise UnknownKeysError(json_key, o, cls, cls_fields) from None")

                        # Exclude JSON keys that don't map to any fields.
                        with fn_gen.if_('field is not ExplicitNull'):

                            with fn_gen.try_():
                                # Note: pass the original cased field to the class constructor;
                                # don't use the lowercase result from `py_case`
                                fn_gen.add_line("init_kwargs[field] = field_to_parser[field](o[json_key])")

                            with fn_gen.except_(ParseError, 'e'):
                                # We run into a parsing error while loading the field value;
                                # Add additional info on the Exception object before re-raising it.
                                #
                                # First confirm these values are not already set by an
                                # inner dataclass. If so, it likely makes it easier to
                                # debug the cause. Note that this should already be
                                # handled by the `setter` methods.
                                fn_gen.add_line("e.class_name, e.field_name, e.json_object = cls, field, o")
                                fn_gen.add_line("raise")

                        if has_catch_all:
                            line = 'catch_all[json_key] = o[json_key]'
                            if has_tag_assigned:
                                with fn_gen.elif_(f'json_key != {meta.tag_key!r}'):
                                    fn_gen.add_line(line)
                            else:
                                with fn_gen.else_():
                                    fn_gen.add_line(line)

                with fn_gen.except_(TypeError):
                    # If the object `o` is None, then raise an error with
                    # the relevant info included.
                    with fn_gen.if_('o is None'):
                        fn_gen.add_line("raise MissingData(cls) from None")

                    # Check if the object `o` is some other type than what we expect -
                    # for example, we could be passed in a `list` type instead.
                    with fn_gen.if_('not isinstance(o, dict)'):
                        fn_gen.add_line("e = TypeError('Incorrect type for field')")
                        fn_gen.add_line("raise ParseError(e, o, dict, 'load', cls, desired_type=dict) from None")

                    # Else, just re-raise the error.
                    fn_gen.add_line("raise")

            if has_catch_all:
                if catch_all_field.endswith('?'):  # Default value
                    with fn_gen.if_('catch_all'):
                        fn_gen.add_line(f'init_kwargs[{catch_all_field.rstrip("?")!r}] = catch_all')
                else:
                    fn_gen.add_line(f'init_kwargs[{catch_all_field!r}] = catch_all')

            # Now pass the arguments to the constructor method, and return
            # the new dataclass instance. If there are any missing fields,
            # we raise them here.

            with fn_gen.try_():
                fn_gen.add_line("return cls(**init_kwargs)")

            with fn_gen.except_(TypeError, 'e'):
                fn_gen.add_line("raise MissingFields(e, o, cls, cls_fields, init_kwargs) from None")

        functions = fn_gen.create_functions(_globals)

        cls_fromdict = functions['cls_fromdict']

        # Save the load function for the main dataclass, so we don't need to run
        # this logic each time.
        if is_main_class:
            # Check if the class has a `from_dict`, and it's
            # a class method bound to `fromdict`.
            if ((from_dict := getattr(cls, 'from_dict', None)) is not None
                    and getattr(from_dict, '__func__', None) is fromdict):
                _set_new_attribute(cls, 'from_dict', cls_fromdict, force=True)
            CLASS_TO_LOAD_FUNC[cls] = cls_fromdict

        return cls_fromdict