Skip to content

Instantly share code, notes, and snippets.

@twardoch
Created January 25, 2025 00:33
Show Gist options
  • Save twardoch/3b8c846537ec84928317c3eb54c8d531 to your computer and use it in GitHub Desktop.
Save twardoch/3b8c846537ec84928317c3eb54c8d531 to your computer and use it in GitHub Desktop.
Quick and dirty JSON parsin with OrJSON, standard JSON and the https://github.com/jsonicjs/jsonic/ JS "relaxed" JSON parser, used via QuickJS
import inspect
import uuid
from functools import lru_cache
from pathlib import Path
def get_cache_path(folder_name: str = None) -> Path:
def generate_uuid() -> str:
"""Generate a UUID based on the file of the caller."""
# Get the stack frame of the caller
caller_frame = inspect.stack()[2]
caller_file = caller_frame.filename
caller_path = Path(caller_file).resolve()
return str(uuid.uuid5(uuid.NAMESPACE_URL, str(caller_path)))
try:
import platformdirs
root_cache_dir = platformdirs.user_cache_dir()
except ImportError:
root_cache_dir = Path.home() / ".cache"
if not folder_name:
folder_name = generate_uuid()
cache_path = Path(root_cache_dir) / folder_name
cache_path.mkdir(parents=True, exist_ok=True)
return cache_path
try:
from joblib import Memory
JOBLIB_MEMORY = Memory(get_cache_path(), verbose=0)
except ImportError:
JOBLIB_MEMORY = None
@lru_cache(maxsize=None)
def ucache(folder_name: str = None):
"""A decorator for caching function results."""
if JOBLIB_MEMORY:
memory = (
JOBLIB_MEMORY
if folder_name is None
else Memory(get_cache_path(folder_name), verbose=0)
)
def decorator(func):
return memory.cache(func)
else:
def decorator(func):
return lru_cache(maxsize=None)(func)
return decorator
#!/usr/bin/env python
try:
import orjson as json_impl
is_orjson = True
import json as json_impl_std
from orjson import JSONDecodeError
except ImportError:
import json as json_impl
is_orjson = False
from json import JSONDecodeError
from microcache import ucache
from quickjs import Context
def qjs_jsonic_parser():
@ucache()
def mjs_jsonic():
import requests
mjs = requests.get("https://esm.sh/[email protected]/es2022/jsonic.bundle.mjs").text
mjs += "\nglobalThis.Zn = Zn;"
return mjs
ctx = Context()
ctx.module(mjs_jsonic())
return ctx.get("Zn")
def get_json_from_any(text: str) -> str:
parser = qjs_jsonic_parser()
return parser(text).json()
def loads(text: str) -> dict:
"""Parse JSON string into Python object, handling relaxed JSON syntax.
Attempts parsing in this order:
1. orjson (if available)
2. relaxed JSON via jsonic + orjson/json
3. standard json module
"""
if is_orjson:
try:
return json_impl.loads(text)
except JSONDecodeError:
try:
cleaned = get_json_from_any(text)
return json_impl.loads(cleaned)
except json_impl.JSONDecodeError:
pass
try:
return json_impl.loads(text)
except JSONDecodeError:
raise ValueError("Could not parse JSON with any available method")
def dumps(obj: dict) -> str:
"""Serialize Python object to JSON string."""
if is_orjson:
return json_impl.dumps(obj).decode("utf-8")
return json_impl.dumps(obj)
if __name__ == "__main__":
# Example of parsing standard JSON
valid_json = '{"name": John, "age": 30}'
parsed = loads(valid_json)
print("Parsed standard JSON:", parsed)
# Example of parsing relaxed JSON (with comments and trailing commas)
relaxed_json = """
{"name": "Jane",//Hello
"age": 25,}
"""
try:
parsed = loads(relaxed_json)
print("\nParsed relaxed JSON:", parsed)
except ValueError as e:
print("Error parsing relaxed JSON:", e)
# Example of serializing Python dict to JSON
python_dict = {"name": "Alice", "scores": [95, 87, 91], "active": True}
json_str = dumps(python_dict)
print("\nSerialized to JSON:", json_str)
print("std", json_impl_std.dumps(python_dict))
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment