fleetingbytes
fleetingbytes

Reputation: 2972

Type hint for nested dict

The kind of data structure I parse in my Python script is a json file which, after json.load(file_handle), is of type <class 'dict'>. So far so good. Now for a function using it as an input argument, I want a type hint for the parsed json. I read in the typing documentation, that for dicts as arguments, I should use Mapping[key_type, value_type]:

from typing import Mapping

def foo(json_data: Mapping[str, str]) -> None:
    ...

The json I parse has str-type keys and str-type values, but more often than not, its structure is highly recursive. Hence a value is more likely to be a dict with str keys and even such dicts as values. It is very nested, until, at the deepest level, the last dict finally has str keys and str values.

So how do I represent this data structure more precisely? I was thinking something, along the lines of this question, that it might be:

Union[Mapping[str, str], Mapping[str, Mapping]]

But it does seem to represent only one level of recursion. Is there a better way to type-hint this?

Upvotes: 23

Views: 22495

Answers (3)

Adam Smith
Adam Smith

Reputation: 54193

Nowadays, mypy and other type checkers support recursive definitions. A (somewhat) complete example of a Json type is:

import typing as t

JsonType: t.TypeAlias = t.List['JsonValue'] | t.Mapping[str, 'JsonValue']
JsonValue: t.TypeAlias = str | int | float | None | JsonType

def foo(json_data: JsonType) -> None:
    """Your implementation here"""

This should type check.

Upvotes: 11

Cameron Wood
Cameron Wood

Reputation: 65

I was trying to comment a reply in the above thread but it won't let me without enough reputation (but will let me post an answer).

I would follow along with the answer vivax provided and then add the following for unknown data:

from typing import Mapping, Any

Mapping[str, Any] # or Mapping[Any, Any] if the keys are also unknown

You could also have more granularity by defining your own JSON type

Upvotes: 0

vivax
vivax

Reputation: 486

simple dicts

you can simply use Mapping[K, V] as the value for another Mapping. let's say your json looks like this:

{
   "person_1": {"money": 1000},
   "person_2": {"money": 1000}
}

in this case you can use a type hint that looks like this:

Mapping[str, Mapping[str, int]]

harder dicts

but let's say you have something more complex, like this:

{
    "person_1": {
        "money": 1000,
        "job": "coder",
    }
}

how could you type hint this then? you can use Union (from typing) to do something like this:

Mapping[str, Mapping[str, Union[str, int]]]

but now we would run into a problem with tools like mypy thinking that our_dict["person_1"]["job"] has the type Union[str, int]

well it is not wrong, that is what we told it after all. something that could help here is TypedDict from typing_extensions.

from typing_extensions import TypedDict

class Person(TypedDict):
    money: int
    job: str

# type hint you would use in your function:
Mapping[str, Person]

NOTE: in python 3.8 TypedDict is part of typing

Upvotes: 16

Related Questions