Estoy usando la dataclass para analizar (solicitud/respuesta HTTP) objetos JSON y hoy encontré un problema que requiere nombres de atributo de transformación/alias dentro de mis clases.
from dataclasses import dataclass, asdict from typing import List import json @dataclass class Foo: foo_name: str # foo_name -> FOO NAME @dataclass class Bar: bar_name: str # bar_name -> barName @dataclass class Baz: baz_name: str # baz_name -> BAZ baz_foo: List[Foo] # baz_foo -> BAZ FOO baz_bar: List[Bar] # baz_bar -> BAZ BARactualmente:
# encode baz_e = Baz("name", [{"foo_name": "one"}, {"foo_name": "two"}], [{"bar_name": "first"}]) json_baz_e = json.dumps(asdict(baz_e)) print(json_baz_e) # {"baz_name": "name", "baz_foo": [{"foo_name": "one"}, {"foo_name": "two"}], "baz_bar": [{"bar_name": "first"}]} # decode json_baz_d = { "baz_name": "name", "baz_foo": [{"foo_name": "one"}, {"foo_name": "two"}], "baz_bar":[{"bar_name": "first"}] } baz_d = Baz(**json_baz_d) # back to class instance print(baz_d) # Baz(baz_name='name', baz_foo=[{'foo_name': 'one'}, {'foo_name': 'two'}], baz_bar=[{'bar_name': 'first'}])previsto:
# encode baz_e = Baz("name", [{"FOO NAME": "one"}, {"FOO NAME": "two"}], [{"barName": "first"}]) json_baz_e = json.dumps(asdict(baz_e)) # decode json_baz_d = { "BA Z": "name", "BAZ FOO": [{"FOO NAME": "one"}, {"FOO NAME": "two"}], "BAZ BAR":[{"barName": "first"}] } baz_d = Baz(**json_baz_d) # back to class instance¿Es la única solución dataclasses-json , o todavía existe la posibilidad sin bibliotecas adicionales?
Ciertamente podría usar dataclasses-json para esto, sin embargo, si no necesita la ventaja de los esquemas de malvavisco , probablemente pueda arreglárselas con una solución alternativa como el dataclass-wizard , que es similarmente una biblioteca de serialización JSON construida sobre clases de datos . Admite asignaciones de campos de alias según sea necesario aquí; otra ventaja es que no tiene ninguna dependencia fuera de Python stdlib, aparte del módulo de typing-extensions para Python <3.10.
Hay algunas opciones disponibles para especificar asignaciones de campos de alias , pero en el siguiente ejemplo, elegí dos opciones para ilustrar:
json_field , que puede considerarse un alias de dataclasses.fieldjson_key_to_field que se puede especificar en la configuración Meta para una clase de datos from dataclasses import dataclass from typing import List from dataclass_wizard import JSONWizard, json_field @dataclass class Foo: # pass all=True, so reverse mapping (field -> JSON) is also added foo_name: str = json_field('FOO NAME', all=True) @dataclass class Bar: # default key transform is `camelCase`, so alias is not needed here bar_name: str @dataclass class Baz(JSONWizard): class _(JSONWizard.Meta): json_key_to_field = { # Pass '__all__', so reverse mapping (field -> JSON) is also added '__all__': True, 'BA Z': 'baz_name', 'BAZ FOO': 'baz_foo', 'BAZ BAR': 'baz_bar' } baz_name: str baz_foo: List[Foo] baz_bar: List[Bar] # encode baz_e = Baz("name", [Foo('one'), Foo('two')], [Bar('first')]) json_baz_d = baz_e.to_dict() print(json_baz_d) # {'BA Z': 'name', 'BAZ FOO': [{'FOO NAME': 'one'}, {'FOO NAME': 'two'}], 'BAZ BAR': [{'barName': 'first'}]} # decode baz_d = Baz.from_dict(json_baz_d) # back to class instance print(repr(baz_d)) # > Baz(baz_name='name', baz_foo=[Foo(foo_name='one'), Foo(foo_name='two')], baz_bar=[Bar(bar_name='first')]) # True assert baz_e == baz_d NB : noté una cosa obvia que quería señalar, ya que parecía no dar como resultado el comportamiento esperado. En la pregunta anterior, parece que está creando una instancia de Baz de la siguiente manera:
baz_e = Baz("name", [{"foo_name": "one"}, {"foo_name": "two"}], [{"bar_name": "first"}]) Sin embargo, tenga en cuenta que el valor del campo baz_foo , en este caso, es una lista de objetos dict de Python en lugar de una lista de instancias de Foo . Para solucionar esto, en la solución anterior cambié {"foo_name": "one"} por ejemplo a Foo('one') .