XPath для JSON

Published by @newtover on 2017-04-05

From http://tarelkin.livejournal.com/6315.html

Jul. 6th, 2010 10:14 pm

По разным причинам иногда хочется уметь делать xpath-запросы к JSON. Основной посыл, понятное дело, - дать возможность снаружи (клиенту) описать, откуда в структуре взять данные.
Хочется обычно в питоне, поэтому делать буду на питоне (если буду), но вопросы встают одинаковые для любой реализации.
Тут буду пока эти вопросы и ответы копить.

+++ Зачем надо
»> XPath(“[@_=’boo’]”)({‘boo’:1, ‘foo’:2})
[1]
»> XPath(“
[@=’foo’]/*”)({‘boo’:1, ‘foo’:[‘asd’, ‘zxc’]})
[‘asd’, ‘zxc’]
»> people = {‘people’:[{‘name’:’Bill’}, {‘name’:’John’}]}
»> XPath(“*[@
=’people’]/*[@_=’name’]”)(people)
[‘Bill’, ‘John’]

+++ Отображение на XML
Для начала надо придумать, как JSON отобразить на XML, потому как xpath смотрит на документ, как на XML (ограничения на имена, атрибуты, неймспейсы). Интернеты уже предлагают несколько вариантов, но они мне не нравятся, поэтому буду выдумывать свое.

Дано:

* хочется мапить имена полей объекта на имена элементов или атрибутов, но:
    * имя поля - произвольная строка, имя элемента - строка далеко не произвольная ("", "::::", "&@ $%&" вполне себе легальные имена пропертей в JSON)
    * нужно придумывать имя корневому элементу
    * нужно придумывать имена членам массива

* порядок ключей в объекте не определен, поэтому логично мапить объект на атрибуты, но:
    * значения могут быть составными (объекты, массивы), поэтому часть все равно придется отображать элементами
    * пункт про имена никуда не делся

* хочется сохранять знание о типе
    * недурно было бы имя это имя использовать в названии элемента (как в каком-нибудь wddx)
    * хочется уметь доставать значения элементов одним и тем же выражением без указания типа; объединяющим именем в грамматике json (на json.org) является value, но оно слишком длинное, поэтому сократим до val
    * вместо единого имени в выражениях можно использовать КритерийИмени *
    * если класть название типа в имя, то можно избежать лишнего атрибута, кроме того sum(number|string) мне нравится больше, чем sum(*[@type='number' or @type='string'])

* как записывать значения
    * с числами проблем нет: <object><number @_="a">1.2</number><number @_="b">1.3</number></object>, sum(/object/number) - ответ предсказуем
    * со строками тоже проблем нет: <object><string @_="a">1.2</string><string @_="b">1.3</string></object>, concat(*[@_="a"], *[@_="b"]) - тоже предсказуемо
    * хуже с null:
        * нужно определить результат sum(null) и concat(null[@_="a"],null[@_="b"]) 
        * в JS [Number(null), String(null)] => [0, 'null'],
        * в питоне None к числу не приводится, а str(None) => 'None'
        * считаю логичным, если sum(null)будет давать NaN, а concat - 'nullnull', то есть в тело кладем null: null
    * true, false
        * проблема та же
        * в JS [String(true),String(false),Number(true),Number(false)] => ["true", "false", 1, 0]
        * в питоне [str(True),str(False),float(True),float(False)] => ['True', 'False', 1.0, 0.0]
        * считаю логичным в тело положить true и false соответственно, так как числового эффекта легко достичь через sum(number|string) + count(true)

Все это навело меня на мысль, что:

* имя элемента будем брать из грамматики json: object, array, string, number, true, false, null
* значение будет лежать внутри элемента
* имя проперти положим в какой-нибудь очень короткий атрибут, например @_
* для элементов null, true, false имя элемента дублируется в текстовое значение: true
* нет проблем с неймспейсами, так как их нет
* порядок элементов внутри object не определен, но (надеюсь, всегда) постоянный

Кроме того:

* Во что бы отобразить NaN в питоне: в питоне, оказывается, тоже есть NaN. Получить его ненароком можно даже в 2.5, но функция проверки isnan появилась только в 2.6. Думаю, это может стать требованием в версии питона.

2015-2017 Mokum.place