2

I'm doing a project to learn more about working with Python dataclasses. Specifically, I'm trying to represent an API response as a dataclass object. However, I'm running into an issue due to how the API response is structured.

Here is an example response from the API:

{
    "@identifier": "example",
    "@name": "John Doe",
}

Some of the fields have special characters in their names. This means I cannot map the attributes of my dataclass directly, since special characters such as @ are not allowed in property names (SyntaxError).

Is there a way to define an alias for my dataclass properties, such that I can map the API response directly to the dataclass object? Or is it necessary to clean the response first?

martineau
  • 119,623
  • 25
  • 170
  • 301
thijsfranck
  • 778
  • 1
  • 10
  • 24
  • 2
    Not a direct answer to your question, but you might consider using the pydantic library which allows you to add aliases to your fields if they would otherwise not be valid – mousetail Apr 08 '21 at 09:53

3 Answers3

8

There is dataclasses-json, which allows you to alias attributes:

from dataclasses import dataclass, field
from dataclasses_json import config, dataclass_json


@dataclass_json
@dataclass
class Person:
    magic_name: str = field(metadata=config(field_name="@name"))
    magic_id: str = field(metadata=config(field_name="@identifier"))


p = Person.from_json('{"@name": "John Doe", "@identifier": "example"}')
print(p.magic_name)
print(p.to_json())

Out:

John Doe
{"@name": "John Doe", "@identifier": "example"}
Maurice Meyer
  • 17,279
  • 4
  • 30
  • 47
0

The fact is that not only attributes of object cannot have special characters in their names, but variables too. In Python just characters a-z, A-Z, 0-9 and _ are allowed in variable names, if you try to do something like this (v@r = 3), as you said, you'll get SyntaxError. Note also that numbers are allowed in variable names, but not at the beginning of a variable:

# Ok
test0 = 'Hello World!'
# SyntaxError
0test = 'Goodbye Moonman!'

I'd suggest changing the names of your attributes, removing special characters such as @ from your dict keys. Because at the end, what would you need to put special characters in attribute names for?

Maybe you could work out some solution using Python object's __getitem__ and __setitem__ magic methods like this:

class Object():
    def __init__(self, props):
        self._props = props
    def __getitem__(self, i):
        return self._props[i]

So you can now work with this:

x = {'Hello': 'World!', '@oodby&': 'Moonman!'}
x = Object(x)

print(x['Hello'])      # World!
print(x['@oodby&'])    # Moonman!

Maybe that's not exactly what you are looking for, though this may have its advantages. For example you can define some class methods to work arround with self._props.

More about __getitem__ here :)

Giuppox
  • 1,393
  • 9
  • 35
  • Thanks. I agree that in a scenario where you have complete control over the structure of the data, cleaning the data at the source (removing the special characters from the dict) is the way to go. However, in my case I'm working with an API where the structure of the data at the source is out of my control. I'm curious whether Python `dataclasses` have an affordance for this scenario (such as a field alias) or whether I need to clean the data first. – thijsfranck Apr 08 '21 at 10:09
  • 1
    @thijsfranck take a look at my recent edit :) – Giuppox Apr 08 '21 at 10:25
  • "In Python just characters a-z, A-Z, 0-9 and _ are allowed in variable names" - this isn't true. Python3 supports quite a wide range of unicode characters (but not `@`) – DavidW Apr 12 '21 at 08:51
0

Add a 'clean' function and wrap the API response dict with it.

The solution requires no external lib.

from dataclasses import dataclass

api_response = {
    "@identifier": "example",
    "@name": "John Doe",
}


def clean(data: dict) -> dict:
    def _clean(key):
        return key[1:]

    result = {}
    for k, v in data.items():
        result[_clean(k)] = v
    return result


@dataclass
class Response:
    name: str
    identifier: str


res = Response(**clean(api_response))
print(res)

output

Response(name='John Doe', identifier='example')
balderman
  • 22,927
  • 7
  • 34
  • 52