{"id":15908095,"url":"https://github.com/asseco-voice/laravel-json-query-builder","last_synced_at":"2025-04-09T12:07:08.478Z","repository":{"id":41837247,"uuid":"280735550","full_name":"asseco-voice/laravel-json-query-builder","owner":"asseco-voice","description":"Building Eloquent queries from JSON ","archived":false,"fork":false,"pushed_at":"2024-12-09T23:45:37.000Z","size":381,"stargazers_count":24,"open_issues_count":8,"forks_count":6,"subscribers_count":9,"default_branch":"master","last_synced_at":"2025-04-02T10:14:01.770Z","etag":null,"topics":["eloquent","eloquent-models","json","laravel","laravel-8-package","laravel-framework","laravel-package","microservice","microservices","php","query","query-builder"],"latest_commit_sha":null,"homepage":"","language":"PHP","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/asseco-voice.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,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2020-07-18T20:38:09.000Z","updated_at":"2024-09-19T13:07:02.000Z","dependencies_parsed_at":"2024-03-04T14:35:38.716Z","dependency_job_id":"76ac78c7-e64a-488a-a609-fa377148af69","html_url":"https://github.com/asseco-voice/laravel-json-query-builder","commit_stats":{"total_commits":63,"total_committers":11,"mean_commits":"5.7272727272727275","dds":0.6507936507936508,"last_synced_commit":"259e62bb0fc510bb97004ead61ca89a404c90894"},"previous_names":[],"tags_count":19,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/asseco-voice%2Flaravel-json-query-builder","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/asseco-voice%2Flaravel-json-query-builder/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/asseco-voice%2Flaravel-json-query-builder/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/asseco-voice%2Flaravel-json-query-builder/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/asseco-voice","download_url":"https://codeload.github.com/asseco-voice/laravel-json-query-builder/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248036067,"owners_count":21037092,"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":["eloquent","eloquent-models","json","laravel","laravel-8-package","laravel-framework","laravel-package","microservice","microservices","php","query","query-builder"],"created_at":"2024-10-06T14:09:37.995Z","updated_at":"2025-04-09T12:07:08.456Z","avatar_url":"https://github.com/asseco-voice.png","language":"PHP","funding_links":[],"categories":[],"sub_categories":[],"readme":"\u003cp align=\"center\"\u003e\u003ca href=\"https://see.asseco.com\" target=\"_blank\"\u003e\u003cimg src=\"https://github.com/asseco-voice/art/blob/main/evil_logo.png\" width=\"500\"\u003e\u003c/a\u003e\u003c/p\u003e\n\n# Laravel JSON query builder\n\nThis package enables building queries from JSON objects following\nthe special logic explained below. \n\n## Installation\n\nInstall the package through composer. It is automatically registered\nas a Laravel service provider.\n\n``composer require asseco-voice/laravel-json-query-builder``\n\n## Usage\n\nIn order to use the package, you need to instantiate ``JsonQuery()`` providing \ntwo dependencies to it. One is ``Illuminate\\Database\\Eloquent\\Builder`` instance,\nand the other is a JSON/array input.\n\nOnce instantiated, you need to run the ``search()`` method, and query will be\nconstructed on the provided builder object.\n\n```\n$jsonQuery = new JsonQuery($builder, $input);\n$jsonQuery-\u003esearch();\n```\n\n## Dev naming conventions for this package\n\n- **parameter** is a top-level JSON key name (see the options [below](#parameter-breakdown))\n- **arguments** are parameter values. Everything within a top-level JSON.\n- **argument** is a single key-value pair.\n- single argument is further broken down to **column / operator / value** \n\n```\n{\n    \"search\": {                         \u003c-- parameter\n        \"first_name\": \"=foo\",           \u003c-- argument\n        \"last_name\": \"  =       bar  \"  \u003c-- argument   \n         ˆˆˆˆˆˆˆˆˆ      ˆ       ˆˆˆ\n          column    operator   value\n    }\n}\n```\n\n## Parameter breakdown\nParameters follow a special logic to query the DB. It is possible to use the following\nJSON parameters (keys):\n\n- ``search`` - will perform the querying logic (explained in detail [below](#search))\n- ``returns`` - will return only the columns provided as values.\n- ``order_by`` - will order the results based on values provided.\n- ``group_by`` - will group the results based on values provided.\n- ``relations`` - will load the relations for the given model.\n- `limit` - will limit the results returned.\n- `offset` - will return a subset of results starting from a point given. This parameter **MUST**\nbe used together with ``limit`` parameter. \n- `count` - will return record count.\n- `soft_deleted` - will include soft deleted models in search results.\n- `doesnt_have_relations` - will only return entries that don't have any of the specified relations.\n\n### Search\n\nThe logic is done in a ``\"column\": \"operator values\"`` fashion in which we assume the \nfollowing:\n\n- `column` represents a column in the database. Multiple keys can be separated as a new\nJSON key-value pair. \n- It is possible to search by related models using ``.`` as a divider i.e. \n`\"relation.column\": \"operator value\")`. **Note** this will execute a `WHERE EXISTS`, it will \nnot filter resulting relations if included within relations. To do a ``WHERE NOT EXISTS`` you can ue `!relation.column`.\n- ``operator`` is one of the available main operators for querying (listed [below](#main-operators))\n- ``values`` is a semicolon (`;`) separated list of values \n(i.e. `\"column\": \"=value;value2;value3\"`) which\ncan have micro-operators on them as well (i.e. `\"column\": \"=value;!value2;%value3%\"`). \n\n#### Main operators\n\n- `=` - equals\n- `!=` - does not equal\n- `\u003c` - less than (requires exactly one value)\n- `\u003e` - greater than (requires exactly one value)\n- `\u003c=` - less than or equal (requires exactly one value)\n- `\u003e=` - greater than or equal (requires exactly one value)\n- `\u003c\u003e` - between (requires exactly two values)\n- `!\u003c\u003e` - not between (requires exactly two values)\n\nExample:\n\n```\n{\n    \"search\": {\n        \"first_name\": \"=foo1;foo2\",\n        \"last_name\": \"!=bar1;bar2\" \n    }\n}\n```\n\nWill perform a ``SELECT * FROM some_table WHERE first_name IN \n('foo1', 'foo2') AND last_name NOT IN ('bar1', 'bar2')``.\n\nIn case you pass a single value for a column on string type, LIKE operator will be used\ninstead of IN. For Postgres databases, ILIKE is used instead of LIKE to support\ncase-insensitive search.\n\n```\n{\n    \"search\": {\n        \"first_name\": \"=foo\",\n        \"last_name\": \"!=bar\" \n    }\n}\n```\n\nWill perform a ``SELECT * FROM some_table WHERE first_name LIKE 'foo' AND\nlast_name NOT LIKE 'bar'``.\n\n#### Micro operators\n\n- `!` - negates the value. Works only on the beginning of the value (i.e. `!value`).\n- `%` - performs a `LIKE` query. Works only on a beginning, end or both ends of the \nvalue (i.e. `%value`, `value%` or `%value%`).\n- logical operators are used to use **multiple operators** (you can't do `=1||2`, but `=1||=2`) for a \nsingle column (order matters!):\n   - `\u0026\u0026` enables you to connect values using AND\n   - `||` enables you to connect values using OR\n\n\n```\n{\n    \"search\": {\n        \"first_name\": \"=!foo\",\n        \"last_name\": \"=bar%\" \n    }\n}\n```\n\nWill perform a ``SELECT * FROM some_table WHERE first_name NOT LIKE \n'foo' AND last_name LIKE 'bar%'``.\n\nNotice that here ``!value`` behaved the same as ``!=`` main operator. The difference\nis that ``!=`` main operator negates the complete list of values, whereas the \n``!value`` only negates that specific value. I.e. `!=value1;value2` is semantically\nthe same as ``=!value1;!value2``.\n\nLogical operator example: \n\n```\n{\n    \"search\": {\n        \"first_name\": \"=foo||=bar\",\n    }\n}\n```\n\nWill perform ``SELECT * FROM some_table WHERE first_name IN \n ('foo') OR first_name IN ('bar')``.\n\nNote that logical operators are using standard bool logic precedence,\ntherefore ``x AND y OR z AND q`` is the same as `(x AND y) OR (z AND q)`.\n\n#### Nested relation searches\n\nYou can nest another search object if the key used is a relation name which\nwill execute a ``whereHas()`` query builder method.\n\nI.e.\n```\n{\n    \"search\": {\n        \"some_relation\": {\n            \"search\": { ... }\n        },\n    }\n}\n```\n\n### Returns\n\nUsing a ``returns`` key will effectively only return the fields given within it.\nThis operator accepts an array of values or a single value. \n\nExample:\n\nReturn single value:\n```\n{\n    \"returns\": \"first_name\",\n}\n```\nWill perform a ``SELECT first_name FROM ...``\n\nReturn multiple values:\n```\n{\n    \"returns\": [\"first_name\", \"last_name\"]\n}\n```\nWill perform a ``SELECT first_name, last_name FROM ...``\n\n### Order by\n\nUsing ``order_by`` key does an 'order by' based on the given key(s). Order of the keys\nmatters!\n\nArguments are presumed to be in a ``\"column\": \"direction\"`` fashion, where `direction`\nMUST be ``asc`` (ascending) or `desc` (descending). In case that only column is provided,\ndirection will be assumed to be an ascending order. \n\nExample:\n```\n{\n    \"order_by\": {\n        \"first_name\": \"asc\",\n        \"last_name\": \"desc\" \n    }\n}\n```\n\nWill perform a ``SELECT ... ORDER BY first_name asc, last_name desc``\n\n### Group by\n\nUsing ``group_by`` key does an 'group by' based on the given key(s). Order of the keys\nmatters!\n\nArguments are presumed to be a single attribute or array of attributes.\n\nSince group by behaves like it would in a plain SQL query, be sure to select\nthe right fields and aggregate functions. \n\nExample:\n```\n{\n    \"group_by\": [\"last_name\", \"first_name\"]\n}\n```\n\nWill perform a ``SELECT ... GROUP BY last_name, first_name``\n\n### Relations\n\nIt is possible to load object relations as well by using ``relations`` parameter.\nThis operator accepts an array of values or a single value. \n\n\n#### Simple \n\nExample:\n\nResolve single relation:\n```\n{\n    \"relations\": \"containers\",\n}\n```\n\nResolve multiple relations:\n```\n{\n    \"relations\": [\"containers\", \"addresses\"]\n}\n```\n\nRelations, if defined properly and following Laravel convention, should be predictable\nto assume:\n\n- 1:M \u0026 M:M - relation name is in plural (i.e. Contact has many **Addresses**, relation \nname is thus 'addresses')\n- M:1 - relation name is in singular (i.e. Comment belongs to a **Post**, relation\nname is thus 'post')\n- **important**: since Laravel returns API responses as **snake_case**, it is enabled to\nprovide a **snake_case'd** relation (even though **camelCase** works as well) for multi-word\nrelations. I.e. doing ``\"relations\": \"workspace_items\"`` is the equivalent of calling \n``\"relations\": \"workspaceItems\"``, but it is recommended to use **snake_case** approach.\n\nIt is possible to recursively load relations using dot notation:\n\n```\n{\n    \"relations\": \"media.type\"\n}\n```\n\nThis will load media relations as well as resolve media types right away. If you have the\nneed to resolve multiple second level relations you can provide an array of those:\n\n```\n{\n    \"relations\": [\"media.type\", \"media.category\"]\n}\n```\n\nThis will load media relations together with resolved type and category for each media object. \n\nIt is also possible to stack relations using dot notation without a limit. It must be taken \ninto account though that this can **seriously hurt performance**!\n\n```\n{\n    \"relations\": \"media.type.contact.title\"\n}\n```\n\n#### Complex\n\nRelation can also be an object with nested search attributes to additionally filter out\nthe given result set.\n\nExample:\n\n```\n\"relations\": [\n    {\n        \"media\": {\n            \"search\": {\n                \"media_type_id\": \"=1\"\n            }\n        }\n    }\n]\n```\n\nwill load a ``media`` relationship on the object, but only returning objects whose \n``media_type_id`` equals to `1`.\n\n### Limit\n\nYou can limit the number of results fetched by doing:\n\n```\n{\n    \"limit\": 10\n}\n```\n\nThis will do a ``SELECT * FROM table LIMIT 10``.\n\n### Offset\n\nYou can use offset to further limit the returned results, however it\nrequires using limit alongside it. \n\n```\n{\n    \"limit\": 10,\n    \"offset\": 5\n}\n```\n\nThis will do a ``SELECT * FROM table LIMIT 10 OFFSET 5``.\n\n### Count\n\nYou can fetch count of records instead of concrete records by adding the count key:\n\n```\n{\n    \"count\": true\n}\n```\n\nThis will do a ``SELECT count(*) FROM table``.\n\n### Soft deleted\n\nBy default, soft deleted records are excluded from the search. This can be overridden\nwith ``soft_deleted``:\n\n```\n{\n    \"soft_deleted\": true\n}\n```\n\n### Doesn't have relations\n\nIn case you want to only find entries without a specified relation, you can do so with ``doesnt_have_relations`` key:\n\n```\n{\n    \"doesnt_have_relations\": \"containers\"\n}\n```\n\nIf you want to specify multiple relations, you can do so in the following way:\n\n```\n{\n    \"doesnt_have_relations\": [\"containers\", \"addresses\"]\n}\n```\n\n## Top level logical operators\n\nAdditionally, it is possible to group search clauses by top-level logical operator.\n\nAvailable operators:\n- ``\u0026\u0026`` AND\n- ``||`` OR\n\n**Using no top-level operator will assume AND operator.**\n\n### Examples \n\nThese operators take in a single object, or an array of objects, with few differences worth mentioning.\nSingle object will apply the operator on given attributes:\n\n```\n{\n    \"search\": {\n        \"\u0026\u0026\": {\n            \"id\": \"=1\",\n            \"name\": \"=foo\"\n        }\n    }\n}\n```\n\nResulting in ```id=1 AND name=foo```. \nWhereas an array of objects will apply the operator between array objects, **not** within the objects themselves:\n\n```\n{\n    \"search\": {\n        \"||\": [\n            {\n                \"id\": \"=1\",\n                \"name\": \"=foo\"\n            },\n            {\n                \"id\": \"=2\",\n                \"name\": \"=bar\"\n            }\n        ]\n    }\n}\n```\n\nResulting in ``(id=1 AND name=foo) OR (id=2 AND name=bar)``. This is done intentionally, default operator is\nAND, thus it will be applied within objects.\n\nIf you'd like inner attributes changed to OR instead, you can go recursive:\n\n```\n{\n    \"search\": {\n        \"||\": [\n            {\n                \"||\": {\n                    \"id\": \"=1\",\n                    \"name\": \"=foo\"\n                }\n            },\n            {\n                \"id\": \"=2\",\n                \"name\": \"=bar\"\n            }\n        ]\n    }\n}\n```\n\nResulting in ``(id=1 OR name=foo) OR (id=2 AND name=bar)``.\n\n### Absurd examples\n\nSince logic is made recursive, you can go as absurd and deep as you'd like, but at this point\nit may be smarter to revise what do you actually want from your life and universe:\n\n```\n{\n    \"search\": {\n        \"||\": {\n            \"\u0026\u0026\": [\n                {\n                    \"||\": [\n                        {\n                            \"id\": \"=2||=3\",\n                            \"name\": \"=foo\"\n                        },\n                        {\n                            \"id\": \"=1\",\n                            \"name\": \"=foo%\u0026\u0026=%bar\"\n                        }\n                    ]\n                },\n                {\n                    \"we\": \"=cool\"\n                }\n            ],\n            \"love\": \"\u003c3\",\n            \"recursion\": \"=rrr\"\n        }\n    }\n}\n```\n\nBreakdown:\n\n- Step 1\n```\n{\n    \"id\": \"=2||=3\",\n    \"name\": \"=foo\"\n},\n```\nResult: ``(id=2 OR id=3) AND name=foo``\n\n- Step 2\n```\n{\n    \"id\": \"=1\",\n    \"name\": \"=foo%\u0026\u0026=%bar\"\n}\n```\nResult: ``id=1 AND (name LIKE foo% AND name LIKE %bar)``\n\n- Step 3 (merge)\n\n```\n\"||\": [\n    {...},\n    {...}\n]\n```\nResult: ``(step1) OR (step2)``\n\n- Step 4 ``we=cool``\n```\n{\n    \"we\": \"=cool\"\n}\n```\n\n- Step 5 (merge) \n```\n\"\u0026\u0026\": [\n    {\n        \"||\": [...]\n    },\n    {\n        \"we\": \"=cool\"\n    }\n],\n```\nResult: ``(step3) AND (step4)``\n\n- Step 6 (ultimate merge)\n\n```\n\"||\": {\n    \"\u0026\u0026\": [...],\n    \"love\": \"\u003c3\",\n    \"recursion\": \"=rrr\"\n}\n```\nResult: ``(step5) OR love\u003c3 OR recursion=rrr``\n\nThe final query (kill it with fire):\n\n``((((id=2 OR id=3) AND name=foo) OR (id=1 AND (name LIKE foo% AND name LIKE %bar))) AND we=cool) OR love\u003c3 OR recursion=rrr``\n\n## Config \n\nAside from standard query string search, it is possible to provide additional \npackage configuration.\n\nPublish the configuration by running \n`php artisan vendor:publish --provider=\"Asseco\\JsonQueryBuilder\\JsonQueryServiceProvider\"`.\n\nAll the keys within the configuration file have a detailed explanation above each key.\n\n### Package extensions\n\nOnce configuration is published you will see several keys which you can extend with your\ncustom code. \n\n- request parameters are registered under ``request_parameters`` config key. \nYou can extend this functionality by adding your own custom parameter. It \nneeds to extend ``Asseco\\JsonQueryBuilder\\RequestParameters\\AbstractParameter``\nin order to work.  \n- operators are registered under ``operators`` config key. Those can be \nextended by adding a class which extends ``Asseco\\JsonQueryBuilder\\SearchCallbacks\\AbstractCallback``.\n- types are registered under ``types`` config key. Those can be extended\nby adding a class which extends ``Asseco\\JsonQueryBuilder\\Types\\AbstractType``.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fasseco-voice%2Flaravel-json-query-builder","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fasseco-voice%2Flaravel-json-query-builder","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fasseco-voice%2Flaravel-json-query-builder/lists"}