如何将JSON数据转换为Python对象


281

我想使用Python将JSON数据转换成Python对象。

我从Facebook API接收了JSON数据对象,我想将其存储在数据库中。

我当前在Django(Python)中的视图(request.POST包含JSON):

response = request.POST
user = FbApiUser(user_id = response['id'])
user.name = response['name']
user.username = response['username']
user.save()
  • 这可以正常工作,但是如何处理复杂的JSON数据对象?

  • 如果我能以某种方式将这个JSON对象转换为Python对象以便于使用,会不会更好呢?


通常,JSON会转换为原始列表或字典。那是你要的吗?还是您希望将JSON直接转换为自定义类型?
2011年

我想将其转换为对象,可以使用“。”进行访问。。就像上面的示例一样-> reponse.name,response.education.id等...。–
克里希纳

44
使用dicts是进行面向对象编程的弱势方式。字典是一种与期望的代码传达期望值的糟糕方法。使用字典,您如何能清楚,可重复地指定某些字典键值对是必需的,而其他则不需要?确认给定值在可接受范围或设置内该怎么办?特定于您正在使用的对象类型的函数(又称方法)又如何呢?字典既方便又通用,但是太多的开发人员却像忘记了Python是一种面向对象的语言一样,表现得很像。

1
这个github.com/jsonpickle/jsonpickle有一个python库(请注意,因为答案太在线程中,将无法到达。)
祝愿

Answers:


355

您可以使用namedtuple和在一行中完成操作object_hook

import json
from collections import namedtuple

data = '{"name": "John Smith", "hometown": {"name": "New York", "id": 123}}'

# Parse JSON into an object with attributes corresponding to dict keys.
x = json.loads(data, object_hook=lambda d: namedtuple('X', d.keys())(*d.values()))
print x.name, x.hometown.name, x.hometown.id

或者,轻松重用此方法:

def _json_object_hook(d): return namedtuple('X', d.keys())(*d.values())
def json2obj(data): return json.loads(data, object_hook=_json_object_hook)

x = json2obj(data)

如果您希望它处理不是好的属性名称的键,请查看namedtuplerenameparameter


8
这可能会导致一个错误的价值,ValueError异常:类型名和字段名不能以数字开头:“123”
PvdL

3
作为Python的新手,我很感兴趣在安全性问题时是否也能省钱。
Benjist 2015年

8
每次在解析时遇到JSON对象,都会创建一个新的不同类,对吗?
fikr4n

2
有趣。我认为依靠d.keys()d.values()以相同的顺序进行迭代不能保证,但我错了。该文件说:“如果键,值和物品意见,而没有中间修改遍历到字典中,项目的顺序会直接对应。” 对于此类小型本地代码块,要知道的是。我会添加一条注释,以明确提醒维护者这种依赖关系的代码。
cfi

1
我不知道任何不错的通用反向操作。可以使用来将任何单个namedtuple转换为dict x._asdict(),这可能对简单的情况有所帮助。
DS。

127

检查出标题为专业JSON对象解码json 模块文档。您可以使用它来将JSON对象解码为特定的Python类型。

这是一个例子:

class User(object):
    def __init__(self, name, username):
        self.name = name
        self.username = username

import json
def object_decoder(obj):
    if '__type__' in obj and obj['__type__'] == 'User':
        return User(obj['name'], obj['username'])
    return obj

json.loads('{"__type__": "User", "name": "John Smith", "username": "jsmith"}',
           object_hook=object_decoder)

print type(User)  # -> <type 'type'>

更新资料

如果要通过json模块访问字典中的数据,请执行以下操作:

user = json.loads('{"__type__": "User", "name": "John Smith", "username": "jsmith"}')
print user['name']
print user['username']

就像普通字典一样。


1
嘿,我只是在阅读,我意识到字典完全可以做到,只是我想知道如何将JSON对象转换为字典,以及如何从字典访问此数据?
西克里希纳

太棒了,这几乎是清楚的,只是想知道一件小事情,如果有这个对象-> {'教育':{'name1':456,'name2':567}},我该如何访问这些数据?
西克里希纳

就是topLevelData ['education'] ['name1'] ==>456。有意义吗?
Shakakai 2011年

