簡體   English   中英

如何將 JSON 結構轉換為數據類對象樹?

[英]How to convert a JSON structure into a dataclass object tree?

我有一個 JSON 數據結構。 每個對象都有一個名為"type"的字段。

json_data_str = """
{
    "type" : "Game",
    "levels" : [
        {
            "type": "Level",
            "map" : {
                "type" : "SquareRoom",
                "name" : "Level 1",
                "width" : 100,
                "height" : 100
            },
            "waves" : [
                {
                    "type" : "Wave",
                    "enemies" : [
                        {
                            "type" : "Wizard",
                            "name" : "Gandalf"
                        },
                        {
                            "type" : "Archer",
                            "name" : "Legolass"
                        }
                    ]
                }
            ]
        }
    ]
}
"""

我想將其轉換為由以下類組成的對象樹

from dataclasses import dataclass
from typing import List

@dataclass
class GameObject:
    ...

@dataclass
class Character(GameObject):
    name: str

@dataclass
class Wave(GameObject):
    enemies: List[Character]

@dataclass
class Wizard(Character):
    ...

@dataclass
class Archer(Character):
    ...

@dataclass
class Map(GameObject):
    name: str

@dataclass
class SquareRoom(Map):
    width: int
    height: int

@dataclass
class Level(GameObject):
    waves: List[Wave]
    map: Map
    
@dataclass
class Game(GameObject):
    levels: List[Level]

我可以使用**運算符很容易地將一個簡單的 json 對象解壓縮到一個數據類中:例如

json_data_str = """
{
   "type" : "Person"
   "name" : "Bob"
   "age" : 29
}
"""

class GameObject(ABC):
    ...

@dataclass
class Person(GameObject):
    name: str
    age: int

game_object_registry: Dict[str, Type[GameObject]] = {}
game_object_registry['Person'] = Person

json_obj = json.loads(json_data_str)
obj_type = json_obj['type']
del json_obj['type']
ObjType = game_object_registry[obj_type]
ObjType(**json_obj)

但是如何擴展它以使用嵌套對象?

我希望它創建這個數據類實例:

game = Game(levels=[Level(map=SquareRoom(name="Level 1", width=100, height=100), waves=[Wave([Wizard(name="Gandalf"), Archer(name="Legolass")])])])

這是我最好的嘗試。 這實際上沒有意義,但它可能是一個起點。 我意識到這個邏輯沒有意義,但我想不出一個有意義的函數。

def json_to_game_object(json_obj: Any, game_object_registry: Dict[str, Type[GameObject]]) -> Any:

    if type(json_obj) is dict:
        obj_type: str = json_obj['type']
        del json_obj['type']
        ObjType = game_object_registry[obj_type]
        for key, value in json_obj.items():
            logging.debug(f'Parsing feild "{key}:{value}"')
            json_to_game_object(value, game_object_registry)
            if type(value) is dict:
                logging.debug(f'Creating object of type {ObjType} with args {value}')
                return ObjType(**value)
    elif type(json_obj) is list:
        logging.debug(f'Parsing JSON List')
        for elem in json_obj:
            logging.debug(f'Parsing list element "{json_obj.index(elem)}"')
            json_to_game_object(elem, game_object_registry)
    else:
        logging.debug(f'Parsing value')

這是一個如何以面向對象的方式導出的示例。 在我個人看來,將其轉換為純數據類對象不會比將所有內容保存在字典中帶來任何好處。 在這里,每個對象都可以有自己的行為。

(我現在已經修改了它以開始添加 repr 處理程序,因此您可以一次打印整個樹。)

json_data_str = """
{
    "type" : "Game",
    "levels" : [
        {
            "type": "Level",
            "map" : {
                "type" : "SquareRoom",
                "name" : "Level 1",
                "width" : 100,
                "height" : 100
            },
            "waves" : [
                {
                    "type" : "Wave",
                    "enemies" : [
                        {
                            "type" : "Wizard",
                            "name" : "Gandalf"
                        },
                        {
                            "type" : "Archer",
                            "name" : "Legolass"
                        }
                    ]
                }
            ]
        }
    ]
}
"""

import json

class GameObject():
    pass

class Game(GameObject):
    def __init__(self, obj):
        self.levels = [Level(k) for k in obj['levels']]
    def __repr__(self):
        s = f"<Game contains {len(self.levels)} levels:>\n"
        s += '\n'.join(repr(l) for l in self.levels)
        return s

class Character(GameObject):
    def __init__(self,obj):
        self.name = obj['name']


class Wave(GameObject):
    def __init__(self, obj):
        self.enemies = [game_object_registry[e['type']](e) for e in obj['enemies']]
    def __repr__(self):
        return f'<Wave contains {len(self.enemies)} enemies'


class Wizard(Character):
    def __init__(self,obj):
        super().__init__(obj)

class Archer(Character):
    def __init__(self,obj):
        super().__init__(obj)

class Map(GameObject):
    pass

class SquareRoom(Map):
    def __init__(self,obj):
        self.name = obj['name']
        self.widdth = obj['width']
        self.height = obj['height']

class Level(GameObject):
    def __init__(self, obj):
        self.waves = [Wave(e) for e in obj['waves']]
        self.map = game_object_registry[obj['map']['type']](obj['map'])
    def __repr__(self):
        s = f'<Level contains {len(self.waves)} waves>\n'
        s += '\n'.join(repr(w) for w in self.waves)
        return s

game_object_registry = {
    'Game': Game,
    'Wave': Wave,
    'Level': Level,
    'SquareRoom': SquareRoom,
    'Archer': Archer,
    'Map': Map,
    'Wizard': Wizard
}

json_obj = json.loads(json_data_str)

g = Game(json_obj)
print(g)
print(g.levels[0].waves[0].enemies[0].name)

假設您可以控制 JSON / dict 結構。 您可以使用像dacite這樣的框架。

它將讓您將數據映射到您的數據類中。

示例(取自 dacite github)如下:

@dataclass
class A:
    x: str
    y: int


@dataclass
class B:
    a: A


data = {
    'a': {
        'x': 'test',
        'y': 1,
    }
}

result = from_dict(data_class=B, data=data)

assert result == B(a=A(x='test', y=1))

作為替代方案,您也可以為此使用dataclass-wizard庫。 從最近的版本開始,這應該支持數據類作為Union類型,但是請注意,到目前為止,標記字段不可配置,因此在下面我更改了 JSON 對象中出現的type字段; 在不需要的情況下,我還完全刪除了這個字段——請注意,當您有一個映射到一個或多個數據類類型的數據類字段時,您只需要一個標簽字段。

下面的示例應該適用於包含__future__導入的 Python 3.7+。

from __future__ import annotations

from dataclasses import dataclass

from dataclass_wizard import JSONWizard


@dataclass
class GameObject:
    ...


@dataclass
class Character(GameObject):
    name: str


@dataclass
class Wizard(Character, JSONWizard):

    class _(JSONWizard.Meta):
        tag = 'Wizard'

    ...


@dataclass
class Archer(Character, JSONWizard):

    class _(JSONWizard.Meta):
        tag = 'Archer'

    ...


@dataclass
class Game(GameObject, JSONWizard):
    levels: list[Level]


@dataclass
class Level(GameObject):
    waves: list[Wave]
    # TODO: define other map classes
    map: SquareRoom | Map


@dataclass
class Map(GameObject):
    name: str


@dataclass
class SquareRoom(Map, JSONWizard):

    class _(JSONWizard.Meta):
        tag = 'SquareRoom'

    width: int
    height: int


@dataclass
class Wave(GameObject):
    enemies: list[Wizard | Archer]


def main():
    json_data_str = """
    {
        "levels": [
            {
                "map": {
                    "__tag__": "SquareRoom",
                    "name": "Level 1",
                    "width": 100,
                    "height": 100
                },
                "waves": [
                    {
                        "enemies": [
                            {
                                "__tag__": "Wizard",
                                "name": "Gandalf"
                            },
                            {
                                "__tag__": "Archer",
                                "name": "Legolass"
                            }
                        ]
                    }
                ]
            }
        ]
    }
    """

    game = Game.from_json(json_data_str)
    print(repr(game))


if __name__ == '__main__':
    main()

輸出:

Game(levels=[Level(waves=[Wave(enemies=[Wizard(name='Gandalf'), Archer(name='Legolass')])], map=SquareRoom(name='Level 1', width=100, height=100))])

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM