A Python implementation of the JSON5 data format.
npx @tessl/cli install tessl/pypi-json5@0.12.0A Python implementation of the JSON5 data format. JSON5 extends standard JSON with JavaScript-style features including single and multi-line comments, unquoted object keys for valid ECMAScript identifiers, trailing commas in objects and arrays, single-quoted strings, and multi-line string literals. This library mirrors the standard Python JSON API for ease of use.
pip install json5import json5All functions are available at the top level:
from json5 import load, loads, dump, dumps, parse, JSON5Encoder, QuoteStyleimport json5
# Parse JSON5 string
data = json5.loads('''
{
// JavaScript-style comments
unquoted: "keys are allowed",
'single': 'quotes work too',
"trailing": "commas are ok",
}
''')
# Serialize Python object to JSON5
obj = {"name": "example", "values": [1, 2, 3]}
json5_string = json5.dumps(obj, indent=2, trailing_commas=True)
print(json5_string)
# Output:
# {
# name: 'example',
# values: [
# 1,
# 2,
# 3,
# ],
# }
# Parse from file
with open('config.json5', 'r') as f:
config = json5.load(f)
# Write to file
with open('output.json5', 'w') as f:
json5.dump(data, f, indent=2, quote_keys=False)Load and parse JSON5 documents into Python objects, supporting file objects and strings with extensive customization options.
def load(
fp,
*,
encoding=None,
cls=None,
object_hook=None,
parse_float=None,
parse_int=None,
parse_constant=None,
strict=True,
object_pairs_hook=None,
allow_duplicate_keys=True,
consume_trailing=True,
start=None
):
"""
Deserialize fp (a .read()-supporting file-like object containing a JSON5 document) to a Python object.
Parameters:
- fp: file-like object with .read() method
- encoding: character encoding (default: UTF-8)
- cls: ignored (for json compatibility)
- object_hook: optional function to transform decoded objects
- parse_float: optional function to parse float values
- parse_int: optional function to parse integer values
- parse_constant: optional function to parse constants (Infinity, NaN)
- strict: if True, control characters (\\x00-\\x1f) in strings raise ValueError
- object_pairs_hook: optional function called with ordered list of key-value pairs
- allow_duplicate_keys: if False, duplicate keys raise ValueError
- consume_trailing: if True, trailing non-whitespace raises ValueError
- start: offset position to start parsing from in file
Returns:
Parsed Python object
Raises:
- ValueError: invalid JSON5 document
- UnicodeDecodeError: invalid UTF-8 encoding
"""
def loads(
s,
*,
encoding=None,
cls=None,
object_hook=None,
parse_float=None,
parse_int=None,
parse_constant=None,
strict=True,
object_pairs_hook=None,
allow_duplicate_keys=True,
consume_trailing=True,
start=None
):
"""
Deserialize s (a string containing a JSON5 document) to a Python object.
Parameters:
- s: JSON5 string to parse
- encoding: character encoding (default: UTF-8)
- cls: ignored (for json compatibility)
- object_hook: optional function to transform decoded objects
- parse_float: optional function to parse float values
- parse_int: optional function to parse integer values
- parse_constant: optional function to parse constants (Infinity, NaN)
- strict: if True, control characters (\\x00-\\x1f) in strings raise ValueError
- object_pairs_hook: optional function called with ordered list of key-value pairs
- allow_duplicate_keys: if False, duplicate keys raise ValueError
- consume_trailing: if True, trailing non-whitespace raises ValueError
- start: offset position to start parsing from in string
Returns:
Parsed Python object
Raises:
- ValueError: invalid JSON5 document
- UnicodeDecodeError: invalid UTF-8 encoding
"""
def parse(
s,
*,
encoding=None,
cls=None,
object_hook=None,
parse_float=None,
parse_int=None,
parse_constant=None,
strict=True,
object_pairs_hook=None,
allow_duplicate_keys=True,
consume_trailing=True,
start=None
):
"""
Parse s, returning positional information along with a value.
Useful for parsing multiple values from a single string by repeatedly calling
with start parameter set to the position returned from previous call.
Parameters:
- s: JSON5 string to parse
- encoding: character encoding (default: UTF-8)
- cls: ignored (for json compatibility)
- object_hook: optional function to transform decoded objects
- parse_float: optional function to parse float values
- parse_int: optional function to parse integer values
- parse_constant: optional function to parse constants (Infinity, NaN)
- strict: if True, control characters (\\x00-\\x1f) in strings raise ValueError
- object_pairs_hook: optional function called with ordered list of key-value pairs
- allow_duplicate_keys: if False, duplicate keys raise ValueError
- consume_trailing: if True, trailing non-whitespace raises ValueError
- start: offset position to start parsing from in string
Returns:
Tuple of (value, error_string, position):
- value: parsed Python object or None if error
- error_string: None if successful, error message if failed
- position: zero-based offset where parsing stopped
Raises:
- UnicodeDecodeError: invalid UTF-8 encoding
"""import json5
# Basic parsing
data = json5.loads('{"name": "example"}')
# Parse with custom hooks
def custom_object_hook(obj):
return {k.upper(): v for k, v in obj.items()}
data = json5.loads('{"name": "example"}', object_hook=custom_object_hook)
# Result: {"NAME": "example"}
# Parse multiple values from string
s = '{"a": 1} {"b": 2} {"c": 3}'
values = []
start = 0
while start < len(s):
value, error, pos = json5.parse(s, start=start, consume_trailing=False)
if error:
break
values.append(value)
start = pos
# Skip whitespace
while start < len(s) and s[start].isspace():
start += 1
# Reject duplicate keys
try:
json5.loads('{"key": 1, "key": 2}', allow_duplicate_keys=False)
except ValueError as e:
print(f"Duplicate key error: {e}")Serialize Python objects to JSON5 format with extensive formatting and style options.
def dump(
obj,
fp,
*,
skipkeys=False,
ensure_ascii=True,
check_circular=True,
allow_nan=True,
cls=None,
indent=None,
separators=None,
default=None,
sort_keys=False,
quote_keys=False,
trailing_commas=True,
allow_duplicate_keys=True,
quote_style=QuoteStyle.ALWAYS_DOUBLE,
**kwargs
):
"""
Serialize obj to a JSON5-formatted stream to fp (a .write()-supporting file-like object).
Parameters:
- obj: Python object to serialize
- fp: file-like object with .write() method
- skipkeys: if True, skip keys that are not basic types
- ensure_ascii: if True, escape non-ASCII characters
- check_circular: if True, check for circular references
- allow_nan: if True, allow NaN, Infinity, -Infinity
- cls: custom JSON5Encoder class
- indent: indentation (int for spaces, str for custom, None for compact)
- separators: (item_separator, key_separator) tuple
- default: function to handle non-serializable objects
- sort_keys: if True, sort object keys
- quote_keys: if True, always quote object keys
- trailing_commas: if True, add trailing commas in multiline output
- allow_duplicate_keys: if True, allow duplicate keys in output
- quote_style: QuoteStyle enum value controlling string quoting
Raises:
- TypeError: non-serializable object
- ValueError: circular reference or invalid value
"""
def dumps(
obj,
*,
skipkeys=False,
ensure_ascii=True,
check_circular=True,
allow_nan=True,
cls=None,
indent=None,
separators=None,
default=None,
sort_keys=False,
quote_keys=False,
trailing_commas=True,
allow_duplicate_keys=True,
quote_style=QuoteStyle.ALWAYS_DOUBLE,
**kwargs
):
"""
Serialize obj to a JSON5-formatted string.
Parameters:
- obj: Python object to serialize
- skipkeys: if True, skip keys that are not basic types
- ensure_ascii: if True, escape non-ASCII characters
- check_circular: if True, check for circular references
- allow_nan: if True, allow NaN, Infinity, -Infinity
- cls: custom JSON5Encoder class
- indent: indentation (int for spaces, str for custom, None for compact)
- separators: (item_separator, key_separator) tuple
- default: function to handle non-serializable objects
- sort_keys: if True, sort object keys
- quote_keys: if True, always quote object keys
- trailing_commas: if True, add trailing commas in multiline output
- allow_duplicate_keys: if True, allow duplicate keys in output
- quote_style: QuoteStyle enum value controlling string quoting
Returns:
JSON5-formatted string
Raises:
- TypeError: non-serializable object
- ValueError: circular reference or invalid value
"""import json5
from json5 import QuoteStyle
# Basic serialization
data = {"name": "example", "values": [1, 2, 3]}
json5_str = json5.dumps(data)
# Pretty-printed with unquoted keys and trailing commas
json5_str = json5.dumps(data, indent=2, quote_keys=False, trailing_commas=True)
# Use single quotes for strings
json5_str = json5.dumps(data, quote_style=QuoteStyle.ALWAYS_SINGLE)
# JSON compatibility mode
json_str = json5.dumps(data, quote_keys=True, trailing_commas=False)
# Custom serialization
def custom_serializer(obj):
if hasattr(obj, '__dict__'):
return obj.__dict__
raise TypeError(f"Object of type {type(obj)} is not JSON5 serializable")
class Person:
def __init__(self, name, age):
self.name = name
self.age = age
person = Person("Alice", 30)
json5_str = json5.dumps(person, default=custom_serializer, indent=2)Customizable JSON5 encoder for advanced serialization control and extensibility.
class JSON5Encoder:
"""
Customizable JSON5 encoder class.
"""
def __init__(
self,
*,
skipkeys=False,
ensure_ascii=True,
check_circular=True,
allow_nan=True,
indent=None,
separators=None,
default=None,
sort_keys=False,
quote_keys=False,
trailing_commas=True,
allow_duplicate_keys=True,
quote_style=QuoteStyle.ALWAYS_DOUBLE,
**kwargs
):
"""
Initialize JSON5 encoder with formatting options.
Parameters match those of dumps() function.
"""
def default(self, obj):
"""
Override to provide custom serialization for objects that are not
natively JSON5 serializable.
Parameters:
- obj: object to serialize
Returns:
JSON5-serializable representation of obj
Raises:
- TypeError: if obj cannot be serialized
"""
def encode(self, obj, seen, level, *, as_key=False):
"""
Return JSON5-encoded version of obj.
Parameters:
- obj: object to encode
- seen: set of object ids for circular reference detection
- level: current nesting level
- as_key: if True, encode as object key
Returns:
JSON5-encoded string
Raises:
- TypeError: unsupported object type
- ValueError: circular reference or invalid value
"""
def is_identifier(self, key):
"""
Check if key can be used as unquoted object key.
Parameters:
- key: string to check
Returns:
True if key is valid ECMAScript identifier
"""
def is_reserved_word(self, key):
"""
Check if key is ECMAScript reserved word.
Parameters:
- key: string to check
Returns:
True if key is reserved word
"""import json5
from json5 import JSON5Encoder, QuoteStyle
# Custom encoder for special objects
class CustomEncoder(JSON5Encoder):
def default(self, obj):
if isinstance(obj, set):
return list(obj)
if hasattr(obj, 'isoformat'): # datetime objects
return obj.isoformat()
return super().default(obj)
# Use custom encoder
from datetime import datetime
data = {
"timestamp": datetime.now(),
"tags": {"python", "json5", "serialization"}
}
json5_str = json5.dumps(data, cls=CustomEncoder, indent=2)
# Encoder with custom quote style
encoder = JSON5Encoder(
quote_style=QuoteStyle.PREFER_SINGLE,
indent=2,
trailing_commas=True
)
json5_str = encoder.encode(data, set(), 0, as_key=False)Control how strings are quoted during JSON5 serialization.
class QuoteStyle(enum.Enum):
"""
Enum controlling how strings are quoted during encoding.
"""
ALWAYS_DOUBLE = 'always_double' # Always use double quotes
ALWAYS_SINGLE = 'always_single' # Always use single quotes
PREFER_DOUBLE = 'prefer_double' # Prefer double, use single if fewer escapes
PREFER_SINGLE = 'prefer_single' # Prefer single, use double if fewer escapesimport json5
from json5 import QuoteStyle
text = "It's a \"wonderful\" day"
# Different quote styles
double_quoted = json5.dumps(text, quote_style=QuoteStyle.ALWAYS_DOUBLE)
# Result: "\"It's a \\\"wonderful\\\" day\""
single_quoted = json5.dumps(text, quote_style=QuoteStyle.ALWAYS_SINGLE)
# Result: "'It\\'s a \"wonderful\" day'"
prefer_double = json5.dumps(text, quote_style=QuoteStyle.PREFER_DOUBLE)
# Result: "'It\\'s a \"wonderful\" day'" (fewer escapes with single quotes)
prefer_single = json5.dumps(text, quote_style=QuoteStyle.PREFER_SINGLE)
# Result: "'It\\'s a \"wonderful\" day'" (preferred single quotes)Access package version information for compatibility and debugging.
__version__: str # Package version string (e.g., "0.12.1")
VERSION: str # Backward compatibility alias for __version__import json5
print(f"Using json5 version: {json5.__version__}")
print(f"Version (legacy): {json5.VERSION}")
# Version checking
from packaging import version
if version.parse(json5.__version__) >= version.parse("0.12.0"):
print("QuoteStyle enum is available")This implementation supports all JSON5 language features:
// and multi-line /* */ commentsThe library raises standard Python exceptions:
import json5
# Handle parsing errors
try:
data = json5.loads('{"invalid": syntax}')
except ValueError as e:
print(f"Parse error: {e}")
# Handle encoding errors
try:
json5.dumps(object()) # object() is not serializable
except TypeError as e:
print(f"Encoding error: {e}")