Nối tiếp chuỗi tên Python thành json


85

Cách đề xuất để tuần tự hóa a namedtupleđến json với các tên trường được giữ lại là gì?

Việc tuần tự hóa từ a namedtupleđến json chỉ dẫn đến các giá trị được tuần tự hóa và tên trường bị mất khi dịch. Tôi muốn các trường cũng được giữ lại khi json-ized và do đó đã làm như sau:

class foobar(namedtuple('f', 'foo, bar')):
    __slots__ = ()
    def __iter__(self):
        yield self._asdict()

Ở trên tuần tự hóa thành json như tôi mong đợi và hoạt động như namedtupleở những nơi khác mà tôi sử dụng (truy cập thuộc tính, v.v.,) ngoại trừ với kết quả không giống như bộ tuple trong khi lặp lại nó (điều này tốt cho trường hợp sử dụng của tôi).

"Cách chính xác" để chuyển đổi thành json với các tên trường được giữ lại là gì?


Câu trả lời:


56

Điều này khá phức tạp, vì namedtuple()là một nhà máy trả về một kiểu mới bắt nguồn từ tuple. Một cách tiếp cận là để lớp của bạn cũng kế thừa từ UserDict.DictMixin, nhưng tuple.__getitem__đã được xác định và mong đợi một số nguyên biểu thị vị trí của phần tử, không phải tên của thuộc tính của nó:

>>> f = foobar('a', 1)
>>> f[0]
'a'

Về cơ bản, nametuple là một sự phù hợp kỳ lạ đối với JSON, vì nó thực sự là một kiểu được xây dựng tùy chỉnh có tên khóa được cố định như một phần của định nghĩa kiểu , không giống như một từ điển nơi các tên khóa được lưu trữ bên trong phiên bản. Điều này ngăn bạn "làm hỏng" một tệp tin có tên, ví dụ như bạn không thể giải mã từ điển trở lại một tệp tin có tên mà không có một số thông tin khác, chẳng hạn như mã đánh dấu loại ứng dụng cụ thể trong dict {'a': 1, '#_type': 'foobar'}, điều này hơi khó hiểu.

Điều này không phải là lý tưởng, nhưng nếu bạn chỉ cần mã hóa các cụm từ có tên vào từ điển, thì một cách tiếp cận khác là mở rộng hoặc sửa đổi bộ mã hóa JSON của bạn thành các loại này. Đây là một ví dụ về phân lớp Python json.JSONEncoder. Điều này giải quyết vấn đề đảm bảo rằng các nhóm có tên lồng nhau được chuyển đổi đúng cách thành từ điển:

from collections import namedtuple
from json import JSONEncoder

class MyEncoder(JSONEncoder):

    def _iterencode(self, obj, markers=None):
        if isinstance(obj, tuple) and hasattr(obj, '_asdict'):
            gen = self._iterencode_dict(obj._asdict(), markers)
        else:
            gen = JSONEncoder._iterencode(self, obj, markers)
        for chunk in gen:
            yield chunk

class foobar(namedtuple('f', 'foo, bar')):
    pass

enc = MyEncoder()
for obj in (foobar('a', 1), ('a', 1), {'outer': foobar('x', 'y')}):
    print enc.encode(obj)

{"foo": "a", "bar": 1}
["a", 1]
{"outer": {"foo": "x", "bar": "y"}}

12
Về cơ bản, nametuple là một sự phù hợp kỳ lạ đối với JSON, vì nó thực sự là một kiểu được xây dựng tùy chỉnh có tên khóa được cố định như một phần của định nghĩa kiểu, không giống như từ điển nơi các tên khóa được lưu trữ bên trong phiên bản. Nhận xét rất sâu sắc. Tôi đã không nghĩ về điều đó. Cảm ơn. Tôi thích các cụm từ có tên vì chúng cung cấp một cấu trúc bất biến đẹp mắt với sự tiện lợi khi đặt tên thuộc tính. Tôi sẽ chấp nhận câu trả lời của bạn. Phải nói rằng, cơ chế tuần tự hóa của Java cung cấp nhiều quyền kiểm soát hơn đối với cách đối tượng được tuần tự hóa và tôi tò mò muốn biết tại sao những móc như vậy dường như không tồn tại trong Python.
calvinkrishy

Đó là cách tiếp cận đầu tiên của tôi, nhưng nó không thực sự hiệu quả (đối với tôi).
zeekay

1
>>> json.dumps(foobar('x', 'y'), cls=MyEncoder) <<< '["x", "y"]'
zeekay

19
Ah, trong python 2.7+ _iterencode không còn là một phương thức của JSONEncoder.
zeekay

2
@calvin Cảm ơn, tôi thấy nametuple cũng hữu ích, ước gì có giải pháp tốt hơn để mã hóa nó một cách đệ quy thành JSON. @zeekay Yep, có vẻ như trong 2.7+ họ ẩn nó đi để nó không thể bị ghi đè nữa. Điều đó thật đáng thất vọng.
samplebias

77

Nếu đó chỉ là một thứ namedtuplebạn đang muốn tuần tự hóa, thì việc sử dụng _asdict()phương pháp của nó sẽ hoạt động (với Python> = 2.7)

>>> from collections import namedtuple
>>> import json
>>> FB = namedtuple("FB", ("foo", "bar"))
>>> fb = FB(123, 456)
>>> json.dumps(fb._asdict())
'{"foo": 123, "bar": 456}'

4
Tôi nhận được AttributeError: Đối tượng 'FB' không có thuộc tính ' dict ' khi chạy mã đó bằng Python 2.7 (x64) trên Windows. Tuy nhiên fb._asdict () hoạt động tốt.
geographika

5
fb._asdict()hoặc vars(fb)sẽ tốt hơn.
jpmc26

1
@ jpmc26: Bạn không thể sử dụng varstrên một đối tượng mà không có __dict__.
Rufflewind

@Rufflewind Bạn cũng không thể sử dụng __dict__chúng. =)
jpmc26

4
Trong python 3 __dict__đã bị loại bỏ. _asdictdường như hoạt động trên cả hai.
Andy Hayden

21

Có vẻ như bạn đã từng có thể phân lớp con simplejson.JSONEncoderđể làm cho điều này hoạt động, nhưng với mã simplejson mới nhất, điều đó không còn đúng nữa: bạn phải thực sự sửa đổi mã dự án. Tôi không hiểu lý do gì mà simplejson không nên hỗ trợ các nametuples, vì vậy tôi đã tách dự án, thêm hỗ trợ nametuple và hiện tôi đang chờ chi nhánh của mình được kéo trở lại dự án chính . Nếu bạn cần bản sửa lỗi ngay bây giờ, chỉ cần kéo từ ngã ba của tôi.

CHỈNH SỬA : Có vẻ như các phiên bản mới nhất simplejsonhiện đã hỗ trợ điều này với namedtuple_as_objecttùy chọn mặc định là True.


3
Chỉnh sửa của bạn là câu trả lời chính xác. simplejson tuần tự hóa các tập tin có tên khác (ý ​​kiến ​​của tôi: tốt hơn) so với json. Điều này thực sự làm cho mẫu: "try: import simplejson as json ngoại trừ: import json", khá rủi ro vì bạn có thể nhận được các hành vi khác nhau trên một số máy tùy thuộc vào việc simplejson được cài đặt hay không. Vì lý do đó, bây giờ tôi yêu cầu simplejson trong rất nhiều tệp thiết lập của mình và tránh mô hình đó.
Marr75,

1
@ marr75 - Ditto cho ujson, mà thậm chí nhiều kỳ lạ và khó lường trong các trường hợp cạnh như vậy ...
mac

Tôi đã có thể nhận được một tệp tin có tên đệ quy được tuần tự hóa thành json (in đẹp) bằng cách sử dụng:simplejson.dumps(my_tuple, indent=4)
KFL

5

Tôi đã viết một thư viện để làm việc này: https://github.com/ltworf/typedload

Nó có thể đi từ và đến tên-tuple và quay lại.

Nó hỗ trợ các cấu trúc lồng nhau khá phức tạp, với danh sách, tập hợp, enum, hợp nhất, giá trị mặc định. Nó sẽ bao gồm hầu hết các trường hợp phổ biến.

chỉnh sửa: Thư viện cũng hỗ trợ các lớp dataclass và attr.


2

Nó chuyển đổi đệ quy dữ liệu tênTuple thành json.

print(m1)
## Message(id=2, agent=Agent(id=1, first_name='asd', last_name='asd', mail='2@mai.com'), customer=Customer(id=1, first_name='asd', last_name='asd', mail='2@mai.com', phone_number=123123), type='image', content='text', media_url='h.com', la=123123, ls=4512313)

def reqursive_to_json(obj):
    _json = {}

    if isinstance(obj, tuple):
        datas = obj._asdict()
        for data in datas:
            if isinstance(datas[data], tuple):
                _json[data] = (reqursive_to_json(datas[data]))
            else:
                 print(datas[data])
                _json[data] = (datas[data])
    return _json

data = reqursive_to_json(m1)
print(data)
{'agent': {'first_name': 'asd',
'last_name': 'asd',
'mail': '2@mai.com',
'id': 1},
'content': 'text',
'customer': {'first_name': 'asd',
'last_name': 'asd',
'mail': '2@mai.com',
'phone_number': 123123,
'id': 1},
'id': 2,
'la': 123123,
'ls': 4512313,
'media_url': 'h.com',
'type': 'image'}

1
+1 Tôi đã thực hiện gần giống nhau. Nhưng sự trở lại của bạn là một điều không phải json. Bạn phải có "không', và nếu một giá trị trong đối tượng của bạn là một boolean, nó sẽ không được chuyển đổi sang đúng tôi nghĩ rằng nó là an toàn hơn để chuyển hóa thành dict, sau đó sử dụng json.dumps để chuyển đổi thành json..
Fred Laurent

2

Có một giải pháp tiện lợi hơn là sử dụng decorator (nó sử dụng trường được bảo vệ _fields).

Python 2.7+:

import json
from collections import namedtuple, OrderedDict

def json_serializable(cls):
    def as_dict(self):
        yield OrderedDict(
            (name, value) for name, value in zip(
                self._fields,
                iter(super(cls, self).__iter__())))
    cls.__iter__ = as_dict
    return cls

#Usage:

C = json_serializable(namedtuple('C', 'a b c'))
print json.dumps(C('abc', True, 3.14))

# or

@json_serializable
class D(namedtuple('D', 'a b c')):
    pass

print json.dumps(D('abc', True, 3.14))

Python 3.6.6+:

import json
from typing import TupleName

def json_serializable(cls):
    def as_dict(self):
        yield {name: value for name, value in zip(
            self._fields,
            iter(super(cls, self).__iter__()))}
    cls.__iter__ = as_dict
    return cls

# Usage:

@json_serializable
class C(NamedTuple):
    a: str
    b: bool
    c: float

print(json.dumps(C('abc', True, 3.14))

Đừng làm vậy, họ thay đổi API nội bộ mọi lúc. Thư viện typedload của tôi có một số trường hợp cho các phiên bản py khác nhau.
LtWorf

Vâng, nó rõ ràng. Tuy nhiên, không ai nên chuyển sang phiên bản Python mới hơn mà không thử nghiệm. Và, các giải pháp khác sử dụng _asdict, cũng là một thành viên lớp "được bảo vệ".
Dmitry T.

1
LtWorf, thư viện của bạn là GPL và không hoạt động với frozensets
Thomas Grainger

2
@LtWorf Thư viện của bạn cũng sử dụng _fields;-) github.com/ltworf/typedload/blob/master/typedload/datadumper.py Đây là một phần của API công khai của nametuple, thực ra: docs.python.org/3.7/library/… Mọi người bối rối bởi gạch dưới (không có gì lạ!). Đó là thiết kế tồi, nhưng tôi không biết họ có sự lựa chọn nào khác.
quant_dev

1
Điều gì? Khi nào? Bạn có thể trích dẫn ghi chú phát hành?
quant_dev

2

Các jsonplus thư viện cung cấp một serializer cho trường hợp NamedTuple. Sử dụng chế độ tương thích của nó để xuất các đối tượng đơn giản nếu cần, nhưng nên chọn chế độ mặc định vì nó hữu ích cho việc giải mã trở lại.


Tôi đã xem xét các giải pháp khác ở đây và thấy chỉ cần thêm sự phụ thuộc này đã giúp tôi tiết kiệm rất nhiều thời gian. Đặc biệt là vì tôi có một danh sách các NamedTuples mà tôi cần chuyển dưới dạng json trong phiên. jsonplus cho phép bạn về cơ bản có được danh sách của các bộ tên vào và ra khỏi json với .dumps().loads()không có cấu hình nó chỉ hoạt động.
Rob

1

Không thể tuần tự hóa các tệp tin có tên một cách chính xác với thư viện python json gốc. Nó sẽ luôn xem các bộ giá trị dưới dạng danh sách và không thể ghi đè bộ tuần tự mặc định để thay đổi hành vi này. Còn tệ hơn nếu các đối tượng được lồng vào nhau.

Tốt hơn nên sử dụng một thư viện mạnh mẽ hơn như orjson :

import orjson
from typing import NamedTuple

class Rectangle(NamedTuple):
    width: int
    height: int

def default(obj):
    if hasattr(obj, '_asdict'):
        return obj._asdict()

rectangle = Rectangle(width=10, height=20)
print(orjson.dumps(rectangle, default=default))

=>

{
    "width":10,
    "height":20
}

1
tôi cũng là một người hâm mộ orjson.
CircleOnCircles

0

Đây là một câu hỏi cũ. Tuy nhiên:

Một gợi ý cho tất cả những ai có cùng câu hỏi, hãy suy nghĩ kỹ về việc sử dụng bất kỳ tính năng riêng tư hoặc nội bộ nào của NamedTuplebởi vì chúng đã có trước đó và sẽ thay đổi lại theo thời gian.

Ví dụ: nếu của bạn NamedTuplelà một đối tượng có giá trị phẳng và bạn chỉ quan tâm đến việc tuần tự hóa nó chứ không phải trong trường hợp nó được lồng vào một đối tượng khác, bạn có thể tránh những rắc rối xảy ra với __dict__việc xóa hoặc _as_dict()thay đổi và chỉ cần làm điều gì đó như (và vâng, đây là Python 3 vì câu trả lời này là cho hiện tại):

from typing import NamedTuple

class ApiListRequest(NamedTuple):
  group: str="default"
  filter: str="*"

  def to_dict(self):
    return {
      'group': self.group,
      'filter': self.filter,
    }

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

Tôi đã cố gắng sử dụng defaultkwarg có thể gọi dumpsđể thực hiện to_dict()cuộc gọi nếu có, nhưng nó không được gọi vì NamedTuplecó thể chuyển đổi thành danh sách.


3
_asdictlà một phần của API công khai têntuple. Họ giải thích lý do cho dấu gạch dưới docs.python.org/3.7/library/… "Ngoài các phương thức kế thừa từ các bộ giá trị, các bộ giá trị được đặt tên hỗ trợ ba phương thức bổ sung và hai thuộc tính. Để tránh xung đột với tên trường, tên phương thức và thuộc tính bắt đầu bằng dấu gạch dưới. "
quant_dev

@quant_dev cảm ơn, tôi không thấy lời giải thích đó. Nó không đảm bảo cho sự ổn định của api, nhưng nó giúp làm cho những phương pháp đó đáng tin cậy hơn. Tôi làm như khả năng đọc to_dict rõ ràng, nhưng tôi có thể thấy nó có vẻ như reimplementing _as_dict
dlamblin

0

Đây là vấn đề của tôi. Nó tuần tự hóa NamedTuple, chăm sóc các NamedTuples và Danh sách được gấp lại bên trong chúng

def recursive_to_dict(obj: Any) -> dict:
_dict = {}

if isinstance(obj, tuple):
    node = obj._asdict()
    for item in node:
        if isinstance(node[item], list): # Process as a list
            _dict[item] = [recursive_to_dict(x) for x in (node[item])]
        elif getattr(node[item], "_asdict", False): # Process as a NamedTuple
            _dict[item] = recursive_to_dict(node[item])
        else: # Process as a regular element
            _dict[item] = (node[item])
return _dict

0

simplejson.dump()thay vì json.dumpthực hiện công việc. Nó có thể chậm hơn.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.