{"id":23626268,"url":"https://github.com/yetanalytics/pathetic","last_synced_at":"2025-09-04T04:41:15.821Z","repository":{"id":43654684,"uuid":"226365513","full_name":"yetanalytics/pathetic","owner":"yetanalytics","description":"Utility Library for handling JSONPath and navigating JSON structures","archived":false,"fork":false,"pushed_at":"2025-01-13T20:40:24.000Z","size":310,"stargazers_count":37,"open_issues_count":0,"forks_count":0,"subscribers_count":7,"default_branch":"master","last_synced_at":"2025-09-02T05:56:51.345Z","etag":null,"topics":["clojure","clojurescript","json","jsonpath"],"latest_commit_sha":null,"homepage":"https://cljdoc.org/d/com.yetanalytics/pathetic","language":"Clojure","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/yetanalytics.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":"CODE_OF_CONDUCT.md","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":"2019-12-06T16:10:50.000Z","updated_at":"2025-04-07T13:29:23.000Z","dependencies_parsed_at":"2024-01-08T22:30:39.015Z","dependency_job_id":"3f0f6333-c10e-4b71-8052-9e4784adacfe","html_url":"https://github.com/yetanalytics/pathetic","commit_stats":{"total_commits":91,"total_committers":3,"mean_commits":"30.333333333333332","dds":0.2637362637362637,"last_synced_commit":"908b1dd8b599e865c7bb384c036aef9101e778ce"},"previous_names":[],"tags_count":9,"template":false,"template_full_name":null,"purl":"pkg:github/yetanalytics/pathetic","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/yetanalytics%2Fpathetic","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/yetanalytics%2Fpathetic/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/yetanalytics%2Fpathetic/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/yetanalytics%2Fpathetic/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/yetanalytics","download_url":"https://codeload.github.com/yetanalytics/pathetic/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/yetanalytics%2Fpathetic/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":273553551,"owners_count":25126144,"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","status":"online","status_checked_at":"2025-09-04T02:00:08.968Z","response_time":61,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"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":["clojure","clojurescript","json","jsonpath"],"created_at":"2024-12-27T22:52:43.441Z","updated_at":"2025-09-04T04:41:15.767Z","avatar_url":"https://github.com/yetanalytics.png","language":"Clojure","funding_links":[],"categories":[],"sub_categories":[],"readme":"# pathetic\n\n\u003cimg src=\"logo/logo.svg\" alt=\"Pathetic Logo\" /\u003e\n\n[![CI](https://github.com/yetanalytics/pathetic/actions/workflows/main.yml/badge.svg)](https://github.com/yetanalytics/pathetic/actions/workflows/main.yml) [![Clojars Project](https://img.shields.io/clojars/v/com.yetanalytics/pathetic.svg)](https://clojars.org/com.yetanalytics/pathetic) [![Contributor Covenant](https://img.shields.io/badge/Contributor%20Covenant-2.1-5e0b73.svg)](CODE_OF_CONDUCT.md)\n\nUtility Library for working with [JSON Path](https://goessner.net/articles/JsonPath/).\n\n## Installation\n\nAdd the following to your `:deps` map in your `deps.edn` file:\n\n```clojure\ncom.yetanalytics/pathetic {:mvn/version \"0.5.0\"}\n```\n\n## Data\n\nAny JSON data that has been converted to EDN with string keys is accepted. Since at Yet Analytics we largely work with [xAPI Statements](https://xapi.com/statements-101/), we will be using those as our JSON examples. The following JSON example was excised and parsed from the xAPI Statement at `dev-resources/pathetic/data/long.json`.\n\n```clojure\n{\n    \"id\" \"6690e6c9-3ef0-4ed3-8b37-7f3964730bee\"\n    \"result\" {\n        \"success\" true\n        \"completion\" true\n    }\n    \"context\" {\n        \"contextActivities\" {\n            \"category\" [\n                {\n                    \"id\" \"http://www.example.com/meetings/categories/teammeeting\"\n                }\n            ]\n        }\n    }\n}\n```\n\nWithin this README, the example will be referred to as `stmt`.\n\n## Core usage\n\nThe core functions of the Pathetic API are found in the namespace `com.yetanalytics.pathetic`. Most functions take an optional `opts-map` argument; common fields in `opts-map` include:\n\n| Argument | Description\n| ---      | ---\n| `:first?` | Parse or apply only the first path, if the JSONPath string contains multiple paths separated by the `|` character. Default `false`.\n| `:strict?` | Disallows recursive descent, array slicing, and negative indices. This makes JSONPath strings conform to [xAPI Profile](https://adlnet.github.io/xapi-profiles/xapi-profiles-about.html) spec [requirements](https://github.com/adlnet/xapi-profiles/blob/master/xapi-profiles-structure.md#statement-template-rules). Default `false`.\n| `:return-missing?` | Return paths and/or values at locations not found in the JSONPath object. Missing values are returned as `nil`. Default `false`.\n| `:return-duplicates?` | Return duplicate values from a JSONPath object. Default `true`.\n| `:prune-empty?` | Remove empty collections and values from a JSONPath object after having values excised. Default `false`.\n| `:wildcard-append?` | Dictates if wildcard paths or values should be appended to the end of existing collections instead of overwriting existing values. Default `false`.\n| `:wildcard-limit?` | Dictates how many wildcard paths should be generated. In overwrite mode, defaults to the length of each coll encountered. In append mode, the default depends on the function (either `1` or, for `apply-multi-value`, the number of values).\n\nEach function has two versions: a regular and a starred version. The regular versions accept JSONPath strings, while the starred versions accept paths parsed using the `parse` or `parse-first` functions in the `pathetic.parse` namespace. This is useful in performance-critical situations. The starred versions do not accept `:first?` or `:strict?` as `opts-map` fields.\n\n### parse-paths\n\nParse a JSONPath string. Each parsed path is a vector with the following entries:\n\n| Path Element | Description\n| ---          | ---\n| `'..`        | Recursive descent operator symbol\n| `'*`         | Wildcard operator symbol\n| `[...]`      | Vector of strings (keys), integers (array indices), or maps (array slicing operations).\n\nSupported `opts-map` arguments: `:first?` and `:strict?`\n\n``` clojure\n(parse-paths stmt \"$.context.contextActivities.grouping[*]\")\n=\u003e [[[\"context\"] [\"contextActivities\"] [\"grouping\"] '*]]\n\n(parse-paths stmt \"$.id | $.timestamp\")\n=\u003e [[[\"id\"]] [[\"timestamp\"]]]\n\n(parse-paths stmt \"$.id | $.timestamp\" {:first? true})\n=\u003e [[[\"id\"]]]\n```\n\n### get-paths\n\nGiven `json` and a JSONPath string `paths`, return a vector of definite key paths. Each key path is a vector of strings (keys) or integers (array indices); non-deterministic path entries like recursive descent and wildcards are removed. If the string contains multiple JSONPaths, the key paths for all strings are returned.\n\nSupported `opts-map` arguments: `:first?`, `:strict?`, and `:return-missing?`\n\n``` clojure\n(get-paths stmt \"$.context.contextActivities.category[*].id\")\n=\u003e [[\"context\" \"contextActivities\" \"category\" 0 \"id\"]]\n\n(get-paths stmt \"$.context.contextActivities.grouping[*]\")\n=\u003e []\n\n(get-paths stmt \"$.context.contextActivities.grouping[*]\" {:return-missing? true})\n=\u003e [[\"context\" \"contextActivities\" \"grouping\"]]\n```\n\n### get-values\n\nGiven `json` and a JSONPath string `paths`, return a vector of JSON values. If the string contains multiple JSONPaths, we return the union of all these values.\n\nSupported `opts-map` arguments: `:first?`, `:strict?`, `:return-missing?`, and `:return-duplicates?`\n\n``` clojure\n(get-values stmt \"$.id\")\n=\u003e [\"6690e6c9-3ef0-4ed3-8b37-7f3964730bee\"]\n\n(get-values stmt \"$.result.score\")\n=\u003e []\n\n(get-values stmt \"$.result.score\" {:return-missing? true})\n=\u003e [nil]\n\n(get-values stmt \"$.result['success','completion']\")\n=\u003e [true true]\n\n(get-values stmt \"$.result['success','completion']\" {:return-duplicates? false})\n=\u003e [true]\n```\n\n### get-path-value-map\n\nGiven `json` and a JSONPath string `paths`, return a map associating JSON paths to JSON values. Does not return duplicates.\n\nSupported `opts-map` arguments: `:first?`, `:strict?`, and `:return-missing?`\n\n```clojure\n(get-path-value-map stmt \"$.context.contextActivities.category[*].id\")\n=\u003e {[\"context\" \"contextActivities\" \"category\" 0 \"id\"]\n    \"http://www.example.com/meetings/categories/teammeeting\"}\n```\n\n### select-keys-at\n\nGiven `json` and a JSONPath string `paths`, return a vector of maps that represent the key path into the JSON value. If the string contains multiple JSONPaths, we return the maps for all strings. If no value exists at the selection, return a truncated map with `{}` as the innermost possible value.\n\nSupported `opts-map` arguments: `:first?` and `:strict?`\n\n``` clojure\n(select-keys-at stmt \"$.id\")\n=\u003e {\"id\" \"6690e6c9-3ef0-4ed3-8b37-7f3964730bee\"}\n\n(select-keys-at stmt \"$.context.contextActivities.category[*].id\")\n=\u003e {\"context\"\n    {\"contextActivities\"\n     {\"category\"\n      [{\"id\" \"http://www.example.com/meetings/categories/teammeeting\"}]}}}\n\n(select-keys-at stmt \"$.context.contextActivities.category[*].foo\")\n=\u003e {\"context\" {\"contextActivities\" {\"category\" [{}]}}}\n```\n\n### excise\n\nGiven `json` and a JSONPath string `paths`, return the JSON value with the elements at the location removed.\n   \nSupported `opts-map` arguments: `:first?`, `:strict?`, and `prune-empty?`\n\n``` clojure\n(= (dissoc stmt \"id\")\n   (excise stmt \"$.id\"))\n\n(= (update-in long-statement\n              [\"context\" \"contextActivities\" \"category\" 0]\n              dissoc\n              \"id\")\n   (p/excise long-statement\n             \"$.context.contextActivities.category[*].id\"))\n```\n\n### speculate-paths\n\nGiven `json` and a JSONPath string `paths`, return a vector of definite key paths, just like `get-paths`. However, unlike `get-paths`, paths will be enumerated even if the corresponding value does not exist in `json` on that path; in other words, it speculates what paths would exist if they are applied. If the string contains multiple JSONPaths, we\nreturn the key paths for all strings.\n\nSupported `opts-map` arguments: `:first?`, `:wildcard-append?`, and `:wildcard-limit?`; `:strict` is always set to `true`.\n\n```clojure\n(speculate-paths stmt \"$.context.contextActivities.grouping[*]\")\n=\u003e [[\"context\" \"contextActivities\" \"grouping\" 0]]\n\n(speculate-paths stmt \"$.context.contextActivities.category[*].id\")\n=\u003e [[\"context\" \"contextActivities\" \"category\" 1 \"id\"]]\n\n(speculate-paths stmt\n                 \"$.context.contextActivities.category[*].id\" \n                 {:wildcard-limit 2})\n=\u003e [[\"context\" \"contextActivities\" \"category\" 1 \"id\"]\n    [\"context\" \"contextActivities\" \"category\" 2 \"id\"]]\n\n(speculate-paths stmt\n                 \"$.context.contextActivities.category[*].id\" \n                 {:wildcard-append? false})\n=\u003e [[\"context\" \"contextActivities\" \"category\" 0 \"id\"]]\n```\n\n### apply-value\n\nGiven `json`, a JSONPath string `paths`, and the JSON data\n`value`, apply `value` to the location given by `paths` If\nthe location exists, update the pre-existing value. Otherwise,\ncreate the necessary data structures needed to contain `value`.\n\nSupported `opts-map` arguments: `:first?`, `:wildcard-append?`, and `:wildcard-limit?`; `:strict` is always set to `true`.\n\n``` clojure\n(= (assoc-in\n    stmt\n    [\"context\" \"contextActivities\" \"category\" 0 \"id\"]\n    \"http://www.example.com/meetings/categories/brainstorm_sesh\")\n   (apply-value stmt\n                \"$.context.contextActivities.category[*].id\"\n                \"http://www.example.com/meetings/categories/brainstorm_sesh\"))\n\n(= (update-in\n    stmt\n    [\"context\" \"contextActivities\" \"category\"]\n    (conj old\n          {\"id\" \"http://www.example.com/meetings/categories/brainstorm_sesh\"}))\n   (apply-value stmt\n                \"$.context.contextActivities.category[*].id\"\n                \"http://www.example.com/meetings/categories/brainstorm_sesh\"\n                {:wildcard-append? true}))\n```\n\n### apply-multi-value\n\n\n\"Given `json`, a JSONPath string `paths`, and a collection of JSON data `values`, apply `values` to the location given by `paths` in the order they are given. If the location exists, update the pre-existing value. Otherwise, create the necessary data structures needed to contain `value`.\n\nFor example, an array specified by `[0,1]` in the path, then the first and second elements of `value` will be applied. Returns the modified `json` once `values` or the available path sequences run out.\n   \nSupported `opts-map` arguments: `:first?`, `:wildcard-append?`, and `:wildcard-limit?`; `:strict` is always set to `true`.\n\n```clojure\n(= (-\u003e stmt\n       (assoc-in [\"context\" \"contextActivities\" \"category\" 0 \"id\"]\n                 \"http://www.example.com/meetings/categories/brainstorm_sesh\")\n       (assoc-in [\"context\" \"contextActivities\" \"category\" 1 \"id\"]\n                 \"http://www.example.com/meetings/categories/whiteboard_sesh\"))\n   (apply-multi-value stmt\n                      \"$.context.contextActivities.category[*].id\"\n                      [\"http://www.example.com/meetings/categories/brainstorm_sesh\"\n                       \"http://www.example.com/meetings/categories/whiteboard_sesh\"]))\n\n(= (update-in\n    stmt\n    [\"context\" \"contextActivities\" \"category\"]\n    (conj old\n          {\"id\" \"http://www.example.com/meetings/categories/brainstorm_sesh\"})\n          {\"id\" \"http://www.example.com/meetings/categories/whiteboard_sesh\"})\n   (apply-multi-value stmt\n                      \"$.context.contextActivities.category[*].id\"\n                      [\"http://www.example.com/meetings/categories/brainstorm_sesh\"\n                       \"http://www.example.com/meetings/categories/whiteboard_sesh\"]\n                      {:wildcard-append? true}))\n```\n\n## Other usage\n\nUseful functions can be found in other namespaces.\n\n### pathetic.parse/parse\n\n\nGiven a JSONPath string, parse it into data. Returns a vector of parsed paths, or the first error map if one or more paths are invalid.\n\n```clojure\n(parse \"$.foo | $.*.bar\")\n=\u003e [[[\"foo\"]] [* [\"bar\"]]]\n```\n\n### pathetic.parse/parse-first\n\nSame as `parse`, but returns the first parsed JSONPath (that would be separated by the `|` character), or `nil` if the paths are invalid.\n\n```clojure\n(parse \"$.foo | $.*.bar\")\n=\u003e [[\"foo\"]]\n```\n\n### pathetic.parse/path-\u003estring\n\nStringify a parsed path back into a JSONPath string.\n\n```clojure\n(path-\u003estring [* [\"books\"]])\n=\u003e \"$[*]['books']\"\n```\n\n### pathetic.path/path-seqs\n\nGiven a JSON object and a parsed JSONPath, return a seq of maps with the following fields:\n\n- `:json`: The JSON value at the JSONPath location.\n- `:path`: The definite JSONPath that was traversed.\n- `:fail`: If the JSONPath traversal failed due to missing keys or indices\n\n### pathetic.path/speculative-path-seqs\n\nSimilar to `path-seqs`, except it continues traversing the path even if the location in the JSON data is missing or incompatible. Returns the same fields as `path-seqs` except for `:fail`. Accepts `wildcard-append?` and `wildcard-limit` arguments; the latter is nilable.\n\n## License\n\nCopyright © 2019-2025 Yet Analytics, Inc.\n\nDistributed under the Apache License version 2.0. \n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fyetanalytics%2Fpathetic","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fyetanalytics%2Fpathetic","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fyetanalytics%2Fpathetic/lists"}