Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions CHANGELOG
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
# Changelog

- Register the JSON formats so they are actually usable.
- Make JSON formats able to encode Decimals and None/NULLs.

## Version 2.5.0

- Added noheader CSV and TSV output formats.
Expand Down
14 changes: 12 additions & 2 deletions cli_helpers/tabular_output/json_output_adapter.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,22 @@
# -*- coding: utf-8 -*-
"""A JSON data output adapter"""

from decimal import Decimal
from itertools import chain
import json

from .preprocessors import bytes_to_string, override_missing_value, convert_to_string
from .preprocessors import bytes_to_string

supported_formats = ("jsonl", "jsonl_escaped")
preprocessors = (override_missing_value, bytes_to_string, convert_to_string)
preprocessors = (bytes_to_string,)


class CustomEncoder(json.JSONEncoder):
def default(self, o):
if isinstance(o, Decimal):
return float(o)
else:
return super(CustomEncoder, self).default(o)


def adapter(data, headers, table_format="jsonl", **_kwargs):
Expand All @@ -22,6 +31,7 @@ def adapter(data, headers, table_format="jsonl", **_kwargs):
for row in chain(data):
yield json.dumps(
dict(zip(headers, row, strict=True)),
cls=CustomEncoder,
separators=(",", ":"),
ensure_ascii=ensure_ascii,
)
12 changes: 12 additions & 0 deletions cli_helpers/tabular_output/output_formatter.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
vertical_table_adapter,
tabulate_adapter,
tsv_output_adapter,
json_output_adapter,
)
from decimal import Decimal

Expand Down Expand Up @@ -253,3 +254,14 @@ def format_output(data, headers, format_name, **kwargs):
tsv_output_adapter.preprocessors,
{"table_format": tsv_format, "missing_value": "", "max_field_width": None},
)

for json_format in json_output_adapter.supported_formats:
TabularOutputFormatter.register_new_formatter(
json_format,
json_output_adapter.adapter,
json_output_adapter.preprocessors,
{
"table_format": json_format,
"max_field_width": None,
},
)
24 changes: 24 additions & 0 deletions tests/tabular_output/test_json_output_adapter.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@

from __future__ import unicode_literals

from decimal import Decimal

from cli_helpers.tabular_output import json_output_adapter


Expand All @@ -29,6 +31,28 @@ def test_unicode_with_jsonl():
)


def test_decimal_with_jsonl():
"""Test that the jsonl wrapper can pass through Decimal values."""
data = [["ab\r\nc", 1], ["d", Decimal(4.56)]]
headers = ["letters", "number"]
output = json_output_adapter.adapter(iter(data), headers, table_format="jsonl")
assert (
"\n".join(output)
== """{"letters":"ab\\r\\nc","number":1}\n{"letters":"d","number":4.56}"""
)


def test_null_with_jsonl():
"""Test that the jsonl wrapper can pass through null values."""
data = [["ab\r\nc", None], ["d", None]]
headers = ["letters", "value"]
output = json_output_adapter.adapter(iter(data), headers, table_format="jsonl")
assert (
"\n".join(output)
== """{"letters":"ab\\r\\nc","value":null}\n{"letters":"d","value":null}"""
)


def test_unicode_with_jsonl_esc():
"""Test that the jsonl_escaped wrapper JSON-escapes non-ascii characters."""
data = [["观音", 1], ["Ποσειδῶν", 456]]
Expand Down
7 changes: 7 additions & 0 deletions tests/tabular_output/test_output_formatter.py
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,13 @@ def test_unsupported_format():
formatter.format_output((), (), format_name="foobar")


def test_supported_json_formats():
"""Test that the JSONl formats are known."""
formatter = TabularOutputFormatter()
assert "jsonl" in formatter.supported_formats
assert "jsonl_escaped" in formatter.supported_formats


def test_tabulate_ansi_escape_in_default_value():
"""Test that ANSI escape codes work with tabulate."""

Expand Down
Loading