{"id":13565659,"url":"https://github.com/mortie/jcof","last_synced_at":"2025-12-16T08:10:56.720Z","repository":{"id":45746078,"uuid":"514279733","full_name":"mortie/jcof","owner":"mortie","description":"An efficient drop-in replacement for JSON.","archived":false,"fork":false,"pushed_at":"2022-07-17T15:05:10.000Z","size":306,"stargazers_count":153,"open_issues_count":7,"forks_count":0,"subscribers_count":4,"default_branch":"main","last_synced_at":"2025-03-08T09:35:38.398Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"isc","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/mortie.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":"2022-07-15T13:28:44.000Z","updated_at":"2024-07-04T13:30:55.000Z","dependencies_parsed_at":"2022-08-12T12:10:26.532Z","dependency_job_id":null,"html_url":"https://github.com/mortie/jcof","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mortie%2Fjcof","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mortie%2Fjcof/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mortie%2Fjcof/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mortie%2Fjcof/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/mortie","download_url":"https://codeload.github.com/mortie/jcof/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247097556,"owners_count":20883121,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","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":[],"created_at":"2024-08-01T13:01:52.414Z","updated_at":"2025-12-16T08:10:51.665Z","avatar_url":"https://github.com/mortie.png","language":"JavaScript","funding_links":[],"categories":["JavaScript"],"sub_categories":[],"readme":"# JCOF: JSON-like Compact Object Format\n\nA more efficient way to represent JSON-style objects.\n\n## Status\n\nThis format isn't nailed down yet.\nMost changes will likely be additive, such that existing JCOF documents will remain valid,\nbut nothing is guaranteed. Use at your own risk.\nIn its current form, JCOF is suitable for closed systems where one party controls every producer\nand consumer and where every implementation can be updated at once.\n\n## About\n\nJCOF tries to be a drop-in replacement for JSON, with most of the same semantics, but with a much\nmore compact representation of objects. The main way it does this is to introduce a string table\nat the beginning of the object, and then replace all strings with indexes into that string table.\nIt also employs a few extra tricks to make objects as small as possible, without losing the most\nimportant benefits of JSON. Most importantly, it remains a text-based, schemaless format.\n\nThe following JSON object:\n\n```json\n{\n\t\"people\": [\n\t\t{\"first-name\": \"Bob\", \"age\": 32, \"occupation\": \"Plumber\", \"full-time\": true},\n\t\t{\"first-name\": \"Alice\", \"age\": 28, \"occupation\": \"Programmer\", \"full-time\": true},\n\t\t{\"first-name\": \"Bernard\", \"age\": 36, \"occupation\": null, \"full-time\": null},\n\t\t{\"first-name\": \"El\", \"age\": 57, \"occupation\": \"Programmer\", \"full-time\": false}\n\t]\n}\n```\n\ncould be represented as the following JCOF object:\n\n```\nProgrammer;\"age\"\"first-name\"\"full-time\"\"occupation\";\n{\"people\"[(0,iw\"Bob\"b\"Plumber\")(0,is\"Alice\"b,s0)(0,iA\"Bernard\"n,n)(0,iV\"El\"B,s0)]}\n```\n\nMinimized, the JSON is 299 bytes, with 71.5 bytes on average per person object.\nThe JCOF is 134 bytes, with only 17.5 bytes per person object; that's 0.45x the size in total,\nand 0.23x the size per person object.\nThe reason the JCOF is so much smaller is threefold:\n\n1. It has a string table, so that strings which occur multiple times only have to be\n   included in the JCOF document once. In this example object, the only duplicated string\n   is \"Programmer\".\n2. It has an object shapes table, so that object shapes which occur multiple times only have to\n   have their keys encoded once. In this example object, the only duplicated object shape\n   is `{\"age\", \"first-name\", \"full-time\", \"occupation\"}`.\n3. It has more compact encodings for various values and syntax.\n   Large integers can be encoded as base 62 rather than base 10,\n   booleans and null are encoded using single characters,\n   and separator characters can be skipped where that results in an unambiguous document.\n\n## Rationale\n\nI was making a JSON-based serialization format for a game I was working on, but found myself\nmaking trade-offs between space efficiency and descriptive key names, so decided to make\na format which makes that a non-issue. I then kept iterating on it until I had\nwhat I call JCOF today.\n\nIn most cases, you would use plain JSON, or if size is a concern, you would use gzipped JSON.\nBut there are times when size is a concern and you can't reasonably use gzip; for example,\ngzipping stuff from JavaScript in the browser is inconvenient until\n[TextEncoderStream](https://developer.mozilla.org/en-US/docs/Web/API/TextEncoderStream)\nis supported in Firefox, and having a smaller uncompressed encoding can be an advantage\nsome cases even where gzip is used.\nI've also observed significant reductions in size between compressed JSON and compressed JCOF\nin certain cases.\n\nI'm publishing it because other people may find it useful too.\nIf you don't find it useful, feel free to disregard it.\n\n## Reference implementations\n\nThe only reference implementation currently is the javascript one, in\n[implementations/javascript/jcof.js](implementations/javascript/jcof.js).\nIt's published on NPM here: https://www.npmjs.com/package/jcof\n\n## Benchmarks\n\nThis is the sizes of various documents in JSON compared to JCOF (from the test suite):\n\n```\ntiny.json:\n  JSON: 299 bytes\n  JCOF: 134 bytes (0.448x)\ncircuitsim.json:\n  JSON: 8315 bytes\n  JCOF: 2093 bytes (0.252x)\npokemon.json:\n  JSON: 219635 bytes\n  JCOF: 39650 bytes (0.181x)\npokedex.json:\n  JSON: 56812 bytes\n  JCOF: 23132 bytes (0.407x)\nmadrid.json:\n  JSON: 37960 bytes\n  JCOF: 11923 bytes (0.314x)\nmeteorites.json:\n  JSON: 244920 bytes\n  JCOF: 87028 bytes (0.355x)\ncomets.json:\n  JSON: 51949 bytes\n  JCOF: 37480 bytes (0.721x)\n````\n\n## The format\n\nHere's the grammar which describes JCOF:\n\n```ebnf\ngrammar ::= string-table ';' object-shape-table ';' value\n\nstring-table ::= (string (','? string)*)?\nstring ::= plain-string | json-string\nplain-string ::= [a-zA-Z0-9]+\njson-string ::= [https://datatracker.ietf.org/doc/html/rfc8259#section-7]\n\nobject-shape-table ::= (object-shape (',' object-shape)*)?\nobject-shape ::= object-key (':'? object-key)*\nobject-key ::= base62 | json-string\nbase62 ::= [0-9a-zA-Z]+\n\nvalue ::=\n  array-value |\n  object-value |\n  number-value |\n  string-value |\n  bool-value |\n  null-value\n\narray-value ::= '[' (value (','? value)*)? ']'\nobject-value ::= shaped-object-value | keyed-object-value\nshaped-object-value ::= '(' base62 (','? value)* ')'\nkeyed-object-value ::= '{' (key-value-pair (','? key-value-pair)*)? '}'\nkey-value-pair ::= object-key ':'? value\nnumber-value ::= 'i' base62 | 'I' base62 | 'finf' | 'fInf' | 'fnan' | float-value\nfloat-value ::= '-'? [0-9]+ ('.' [0-9]+)? (('e' | 'E') ('-' | '+')? [0-9]+)?\nstring-value ::= 's' base62 | json-string\nbool-value ::= 'b' | 'B'\nnull-value ::= 'n'\n```\n\nSee the bottom of the readme for a [railroad diagram](#railroad-diagram).\n\nIn addition to the grammar, you should know the following:\n\n### Many separators are optional\n\nThe grammar contains optional separators (`','?`, `':'?`). These separators can be skipped\nif either the character before or the character after is any of the following:\n`[`, `]`, `{`, `}`, `(`, `)`, `,`, `:` or `\"`. This saves a bunch of bytes.\nJCOF generators can choose to always emit separators, but parsers must accept\nJCOF documents with missing separators.\n\n### The string table\n\nAll JCOF objects start with a string table, which is a list of strings separated by\nan optional `,`.\n\n### The object shapes table\n\nAn \"object shape\" is defined as a list of keys. If you have a bunch of objects with\nthe same keys, it's usually advantageous to define that set of keys once in the\nobject shapes table and encode the objects with the shaped objects syntax.\nAn object shape is a list of object keys optionally separated by `:`,\nand the object shape table is a list of object shapes (non-optionally) separated by `,`\n\n### Base62\n\nBase62 encoding just refers to writing integer numbers in base 62 rather than base 10.\nThis lets us use 0-9, a-z and A-Z as digits. The characters from `0` to `9` represent\n0-9, the characters `a` to `z` represent 10-35, and the characters `A` to `Z` represent\n36-61.\n\n### Values\n\nA value can be:\n\n* An array literal: `[`, followed by 0 or more values, followed by `]`\n* A shaped object literal: `(`, followed by an object shape index, followed by values, followed by `)`\n\t* The object shape index is a base62-encoded index into the object shapes table\n* An object literal: `{`, followed by 0 or more key-value pairs, followed by `}`\n\t* A key-value pair is a base62 index into the header, followed by a `:`, followed by a value\n* A string reference: `s` followed by a base62 index into the header\n* A JSON string literal\n* A number literal:\n\t* `i` followed by a base62 number: A positive integer\n\t* `I` followed by a base62 number: A negative integer\n\t* A floating point number written in decimal, with an optional fractional part and\n\t  an optional exponent part\n* A bool literal:\n\t`b`: true\n\t`B`: false\n* A null literal: `n`\n\n### Railroad diagram\n\ngenerated with\n[bnf-railroad-generator](https://github.com/mortie/bnf-railroad-generator)\n\n![railroad diagram](railroad-diagram.png)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmortie%2Fjcof","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmortie%2Fjcof","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmortie%2Fjcof/lists"}