1
@本:我认为你的评论是不合适的。目前,在所有答案中,这是唯一正确上课的答案。这意味着:这是一次通过操作,并且结果使用正确的类型。Pickle本身适用于与JSON(二进制和文本表示)不同的应用程序,而jsonpickle是非标准的库。我很想知道您如何解决std json lib无法为对象挂钩提供上层解析树的问题
CFI

我必须对此同意@Ben。这是一个非常糟糕的解决方案。根本无法扩展。您需要将字段名称保持为字符串和字段。如果您想重构字段,则解码将失败(当然,已经序列化的数据将不再相关)。jsonpickle
Guyarad '16

98

这不是代码编程,但这是我最短的技巧,types.SimpleNamespace用作JSON对象的容器。

与领先的namedtuple解决方案相比,它是:

  • 可能更快/更小,因为它不会为每个对象创建一个类
  • 短一点
  • 没有rename选项,并且对于无效标识符的密钥可能有相同的限制(在幕后使用setattr

例:

from __future__ import print_function
import json

try:
    from types import SimpleNamespace as Namespace
except ImportError:
    # Python 2.x fallback
    from argparse import Namespace

data = '{"name": "John Smith", "hometown": {"name": "New York", "id": 123}}'

x = json.loads(data, object_hook=lambda d: Namespace(**d))

print (x.name, x.hometown.name, x.hometown.id)

2
顺便说一下,序列化库Marshmallow的@post_load装饰器提供了类似的功能。marshmallow.readthedocs.io/en/latest/...
泰勒埃德米斯顿

3
为避免依赖于argparse:用argparse替换from types import SimpleNamespace并使用:x = json.loads(data, object_hook=lambda d: SimpleNamespace(**d))
maxschlepzig

8
这是最优雅的解决方案,应该放在顶部。
ScalaWilliam

4
编辑为在Python 3.x下运行时使用@maxschlepzig的解决方案(types.SimpleNamespace不幸的是,在2.7中不存在)。
丹·伦斯基

1
为什么print_function呢?
chwi

89

您可以尝试以下方法:

class User(object):
    def __init__(self, name, username, *args, **kwargs):
        self.name = name
        self.username = username

import json
j = json.loads(your_json)
u = User(**j)

只需创建一个新的对象,然后将参数作为映射传递即可。


1
我收到TypeError:“用户”对象不可下标
Mahdi

1
这应该是公认的答案。为我工作的广告比其他所有广告都要简单得多。
伊兹克

我没有使用* args,** kwargs,但是解决方案有效。
Malkaviano

1
用户(** j)说它缺少名称和用户名参数,该字典如何初始化?
亚伦·斯坦巴克

40

这是一种快速而肮脏的JSON泡菜替代品

import json

class User:
    def __init__(self, name, username):
        self.name = name
        self.username = username

    def to_json(self):
        return json.dumps(self.__dict__)

    @classmethod
    def from_json(cls, json_str):
        json_dict = json.loads(json_str)
        return cls(**json_dict)

# example usage
User("tbrown", "Tom Brown").to_json()
User.from_json(User("tbrown", "Tom Brown").to_json()).to_json()

1
这不是一个好方法。首先,不应将to_json和from_json放在您的类中。第二,它不适用于嵌套类。
侏罗纪

17

对于复杂的对象,可以使用JSON Pickle

用于将任意对象图序列化为JSON的Python库。它几乎可以使用任何Python对象并将该对象转换为JSON。此外,它可以将对象重新构造回Python。


6
我认为jsonstruct更好。 jsonstruct originally a fork of jsonpickle (Thanks guys!). The key difference between this library and jsonpickle is that during deserialization, jsonpickle requires Python types to be recorded as part of the JSON. This library intends to remove this requirement, instead, requires a class to be passed in as an argument so that its definition can be inspected. It will then return an instance of the given class. This approach is similar to how Jackson (of Java) works.
Abhishek Gupta

3
jsonstruct的问题在于它似乎没有得到维护(实际上,它看起来已被放弃)并且它无法转换对象列表,例如'[{"name":"object1"},{"name":"object2"}]'。jsonpickle也不能很好地处理它。
LS

1
我不知道为什么这个答案得不到更多的选票。大多数其他解决方案都远远不够。有人为JSON反序列化开发了一个很棒的库-为什么不使用它呢?此外,似乎可以很好地使用列表-@LS出现了什么问题?
Guyarad

1
@guyarad,问题是:x = jsonpickle.decode('[{“ name”:“ object1”},{“ name”:“ object2”}]'))给出字典列表([{'name':' object1'},{'name':'object2'}]),而不是具有属性(x [0] .name =='object1')的对象的列表,这是原始问题所需要的。为此,我最终使用了eddygeek建议的object_hook / Namespace方法,但是ubershmekel的快速/肮脏方法看起来也不错。我想我可以将object_hook与jsonpickle的set_encoder_options()(未公开说明!)一起使用,但是它将比基本的json模块花费更多的代码。我希望证明自己是错误的!
LS

@LS如果您无法控制输入(这确实是OP所要求的),则jsonpickle并不理想,因为它期望每个级别中的实际类型(如果缺少,将假定基本类型)。两种解决方案都是“可爱的”。
guyarad

12

如果您使用的是Python 3.5+,则可以用于jsons序列化和反序列化为普通的旧Python对象:

import jsons

response = request.POST

# You'll need your class attributes to match your dict keys, so in your case do:
response['id'] = response.pop('user_id')

# Then you can load that dict into your class:
user = jsons.load(response, FbApiUser)

user.save()

您也可以FbApiUser从继承继承,jsons.JsonSerializable以获得更多的优雅:

user = FbApiUser.from_json(response)

如果您的类由Python默认类型(例如字符串,整数,列表,日期时间等)组成,则这些示例将起作用。但是jsonslib将需要自定义类型的类型提示。



5

我编写了一个名为any2any的小型(反序列化)框架,该有助于在两种Python类型之间进行复杂的转换。

在您的情况下,我想您想将字典(由获取json.loads)转换为response.education ; response.name具有嵌套结构response.education.id等的复杂对象……所以这正是该框架的目的。该文档尚不完善,但是通过使用any2any.simple.MappingToObject,您应该可以很轻松地做到这一点。请询问是否需要帮助。


Sebpiq,已经安装了any2any,并且在理解方法调用的预期顺序时遇到了麻烦。您能否举一个简单的示例,将字典转换为具有每个键属性的Python对象?
sansjoe 2012年

嗨@sansjoe!如果您是从pypi安装的,则该版本已完全过时,我几周前已进行了完整的重构。您应该使用github版本(我需要发布适当的信息!)
sebpiq 2012年

我从pypy安装了它,因为github表示要从pypy安装它。另外,您说pypy几个月前已经过时了。.它不起作用:(我提交了一个错误报告tho!github.com/sebpiq/any2any/issues/11
sneilan 2012年

5

改善lovasoa的很好答案。

如果您使用的是python 3.6+,则可以使用:
pip install marshmallow-enum
pip install marshmallow-dataclass

它简单且类型安全。

您可以使用string-json转换类,反之亦然:

从对象到字符串Json:

    from marshmallow_dataclass import dataclass
    user = User("Danilo","50","RedBull",15,OrderStatus.CREATED)
    user_json = User.Schema().dumps(user)
    user_json_str = user_json.data

从String Json到Object:

    json_str = '{"name":"Danilo", "orderId":"50", "productName":"RedBull", "quantity":15, "status":"Created"}'
    user, err = User.Schema().loads(json_str)
    print(user,flush=True)

类定义:

class OrderStatus(Enum):
    CREATED = 'Created'
    PENDING = 'Pending'
    CONFIRMED = 'Confirmed'
    FAILED = 'Failed'

@dataclass
class User:
    def __init__(self, name, orderId, productName, quantity, status):
        self.name = name
        self.orderId = orderId
        self.productName = productName
        self.quantity = quantity
        self.status = status

    name: str
    orderId: str
    productName: str
    quantity: int
    status: OrderStatus

1
您不需要构造函数,只需将init = True传递给dataclass即可。
约瑟夫·科贝尔

4

由于没有人像我一样提供答案,因此我将在此处发布。

这是一个强大的类,它可以很容易地来回转换JSON之间strdict我从复制我的回答另一个问题

import json

class PyJSON(object):
    def __init__(self, d):
        if type(d) is str:
            d = json.loads(d)

        self.from_dict(d)

    def from_dict(self, d):
        self.__dict__ = {}
        for key, value in d.items():
            if type(value) is dict:
                value = PyJSON(value)
            self.__dict__[key] = value

    def to_dict(self):
        d = {}
        for key, value in self.__dict__.items():
            if type(value) is PyJSON:
                value = value.to_dict()
            d[key] = value
        return d

    def __repr__(self):
        return str(self.to_dict())

    def __setitem__(self, key, value):
        self.__dict__[key] = value

    def __getitem__(self, key):
        return self.__dict__[key]

json_str = """... json string ..."""

py_json = PyJSON(json_str)

2

稍微修改@DS响应以从文件加载:

def _json_object_hook(d): return namedtuple('X', d.keys())(*d.values())
def load_data(file_name):
  with open(file_name, 'r') as file_data:
    return file_data.read().replace('\n', '')
def json2obj(file_name): return json.loads(load_data(file_name), object_hook=_json_object_hook)

一件事:这无法加载前面带有数字的项目。像这样:

{
  "1_first_item": {
    "A": "1",
    "B": "2"
  }
}

因为“ 1_first_item”不是有效的python字段名称。


2

在寻找解决方案时,我偶然发现了此博客文章:https : //blog.mosthege.net/2016/11/12/json-deserialization-of-nested-objects/

它使用与先前答案相同的技术,但使用了装饰器。我发现有用的另一件事是事实,它在反序列化结束时返回一个类型化的对象

class JsonConvert(object):
    class_mappings = {}

    @classmethod
    def class_mapper(cls, d):
        for keys, cls in clsself.mappings.items():
            if keys.issuperset(d.keys()):   # are all required arguments present?
                return cls(**d)
        else:
            # Raise exception instead of silently returning None
            raise ValueError('Unable to find a matching class for object: {!s}'.format(d))

    @classmethod
    def complex_handler(cls, Obj):
        if hasattr(Obj, '__dict__'):
            return Obj.__dict__
        else:
            raise TypeError('Object of type %s with value of %s is not JSON serializable' % (type(Obj), repr(Obj)))

    @classmethod
    def register(cls, claz):
        clsself.mappings[frozenset(tuple([attr for attr,val in cls().__dict__.items()]))] = cls
        return cls

    @classmethod
    def to_json(cls, obj):
        return json.dumps(obj.__dict__, default=cls.complex_handler, indent=4)

    @classmethod
    def from_json(cls, json_str):
        return json.loads(json_str, object_hook=cls.class_mapper)

用法:

@JsonConvert.register
class Employee(object):
    def __init__(self, Name:int=None, Age:int=None):
        self.Name = Name
        self.Age = Age
        return

@JsonConvert.register
class Company(object):
    def __init__(self, Name:str="", Employees:[Employee]=None):
        self.Name = Name
        self.Employees = [] if Employees is None else Employees
        return

company = Company("Contonso")
company.Employees.append(Employee("Werner", 38))
company.Employees.append(Employee("Mary"))

as_json = JsonConvert.to_json(company)
from_json = JsonConvert.from_json(as_json)
as_json_from_json = JsonConvert.to_json(from_json)

assert(as_json_from_json == as_json)

print(as_json_from_json)

2

稍微扩展一下DS的答案,如果您需要对象可变(不包括namedtuple),则可以使用recordclass库而不是namedtuple

import json
from recordclass import recordclass

data = '{"name": "John Smith", "hometown": {"name": "New York", "id": 123}}'

# Parse into a mutable object
x = json.loads(data, object_hook=lambda d: recordclass('X', d.keys())(*d.values()))

然后可以使用simplejson轻松地将修改后的对象转换回json :

x.name = "John Doe"
new_json = simplejson.dumps(x)

1

如果您使用的是Python 3.6或更高版本,则可以看看squema-一种用于静态类型数据结构的轻量级模块。它使您的代码易于阅读,同时无需任何额外工作即可提供简单的数据验证,转换和序列化。您可以将其视为命名元组和数据类的更复杂,更自以为是的替代方案。使用方法如下:

from uuid import UUID
from squema import Squema


class FbApiUser(Squema):
    id: UUID
    age: int
    name: str

    def save(self):
        pass


user = FbApiUser(**json.loads(response))
user.save()

这也更类似于JVM语言的实现方式。
javadba

1

我正在寻找一种可与recordclass.RecordClass,支持嵌套对象同时适用于json序列化和json反序列化的解决方案。

扩展DS的答案,并扩展BeneStr的解决方案,我想到了以下似乎可行的方法:

码:

import json
import recordclass

class NestedRec(recordclass.RecordClass):
    a : int = 0
    b : int = 0

class ExampleRec(recordclass.RecordClass):
    x : int       = None
    y : int       = None
    nested : NestedRec = NestedRec()

class JsonSerializer:
    @staticmethod
    def dumps(obj, ensure_ascii=True, indent=None, sort_keys=False):
        return json.dumps(obj, default=JsonSerializer.__obj_to_dict, ensure_ascii=ensure_ascii, indent=indent, sort_keys=sort_keys)

    @staticmethod
    def loads(s, klass):
        return JsonSerializer.__dict_to_obj(klass, json.loads(s))

    @staticmethod
    def __obj_to_dict(obj):
        if hasattr(obj, "_asdict"):
            return obj._asdict()
        else:
            return json.JSONEncoder().default(obj)

    @staticmethod
    def __dict_to_obj(klass, s_dict):
        kwargs = {
            key : JsonSerializer.__dict_to_obj(cls, s_dict[key]) if hasattr(cls,'_asdict') else s_dict[key] \
                for key,cls in klass.__annotations__.items() \
                    if s_dict is not None and key in s_dict
        }
        return klass(**kwargs)

用法:

example_0 = ExampleRec(x = 10, y = 20, nested = NestedRec( a = 30, b = 40 ) )

#Serialize to JSON

json_str = JsonSerializer.dumps(example_0)
print(json_str)
#{
#  "x": 10,
#  "y": 20,
#  "nested": {
#    "a": 30,
#    "b": 40
#  }
#}

# Deserialize from JSON
example_1 = JsonSerializer.loads(json_str, ExampleRec)
example_1.x += 1
example_1.y += 1
example_1.nested.a += 1
example_1.nested.b += 1

json_str = JsonSerializer.dumps(example_1)
print(json_str)
#{
#  "x": 11,
#  "y": 21,
#  "nested": {
#    "a": 31,
#    "b": 41
#  }
#}

1

此处给出的答案未返回正确的对象类型,因此我在下面创建了这些方法。如果您尝试将更多字段添加到给定JSON中不存在的类,它们也会失败:

def dict_to_class(class_name: Any, dictionary: dict) -> Any:
    instance = class_name()
    for key in dictionary.keys():
        setattr(instance, key, dictionary[key])
    return instance


def json_to_class(class_name: Any, json_string: str) -> Any:
    dict_object = json.loads(json_string)
    return dict_to_class(class_name, dict_object)

0

Python3.x

我所能达到的最好的方法就是这个。
注意,此代码也对待set()。
这种方法是通用的,只需要扩展类(在第二个示例中)。
请注意,我只是对文件进行处理,但是可以根据自己的喜好修改行为。

但是,这是CoDec。

通过更多的工作,您可以用其他方式构造您的课程。我假定使用默认的构造函数来实例化它,然后更新类dict。

import json
import collections


class JsonClassSerializable(json.JSONEncoder):

    REGISTERED_CLASS = {}

    def register(ctype):
        JsonClassSerializable.REGISTERED_CLASS[ctype.__name__] = ctype

    def default(self, obj):
        if isinstance(obj, collections.Set):
            return dict(_set_object=list(obj))
        if isinstance(obj, JsonClassSerializable):
            jclass = {}
            jclass["name"] = type(obj).__name__
            jclass["dict"] = obj.__dict__
            return dict(_class_object=jclass)
        else:
            return json.JSONEncoder.default(self, obj)

    def json_to_class(self, dct):
        if '_set_object' in dct:
            return set(dct['_set_object'])
        elif '_class_object' in dct:
            cclass = dct['_class_object']
            cclass_name = cclass["name"]
            if cclass_name not in self.REGISTERED_CLASS:
                raise RuntimeError(
                    "Class {} not registered in JSON Parser"
                    .format(cclass["name"])
                )
            instance = self.REGISTERED_CLASS[cclass_name]()
            instance.__dict__ = cclass["dict"]
            return instance
        return dct

    def encode_(self, file):
        with open(file, 'w') as outfile:
            json.dump(
                self.__dict__, outfile,
                cls=JsonClassSerializable,
                indent=4,
                sort_keys=True
            )

    def decode_(self, file):
        try:
            with open(file, 'r') as infile:
                self.__dict__ = json.load(
                    infile,
                    object_hook=self.json_to_class
                )
        except FileNotFoundError:
            print("Persistence load failed "
                  "'{}' do not exists".format(file)
                  )


class C(JsonClassSerializable):

    def __init__(self):
        self.mill = "s"


JsonClassSerializable.register(C)


class B(JsonClassSerializable):

    def __init__(self):
        self.a = 1230
        self.c = C()


JsonClassSerializable.register(B)


class A(JsonClassSerializable):

    def __init__(self):
        self.a = 1
        self.b = {1, 2}
        self.c = B()

JsonClassSerializable.register(A)

A().encode_("test")
b = A()
b.decode_("test")
print(b.a)
print(b.b)
print(b.c.a)

编辑

通过更多的研究,我找到了一种使用元类进行泛化而无需SUPERCLASS寄存器方法调用的方法。

import json
import collections

REGISTERED_CLASS = {}

class MetaSerializable(type):

    def __call__(cls, *args, **kwargs):
        if cls.__name__ not in REGISTERED_CLASS:
            REGISTERED_CLASS[cls.__name__] = cls
        return super(MetaSerializable, cls).__call__(*args, **kwargs)


class JsonClassSerializable(json.JSONEncoder, metaclass=MetaSerializable):

    def default(self, obj):
        if isinstance(obj, collections.Set):
            return dict(_set_object=list(obj))
        if isinstance(obj, JsonClassSerializable):
            jclass = {}
            jclass["name"] = type(obj).__name__
            jclass["dict"] = obj.__dict__
            return dict(_class_object=jclass)
        else:
            return json.JSONEncoder.default(self, obj)

    def json_to_class(self, dct):
        if '_set_object' in dct:
            return set(dct['_set_object'])
        elif '_class_object' in dct:
            cclass = dct['_class_object']
            cclass_name = cclass["name"]
            if cclass_name not in REGISTERED_CLASS:
                raise RuntimeError(
                    "Class {} not registered in JSON Parser"
                    .format(cclass["name"])
                )
            instance = REGISTERED_CLASS[cclass_name]()
            instance.__dict__ = cclass["dict"]
            return instance
        return dct

    def encode_(self, file):
        with open(file, 'w') as outfile:
            json.dump(
                self.__dict__, outfile,
                cls=JsonClassSerializable,
                indent=4,
                sort_keys=True
            )

    def decode_(self, file):
        try:
            with open(file, 'r') as infile:
                self.__dict__ = json.load(
                    infile,
                    object_hook=self.json_to_class
                )
        except FileNotFoundError:
            print("Persistence load failed "
                  "'{}' do not exists".format(file)
                  )


class C(JsonClassSerializable):

    def __init__(self):
        self.mill = "s"


class B(JsonClassSerializable):

    def __init__(self):
        self.a = 1230
        self.c = C()


class A(JsonClassSerializable):

    def __init__(self):
        self.a = 1
        self.b = {1, 2}
        self.c = B()


A().encode_("test")
b = A()
b.decode_("test")
print(b.a)
# 1
print(b.b)
# {1, 2}
print(b.c.a)
# 1230
print(b.c.c.mill)
# s

0

您可以使用

x = Map(json.loads(response))
x.__class__ = MyClass

哪里

class Map(dict):
    def __init__(self, *args, **kwargs):
        super(Map, self).__init__(*args, **kwargs)
        for arg in args:
            if isinstance(arg, dict):
                for k, v in arg.iteritems():
                    self[k] = v
                    if isinstance(v, dict):
                        self[k] = Map(v)

        if kwargs:
            # for python 3 use kwargs.items()
            for k, v in kwargs.iteritems():
                self[k] = v
                if isinstance(v, dict):
                    self[k] = Map(v)

    def __getattr__(self, attr):
        return self.get(attr)

    def __setattr__(self, key, value):
        self.__setitem__(key, value)

    def __setitem__(self, key, value):
        super(Map, self).__setitem__(key, value)
        self.__dict__.update({key: value})

    def __delattr__(self, item):
        self.__delitem__(item)

    def __delitem__(self, key):
        super(Map, self).__delitem__(key)
        del self.__dict__[key]

一个通用的,面向未来的解决方案。


By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.