Skip to content

Repository files navigation

简中文档

logo

jsonc_sdict 即典

PyPI - Version

Round-trip comments for JSONC/HJSON, with dict-like APIs for config editing.

  • Keep comments on read and write (loads → edit → body / full).
  • Work with nested structures via sdictdeepmerge + deepdiff + benedict

Comments are data too, just like codes are data too by John von Neumann

Warning

This project is currently 0.2.x and still evolving.

Install

pip install "jsonc-sdict[full]"
# pip install "jsonc-sdict[full] @ git+https://github.com/AClon314/jsonc-sdict.git"

For local development:

git clone https://github.com/AClon314/jsonc-sdict.git
cd jsonc-sdict
pip install -e ".[dev]"

Usage

jsonc

from jsonc_sdict import jsoncDict, CommentIn, NONE

jc = jsoncDict(
    {
        CommentIn(NONE, "a"): "// before a",
        "a": 1,
        CommentIn("a", "b"): "// between a and b",
        "b": 2,
    }
)
jc[CommentIn("b")] = {CommentIn(":", "v"): "/* before value */"}
jc["b"] = 3

print(jc.full)

Use CommentIn to mark comment positions. See test_jsonc.py.

  • CommentIn(left, right) means a comment between two logical items.
  • CommentIn(key) means comments attached to one pair's internal k: / :v / v, slots.

If you start from JSONC/HJSON text instead of a mapping, use jsoncDict(raw, loads=hjson.loads, dumps=hjson.dumps).

Invalid JSONC examples

// /* this is still single-line comment
so this line is illegal */
/* // this is block comment */ trailing-text-is-illegal

sdict

See test_sdict.py.

Common operations

from jsonc_sdict import sdict

data = sdict({"node": {"items": [0, {"value": 1}]}, "a": 1, "b": 2, "c": 2})

print(data["node", "items", 1, "value"])
data["node", "items", 1, "value"] = 2

node = data["node"]
print(node.keypath)
print(node.parent is data)

data.insert({"x": 9}, key="a", after=True)
data.rename_key("x", "y")
data.sort(reverse=True)

print(list(data.items()))
print(data.unref())

merge()

Based on DeepDiff. See Merge.

from functools import partial
from jsonc_sdict import get1, merge

old = {"children": [{"id": 1, "name": "1", "old": None}]}
new = {"children": [{"id": 1, "name": "2", "new": ""}, {"id": 2, "name": "3"}]}

result = merge(
    (old, new),
    dictDict={"value_of_idKey": partial(get1.item, keys="id")},
    unMergeable="new",
)()

print(result)

deep-Merge

merge((old, new))() is the shortest form. For list[dict], pass dictDict=... so items can be matched by an id-like key before diff/merge.

from functools import partial
from jsonc_sdict import get1, merge

old = {"items": [{"id": 1, "name": "old", "keep": True}]}
new = {"items": [{"id": 1, "name": "new"}, {"id": 2, "name": "add"}]}

merged = merge(
    (old, new),
    dictDict={"value_of_idKey": partial(get1.item, keys="id")},
    unMergeable="new",
)()

print(merged)

CLI merge

deep-merge -i '{a:{b:1}}' '{a:{b:2}}' -fo json -m new
# {"a": {"b": 2}}

GetSetDel

See test_get_set_del.py.

from jsonc_sdict import get1, set1
from jsonc_sdict.GetSetDel import del1

obj = {"a": {"b": 1}}

print(get1.item(obj, ("a", "b")))
print(set1.item(obj, ("a", "b"), 2))
print(set1.item(obj, ("a", "c"), 3))
del1.item(obj, ("a", "b"))

print(obj)

Develop

env

LOG=DEBUG enables debug-level logging.

Common setup:

python -m venv .venv
source .venv/bin/activate
pip install -e ".[dev]"
LOG=DEBUG pytest -q

Internal design

jsonc

  • jsoncDict.loads() parses comments with tree-sitter and stores them into comments.
  • jsoncDict.body renders the current value with comments restored.
  • jsoncDict.full returns header + body + footer.
  • CommentIn(key) comments are stored as slot maps, not raw strings, so key/colon/value/comma placement stays explicit.

sdict (common pitfalls)

  • sdict wraps both mapping and iterable nodes; nested access may return sdict views, not raw dict/list.
  • jsoncDict output depends on comment/data mutation paths; bypassing public APIs can leave internal state inconsistent.
  • dfs() warns against mutating yielded data during iteration.
  • insert(update, key=...|index=...) is ordering-oriented: it inserts by reordering keys after update.

weakList (common pitfalls)

  • Items must support both __hash__ and weak references (__weakref__); built-in int/str/list/dict do not qualify.
  • Weak references can disappear when no strong references exist; list length can shrink unexpectedly.
  • WeakList(noRepeat=True) is not identical to OrderedWeakSet: repeated append/insert can move item position.

Related projects

json loads()

pypi commits issues about comment
spyoungtech/json-five ⭐ 🕒 LAST🕒 🎯 🎯close Python JSON5 parser with round-trip preservation of comments can keep comment in another API style (e.g: BlockComment/wsc_before)
tusharsadhwani/json5kit ⭐ 🕒 LAST🕒 🎯 🎯close A Roundtrip parser and CST for JSON, JSONC and JSON5.
dpranke/pyjson5 ⭐ 🕒 LAST🕒 🎯 🎯close A Python implementation of the JSON5 data format
austinyu/ujson5 ⭐ 🕒 LAST🕒 🎯 🎯close A fast JSON5 encoder/decoder for Python
qvecs/qjson5 ⭐ 🕒 LAST🕒 🎯 🎯close 📎 A quick JSON5 implementation written in C, with Python bindings.

other format that support round-trip

About

keep comment(round-trip) when read & write jsonc/hjson, sdict with extra context(parent/keypath/merge)

Topics

Resources

Stars

2 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages