rnag / rnag/dataclass-wizard

Type hint Dict[int | Literal["123"], str] incorectly processed

Open Beginner friendly
#245 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Dominant language
Python
Stars
250
Forks
36
PR merge metrics
No merged PRs in 30d

Description

Following this example:

from dataclass_wizard import DataclassWizard
from typing import Dict, Literal

class Test(DataclassWizard):
    error: Dict[int | Literal["123"], str]
    successful: int | Literal["123"]

Test.from_json('{"error": {"1": "1", "2": "2", "3": "3", "123": "4"}, "successful": "1"}')

we've got this exception:

dataclass_wizard.errors.ParseError: Failed to load field `a` in class `A`. Expected a type (<class 'int'>, typing.Literal['123']), got str.
  phase: load
  value: '1'
  error: Object was not in any of Union types
  tag_key: __tag__
  json_object: {"a": {"1": "1", "2": "2", "3": "3", "123": "4"}, "b": "test"}

but we shouldn't as every key in the Dict is valid

going deeper we can see that the output function is:

def __create__load_Test_union_7f0ed97afcc1_fn__(cls, fields, tag_key, as_int):
 def _load_Test_union_7f0ed97afcc1(k2):
  tp = type(k2)
  if tp is int:
    return k2
  try:
    return _load_literal_093b94741fad(v2)
  except Exception:
    pass
  try:
    return v2 if (t := v2.__class__) is int else int(f if '.' in v2 and (f := float(v2)).is_integer() else v2) if t is str else as_int(v2, t, int)
  except Exception:
    pass
  raise ParseError(TypeError('Object was not in any of Union types'),k2,fields,'load',tag_key=tag_key)
 return _load_Test_union_7f0ed97afcc1

v2 is used instead of k2 in this function only when a Dict contains a Union

possible fix at:
https://github.com/rnag/dataclass-wizard/blob/287fa66740042750ac0e4d07dcd57f31de4f0d48/dataclass_wizard/v1/loaders.py#L570
the line should be tp_new = TypeInfo(possible_tp, i=i, prefix=tp.prefix)
so that the prefix is correctly transmit to the next step

with this fix the output is

def __create__load_Test_union_7f0ed97afcc1_fn__(cls, fields, tag_key, as_int):
 def _load_Test_union_7f0ed97afcc1(k2):
  tp = type(k2)
  if tp is int:
    return k2
  try:
    return _load_literal_093b94741fad(k2)
  except Exception:
    pass
  try:
    return k2 if (t := k2.__class__) is int else int(f if '.' in k2 and (f := float(k2)).is_integer() else k2) if t is str else as_int(k2, t, int)
  except Exception:
    pass
  raise ParseError(TypeError('Object was not in any of Union types'),k2,fields,'load',tag_key=tag_key)
 return _load_Test_union_7f0ed97afcc1

full final test:

from dataclass_wizard import DataclassWizard
from typing import Dict, Literal

class Test(DataclassWizard):
    error: Dict[int | Literal["AAA"], str]
    successful: int | Literal["AAA"]

Test.from_json('{"error": {"1": "1", "2": "2", "3": "3", "AAA": "4"}, "successful": "1"}')
Test.from_json('{"error": {"1": "1", "2": "2", "3": "3", "AAA": "4"}, "successful": "AAA"}')

Test.from_json('{"error": {"1": "1", "2": "2", "3": "3", "BBB": "4"}, "successful": "1"}')
Test.from_json('{"error": {"1": "1", "2": "2", "3": "3", "AAA": "4"}, "successful": "BBB"}')

result:

>>> Test.from_json('{"error": {"1": "1", "2": "2", "3": "3", "AAA": "4"}, "successful": "1"}')
"2": "2", "3": "3", "AAA": "4"}, "successful": "AAA"}')Test(error={1: '1', 2: '2', 3: '3', 'AAA': '4'}, successful=1)
>>> Test.from_json('{"error": {"1": "1", "2": "2", "3": "3", "AAA": "4"}, "successful": "AAA"}')
Test(error={1: '1', 2: '2', 3: '3', 'AAA': '4'}, successful='AAA')
>>> Test.from_json('{"error": {"1": "1", "2": "2", "3": "3", "BBB": "4"}, "successful": "1"}')
dataclass_wizard.errors.ParseError: Failed to load field `error` in class `Test`. Expected a type (<class 'int'>, typing.Literal['AAA']), got str.
  phase: load
  value: 'BBB'
  error: Object was not in any of Union types
  tag_key: __tag__
  json_object: {"error": {"1": "1", "2": "2", "3": "3", "BBB": "4"}, "successful": "1"}
>>> Test.from_json('{"error": {"1": "1", "2": "2", "3": "3", "AAA": "4"}, "successful": "BBB"}')
dataclass_wizard.errors.ParseError: Failed to load field `successful` in class `Test`. Expected a type (<class 'int'>, typing.Literal['AAA']), got str.
  phase: load
  value: 'BBB'
  error: Object was not in any of Union types
  tag_key: __tag__
  json_object: {"error": {"1": "1", "2": "2", "3": "3", "AAA": "4"}, "successful": "BBB"}

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start in dataclass_wizard/v1/loaders.py around line 570 and inspect how TypeInfo prefixes are passed when loading a dictionary key union. Reproduce the provided Dict[int | Literal["AAA"], str] examples, then add a regression test covering accepted integer and literal keys plus rejected values such as "BBB". Done means valid inputs deserialize and invalid keys still raise ParseError.

Written by the indexing model from the issue text.

Assessment

Tech stack
python
Domain
tooling
Issue type
Bug
Difficulty
2/5
Estimated time
1-3 hours
Activity status
Quiet
Clarity
Clearly specified
Newbie friendliness
82/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.