Làm cách nào để tôi có thể đọc nhiều giá trị JSON từ một tệp / luồng bằng Python một cách lười biếng?


100

Tôi muốn đọc nhiều đối tượng JSON từ một tệp / luồng bằng Python, từng đối tượng một. Thật không may json.load()chỉ .read()s cho đến khi kết thúc tập tin; dường như không có bất kỳ cách nào để sử dụng nó để đọc một đối tượng hoặc để lặp lại các đối tượng một cách lười biếng.

Có cách nào để làm điều này? Sử dụng thư viện tiêu chuẩn sẽ là lý tưởng, nhưng nếu có thư viện của bên thứ ba, tôi sẽ sử dụng thư viện đó.

Hiện tại, tôi đang đặt mỗi đối tượng trên một dòng riêng biệt và sử dụng json.loads(f.readline()), nhưng tôi thực sự không muốn làm điều này.

Sử dụng ví dụ

example.py

import my_json as json
import sys

for o in json.iterload(sys.stdin):
    print("Working on a", type(o))

in.txt

{"foo": ["bar", "baz"]} 1 2 [] 4 5 6

phiên ví dụ

$ python3.2 example.py < in.txt
Working on a dict
Working on a int
Working on a int
Working on a list
Working on a int
Working on a int
Working on a int

Bạn có thể thêm một ví dụ về hành vi bạn muốn từ các đối tượng lồng nhau được không?
Tim McNamara,

@TimMcNamara: Hành vi của đối tượng lồng nhau không được thay đổi. Tuy nhiên, khi chúng ta đã đi đến phần cuối của đối tượng cấp cao nhất đầu tiên ( {"foo": ["bar", "baz"]}trong ví dụ của tôi), thì đối tượng đó sẽ yieldđến và sau đó tiếp tục đến đối tượng tiếp theo ( 1).
Jeremy

1
tại sao phải tránh "dòng json"? Luôn luôn có thể tuần tự hóa một đối tượng thành json sao cho nó không có '\n'(một dòng mới, không phải hai ký tự) trong biểu diễn json của nó bởi vì nó '\n'phải được thoát bên trong một chuỗi json và do đó chỉ '\n'có thể được sử dụng để định dạng, ví dụ, tôi tin là json.dumps()không ' t giới thiệu '\n'theo mặc định. Hãy lưu ý rằng các dòng mới Unicode như U + 0085 có thể không thoát bên trong chuỗi json.
jfs

2
Các ijson thư viện có thể hữu ích trong trường hợp này. pypi.python.org/pypi/ijson github.com/isagalaev/ijson
Boris Chervenkov

1
Tiêu đề không nên là "Làm cách nào để tôi có thể đọc nhiều giá trị JSON từ một tệp / luồng bằng Python một cách lười biếng ?" Vì một đối tượng cũng là một giá trị cũng như json int, string, v.v. trong khi điều ngược lại là không cần thiết đúng không?
hetepeperfan

Câu trả lời:


20

Đây là một giải pháp đơn giản hơn nhiều. Bí quyết là thử, thất bại và sử dụng thông tin trong ngoại lệ để phân tích cú pháp chính xác. Hạn chế duy nhất là tệp phải có thể tìm kiếm được.

def stream_read_json(fn):
    import json
    start_pos = 0
    with open(fn, 'r') as f:
        while True:
            try:
                obj = json.load(f)
                yield obj
                return
            except json.JSONDecodeError as e:
                f.seek(start_pos)
                json_str = f.read(e.pos)
                obj = json.loads(json_str)
                start_pos += e.pos
                yield obj

Chỉnh sửa: chỉ cần lưu ý rằng điều này sẽ chỉ hoạt động cho Python> = 3.5. Trước đó, các lỗi trả về một ValueError và bạn phải phân tích cú pháp vị trí từ chuỗi, ví dụ:

def stream_read_json(fn):
    import json
    import re
    start_pos = 0
    with open(fn, 'r') as f:
        while True:
            try:
                obj = json.load(f)
                yield obj
                return
            except ValueError as e:
                f.seek(start_pos)
                end_pos = int(re.match('Extra data: line \d+ column \d+ .*\(char (\d+).*\)',
                                    e.args[0]).groups()[0])
                json_str = f.read(end_pos)
                obj = json.loads(json_str)
                start_pos += end_pos
                yield obj

Chào mừng bạn đến với Stack Overflow và cảm ơn vì câu trả lời! Điều đó gần hơn nhiều so với những gì tôi hy vọng sẽ tìm thấy. Tôi sẽ có thể điều chỉnh điều này cho các loại trường hợp mà tôi đã nghĩ đến, ngay cả khi họ không trực tiếp cung cấp việc tìm kiếm.
Jeremy

Điều đó resẽ không hoạt động - các dấu gạch chéo ngược cần phải thoát. Hãy xem xét một chuỗi thô r'...'.
Tom Swirly

2
Tôi cần thiết này cho công việc của riêng tôi, vì vậy II tạo ra một chút thư viện python để làm điều này, sử dụng nhiều hay ít kỹ thuật của bạn với một số chi tiết, và nó ở đây: pypi.python.org/pypi/Streamy
Tom xoáy

2
Nếu bạn sử dụng ujsonthay vì jsonbạn sẽ nhận được sự tăng tốc rất lớn
OddNorg

39

JSON thường không tốt cho loại sử dụng gia tăng này; không có cách tiêu chuẩn nào để nối tiếp nhiều đối tượng để chúng có thể dễ dàng tải từng đối tượng một mà không cần phân tích cú pháp toàn bộ.

Giải pháp đối tượng trên mỗi dòng mà bạn đang sử dụng cũng được nhìn thấy ở những nơi khác. Scrapy gọi nó là 'dòng JSON':

Bạn có thể làm điều đó theo kiểu Pythoical hơn một chút:

for jsonline in f:
    yield json.loads(jsonline)   # or do the processing in this loop

Tôi nghĩ đây là cách tốt nhất - nó không dựa vào bất kỳ thư viện nào của bên thứ ba và rất dễ hiểu những gì đang xảy ra. Tôi cũng đã sử dụng nó trong một số mã của riêng mình.


4
re: "no standard way": Tôi không thấy vấn đề, cú pháp dường như làm cho nhiều đối tượng liên tiếp không rõ ràng miễn là bạn có bộ đệm một ký tự. Cảm ơn bạn đã chỉ ra rằng những người khác sử dụng "dòng JSON", tôi cảm thấy đỡ tệ hơn khi sử dụng nó.
Jeremy,

30

Có thể hơi muộn, nhưng tôi đã gặp vấn đề chính xác này (tốt, ít nhiều). Giải pháp tiêu chuẩn của tôi cho những vấn đề này thường là chỉ thực hiện tách regex trên một số đối tượng gốc nổi tiếng, nhưng trong trường hợp của tôi thì điều đó là không thể. Cách khả thi duy nhất để thực hiện điều này một cách tổng quát là triển khai một trình phân bổ thích hợp .

Sau khi không tìm ra giải pháp đủ chung và hiệu quả hợp lý, tôi đã tự mình kết thúc việc này, viết splitstream mô-đun. Nó là một pre-tokenizer hiểu JSON và XML và chia một luồng liên tục thành nhiều phần để phân tích cú pháp (mặc dù vậy, việc phân tích cú pháp thực tế tùy thuộc vào bạn). Để có được một số loại hiệu suất từ ​​nó, nó được viết dưới dạng mô-đun C.

Thí dụ:

from splitstream import splitfile

for jsonstr in splitfile(sys.stdin, format="json")):
    yield json.loads(jsonstr)

Thật tuyệt vời. Cảm ơn vì đã chia sẻ nó.
Jeremy

Đây là giải pháp dứt điểm. Tôi hy vọng bạn tiếp tục cập nhật nó.
Bartvds

Nó chỉ đơn giản là hoạt động. Cảm ơn bạn đã cung cấp một mô-đun hữu ích như vậy.
Vinod Sharma

1
Bạn có thể tải lên phiên bản .py đã biên dịch không? Tôi đã cố gắng xây dựng và cài đặt mô-đun nhưng ... nó tạo ra một loạt lỗi liên quan đến việc xác định lại các hằng số và như vậy.
SirJames

Mô-đun được viết bằng C. Việc chuyển nó sang Python thuần túy được để lại như một bài tập cho bất kỳ ai thực hiện nhiệm vụ :). Nó có thể sẽ quá chậm so với mục đích mà nó đã được viết. Nếu bạn gặp sự cố khi biên dịch, bạn có thể cần cài đặt gói python-dev.
Krumelur

25

Chắc chắn bạn có thể làm được điều này. Bạn chỉ cần đến raw_decodetrực tiếp. Việc triển khai này tải toàn bộ tệp vào bộ nhớ và hoạt động trên chuỗi đó (nhiều như json.loadvậy); nếu bạn có tệp lớn, bạn có thể sửa đổi nó để chỉ đọc từ tệp khi cần thiết mà không gặp nhiều khó khăn.

import json
from json.decoder import WHITESPACE

def iterload(string_or_fp, cls=json.JSONDecoder, **kwargs):
    if isinstance(string_or_fp, file):
        string = string_or_fp.read()
    else:
        string = str(string_or_fp)

    decoder = cls(**kwargs)
    idx = WHITESPACE.match(string, 0).end()
    while idx < len(string):
        obj, end = decoder.raw_decode(string, idx)
        yield obj
        idx = WHITESPACE.match(string, end).end()

Sử dụng: đúng như bạn yêu cầu, đó là một máy phát điện.


2
Có vẻ như phần khó khăn sẽ là đảm bảo rằng các lần đọc trực tuyến mang lại đủ tệp mà bạn có toàn bộ đối tượng để giải mã. Vì vậy, đây là một cách tiếp cận đơn giản hiệu quả nếu bạn giả sử các đối tượng không bao giờ có dòng mới trong chúng. Nhưng trừ khi bạn áp đặt rằng loại cấu trúc bổ sung về các tập tin, mà OP đang cố gắng tránh, có vẻ như bạn cần một giải pháp như vậy từ @Benedict
nealmcb

24

Đây thực sự là một vấn đề khá khó chịu vì bạn phải phân luồng theo dòng, nhưng mẫu khớp trên nhiều dòng với dấu ngoặc nhọn, nhưng cũng khớp mẫu với json. Đó là một loại phân tích json, theo sau là phân tích cú pháp json. Json, so với các định dạng khác, dễ phân tích cú pháp nên không phải lúc nào cũng cần đến thư viện phân tích cú pháp, tuy nhiên, chúng ta nên giải quyết những vấn đề mâu thuẫn này như thế nào?

Máy phát điện để giải cứu!

Cái hay của máy phát điện đối với một vấn đề như thế này là bạn có thể xếp chúng chồng lên nhau, dần dần loại bỏ khó khăn của vấn đề trong khi duy trì sự lười biếng. Tôi cũng đã xem xét việc sử dụng cơ chế chuyển lại các giá trị vào trình tạo (send ()) nhưng may mắn thay, tôi không cần phải sử dụng điều đó.

Để giải quyết vấn đề đầu tiên, bạn cần một số loại streamingfinditer, như một phiên bản phát trực tuyến của re.finditer. Nỗ lực của tôi về điều này bên dưới kéo các dòng khi cần thiết (bỏ ghi chú câu lệnh gỡ lỗi để xem) trong khi vẫn trả về các kết quả phù hợp. Sau đó, tôi thực sự đã sửa đổi nó một chút để mang lại các dòng không khớp cũng như các dòng trùng khớp (được đánh dấu là 0 hoặc 1 trong phần đầu tiên của bộ giá trị được kết quả).

import re

def streamingfinditer(pat,stream):
  for s in stream:
#    print "Read next line: " + s
    while 1:
      m = re.search(pat,s)
      if not m:
        yield (0,s)
        break
      yield (1,m.group())
      s = re.split(pat,s,1)[1]

Với điều đó, sau đó có thể khớp cho đến khi niềng răng, tính toán từng thời điểm xem các mắc cài có cân bằng hay không và sau đó trả về các đối tượng đơn giản hoặc phức hợp nếu thích hợp.

braces='{}[]'
whitespaceesc=' \t'
bracesesc='\\'+'\\'.join(braces)
balancemap=dict(zip(braces,[1,-1,1,-1]))
bracespat='['+bracesesc+']'
nobracespat='[^'+bracesesc+']*'
untilbracespat=nobracespat+bracespat

def simpleorcompoundobjects(stream):
  obj = ""
  unbalanced = 0
  for (c,m) in streamingfinditer(re.compile(untilbracespat),stream):
    if (c == 0): # remainder of line returned, nothing interesting
      if (unbalanced == 0):
        yield (0,m)
      else:
        obj += m
    if (c == 1): # match returned
      if (unbalanced == 0):
        yield (0,m[:-1])
        obj += m[-1]
      else:
        obj += m
      unbalanced += balancemap[m[-1]]
      if (unbalanced == 0):
        yield (1,obj)
        obj="" 

Điều này trả về các bộ giá trị như sau:

(0,"String of simple non-braced objects easy to parse")
(1,"{ 'Compound' : 'objects' }")

Về cơ bản, đó là phần khó chịu được thực hiện. Bây giờ chúng ta chỉ cần thực hiện mức phân tích cú pháp cuối cùng khi chúng ta thấy phù hợp. Ví dụ, chúng ta có thể sử dụng hàm iterload của Jeremy Roman (Cảm ơn!) Để phân tích cú pháp cho một dòng:

def streamingiterload(stream):
  for c,o in simpleorcompoundobjects(stream):
    for x in iterload(o):
      yield x 

Kiểm tra nó:

of = open("test.json","w") 
of.write("""[ "hello" ] { "goodbye" : 1 } 1 2 {
} 2
9 78
 4 5 { "animals" : [ "dog" , "lots of mice" ,
 "cat" ] }
""")
of.close()
// open & stream the json
f = open("test.json","r")
for o in streamingiterload(f.readlines()):
  print o
f.close()

Tôi nhận được những kết quả này (và nếu bạn bật dòng gỡ lỗi đó, bạn sẽ thấy nó kéo các dòng khi cần thiết):

[u'hello']
{u'goodbye': 1}
1
2
{}
2
9
78
4
5
{u'animals': [u'dog', u'lots of mice', u'cat']}

Điều này sẽ không hoạt động cho mọi tình huống. Do việc triển khai jsonthư viện, không thể hoạt động hoàn toàn chính xác nếu không tự mình thực hiện lại trình phân tích cú pháp.


8
Nếu bạn muốn thực hiện điều này một cách chính xác, bạn cũng cần phải chú ý đến các mắc cài và dấu ngoặc nhọn bên trong chuỗi. Và sau đó cũng để ý các dấu ngoặc kép bị thoát. Trước khi bạn biết điều đó, “preparser” sẽ gần như phức tạp như một trình phân tích cú pháp JSON đầy đủ.
Petr Viktorin

Cảm ơn Jeremy. Đó là một thử thách hay của một câu hỏi! Có Petr - tất nhiên bạn hoàn toàn đúng :)
Benedict

1
Tốt lắm. Điều này có hoạt động chính xác không nếu các ký tự thích "}""]"xuất hiện bên trong chuỗi JSON? Tôi nghĩ rằng đây là một hạn chế chung của việc phân tích cú pháp với regex.
Thomas K,

2
Khi thăm dò xung quanh, tôi thấy rằng chức năng phân tích cú pháp chính được xây dựng theo cách mà không thể sử dụng nó một cách lười biếng, vì vậy bạn sẽ không nhận được kết quả hoàn hảo nếu không tự mình thực hiện một trình phân tích cú pháp hoàn chỉnh. Câu trả lời này thể hiện một số điều hữu ích có liên quan và xử lý các trường hợp đơn giản một cách độc đáo.
Jeremy

3
Câu trả lời này thật kinh khủng và tôi không biết tại sao nó lại được ủng hộ. Tác giả thừa nhận rằng nó không thực sự hoạt động cho tất cả các đầu vào nên theo định nghĩa, nó thậm chí không phải là một câu trả lời đúng và nó sử dụng một biểu thức chính quy phức tạp được tính toán , vì vậy chúng tôi thậm chí không thể đọc nó là gì. Điều gì tốt là một hàm đôi khi cho kết quả đúng?
Tom Swirly

10

Tôi tin rằng một cách tốt hơn để làm điều đó là sử dụng một cỗ máy trạng thái. Dưới đây là mã mẫu mà tôi đã tìm ra bằng cách chuyển đổi mã NodeJS trên liên kết bên dưới sang Python 3 (từ khóa phi địa phương được sử dụng chỉ có sẵn trong Python 3, mã sẽ không hoạt động trên Python 2)

Chỉnh sửa-1: Đã cập nhật và tạo mã tương thích với Python 2

Chỉnh sửa-2: Đã cập nhật và thêm phiên bản chỉ Python3

https://gist.github.com/creationix/5992451

Phiên bản chỉ dành cho Python 3

# A streaming byte oriented JSON parser.  Feed it a single byte at a time and
# it will emit complete objects as it comes across them.  Whitespace within and
# between objects is ignored.  This means it can parse newline delimited JSON.
import math


def json_machine(emit, next_func=None):
    def _value(byte_data):
        if not byte_data:
            return

        if byte_data == 0x09 or byte_data == 0x0a or byte_data == 0x0d or byte_data == 0x20:
            return _value  # Ignore whitespace

        if byte_data == 0x22:  # "
            return string_machine(on_value)

        if byte_data == 0x2d or (0x30 <= byte_data < 0x40):  # - or 0-9
            return number_machine(byte_data, on_number)

        if byte_data == 0x7b:  #:
            return object_machine(on_value)

        if byte_data == 0x5b:  # [
            return array_machine(on_value)

        if byte_data == 0x74:  # t
            return constant_machine(TRUE, True, on_value)

        if byte_data == 0x66:  # f
            return constant_machine(FALSE, False, on_value)

        if byte_data == 0x6e:  # n
            return constant_machine(NULL, None, on_value)

        if next_func == _value:
            raise Exception("Unexpected 0x" + str(byte_data))

        return next_func(byte_data)

    def on_value(value):
        emit(value)
        return next_func

    def on_number(number, byte):
        emit(number)
        return _value(byte)

    next_func = next_func or _value
    return _value


TRUE = [0x72, 0x75, 0x65]
FALSE = [0x61, 0x6c, 0x73, 0x65]
NULL = [0x75, 0x6c, 0x6c]


def constant_machine(bytes_data, value, emit):
    i = 0
    length = len(bytes_data)

    def _constant(byte_data):
        nonlocal i
        if byte_data != bytes_data[i]:
            i += 1
            raise Exception("Unexpected 0x" + str(byte_data))

        i += 1
        if i < length:
            return _constant
        return emit(value)

    return _constant


def string_machine(emit):
    string = ""

    def _string(byte_data):
        nonlocal string

        if byte_data == 0x22:  # "
            return emit(string)

        if byte_data == 0x5c:  # \
            return _escaped_string

        if byte_data & 0x80:  # UTF-8 handling
            return utf8_machine(byte_data, on_char_code)

        if byte_data < 0x20:  # ASCII control character
            raise Exception("Unexpected control character: 0x" + str(byte_data))

        string += chr(byte_data)
        return _string

    def _escaped_string(byte_data):
        nonlocal string

        if byte_data == 0x22 or byte_data == 0x5c or byte_data == 0x2f:  # " \ /
            string += chr(byte_data)
            return _string

        if byte_data == 0x62:  # b
            string += "\b"
            return _string

        if byte_data == 0x66:  # f
            string += "\f"
            return _string

        if byte_data == 0x6e:  # n
            string += "\n"
            return _string

        if byte_data == 0x72:  # r
            string += "\r"
            return _string

        if byte_data == 0x74:  # t
            string += "\t"
            return _string

        if byte_data == 0x75:  # u
            return hex_machine(on_char_code)

    def on_char_code(char_code):
        nonlocal string
        string += chr(char_code)
        return _string

    return _string


# Nestable state machine for UTF-8 Decoding.
def utf8_machine(byte_data, emit):
    left = 0
    num = 0

    def _utf8(byte_data):
        nonlocal num, left
        if (byte_data & 0xc0) != 0x80:
            raise Exception("Invalid byte in UTF-8 character: 0x" + byte_data.toString(16))

        left = left - 1

        num |= (byte_data & 0x3f) << (left * 6)
        if left:
            return _utf8
        return emit(num)

    if 0xc0 <= byte_data < 0xe0:  # 2-byte UTF-8 Character
        left = 1
        num = (byte_data & 0x1f) << 6
        return _utf8

    if 0xe0 <= byte_data < 0xf0:  # 3-byte UTF-8 Character
        left = 2
        num = (byte_data & 0xf) << 12
        return _utf8

    if 0xf0 <= byte_data < 0xf8:  # 4-byte UTF-8 Character
        left = 3
        num = (byte_data & 0x07) << 18
        return _utf8

    raise Exception("Invalid byte in UTF-8 string: 0x" + str(byte_data))


# Nestable state machine for hex escaped characters
def hex_machine(emit):
    left = 4
    num = 0

    def _hex(byte_data):
        nonlocal num, left

        if 0x30 <= byte_data < 0x40:
            i = byte_data - 0x30
        elif 0x61 <= byte_data <= 0x66:
            i = byte_data - 0x57
        elif 0x41 <= byte_data <= 0x46:
            i = byte_data - 0x37
        else:
            raise Exception("Expected hex char in string hex escape")

        left -= 1
        num |= i << (left * 4)

        if left:
            return _hex
        return emit(num)

    return _hex


def number_machine(byte_data, emit):
    sign = 1
    number = 0
    decimal = 0
    esign = 1
    exponent = 0

    def _mid(byte_data):
        if byte_data == 0x2e:  # .
            return _decimal

        return _later(byte_data)

    def _number(byte_data):
        nonlocal number
        if 0x30 <= byte_data < 0x40:
            number = number * 10 + (byte_data - 0x30)
            return _number

        return _mid(byte_data)

    def _start(byte_data):
        if byte_data == 0x30:
            return _mid

        if 0x30 < byte_data < 0x40:
            return _number(byte_data)

        raise Exception("Invalid number: 0x" + str(byte_data))

    if byte_data == 0x2d:  # -
        sign = -1
        return _start

    def _decimal(byte_data):
        nonlocal decimal
        if 0x30 <= byte_data < 0x40:
            decimal = (decimal + byte_data - 0x30) / 10
            return _decimal

        return _later(byte_data)

    def _later(byte_data):
        if byte_data == 0x45 or byte_data == 0x65:  # E e
            return _esign

        return _done(byte_data)

    def _esign(byte_data):
        nonlocal esign
        if byte_data == 0x2b:  # +
            return _exponent

        if byte_data == 0x2d:  # -
            esign = -1
            return _exponent

        return _exponent(byte_data)

    def _exponent(byte_data):
        nonlocal exponent
        if 0x30 <= byte_data < 0x40:
            exponent = exponent * 10 + (byte_data - 0x30)
            return _exponent

        return _done(byte_data)

    def _done(byte_data):
        value = sign * (number + decimal)
        if exponent:
            value *= math.pow(10, esign * exponent)

        return emit(value, byte_data)

    return _start(byte_data)


def array_machine(emit):
    array_data = []

    def _array(byte_data):
        if byte_data == 0x5d:  # ]
            return emit(array_data)

        return json_machine(on_value, _comma)(byte_data)

    def on_value(value):
        array_data.append(value)

    def _comma(byte_data):
        if byte_data == 0x09 or byte_data == 0x0a or byte_data == 0x0d or byte_data == 0x20:
            return _comma  # Ignore whitespace

        if byte_data == 0x2c:  # ,
            return json_machine(on_value, _comma)

        if byte_data == 0x5d:  # ]
            return emit(array_data)

        raise Exception("Unexpected byte: 0x" + str(byte_data) + " in array body")

    return _array


def object_machine(emit):
    object_data = {}
    key = None

    def _object(byte_data):
        if byte_data == 0x7d:  #
            return emit(object_data)

        return _key(byte_data)

    def _key(byte_data):
        if byte_data == 0x09 or byte_data == 0x0a or byte_data == 0x0d or byte_data == 0x20:
            return _object  # Ignore whitespace

        if byte_data == 0x22:
            return string_machine(on_key)

        raise Exception("Unexpected byte: 0x" + str(byte_data))

    def on_key(result):
        nonlocal key
        key = result
        return _colon

    def _colon(byte_data):
        if byte_data == 0x09 or byte_data == 0x0a or byte_data == 0x0d or byte_data == 0x20:
            return _colon  # Ignore whitespace

        if byte_data == 0x3a:  # :
            return json_machine(on_value, _comma)

        raise Exception("Unexpected byte: 0x" + str(byte_data))

    def on_value(value):
        object_data[key] = value

    def _comma(byte_data):
        if byte_data == 0x09 or byte_data == 0x0a or byte_data == 0x0d or byte_data == 0x20:
            return _comma  # Ignore whitespace

        if byte_data == 0x2c:  # ,
            return _key

        if byte_data == 0x7d:  #
            return emit(object_data)

        raise Exception("Unexpected byte: 0x" + str(byte_data))

    return _object

Phiên bản tương thích Python 2

# A streaming byte oriented JSON parser.  Feed it a single byte at a time and
# it will emit complete objects as it comes across them.  Whitespace within and
# between objects is ignored.  This means it can parse newline delimited JSON.
import math


def json_machine(emit, next_func=None):
    def _value(byte_data):
        if not byte_data:
            return

        if byte_data == 0x09 or byte_data == 0x0a or byte_data == 0x0d or byte_data == 0x20:
            return _value  # Ignore whitespace

        if byte_data == 0x22:  # "
            return string_machine(on_value)

        if byte_data == 0x2d or (0x30 <= byte_data < 0x40):  # - or 0-9
            return number_machine(byte_data, on_number)

        if byte_data == 0x7b:  #:
            return object_machine(on_value)

        if byte_data == 0x5b:  # [
            return array_machine(on_value)

        if byte_data == 0x74:  # t
            return constant_machine(TRUE, True, on_value)

        if byte_data == 0x66:  # f
            return constant_machine(FALSE, False, on_value)

        if byte_data == 0x6e:  # n
            return constant_machine(NULL, None, on_value)

        if next_func == _value:
            raise Exception("Unexpected 0x" + str(byte_data))

        return next_func(byte_data)

    def on_value(value):
        emit(value)
        return next_func

    def on_number(number, byte):
        emit(number)
        return _value(byte)

    next_func = next_func or _value
    return _value


TRUE = [0x72, 0x75, 0x65]
FALSE = [0x61, 0x6c, 0x73, 0x65]
NULL = [0x75, 0x6c, 0x6c]


def constant_machine(bytes_data, value, emit):
    local_data = {"i": 0, "length": len(bytes_data)}

    def _constant(byte_data):
        # nonlocal i, length
        if byte_data != bytes_data[local_data["i"]]:
            local_data["i"] += 1
            raise Exception("Unexpected 0x" + byte_data.toString(16))

        local_data["i"] += 1

        if local_data["i"] < local_data["length"]:
            return _constant
        return emit(value)

    return _constant


def string_machine(emit):
    local_data = {"string": ""}

    def _string(byte_data):
        # nonlocal string

        if byte_data == 0x22:  # "
            return emit(local_data["string"])

        if byte_data == 0x5c:  # \
            return _escaped_string

        if byte_data & 0x80:  # UTF-8 handling
            return utf8_machine(byte_data, on_char_code)

        if byte_data < 0x20:  # ASCII control character
            raise Exception("Unexpected control character: 0x" + byte_data.toString(16))

        local_data["string"] += chr(byte_data)
        return _string

    def _escaped_string(byte_data):
        # nonlocal string

        if byte_data == 0x22 or byte_data == 0x5c or byte_data == 0x2f:  # " \ /
            local_data["string"] += chr(byte_data)
            return _string

        if byte_data == 0x62:  # b
            local_data["string"] += "\b"
            return _string

        if byte_data == 0x66:  # f
            local_data["string"] += "\f"
            return _string

        if byte_data == 0x6e:  # n
            local_data["string"] += "\n"
            return _string

        if byte_data == 0x72:  # r
            local_data["string"] += "\r"
            return _string

        if byte_data == 0x74:  # t
            local_data["string"] += "\t"
            return _string

        if byte_data == 0x75:  # u
            return hex_machine(on_char_code)

    def on_char_code(char_code):
        # nonlocal string
        local_data["string"] += chr(char_code)
        return _string

    return _string


# Nestable state machine for UTF-8 Decoding.
def utf8_machine(byte_data, emit):
    local_data = {"left": 0, "num": 0}

    def _utf8(byte_data):
        # nonlocal num, left
        if (byte_data & 0xc0) != 0x80:
            raise Exception("Invalid byte in UTF-8 character: 0x" + byte_data.toString(16))

        local_data["left"] -= 1

        local_data["num"] |= (byte_data & 0x3f) << (local_data["left"] * 6)
        if local_data["left"]:
            return _utf8
        return emit(local_data["num"])

    if 0xc0 <= byte_data < 0xe0:  # 2-byte UTF-8 Character
        local_data["left"] = 1
        local_data["num"] = (byte_data & 0x1f) << 6
        return _utf8

    if 0xe0 <= byte_data < 0xf0:  # 3-byte UTF-8 Character
        local_data["left"] = 2
        local_data["num"] = (byte_data & 0xf) << 12
        return _utf8

    if 0xf0 <= byte_data < 0xf8:  # 4-byte UTF-8 Character
        local_data["left"] = 3
        local_data["num"] = (byte_data & 0x07) << 18
        return _utf8

    raise Exception("Invalid byte in UTF-8 string: 0x" + str(byte_data))


# Nestable state machine for hex escaped characters
def hex_machine(emit):
    local_data = {"left": 4, "num": 0}

    def _hex(byte_data):
        # nonlocal num, left
        i = 0  # Parse the hex byte
        if 0x30 <= byte_data < 0x40:
            i = byte_data - 0x30
        elif 0x61 <= byte_data <= 0x66:
            i = byte_data - 0x57
        elif 0x41 <= byte_data <= 0x46:
            i = byte_data - 0x37
        else:
            raise Exception("Expected hex char in string hex escape")

        local_data["left"] -= 1
        local_data["num"] |= i << (local_data["left"] * 4)

        if local_data["left"]:
            return _hex
        return emit(local_data["num"])

    return _hex


def number_machine(byte_data, emit):
    local_data = {"sign": 1, "number": 0, "decimal": 0, "esign": 1, "exponent": 0}

    def _mid(byte_data):
        if byte_data == 0x2e:  # .
            return _decimal

        return _later(byte_data)

    def _number(byte_data):
        # nonlocal number
        if 0x30 <= byte_data < 0x40:
            local_data["number"] = local_data["number"] * 10 + (byte_data - 0x30)
            return _number

        return _mid(byte_data)

    def _start(byte_data):
        if byte_data == 0x30:
            return _mid

        if 0x30 < byte_data < 0x40:
            return _number(byte_data)

        raise Exception("Invalid number: 0x" + byte_data.toString(16))

    if byte_data == 0x2d:  # -
        local_data["sign"] = -1
        return _start

    def _decimal(byte_data):
        # nonlocal decimal
        if 0x30 <= byte_data < 0x40:
            local_data["decimal"] = (local_data["decimal"] + byte_data - 0x30) / 10
            return _decimal

        return _later(byte_data)

    def _later(byte_data):
        if byte_data == 0x45 or byte_data == 0x65:  # E e
            return _esign

        return _done(byte_data)

    def _esign(byte_data):
        # nonlocal esign
        if byte_data == 0x2b:  # +
            return _exponent

        if byte_data == 0x2d:  # -
            local_data["esign"] = -1
            return _exponent

        return _exponent(byte_data)

    def _exponent(byte_data):
        # nonlocal exponent
        if 0x30 <= byte_data < 0x40:
            local_data["exponent"] = local_data["exponent"] * 10 + (byte_data - 0x30)
            return _exponent

        return _done(byte_data)

    def _done(byte_data):
        value = local_data["sign"] * (local_data["number"] + local_data["decimal"])
        if local_data["exponent"]:
            value *= math.pow(10, local_data["esign"] * local_data["exponent"])

        return emit(value, byte_data)

    return _start(byte_data)


def array_machine(emit):
    local_data = {"array_data": []}

    def _array(byte_data):
        if byte_data == 0x5d:  # ]
            return emit(local_data["array_data"])

        return json_machine(on_value, _comma)(byte_data)

    def on_value(value):
        # nonlocal array_data
        local_data["array_data"].append(value)

    def _comma(byte_data):
        if byte_data == 0x09 or byte_data == 0x0a or byte_data == 0x0d or byte_data == 0x20:
            return _comma  # Ignore whitespace

        if byte_data == 0x2c:  # ,
            return json_machine(on_value, _comma)

        if byte_data == 0x5d:  # ]
            return emit(local_data["array_data"])

        raise Exception("Unexpected byte: 0x" + str(byte_data) + " in array body")

    return _array


def object_machine(emit):
    local_data = {"object_data": {}, "key": ""}

    def _object(byte_data):
        # nonlocal object_data, key
        if byte_data == 0x7d:  #
            return emit(local_data["object_data"])

        return _key(byte_data)

    def _key(byte_data):
        if byte_data == 0x09 or byte_data == 0x0a or byte_data == 0x0d or byte_data == 0x20:
            return _object  # Ignore whitespace

        if byte_data == 0x22:
            return string_machine(on_key)

        raise Exception("Unexpected byte: 0x" + byte_data.toString(16))

    def on_key(result):
        # nonlocal object_data, key
        local_data["key"] = result
        return _colon

    def _colon(byte_data):
        # nonlocal object_data, key
        if byte_data == 0x09 or byte_data == 0x0a or byte_data == 0x0d or byte_data == 0x20:
            return _colon  # Ignore whitespace

        if byte_data == 0x3a:  # :
            return json_machine(on_value, _comma)

        raise Exception("Unexpected byte: 0x" + str(byte_data))

    def on_value(value):
        # nonlocal object_data, key
        local_data["object_data"][local_data["key"]] = value

    def _comma(byte_data):
        # nonlocal object_data
        if byte_data == 0x09 or byte_data == 0x0a or byte_data == 0x0d or byte_data == 0x20:
            return _comma  # Ignore whitespace

        if byte_data == 0x2c:  # ,
            return _key

        if byte_data == 0x7d:  #
            return emit(local_data["object_data"])

        raise Exception("Unexpected byte: 0x" + str(byte_data))

    return _object

Kiểm tra nó

if __name__ == "__main__":
    test_json = """[1,2,"3"] {"name": 
    "tarun"} 1 2 
    3 [{"name":"a", 
    "data": [1,
    null,2]}]
"""
    def found_json(data):
        print(data)

    state = json_machine(found_json)

    for char in test_json:
        state = state(ord(char))

Đầu ra của cùng là

[1, 2, '3']
{'name': 'tarun'}
1
2
3
[{'name': 'a', 'data': [1, None, 2]}]

Giải pháp tốt! Tôi sẽ xem xét kỹ hơn sau nhưng điều này rất hứa hẹn. Nhưng đối với những gì nó đáng giá, tôi thích phiên bản chỉ dành cho Python 3. Việc sử dụng di lệnh cho tất cả các biến cục bộ của bạn khá là khó xử và tôi rất vui khi rời khỏi Python 2 trong quá khứ. ;)
Jeremy

@JeremyBanks, chắc chắn rằng tôi không biết bạn đã nhắm mục tiêu phiên bản nào. Bây giờ tôi đã thêm phiên bản chỉ dành cho Python3 và phiên bản tương thích với Py2 cũng trong câu trả lời cho người khác có thể vẫn đang sử dụng Python 2
Tarun Lalwani

@JeremyBanks, chỉ 1 ngày nữa với tiền thưởng, hy vọng bạn có thể xem xét và đưa ra phản hồi về câu trả lời
Tarun Lalwani

Có vẻ như người duy nhất thực sự hiểu vấn đề là Tarun. Hiệu quả của việc phân tích cú pháp phụ thuộc vào số lần đi qua xảy ra trên đầu vào. Hầu hết các câu trả lời sử dụng regex hoặc đọc trước một dòng (điều này cũng có thể nguy hiểm) hoặc tệ hơn là không phân tích cú pháp một số lần không xác định. Thật tệ, đây không phải là một phần của Python.
mschonaker

4

Tôi muốn cung cấp một giải pháp. Ý nghĩ chính là "cố gắng" để giải mã: nếu nó không thành công, hãy cho nó thêm nguồn cấp dữ liệu, nếu không, hãy sử dụng thông tin bù đắp để chuẩn bị giải mã tiếp theo.

Tuy nhiên, mô-đun json hiện tại không thể dung nạp SPACE trong đầu chuỗi được giải mã, vì vậy tôi phải loại bỏ chúng.

import sys
import json

def iterload(file):
    buffer = ""
    dec = json.JSONDecoder()
    for line in file:         
        buffer = buffer.strip(" \n\r\t") + line.strip(" \n\r\t")
        while(True):
            try:
                r = dec.raw_decode(buffer)
            except:
                break
            yield r[0]
            buffer = buffer[r[1]:].strip(" \n\r\t")


for o in iterload(sys.stdin):
    print("Working on a", type(o),  o)

========================= Tôi đã kiểm tra một số tệp txt và nó hoạt động tốt. (in1.txt)

{"foo": ["bar", "baz"]
}
 1 2 [
  ]  4
{"foo1": ["bar1", {"foo2":{"A":1, "B":3}, "DDD":4}]
}
 5   6

(in2.txt)

{"foo"
: ["bar",
  "baz"]
  } 
1 2 [
] 4 5 6

(in.txt, tên viết tắt của bạn)

{"foo": ["bar", "baz"]} 1 2 [] 4 5 6

(đầu ra cho testcase của Benedict)

python test.py < in.txt
('Working on a', <type 'list'>, [u'hello'])
('Working on a', <type 'dict'>, {u'goodbye': 1})
('Working on a', <type 'int'>, 1)
('Working on a', <type 'int'>, 2)
('Working on a', <type 'dict'>, {})
('Working on a', <type 'int'>, 2)
('Working on a', <type 'int'>, 9)
('Working on a', <type 'int'>, 78)
('Working on a', <type 'int'>, 4)
('Working on a', <type 'int'>, 5)
('Working on a', <type 'dict'>, {u'animals': [u'dog', u'lots of mice', u'cat']})

3

Đây là của tôi:

import simplejson as json
from simplejson import JSONDecodeError
class StreamJsonListLoader():
    """
    When you have a big JSON file containint a list, such as

    [{
        ...
    },
    {
        ...
    },
    {
        ...
    },
    ...
    ]

    And it's too big to be practically loaded into memory and parsed by json.load,
    This class comes to the rescue. It lets you lazy-load the large json list.
    """

    def __init__(self, filename_or_stream):
        if type(filename_or_stream) == str:
            self.stream = open(filename_or_stream)
        else:
            self.stream = filename_or_stream

        if not self.stream.read(1) == '[':
            raise NotImplementedError('Only JSON-streams of lists (that start with a [) are supported.')

    def __iter__(self):
        return self

    def next(self):
        read_buffer = self.stream.read(1)
        while True:
            try:
                json_obj = json.loads(read_buffer)

                if not self.stream.read(1) in [',',']']:
                    raise Exception('JSON seems to be malformed: object is not followed by comma (,) or end of list (]).')
                return json_obj
            except JSONDecodeError:
                next_char = self.stream.read(1)
                read_buffer += next_char
                while next_char != '}':
                    next_char = self.stream.read(1)
                    if next_char == '':
                        raise StopIteration
                    read_buffer += next_char

Xin chào, điều này rất hữu ích nhưng bạn có thể chỉ cho tôi cách sử dụng lớp để tải tệp json không?
song0089

3

Tôi đã sử dụng giải pháp thanh lịch của @ wuilang. Cách tiếp cận đơn giản - đọc một byte, cố gắng giải mã, đọc một byte, cố gắng giải mã, ... - đã hoạt động, nhưng tiếc là nó rất chậm.

Trong trường hợp của tôi, tôi đang cố đọc các đối tượng JSON "in đẹp" của cùng một loại đối tượng từ một tệp. Điều này cho phép tôi tối ưu hóa cách tiếp cận; Tôi có thể đọc từng dòng của tệp, chỉ giải mã khi tôi tìm thấy một dòng chứa chính xác "}":

def iterload(stream):
    buf = ""
    dec = json.JSONDecoder()
    for line in stream:
        line = line.rstrip()
        buf = buf + line
        if line == "}":
            yield dec.raw_decode(buf)
            buf = ""

Nếu bạn tình cờ làm việc với JSON nhỏ gọn một dòng trên mỗi dòng thoát khỏi dòng mới trong chuỗi ký tự, thì bạn có thể đơn giản hóa cách tiếp cận này một cách an toàn hơn nữa:

def iterload(stream):
    dec = json.JSONDecoder()
    for line in stream:
        yield dec.raw_decode(line)

Rõ ràng, những cách tiếp cận đơn giản này chỉ hoạt động với các loại JSON rất cụ thể. Tuy nhiên, nếu những giả định này đúng thì các giải pháp này hoạt động chính xác và nhanh chóng.


2

Nếu bạn sử dụng phiên bản json.JSONDecoder, bạn có thể sử dụng raw_decodehàm thành viên. Nó trả về một loạt các biểu diễn python giá trị JSON và một chỉ mục đến nơi quá trình phân tích cú pháp dừng lại. Điều này giúp bạn dễ dàng cắt (hoặc tìm kiếm trong một đối tượng luồng) các giá trị JSON còn lại. Tôi không hài lòng lắm về vòng lặp while bổ sung để bỏ qua khoảng trắng giữa các giá trị JSON khác nhau trong đầu vào nhưng nó đã hoàn thành công việc theo ý kiến ​​của tôi.

import json

def yield_multiple_value(f):
    '''
    parses multiple JSON values from a file.
    '''
    vals_str = f.read()
    decoder = json.JSONDecoder()
    try:
        nread = 0
        while nread < len(vals_str):
            val, n = decoder.raw_decode(vals_str[nread:])
            nread += n
            # Skip over whitespace because of bug, below.
            while nread < len(vals_str) and vals_str[nread].isspace():
                nread += 1
            yield val
    except json.JSONDecodeError as e:
        pass
    return

Phiên bản tiếp theo ngắn hơn nhiều và ăn một phần của chuỗi đã được phân tích cú pháp. Có vẻ như vì lý do nào đó mà lệnh gọi json.JSONDecoder.raw_decode () thứ hai dường như không thành công khi ký tự đầu tiên trong chuỗi là khoảng trắng, đó cũng là lý do tại sao tôi bỏ qua khoảng trắng trong khoảng trắng ở trên ...

def yield_multiple_value(f):
    '''
    parses multiple JSON values from a file.
    '''
    vals_str = f.read()
    decoder = json.JSONDecoder()
    while vals_str:
        val, n = decoder.raw_decode(vals_str)
        #remove the read characters from the start.
        vals_str = vals_str[n:]
        # remove leading white space because a second call to decoder.raw_decode()
        # fails when the string starts with whitespace, and
        # I don't understand why...
        vals_str = vals_str.lstrip()
        yield val
    return

Trong tài liệu về lớp json.JSONDecoder, phương thức raw_decode https://docs.python.org/3/library/json.html#encoders-and-decoders chứa những điều sau:

Điều này có thể được sử dụng để giải mã tài liệu JSON từ một chuỗi có thể có dữ liệu không liên quan ở cuối.

Và dữ liệu không liên quan này có thể dễ dàng là một giá trị JSON khác. Nói cách khác, phương pháp này có thể được viết với mục đích này.

Với input.txt sử dụng hàm trên, tôi nhận được kết quả đầu ra ví dụ như được trình bày trong câu hỏi ban đầu.


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.