简体   繁体   中英

dataclass to json with specific variable name

I have a data structure, I want to turn it into a json. Here my classes:

@dataclass
class RType:
    type_name: str

    class Meta:
        name = '@r_type'
        content = 'type_name'


@dataclass
class Options:
    type: RType
    content: str

    class Meta:
        name = 'options'
        content = 'content'

What I want to get in the result (we always have r_type in nested and normal structures):

{
'@r_type' : 'init',
 'options': {
   '@r_type' : 'options',
   'options' : 'some_options'
}

another example

{
'@r_type' : 'dir_init',
 'directory': {
   '@r_type' : 'directory',
   'directory' : 'path_to'
}

The problem is that we can easily convert a regular class to json using addict or libraries, but my variable name contains '@'

How can I do this? (ideally without using libraries)

This does what you ask in a mostly sane manner. The issue I have with your structure is that RType is not an object type. It's just an attribute of your other types.

This modification of my original script adds a function to move the r_types value to the front of each dict. This is not a completely general solution; if you have lists nested in lists, it will need changing. As it is, it works with dicts, or with lists containing dicts.

But your JSON reader is defective. You should file a bug report.

from dataclasses import dataclass, asdict
import json

@dataclass
class Options:
    options: str
    r_type: str = "options"

@dataclass
class Init:
    options: Options
    r_type: str = "init"

def movetypes(dct):
    newdct = {}
    if 'r_type' in dct:
        newdct['@r_type'] = dct['r_type']
    for key,value in dct.items():
        if key == 'r_type':
            continue
        if isinstance(value,dict):
            value = movetypes(value)
        elif isinstance(value,list):
            value = [movetypes(v) for v in value]
        newdct[key] = value
    return newdct

x = Options('some_options')
y = Init([x])
dct = asdict(y)
print(dct)
dct = movetypes(dct)
print(dct)
js = json.dumps(dct)
print(js)

Output:

{'options': [{'options': 'some_options', 'r_type': 'options'}], 'r_type': 'init'}
{'@r_type': 'init', 'options': [{'@r_type': 'options', 'options': 'some_options'}]}
{"@r_type": "init", "options": [{"@r_type": "options", "options": "some_options"}]}

I know you asked for a solution without libraries, but here's a clean way which actually looks Pythonic to me at least. This does make use of an external library, dataclass-wizard . The json_field is synonymous usage to dataclasses.field , but specifies an alias used for (de)serialization.

Note also: I've needed to swap the order of the fields, so that requires declaring options field as an optional field. Since dictionary order should be the order in which the dataclass fields are defined, this ensures @r_type shows up first in the serialized dict result.

import json
from dataclasses import dataclass

from dataclass_wizard import asdict, json_field


@dataclass
class Init:
    r_type: str = json_field('@r_type', all=True, default='init')
    options: 'Options' = None


@dataclass
class Options:
    r_type: str = json_field('@r_type', all=True, default='options')
    options: str = None


x = Options(options='some_options')
y = Init(options=[x])
dct = asdict(y)
# print(dct)
# {'@r_type': 'init', 'options': [{'@r_type': 'options', 'options': 'some_options'}]}
print(json.dumps(dct, indent=2))

Output:

{
  "@r_type": "init",
  "options": [
    {
      "@r_type": "options",
      "options": "some_options"
    }
  ]
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM