amarao (amarao_san) wrote,
amarao
amarao_san

JSONStream

Итак, я капитально застрял в написании json4shell на попытке сделать нормальную поддержку "выковыривания" целых словарей из списка по мере их появления.

Основная проблема - хочется иметь генерализованное решение. Для этого надо понимать, как и что возвращает JSONStream. Поскольку я ощущаю некоторый туман в голове в этом вопросе, буду писать этот текст (насколько я понимаю, единственный по JSONStream, т.к. я ничего не нашёл - если кто-то знает, буду благодарен за ссылку).

Итак, что делает jsonstream? Он создаёт генератор событий по мере чтения содержимого из "файлоподобного" генератора.

Тривиальный код выглядит так:

def filestream():
    while True:
        line = sys.stdin.readline()
        if not line:
            break # EOF
        yield line


for o in JSONStream(filestream()):
     print o


А вывод вот так: (слева json, справа вывод)


[1,2,3]

((), [])
((0,), 1)
((1,), 2)
((2,), 3)

[
{"1":"b"},
{"2":"c"},
{"3":{"d":{"e1":"e"}}},
{"4":["f","g"]},
{"5":"h"}
]

((), [])
((0,), {})
((0, u'1'), u'b')
((1,), {})
((1, u'2'), u'c')
((2,), {})
((2, u'3'), {})
((2, u'3', u'd'), {})
((2, u'3', u'd', u'e1'), u'e')
((3,), {})
((3, u'4'), [])
((3, u'4', 0), u'f')
((3, u'4', 1), u'g')
((4,), {})
((4, u'5'), u'h')

[
{"a1":"11","a2":"12","a3":"13" },
{"a1":"21","a2":"22","a3":"23" },
{"a1":"31","a2":"32","a3":"33" }
]
{
"a":"b",
"c":"d"
}


((), [])
((0,), {})
((0, u'a1'), u'11')
((0, u'a2'), u'12')
((0, u'a3'), u'13')
((1,), {})
((1, u'a1'), u'21')
((1, u'a2'), u'22')
((1, u'a3'), u'23')
((2,), {})
((2, u'a1'), u'31')
((2, u'a2'), u'32')
((2, u'a3'), u'33')
((), {})
((u'a',), u'b')
((u'c',), u'd')


Что можно увидеть в этом выводе? Нам возвращают пары элементов: положение в дереве и новое значение в дереве. Со значением всё понятно - если нам возвращают лист, то это лист. Если в новом узле находится ветвление, то возвращают тип ветвления - то есть словарь или список (объект или массив в терминологии json).

С путём всё интереснее. () - корень.
Дальше нам перечисляют путь, причём, внимание, и это важно, путь не различается между путём в словаре и путём в списке - и там и там фигурирует ключ, но в случае словаря это настоящий ключ, а в случае списка - номер элемента в массиве (начиная с нуля).

Ключевым тут является то, что "возврат" по дереву вверх не фиксируется, нам просто приходит другой ключ.

Поскольку туман в голове не до конца развеялся, я начну с того, что реализую "собиралку" таких эвентов. В принципе, у нас есть готовый метод jsonstream.assembled(), который из такого набора как раз и соберёт всё, что нужно:

a=[o for o in jsonstream.JSONStream(file('sample_data'))]
jsonstream.assembled(a)
Заметим, он честно перезатирает состояние дерева, так что если мы ему скормим стопку json'ов, которые нам честно распарсят, он поймёт там только последний.

Однако, в рамках борьбы с туманом в голове, напишем аналог assembled сами.

Кажется, я нашёл суть проблемы: хочется иметь возможность поменять значение по ссылке. Питон такое позволяет только для меняющихся объектов (то есть можно передать список и поменять в нём значение, но нельзя "поменять" сам список, то есть заменить его другим). Таким образом, простейшая рекурсия, когда нам передают указатель на 'current' и путь, с постепенным обрезанием пути, не работает.

Я подсмотрел в assembled, он основан на _merged. Там используется грязный трюк - поскольку мы не можем менять значение по указателю (нету указателей), то давайте его возвращать! А нам его обратно передавать будут.

Выглядит это так: key (путь), obj - то, что мы конструируем, value понятно.

Опуская часть, связанную с присваиваниями:

obj[k] = _merged(obj[k], key[1:], value)
return obj

Другими словами, у нас нет статического "дерева", по которому мы шаримся, вместо этого есть объект, который мы получаем, меняем и создаём. Поскольку мы не можем получить указатель на "к-тый элемент в массиве" (так, чтобы можно было его поменять), вместо этого мы получаем массив и "к", меняем к-тый элемент и массив возвращаем.

Грязновато, но компактно. Пожалуй, первую проблему, с которой я столкнулся и на которой затупил, я решил. Вместо "current" будем использовать объект, который будем менять в оборачивающей (enveloping) функции.

В грубом виде получается такой вот рекурсер:

def filestream():
    while True:
        line = sys.stdin.readline()
        if not line:
            break # EOF
        yield line

class JStore:
    def __init__(self, sequence):
        self.value = None
        for e in sequence:
            self.value =  self.add(self.value, e[0], e[1])

    def add(self, obj, path, value):
        if obj is None or not path:
            return value

        if len(path)==1:
            while(len(obj)<=path[0] and type(obj)==list):
                obj.append(None)
            obj[path[0]]=value
        else:
            obj[path[0]]=self.add(obj[path[0]], path[1:], value)
        return obj

    def res(self):
        return self.value


a= [o for o in JSONStream(filestream())]
print "input", a
b=JStore(a)
print "assembed", assembled(a)
print "jstore  ", b.res()


У меня по тестам работает как надо. Теперь надо из скучного режима "собрать всё обратно" сделать интеллектуальный режим "yield'ить словари".

Но, про это в следующем посте.
Tags: json4shell, jsonstream, python, программирование
Subscribe

  • Post a new comment

    Error

    default userpic

    Your IP address will be recorded 

    When you submit the form an invisible reCAPTCHA check will be performed.
    You must follow the Privacy Policy and Google Terms of use.
  • 0 comments