{"id":21331557,"url":"https://github.com/lukechampine/jj","last_synced_at":"2026-05-21T10:05:31.118Z","repository":{"id":57604698,"uuid":"82418949","full_name":"lukechampine/jj","owner":"lukechampine","description":"The JSON Journal","archived":false,"fork":false,"pushed_at":"2017-02-20T22:26:32.000Z","size":41,"stargazers_count":1,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"master","last_synced_at":"2026-03-11T04:07:19.665Z","etag":null,"topics":["journaling","json"],"latest_commit_sha":null,"homepage":null,"language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/lukechampine.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2017-02-18T22:04:48.000Z","updated_at":"2018-12-21T12:29:42.000Z","dependencies_parsed_at":"2022-09-13T01:33:26.300Z","dependency_job_id":null,"html_url":"https://github.com/lukechampine/jj","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/lukechampine/jj","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lukechampine%2Fjj","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lukechampine%2Fjj/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lukechampine%2Fjj/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lukechampine%2Fjj/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/lukechampine","download_url":"https://codeload.github.com/lukechampine/jj/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lukechampine%2Fjj/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":33297188,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-21T02:57:32.698Z","status":"ssl_error","status_checked_at":"2026-05-21T02:57:31.990Z","response_time":62,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["journaling","json"],"created_at":"2024-11-21T22:42:20.920Z","updated_at":"2026-05-21T10:05:31.098Z","avatar_url":"https://github.com/lukechampine.png","language":"Go","funding_links":[],"categories":[],"sub_categories":[],"readme":"jj\n--\n\n[![GoDoc](https://godoc.org/github.com/lukechampine/jj?status.svg)](https://godoc.org/github.com/lukechampine/jj)\n[![Go Report Card](http://goreportcard.com/badge/github.com/lukechampine/jj)](https://goreportcard.com/report/github.com/lukechampine/jj)\n\n```\ngo get github.com/lukechampine/jj\n```\n\n`jj` implements a JSON transaction journal. It enables efficient ACID\ntransactions on JSON objects. If your disk operations require something faster\nthan rewriting a whole file on each save, but you aren't ready to design a\nfull database schema, `jj` might be for you.\n\nEach Journal represents a single JSON object. The object is serialized as an\n\"initial object\" followed by a series of update sets, one per line. Each\nupdate specifies a field and a modification. See the `Update` section for a\nfull specification.\n\nDuring operation, the object is first loaded by reading the file and applying\neach update to the initial object. It is subsequently modified by appending\nupdate sets to the file, one per line. At any time, a \"checkpoint\" may be\ncreated, which clears the Journal and starts over with a new initial object.\nThis allows for compaction of the Journal file.\n\nIn the event of power failure or other serious disruption, the most recent\nupdate set may be only partially written. Partially written update sets are\nsimply ignored when reading the Journal. Individual updates may also be\nignored if they are malformed, though other updates in the set may be applied.\nSee the `Update` section for an explanation of malformed updates.\n\n\n## Example ##\n\n```go\n// create initial object\nvar obj struct {\n\tFoo struct {\n\t\tBar struct {\n\t\t\tBaz []int `json:\"baz\"`\n\t\t} `json:\"bar\"`\n\t\tQuux int `json:\"quux\"`\n\t} `json:\"foo\"`\n}\nobj.Foo.Bar.Baz = []int{1,2,3}\nobj.Foo.Quux = 6\n\n// create journal with initial object\n// (error handling omitted)\nj, _ := jj.OpenJournal(\"myjournal\", obj)\n\n// record a set of updates to the object\n_ = j.Update([]jj.Update{\n\tjj.NewUpdate(\"foo.bar.baz.2\", 7),\n\tjj.NewUpdate(\"foo.quux\", 7),\n})\n\n// close and reopen the journal, applying the updates\n_ = j.Close()\nj, _ = jj.OpenJournal(\"myjournal\", \u0026obj)\nprintln(obj.Foo.Bar.Baz[2]) // -\u003e 7\nprintln(obj.Foo.Quux)       // -\u003e 7\n```\n\n## Updates ##\n\nAn Update is a modification of a path in a JSON object. A \"path\" in this\ncontext means an object or array element. Syntactically, a path is a set of\naccessors joined by the `.` character. An accessor is either an object key\nor an array index. For example, given this object:\n\n```json\n{\n\t\"foo\": {\n\t\t\"bars\": [\n\t\t\t{\"baz\":3}\n\t\t]\n\t}\n}\n```\n\nThe following path accesses the value \"3\":\n\n```\nfoo.bars.0.baz\n```\n\nThe path is accompanied by a new object. Thus, to increment the value \"3\"\nin the above object, we would use the following Update:\n\n```json\n{\n\t\"p\": \"foo.bars.0.baz\",\n\t\"v\": 4\n}\n```\n\nAll permutations of the Update object are legal. However, malformed updates\nare ignored during application. An Update is considered malformed in three\ncircumstances:\n\n- Its Path references an element that does not exist at application time.\n  This includes out-of-bounds array indices.\n- Its Path contains invalid characters (e.g. `\"`). See the JSON spec.\n- Value contains invalid JSON or is empty.\n\nOther special cases are handled as follows:\n\n- If Path is `\"\"`, the entire object is replaced.\n- If an object contains duplicate keys, the first key encountered is used.\n\nFinally, to enable efficient array updates, the length of the array (at\napplication time) may be used as a special array index.  When this index is\nthe last accessor in Path, Value will be appended to the end of the array.\nIf the index is not the last accessor, the Update is considered malformed\n(and thus is ignored).\n\n## Caveats ##\n\nAn important aspect of `jj` is that you cannot \"read\" from the journal; you\ncan only apply updates. The only time you retrieve data from the journal is\nwhen loading it from disk, usually during initialization. This means that you\nmust keep your in-memory copy of the data in sync with the journal.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Flukechampine%2Fjj","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Flukechampine%2Fjj","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Flukechampine%2Fjj/lists"}