Skip to content

Instantly share code, notes, and snippets.

@JGalego
Created November 13, 2025 09:57
Show Gist options
  • Select an option

  • Save JGalego/ae7433c2bbe219e53c28f9a84079266b to your computer and use it in GitHub Desktop.

Select an option

Save JGalego/ae7433c2bbe219e53c28f9a84079266b to your computer and use it in GitHub Desktop.
A simple JSON / JSONLines to TOON Converter 🐰
r"""
A simple JSON / JSONLines to TOON Converter
.------..
- -
/ \
/ \
/ .--._ .---. |
| / -__- \ |
| | | |
|| ._ _. ||
|| o o ||
|| _ |_ ||
C| (o\_/o) |O Uhhh, this computer
\ _____ / is like, busted or
\ ( /#####\ ) / something. So go away.
\ `=====' /
\ -___- /
| |
/-_____-\
/ \
/ \
/__| AC / DC |__\
| || |\ \
"""
import json
import re
import sys
def should_quote(s):
"""Quote strings only when TOON spec requires it"""
if not isinstance(s, str):
return s
# Quote if: empty, whitespace, reserved words, numbers, or special chars
needs_quotes = (
s == '' or # empty string
s[0:1] == ' ' or s[-1:] == ' ' or # leading/trailing spaces
s in ['true', 'false', 'null'] or # reserved literals
re.match(r'^-?\d+(\.\d+)?(e[+-]?\d+)?$', s, re.I) or # numeric pattern
re.match(r'^0\d+$', s) or # leading zero numbers
any(c in s for c in ':\"\\[]{},-') # structural characters
)
if needs_quotes:
escaped = s.replace('\\', '\\\\').replace('"', '\\"')
escaped = escaped.replace('\n', '\\n').replace('\r', '\\r').replace('\t', '\\t')
return f'"{escaped}"'
return s
def _convert_primitive(value):
"""Convert primitive JSON values to TOON format"""
if value is None:
return 'null'
if isinstance(value, bool):
return 'true' if value else 'false'
if isinstance(value, (int, float)):
# Convert floats that are whole numbers to integers
return str(int(value) if isinstance(value, float) and value == int(value) else value)
if isinstance(value, str):
return should_quote(value)
return None
def _is_uniform_primitive_objects(array):
"""Check if array contains uniform objects with only primitive values"""
if not array or not isinstance(array[0], dict):
return False
first_keys = array[0].keys()
return all(isinstance(x, dict) and x.keys() == first_keys and
all(not isinstance(y, (dict, list)) for y in x.values())
for x in array)
def _convert_array_tabular(array):
"""Convert array to tabular TOON format"""
keys = list(array[0].keys())
header = f'[{len(array)}]{{{",".join(keys)}}}:'
rows = '\n'.join(' ' + ','.join(str(to_toon(x[k])) for k in keys) for x in array)
return f'{header}\n{rows}'
def _convert_array_inline(array):
"""Convert array to inline TOON format"""
return f'[{len(array)}]: ' + ','.join(str(to_toon(x)) for x in array)
def _convert_array_list(array):
"""Convert array to list TOON format"""
items = []
for x in array:
if isinstance(x, dict) and x:
# Object as list item: first field on hyphen line, rest indented
keys = list(x.keys())
first_line = f'- {keys[0]}: {to_toon(x[keys[0]])}'
remaining = ''.join(f'\n {k}: {to_toon(x[k])}' for k in keys[1:])
items.append(first_line + remaining)
else:
items.append(f'- {to_toon(x)}')
return f'[{len(array)}]:\n ' + '\n '.join(items)
def _convert_array(value):
"""Convert JSON array to TOON format"""
if not value:
return '[0]:' # empty array
# Tabular format: uniform objects with primitive values only
if _is_uniform_primitive_objects(value):
return _convert_array_tabular(value)
# Inline format: primitive values only
if all(not isinstance(x, (dict, list)) for x in value):
return _convert_array_inline(value)
# List format: mixed or complex values
return _convert_array_list(value)
def _convert_object(value, depth=0):
"""Convert JSON object to TOON format"""
if not value:
return '' # empty object
lines = []
for key in value:
# Quote key if it doesn't match identifier pattern
key_str = should_quote(key) if not re.match(r'^[A-Za-z_][A-Za-z0-9_.]*$', key) else key
indent = ' ' * depth
if isinstance(value[key], list):
# Array: put header on same line as key
array_toon = to_toon(value[key], depth) or ''
if '\n' in array_toon:
lines.append(indent + key_str + array_toon.replace('\n', '\n' + ' ' * depth))
else:
lines.append(indent + key_str + array_toon)
elif not isinstance(value[key], (dict, list)) or not value[key]:
# Primitive or empty: inline format
lines.append(f'{indent}{key_str}: {to_toon(value[key], depth + 1)}')
else:
# Nested object: key on its own line, content indented
lines.append(f'{indent}{key_str}:\n{to_toon(value[key], depth + 1)}')
return '\n'.join(lines)
def to_toon(value, depth=0):
"""Convert JSON value to TOON format"""
# Try primitive conversion first
primitive_result = _convert_primitive(value)
if primitive_result is not None:
return primitive_result
# Handle arrays
if isinstance(value, list):
return _convert_array(value)
# Handle objects
if isinstance(value, dict):
return _convert_object(value, depth)
# Fallback for unexpected types
return str(value)
def _read_input_lines():
"""Read and filter input lines from stdin"""
input_lines = []
for line in sys.stdin:
line = line.strip()
if line: # Skip empty lines
input_lines.append(line)
return input_lines
def _is_jsonlines_format(input_lines):
"""Determine if input is JSONLines format"""
if len(input_lines) > 1:
return True
# Single line - try parsing as single JSON first
try:
json.loads(input_lines[0])
return False
except json.JSONDecodeError:
return True
def _process_jsonlines(input_lines):
"""Process JSONLines format input"""
objects = []
for i, line in enumerate(input_lines, 1):
try:
parsed_json = json.loads(line)
objects.append(parsed_json)
except json.JSONDecodeError as e:
print(f"Error parsing line {i}: {e}", file=sys.stderr)
continue
# Output each object as TOON with separators
for i, obj in enumerate(objects):
if i > 0:
print("---") # Separator between objects
toon_output = to_toon(obj)
print(toon_output)
def _process_single_json(input_lines):
"""Process single JSON object input"""
try:
# Join all lines in case JSON is pretty-printed across multiple lines
json_input = '\n'.join(input_lines)
parsed_json = json.loads(json_input)
toon_output = to_toon(parsed_json)
print(toon_output)
except json.JSONDecodeError as e:
print(f"Error parsing JSON: {e}", file=sys.stderr)
sys.exit(1)
def process_input():
"""Process JSON or JSONLines input from stdin"""
input_lines = _read_input_lines()
if not input_lines:
return
if _is_jsonlines_format(input_lines):
_process_jsonlines(input_lines)
else:
_process_single_json(input_lines)
# Main program: read JSON/JSONLines from stdin and output TOON
if __name__ == "__main__":
process_input()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment