Export option: _shape=array&_nl=on for newline-delimited JSON

This commit is contained in:
Simon Willison 2019-01-27 17:40:23 -08:00
commit b5dd83981a
6 changed files with 58 additions and 11 deletions

View file

@ -140,7 +140,13 @@
{% if display_rows %}
<div id="export" class="advanced-export">
<h3>Advanced export</h3>
<p>JSON shape: <a href="{{ url_json }}">default</a>, <a href="{{ append_querystring(url_json, '_shape=array') }}">array</a>{% if primary_keys %}, <a href="{{ append_querystring(url_json, '_shape=object') }}">object</a>{% endif %}</p>
<p>JSON shape:
<a href="{{ url_json }}">default</a>,
<a href="{{ append_querystring(url_json, '_shape=array') }}">array</a>,
<a href="{{ append_querystring(url_json, '_shape=array&_nl=on') }}">newline-delimited</a>{% if primary_keys %},
<a href="{{ append_querystring(url_json, '_shape=object') }}">object</a>
{% endif %}
</p>
<form action="{{ url_csv_path }}" method="get">
<p>
CSV options:

View file

@ -432,10 +432,18 @@ class BaseView(RenderMixin):
headers = {}
if self.ds.cors:
headers["Access-Control-Allow-Origin"] = "*"
# Handle _nl option for _shape=array
nl = request.args.get("_nl", "")
if nl and shape == "array":
body = "\n".join(json.dumps(item) for item in data)
content_type = "text/plain"
else:
body = json.dumps(data, cls=CustomJSONEncoder)
content_type = "application/json"
r = response.HTTPResponse(
json.dumps(data, cls=CustomJSONEncoder),
body,
status=status_code,
content_type="application/json",
content_type=content_type,
headers=headers,
)
else:

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.5 KiB

After

Width:  |  Height:  |  Size: 24 KiB

Before After
Before After

View file

@ -61,11 +61,12 @@ options:
* ``?_shape=arrays`` - ``"rows"`` is the default option, shown above
* ``?_shape=objects`` - ``"rows"`` is a list of JSON key/value objects
* ``?_shape=array`` - the entire response is an array of objects
* ``?_shape=arrayfirst`` - the entire response is a flat JSON array containing just the first value from each row
* ``?_shape=object`` - the entire response is a JSON object keyed using the primary keys of the rows
* ``?_shape=array`` - an JSON array of objects
* ``?_shape=array&_nl=on`` - a newline-separated list of JSON objects
* ``?_shape=arrayfirst`` - a flat JSON array containing just the first value from each row
* ``?_shape=object`` - a JSON object keyed using the primary keys of the rows
``objects`` looks like this::
``_shape=objects`` looks like this::
{
"database": "sf-trees",
@ -86,7 +87,7 @@ options:
]
}
``array`` looks like this::
``_shape=array`` looks like this::
[
{
@ -103,11 +104,17 @@ options:
}
]
``arrayfirst`` looks like this::
``_shape=array&_nl=on`` looks like this::
{"id": 1, "value": "Myoporum laetum :: Myoporum"}
{"id": 2, "value": "Metrosideros excelsa :: New Zealand Xmas Tree"}
{"id": 3, "value": "Pinus radiata :: Monterey Pine"}
``_shape=arrayfirst`` looks like this::
[1, 2, 3]
``object`` looks like this::
``_shape=object`` looks like this::
{
"1": {
@ -140,6 +147,9 @@ querystring arguments:
``?_shape=SHAPE``
The shape of the JSON to return, documented above.
``?_nl=on``
When used with ``?_shape=array`` produces newline-delimited JSON objects.
``?_json=COLUMN1&_json=COLUMN2``
If any of your SQLite columns contain JSON values, you can use one or more
``_json=`` parameters to request that those columns be returned as regular

View file

@ -9,6 +9,7 @@ from .fixtures import ( # noqa
make_app_client,
METADATA,
)
import json
import pytest
import urllib
@ -547,6 +548,27 @@ def test_table_shape_array(app_client):
}] == response.json
def test_table_shape_array_nl(app_client):
response = app_client.get(
'/fixtures/simple_primary_key.json?_shape=array&_nl=on'
)
lines = response.text.split("\n")
results = [json.loads(line) for line in lines]
assert [{
'id': '1',
'content': 'hello',
}, {
'id': '2',
'content': 'world',
}, {
'id': '3',
'content': '',
}, {
'id': '4',
'content': 'RENDER_CELL_DEMO',
}] == results
def test_table_shape_invalid(app_client):
response = app_client.get(
'/fixtures/simple_primary_key.json?_shape=invalid'

View file

@ -422,6 +422,7 @@ def test_table_csv_json_export_interface(app_client):
assert [
"simple_primary_key.json?id__gt=2",
"simple_primary_key.json?id__gt=2&_shape=array",
"simple_primary_key.json?id__gt=2&_shape=array&_nl=on",
"simple_primary_key.json?id__gt=2&_shape=object"
] == json_links
# And the CSV form
@ -796,7 +797,7 @@ def test_advanced_export_box(app_client, path, has_object, has_stream, has_expan
assert response.status == 200
soup = Soup(response.body, "html.parser")
# JSON shape options
expected_json_shapes = ["default", "array"]
expected_json_shapes = ["default", "array", "newline-delimited"]
if has_object:
expected_json_shapes.append("object")
div = soup.find("div", {"class": "advanced-export"})