{"id":17087898,"url":"https://github.com/geraintluff/json-store","last_synced_at":"2025-08-31T20:03:30.312Z","repository":{"id":8516629,"uuid":"10130050","full_name":"geraintluff/json-store","owner":"geraintluff","description":"Storing JSON data in MySQL, as simply as possible","archived":false,"fork":false,"pushed_at":"2013-11-05T17:24:48.000Z","size":516,"stargazers_count":26,"open_issues_count":1,"forks_count":10,"subscribers_count":4,"default_branch":"master","last_synced_at":"2025-03-26T16:04:53.947Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"","language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":"Zoomicon/PushPullRotate","license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/geraintluff.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2013-05-17T19:03:50.000Z","updated_at":"2022-05-16T18:24:04.000Z","dependencies_parsed_at":"2022-09-19T09:01:23.872Z","dependency_job_id":null,"html_url":"https://github.com/geraintluff/json-store","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/geraintluff%2Fjson-store","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/geraintluff%2Fjson-store/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/geraintluff%2Fjson-store/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/geraintluff%2Fjson-store/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/geraintluff","download_url":"https://codeload.github.com/geraintluff/json-store/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248637780,"owners_count":21137538,"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-10-14T13:35:20.367Z","updated_at":"2025-04-12T22:01:33.111Z","avatar_url":"https://github.com/geraintluff.png","language":"JavaScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# JSON Store\n\nStoring JSON data with PHP/MySQL, as simply as possible.\n\nThe idea is that the database tables can look completely normal - in fact, in many cases you can write configs for your existing table structure.\n\nOnce you have supplied a config, then loading/saving/creation/deletion are all handled automatically.  Searches can be performed using [JSON Schema](http://json-schema.org/) as a query language.\n\nIt is **not** intended to be a full-featured ORM.  It is simply a data-store, and intends to remain lightweight - however, an ORM could be built on top of it if needed.\n\n### Example usage:\n\n```php\nclass MyClass extends JsonStore {\n\tstatic public function open($id) {\n\t\t$schema = new JsonSchema;\n\t\t$schema-\u003eproperties-\u003eid-\u003eenum = array((int)$id);\n\t\t$results = self::search($schema);\n\t\tif (count($results) == 1) {\n\t\t\treturn $results[0];\n\t\t}\n\t}\n\tstatic public function search($schema) {\n\t\t$results = JsonStore::schemaSearch('MyClass', $schema, array(\"integer/id\" =\u003e \"ASC\"));\n\t\tforeach ($results as $index =\u003e $item) {\n\t\t\t$results[$index] = new MyClass($item);\n\t\t}\n\t\treturn $results;\n\t}\n\tstatic public function create($startingData) {\n\t\t$startingData = (object)$startingData;\n\t\tunset($startingData-\u003eid); // For safety\n\t\tif (!isset($startingData-\u003esource)) {\n\t\t\t$startingData-\u003esource = new StdClass;\n\t\t} else {\n\t\t\t$startingData-\u003esource = (object)$startingData-\u003esource;\n\t\t}\n\t\treturn new MyClass($startingData); // constructor is protected\n\t}\n}\nJsonStore::addMysqlConfig('MyClass', array(\n\t\"table\" =\u003e \"my_table_name\",\n\t\"keyColumn\" =\u003e \"integer/id\",\n\t\"columns\" =\u003e array(\n\t\t\"integer/id\" =\u003e \"id\",\n\t\t\"string/title\" =\u003e \"title\",\n\t\t\"string/source/url\" =\u003e \"source_url\",\n\t\t\"boolean/source/verified\" =\u003e \"source_verified\"\n\t)\n));\n\n$myObj = MyClass::create(array(\n\t\"title\" =\u003e \"Hello, world!\",\n\t\"source\" =\u003e array(\n\t\t\"url\" =\u003e \"http://example.com/\",\n\t\t\"verified\" =\u003e FALSE\n\t)\n));\nisset($myObj-\u003eid); // FALSE\n$myObj-\u003etitle; // \"Hello, world!\";\n\n$myObj-\u003esave(); // performs an INSERT\n$myObj-\u003eid; // taken from the auto-increment in the database\n\n$myObj-\u003esource-\u003everified = TRUE;\n$myObj-\u003esave(); // performs an UPDATE\n```\n\n## Using the library\n\nYou'll need to include `include/json-store.php`.\n\nYou then just subclass `JsonStore`.  The rules are:\n\n*\tThe default constructor takes either:\n    *\ta plain object (such as returned by `JsonStore::schemaSearch()`)\n    *\tan associative array representing the database row (as returned by `$mysqli-\u003efetch_assoc()` or `JsonStore::mysqlQuery()`)\n*\tYou then make a call to `JsonStore::addMysqlConfig`, looking something like this:\n\n```php\nJsonStore::addMysqlConfig('MyCustomClass', array(\n\t\"table\" =\u003e {{table name}},\n\t\"keyColumn\" =\u003e \"integer/id\",\n\t\"columns\" =\u003e array(...)\n);\n```\n\nIt's a good idea to have either `\"keyColumn\"` (single value) or `\"keyColumns\"` (array representing a composite key).\n\nIf `\"keyColumn\"` is present (and begins with \"integer\"), it is updated using the auto-increment value from the table.\n\n## Table structure and column names\n\nUnder the hood, JsonStore assumes that column names follow a particular pattern: `{type}{pointer}`.\n\nThe `type` part of the column name denotes the JSON type to be stored there - one of \"json\" (raw JSON text), \"integer\", \"number\", \"string\" or \"array\".\n\nThe remaining part of the column name is a JSON Pointer representing where that data maps to in the JSON object.  For example, the column name `integer/id`\n\n### Simple Example\n\nSay we are storing this data:\n\n```json\n{\n\t\"id\": 1,\n\t\"title\": \"Hello, World!\",\n\t\"someOtherProperty\": [1, 2, 3]\n}\n```\n\nAnd say our columns are:\n\n*\t`json`\n*\t`integer/id`\n*\t`string/title`\n\nThen the table entry will look something like this:\n\n```\n----------------------------------------------\n|   json    |  integer/id  |  string/title   |\n----------------------------------------------\n| '{ ... }' |      1       | 'Hello, World!' |\n----------------------------------------------\n```\n\n### Aliasing column names\n\nBecause those column names are a bit messy, they can be aliased.  To do this, simply use the JsonStore representation (e.g. \"string/owner/name\") as the key, and the actual column name as the value:\n```php\nJsonStore::addMysqlConfig('MyCustomClass', array(\n\t\"table\" =\u003e 'MyTableName',\n\t\"keyColumn\" =\u003e \"integer/id\",\n\t\"columns\" =\u003e array(\n\t\t\"integer/id\" =\u003e \"id\",\n\t\t\"string/owner/id\" =\u003e \"owner_id\",\n\t\t\"string/owner/name\" =\u003e \"owner_name\"\n\t)\n);\n```\n\nThis aliasing can also be specified using `\"alias\"` - the two are largely equivalent:\n```php\nJsonStore::addMysqlConfig('MyCustomClass', array(\n\t\"table\" =\u003e 'MyTableName',\n\t\"keyColumn\" =\u003e \"integer/id\",\n\t\"columns\" =\u003e array(\"integer/id\", ...),\n\t\"alias\" =\u003e array(\n\t\t\"integer/id\" =\u003e \"id\",\n\t\t...\n\t)\n);\n```\n\n## Circular references\n\nDon't use them.  In fact, don't even reference JsonStore objects from each other.\n\nA better pattern is in fact to store an identifier in the object, and then define a method like this:\n\n```php\nclass MyClass extends JsonStore {\n\tpublic function open($id) {\n\t\t$sql = \"SELECT * FROM my_table WHERE `integer/id`=\".(int)$id;\n\t\t$rows = self::mysqlQuery($sql);\n\t\tif (count($rows)) {\n\t\t\treturn new MyClass($rows[0]);  // if you pass in an array, it inflates it into a full object\n\t\t}\n\t}\n\n\tpublic function parent() {\n\t\treturn MyClass::open($this-\u003eparentId);\n\t}\n}\n```\n\n## Arrays\n\nIf an entry in `\"columns\"` is an array (i.e. the column name begins `array/...`), then the corresponding value should be itself be a config.  The format is similar to above, with the following differences:\n\n* `\"keyColumn\"`/`\"keyColumns\"` will be ignored\n* If the optional parameter `\"parentColumn\"` is present, then this column (actual/aliased name, not the JsonStore internal one) is used to match against the array table.\n* There are two implied columns: `\"group\"` and `\"index\"`.  These can be renamed just like any other column.\n    * If `\"parentColumn\"` is specified, `\"group\"` must be of the same type.  Otherwise, `\"group\"` must be an auto-incrementing integer, as must the `\"array/...\"` column in the original table.\n    * `\"index\"` must be an integer type\n\n### Basic config example\n\nHere is a basic example using an array from a separate table:\n```php\nJsonStore::addMysqlConfig('MyCustomClass', array(\n\t\"table\" =\u003e 'my_table',\n\t\"keyColumn\" =\u003e \"integer/id\",\n\t\"columns\" =\u003e array(\n\t\t\"integer/id\",\n\t\t\"string/title\",\n\t\t\"array/integerList\" =\u003e array(\n\t\t\t\"table\" =\u003e 'my_table_integer_list',\n\t\t\t\"columns\" =\u003e array(\n\t\t\t\t\"integer\"\n\t\t\t)\n\t\t)\n\t),\n);\n```\n\nThat example assumes the following columns for `my_table`:\n\n* `integer/id` - auto-incrementing integer\n* `string/title` - some string type\n* `array/integerList` - an integer\n\nand the following columns for `my_table_integer_list`:\n\n* `group` - auto-incrementing integer - will match up with the values in `array/integerList`\n* `index` - integer\n* `integer` - integer (represents the actual value at that index for that array)\n\n### Complex array example\n\nSay we are storing this data:\n\n```json\n{\n\t\"id\": 5,\n\t\"title\": \"Hello!\",\n\t\"myArray\": [\n\t\t1,\n\t\t2,\n\t\t{\"id\": 3, \"name\": \"three\"}\n\t]\n}\n```\n\nAnd say our config looks like this:\n\n```php\nJsonStore::addMysqlConfig('MyCustomClass', array(\n\t\"table\" =\u003e \"my_table\",\n\t\"keyColumn\" =\u003e \"integer/id\",\n\t\"columns\" =\u003e array(\n\t\t\"integer/id\" =\u003e \"id\",\n\t\t\"string/title\" =\u003e \"title\",\n\t\t\"array/myArray\" =\u003e array(\n\t\t\t\"table\" =\u003e \"my_array_items_table\",\n\t\t\t\"parentKey\" =\u003e \"id\",\n\t\t\t\"columns\" =\u003e array(\n\t\t\t\t\"group\" =\u003e \"parent_id\",\n\t\t\t\t\"index\" =\u003e \"pos\",\n\t\t\t\t\"integer\" =\u003e \"int_value\",\n\t\t\t\t\"integer/id\" =\u003e \"obj_id\",\n\t\t\t\t\"string/name\" =\u003e \"obj_name\"\n\t\t\t)\n\t\t)\n\t)\n);\n```\n\nThen our main table (`my_table`) will look something like this:\n\n```\n--------------------------------\n|      id      |     title     |\n--------------------------------\n|       5      |    \"Hello!\"   |\n--------------------------------\n```\n\nAnd our array table (`my_array_items_table`) will look something like this:\n\n```\n-------------------------------------------------------------\n|  parent_id  |  pos  |  int_value  |  obj_id  |  obj_name  |\n-------------------------------------------------------------\n|      5      |   0   |      1      |   NULL   |    NULL    |\n-------------------------------------------------------------\n|      5      |   1   |      2      |   NULL   |    NULL    |\n-------------------------------------------------------------\n|      5      |   2   |     NULL    |    3     |    three   |\n-------------------------------------------------------------\n```\n\nNote that because we specified `\"parentKey\"` in the config, `my_table` doesn't need a separate column for the array ID.  However, this means that there will *always* be an array in `$data-\u003emyArray`, it will simply be empty if there are no entries in `my_array_items_table`.\n\n## Searching with JSON Schema\n\nJsonStore provides a helper class: JsonSchema.\n\nThis class doesn't really do anything - it just makes it easier to assemble JSON Schemas by creating properties as they are needed.  It also contains some shortcuts - for example, if you specify \"properties\", but haven't specified \"type\", then \"type\" will default to \"object\".\n\n```php\n$schema = new JsonSchema();\n$schema-\u003eproperties-\u003eid = enum(5);  // $schema-\u003etype has now defaulted to \"object\"\n```\n\nYou will need to write your own static method for each of your classes, for example:\n\n```php\nclass MyClass extends JsonStore {\n\tpublic static function openAll($schema=NULL, $orderBy=NULL) {\n\t\tif (!$schema) {\n\t\t\t$schema = new JsonSchema(); // empty query\n\t\t}\n\t\tif (!$orderBy) {\n\t\t\t$orderBy = array(\"integer/id\" =\u003e \"ASC\");\n\t\t}\n\t\t$results = self::schemaSearch('MyClass', $schema, $orderBy);\n\t\tforeach ($results as $index =\u003e $item) {\n\t\t\t$results[$index] = new MyClass($item); // JsonStore::schemaSearch gives us an array of objects back\n\t\t}\n\t\treturn $results;\n\t}\n}\n```\n\n`JsonStore::schemaSearch()` will convert the schema into an SQL query representing the same constraints.\n\nIf there are any constraints that cannot be translated into the SQL query (because the database structure doesn't support them, or JsonStore doesn't recognise the keywords), then the results are filtered using the `jsv4-php` validation library before being returned.\n\n## Caching loaded values\n\nCacheing is not done automatically, however some convenience functions are provided.\n\n```php\nclass MyClass extends JsonStore {\n\tstatic public function open($id) {\n\t\tif ($cached = JsonStore::cached('MyClass', $id)) {\n\t\t\treturn $cached;\n\t\t}\n\t\t$schema = new JsonSchema;\n\t\t$schema-\u003eproperties-\u003eid-\u003eenum = array((int)$id);\n\t\t$results = self::search($schema);\n\t\tif (count($results) == 1) {\n\t\t\treturn JsonStore::setCached('MyClass', $id, $results[0]);\n\t\t}\n\t}\n\t...\n}\n```\n\nSimilar things would obviously need to be done for search results as well.\n\n## Precedence when loading data\n\nWhen loading data, values from more specific columns (longer paths) always take precedence.\n\nSo for instance, given the following table:\n```\n-----------------------------------\n|    json/key     | boolean/key/b |\n-----------------------------------\n| '{\"a\":1,\"b\":2}' |       1       |\n-----------------------------------\n```\n\nThe data we will load will look like:\n```json\n{\n\t\"key\": {\n\t\t\"a\": 1,\n\t\t\"b\": true\n\t}\n}\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fgeraintluff%2Fjson-store","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fgeraintluff%2Fjson-store","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fgeraintluff%2Fjson-store/lists"}