mirror of
https://github.com/httpie/cli.git
synced 2024-11-25 09:13:25 +01:00
df58ec683e
* Add support for nested JSON syntax (#1169) Co-authored-by: Batuhan Taskaya <isidentical@gmail.com> Co-authored-by: Jakub Roztocil <jakub@roztocil.co> * minor improvements * unpack top level lists * Write more docs * doc style changes * fix double quotes Co-authored-by: Mickaël Schoentgen <contact@tiger-222.fr> Co-authored-by: Jakub Roztocil <jakub@roztocil.co>
151 lines
4.4 KiB
Python
151 lines
4.4 KiB
Python
"""
|
|
Routines for JSON form syntax, used to support nested JSON request items.
|
|
|
|
Highly inspired from the great jarg project <https://github.com/jdp/jarg/blob/master/jarg/jsonform.py>.
|
|
"""
|
|
import re
|
|
import operator
|
|
from typing import Optional
|
|
|
|
|
|
def step(value: str, is_escaped: bool) -> str:
|
|
if is_escaped:
|
|
value = value.replace(r'\[', '[').replace(r'\]', ']')
|
|
return value
|
|
|
|
|
|
def find_opening_bracket(
|
|
value: str,
|
|
search=re.compile(r'(?<!\\)\[').search
|
|
) -> Optional[int]:
|
|
match = search(value)
|
|
if not match:
|
|
return None
|
|
return match.start()
|
|
|
|
|
|
def find_closing_bracket(
|
|
value: str,
|
|
search=re.compile(r'(?<!\\)\]').search
|
|
) -> Optional[int]:
|
|
match = search(value)
|
|
if not match:
|
|
return None
|
|
return match.start()
|
|
|
|
|
|
def parse_path(path):
|
|
"""
|
|
Parse a string as a JSON path.
|
|
|
|
An implementation of 'steps to parse a JSON encoding path'.
|
|
<https://www.w3.org/TR/html-json-forms/#dfn-steps-to-parse-a-json-encoding-path>
|
|
|
|
"""
|
|
original = path
|
|
is_escaped = r'\[' in original
|
|
|
|
opening_bracket = find_opening_bracket(original)
|
|
last_step = [(step(path, is_escaped), {'last': True, 'type': 'object'})]
|
|
if opening_bracket is None:
|
|
return last_step
|
|
|
|
steps = [(step(original[:opening_bracket], is_escaped), {'type': 'object'})]
|
|
path = original[opening_bracket:]
|
|
while path:
|
|
if path.startswith('[]'):
|
|
steps[-1][1]['append'] = True
|
|
path = path[2:]
|
|
if path:
|
|
return last_step
|
|
elif path[0] == '[':
|
|
path = path[1:]
|
|
closing_bracket = find_closing_bracket(path)
|
|
if closing_bracket is None:
|
|
return last_step
|
|
key = path[:closing_bracket]
|
|
path = path[closing_bracket + 1:]
|
|
try:
|
|
steps.append((int(key), {'type': 'array'}))
|
|
except ValueError:
|
|
steps.append((key, {'type': 'object'}))
|
|
elif path[:2] == r'\[':
|
|
key = step(path[1:path.index(r'\]') + 2], is_escaped)
|
|
path = path[path.index(r'\]') + 2:]
|
|
steps.append((key, {'type': 'object'}))
|
|
else:
|
|
return last_step
|
|
|
|
for i in range(len(steps) - 1):
|
|
steps[i][1]['type'] = steps[i + 1][1]['type']
|
|
steps[-1][1]['last'] = True
|
|
return steps
|
|
|
|
|
|
def set_value(context, step, current_value, entry_value):
|
|
"""Apply a JSON value to a context object.
|
|
|
|
An implementation of 'steps to set a JSON encoding value'.
|
|
<https://www.w3.org/TR/html-json-forms/#dfn-steps-to-set-a-json-encoding-value>
|
|
|
|
"""
|
|
key, flags = step
|
|
if flags.get('last', False):
|
|
if current_value is None:
|
|
if flags.get('append', False):
|
|
context[key] = [entry_value]
|
|
else:
|
|
if isinstance(context, list) and len(context) <= key:
|
|
context.extend([None] * (key - len(context) + 1))
|
|
context[key] = entry_value
|
|
elif isinstance(current_value, list):
|
|
context[key].append(entry_value)
|
|
else:
|
|
context[key] = [current_value, entry_value]
|
|
return context
|
|
|
|
if current_value is None:
|
|
if flags.get('type') == 'array':
|
|
context[key] = []
|
|
else:
|
|
if isinstance(context, list) and len(context) <= key:
|
|
context.extend([None] * (key - len(context) + 1))
|
|
context[key] = {}
|
|
return context[key]
|
|
elif isinstance(current_value, dict):
|
|
return context[key]
|
|
elif isinstance(current_value, list):
|
|
if flags.get('type') == 'array':
|
|
return current_value
|
|
|
|
obj = {}
|
|
for i, item in enumerate(current_value):
|
|
if item is not None:
|
|
obj[i] = item
|
|
else:
|
|
context[key] = obj
|
|
return obj
|
|
else:
|
|
obj = {'': current_value}
|
|
context[key] = obj
|
|
return obj
|
|
|
|
|
|
def interpret_json_form(pairs):
|
|
"""The application/json form encoding algorithm.
|
|
|
|
<https://www.w3.org/TR/html-json-forms/#dfn-application-json-encoding-algorithm>
|
|
|
|
"""
|
|
result = {}
|
|
for key, value in pairs:
|
|
steps = parse_path(key)
|
|
context = result
|
|
for step in steps:
|
|
try:
|
|
current_value = operator.getitem(context, step[0])
|
|
except LookupError:
|
|
current_value = None
|
|
context = set_value(context, step, current_value, value)
|
|
return result
|