【问题标题】:How to convert multiple data sources into a predefined data structure in python?如何在python中将多个数据源转换为预定义的数据结构?
【发布时间】:2021-11-25 12:18:15
【问题描述】:

我正在创建一个解析器,它从具有多个数据架构的多个源获取数据,然后将它们转换为标准化的结构化架构。

例如,我有 2 个数据源:

来源 1:

{
  "students: [{
     "id": 129939,
     "name": "Alice",
     "gender": "female",
  }]
}

来源 2:

{
  "students: [{
     "id": 129939,
     "fullname": "Alice",
     "sex": "female",
  }]
}

两种数据源都可以转换成我已经定义好的标准化结构化数据:

class Student:
   id: int
   name: str
   gender: str

您知道是否有任何现有的库支持为每个输入数据源定义架构,然后允许将输入源的每个字段映射到所需的数据结构?

例如,它可以是这样的映射器:

class Input1toStudentMapper:
   id -> Student.id
   name -> Student.name
   gender -> Student.gender
class Input2toStudentMapper:
   id -> Student.id
   fullname -> Student.name
   sex -> Student.gender

任何建议将不胜感激。

【问题讨论】:

  • 为什么不选择一个结构并创建一个解析器,然后你可以使用正则表达式来编辑 jsons。例如读取 json,使用 re.sub 将 "fullname" 替换为 "name" 然后解析

标签: python json serialization


【解决方案1】:

我不知道任何库,但对于您描述的问题 - 您可能可以将逻辑编码到类本身的 __init__ 中 -

class Student:
    name_field_variations = ['name', 'fullname']
    sex_field_variations = ['gender', 'sex']
    def __init__(self, **kwargs):
        self.id = kwargs['id']
        _name_field = set(kwargs.keys()) & set(Student.name_field_variations)
        self.name = kwargs[_name_field.pop()]
        _sex_field = set(kwargs.keys()) & set(Student.sex_field_variations)
        self.gender = kwargs[_sex_field.pop()]

print(js1) # {'students': [{'id': 129939, 'name': 'Alice', 'gender': 'female'}]}
print(js2) # {'students': [{'id': 129939, 'fullname': 'Alice', 'sex': 'female'}]}
s1 = Student(**js1['students'][0])
s2 = Student(**js2['students'][0])
print(s1.gender) # female
print(s2.gender) # female

【讨论】:

    【解决方案2】:

    我会为此查看 dataclass-wizard 库。它与 Python 中内置的 dataclasses 模块配合得很好。它支持每个字段的多个别名(或键映射),以及在这种情况下我们可能想要的单向别名 - 例如。如果我们想允许fullnamename 字段的额外映射,但使用默认键name 进行序列化。

    在下面的示例中,我还添加了一个 __future__ 导入,它在 Python 3.7 或更高版本中受支持。这主要是为了支持前向引用的使用;如果没有这个未来的导入,我们将需要明确定义前向引用,例如。喜欢List['Student']

    from __future__ import annotations
    
    from dataclasses import dataclass
    from typing import List
    
    from dataclass_wizard import json_key
    from typing_extensions import Annotated
    
    
    @dataclass
    class Container:
        students: List[Student]
    
    
    @dataclass
    class Student:
        id: int
        name: Annotated[str, json_key('fullname')]
        gender: Annotated[str, json_key('sex')]
    

    在上面的注释中,将其声明为json_key('fullname')json_key('name', 'fullname', all=True) 的简写,这也是多个别名可以映射到一个字段的方式。

    请注意,如果您只打算支持 Python 3.9 或更高版本,您可以进行以下更改:

    • 改为从typing 模块导入Annotated
    • 删除from typing import List 导入并定义像list[Student] 这样的注解

    这是测试上述代码的示例用法:

    if __name__ == '__main__':
        from dataclass_wizard import asdict, fromdict, fromlist
    
        source_1 = {
            "students": [{
                "id": 129939,
                "name": "Alice",
                "gender": "female",
            }]
        }
    
        source_2 = {
            "students": [{
                "id": 129940,
                "fullname": "Johnny",
                "sex": "male",
            }]
        }
    
        c1 = fromdict(Container, source_1)
        print(c1)
        # Container(students=[Student(id=129939, name='Alice', gender='female')])
    
        c2 = fromdict(Container, source_2)
        print(c2)
        # Container(students=[Student(id=129940, name='Johnny', gender='male')])
    
        # alternatively, if you just need a list of the `Student` instances:
        students = fromlist(Student, source_1['students'])
        print(students)
        # [Student(id=129939, name='Alice', gender='female')]
    
        # assert we get the same data when serializing the Container instance as a
        # Python dict object.
        serialized_dict = asdict(c1)
        assert serialized_dict == source_1
    

    【讨论】:

      猜你喜欢
      • 2023-03-03
      • 2023-03-27
      • 2022-01-27
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多