{"id":40506188,"url":"https://github.com/tomhodgins/espath","last_synced_at":"2026-01-20T19:36:14.548Z","repository":{"id":57230482,"uuid":"172018400","full_name":"tomhodgins/espath","owner":"tomhodgins","description":"Query deeply nested JavaScript objects with XPath","archived":false,"fork":false,"pushed_at":"2019-02-25T02:06:16.000Z","size":44,"stargazers_count":6,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-10-21T11:03:49.296Z","etag":null,"topics":["dom","filter","find","javascript","json","microxml","object","query","search","selector","xml","xpath"],"latest_commit_sha":null,"homepage":null,"language":"HTML","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/tomhodgins.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":"2019-02-22T07:45:04.000Z","updated_at":"2025-04-05T09:19:27.000Z","dependencies_parsed_at":"2022-09-01T22:30:46.165Z","dependency_job_id":null,"html_url":"https://github.com/tomhodgins/espath","commit_stats":null,"previous_names":["tomhodgins/deepmatch"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/tomhodgins/espath","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tomhodgins%2Fespath","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tomhodgins%2Fespath/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tomhodgins%2Fespath/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tomhodgins%2Fespath/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/tomhodgins","download_url":"https://codeload.github.com/tomhodgins/espath/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tomhodgins%2Fespath/sbom","scorecard":{"id":892604,"data":{"date":"2025-08-18","repo":{"name":"github.com/tomhodgins/espath","commit":"4281b266073f51d38e960d9976e1d9f523fc4f2a"},"scorecard":{"version":"v5.2.1-41-g40576783","commit":"40576783fda6698350fcbbeaea760ff827433034"},"score":3,"checks":[{"name":"Dangerous-Workflow","score":-1,"reason":"no workflows found","details":null,"documentation":{"short":"Determines if the project's GitHub Action workflows avoid dangerous patterns.","url":"https://github.com/ossf/scorecard/blob/40576783fda6698350fcbbeaea760ff827433034/docs/checks.md#dangerous-workflow"}},{"name":"Token-Permissions","score":-1,"reason":"No tokens found","details":null,"documentation":{"short":"Determines if the project's workflows follow the principle of least privilege.","url":"https://github.com/ossf/scorecard/blob/40576783fda6698350fcbbeaea760ff827433034/docs/checks.md#token-permissions"}},{"name":"Pinned-Dependencies","score":-1,"reason":"no dependencies found","details":null,"documentation":{"short":"Determines if the project has declared and pinned the dependencies of its build process.","url":"https://github.com/ossf/scorecard/blob/40576783fda6698350fcbbeaea760ff827433034/docs/checks.md#pinned-dependencies"}},{"name":"Packaging","score":-1,"reason":"packaging workflow not detected","details":["Warn: no GitHub/GitLab publishing workflow detected."],"documentation":{"short":"Determines if the project is published as a package that others can easily download, install, easily update, and uninstall.","url":"https://github.com/ossf/scorecard/blob/40576783fda6698350fcbbeaea760ff827433034/docs/checks.md#packaging"}},{"name":"SAST","score":0,"reason":"no SAST tool detected","details":["Warn: no pull requests merged into dev branch"],"documentation":{"short":"Determines if the project uses static code analysis.","url":"https://github.com/ossf/scorecard/blob/40576783fda6698350fcbbeaea760ff827433034/docs/checks.md#sast"}},{"name":"Maintained","score":0,"reason":"0 commit(s) and 0 issue activity found in the last 90 days -- score normalized to 0","details":null,"documentation":{"short":"Determines if the project is \"actively maintained\".","url":"https://github.com/ossf/scorecard/blob/40576783fda6698350fcbbeaea760ff827433034/docs/checks.md#maintained"}},{"name":"Binary-Artifacts","score":10,"reason":"no binaries found in the repo","details":null,"documentation":{"short":"Determines if the project has generated executable (binary) artifacts in the source repository.","url":"https://github.com/ossf/scorecard/blob/40576783fda6698350fcbbeaea760ff827433034/docs/checks.md#binary-artifacts"}},{"name":"Code-Review","score":0,"reason":"Found 0/13 approved changesets -- score normalized to 0","details":null,"documentation":{"short":"Determines if the project requires human code review before pull requests (aka merge requests) are merged.","url":"https://github.com/ossf/scorecard/blob/40576783fda6698350fcbbeaea760ff827433034/docs/checks.md#code-review"}},{"name":"CII-Best-Practices","score":0,"reason":"no effort to earn an OpenSSF best practices badge detected","details":null,"documentation":{"short":"Determines if the project has an OpenSSF (formerly CII) Best Practices Badge.","url":"https://github.com/ossf/scorecard/blob/40576783fda6698350fcbbeaea760ff827433034/docs/checks.md#cii-best-practices"}},{"name":"Security-Policy","score":0,"reason":"security policy file not detected","details":["Warn: no security policy file detected","Warn: no security file to analyze","Warn: no security file to analyze","Warn: no security file to analyze"],"documentation":{"short":"Determines if the project has published a security policy.","url":"https://github.com/ossf/scorecard/blob/40576783fda6698350fcbbeaea760ff827433034/docs/checks.md#security-policy"}},{"name":"Fuzzing","score":0,"reason":"project is not fuzzed","details":["Warn: no fuzzer integrations found"],"documentation":{"short":"Determines if the project uses fuzzing.","url":"https://github.com/ossf/scorecard/blob/40576783fda6698350fcbbeaea760ff827433034/docs/checks.md#fuzzing"}},{"name":"Vulnerabilities","score":10,"reason":"0 existing vulnerabilities detected","details":null,"documentation":{"short":"Determines if the project has open, known unfixed vulnerabilities.","url":"https://github.com/ossf/scorecard/blob/40576783fda6698350fcbbeaea760ff827433034/docs/checks.md#vulnerabilities"}},{"name":"License","score":10,"reason":"license file detected","details":["Info: project has a license file: LICENSE:0","Info: FSF or OSI recognized license: MIT License: LICENSE:0"],"documentation":{"short":"Determines if the project has defined a license.","url":"https://github.com/ossf/scorecard/blob/40576783fda6698350fcbbeaea760ff827433034/docs/checks.md#license"}},{"name":"Signed-Releases","score":-1,"reason":"no releases found","details":null,"documentation":{"short":"Determines if the project cryptographically signs release artifacts.","url":"https://github.com/ossf/scorecard/blob/40576783fda6698350fcbbeaea760ff827433034/docs/checks.md#signed-releases"}},{"name":"Branch-Protection","score":0,"reason":"branch protection not enabled on development/release branches","details":["Warn: branch protection not enabled for branch 'master'"],"documentation":{"short":"Determines if the default and release branches are protected with GitHub's branch protection settings.","url":"https://github.com/ossf/scorecard/blob/40576783fda6698350fcbbeaea760ff827433034/docs/checks.md#branch-protection"}}]},"last_synced_at":"2025-08-24T12:26:26.695Z","repository_id":57230482,"created_at":"2025-08-24T12:26:26.696Z","updated_at":"2025-08-24T12:26:26.696Z"},"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":28610769,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-01-20T18:56:40.769Z","status":"ssl_error","status_checked_at":"2026-01-20T18:54:26.653Z","response_time":117,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.5:443 state=error: 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":["dom","filter","find","javascript","json","microxml","object","query","search","selector","xml","xpath"],"created_at":"2026-01-20T19:36:14.309Z","updated_at":"2026-01-20T19:36:14.541Z","avatar_url":"https://github.com/tomhodgins.png","language":"HTML","funding_links":[],"categories":[],"sub_categories":[],"readme":"# ESpath\n\nQuery deeply-nested JavaScript objects with XPath\n\n## About\n\nHave you ever wished there was a query language for sifting, filtering, and sorting deeply-nested JSON or Js objects to find exactly the values you're looking for?\n\nThat language already exists, and it's called [XPath](https://developer.mozilla.org/en-US/docs/Web/XPath), and this package provides a way that you can use XPath to query JSON, or JSON-compatible objects in JavaScript.\n\n## Usage\n\nThis package is provided as an ES module. The easiest way to get started with ESpath is to import [index.js](./index.js) into a module:\n\n```js\nimport esPath from 'https://unpkg.com/espath'\n\nconsole.log(esPath(['one', 2, {three: 3}], '//object')) // {three: 3}\n```\n\nTo use ESpath you will be providing the function you import with two arguments: `object`, and `path`:\n\n```js\nfunction(object, path)\n```\n\n- `object` represents a JavaScript or JSON object containing [supported data types](#supported-data-types)\n- `path` represents a valid XPath selector\n\n## How\n\nThis process works by joining together a number of different technologies, JSON for the objects we want to query, XML for an intermediate representation of the same information, and DOM to allow us to query that XML representation with XPath. Here's how each step works.\n\n### Step 1: Convert JS objects into MicroXML\n\nYou supply a JSON-compatible object from JavaScript - this can be the output of [`JSON.parse()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/parse), or it can be any object from JavaScript included in the supported data types.\n\n\u003e #### Supported data types\n\u003e\n\u003e - [`null`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/null)\n\u003e - [`String`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String)\n\u003e - [`Number`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number)\n\u003e - [`Boolean`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Boolean)\n\u003e - [`Array`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array)\n\u003e - [`Object`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object)\n\nWhen fed into the function provided by [object-to-microxml.js](./object-to-microxml.js) the object will be consumed and converted into a [MicroXML](https://dvcs.w3.org/hg/microxml/raw-file/tip/spec/microxml.html) representation of the same value. For example, the string `Hello` might be represented in MicroXML as `['string', {}, ['Hello']]`.\n\nIf the objects contain arrays or other objects, the entire structure will be converted into a nested tree structure of MicroXML.\n\n```js\n// JS\n[1, 2, 3]\n\n// MircoXML\n['array', {}, [\n  ['number', {}, ['1']],\n  ['number', {}, ['2']],\n  ['number', {}, ['3']],\n]]\n```\n\n\u003e View the [object-to-microxml.js](https://tomhodgins.github.io/espath/object-to-microxml.html) for more examples\n\n### Step 2: Convert MicroXML into DOM\n\nOnce the original object has been converted into a tree of MicroXML, we can consume this data with the function in [microxml-to-dom.js](./microxml-to-dom.js) and build an XML DOM from what we find. This means that input like `['string', {}, ['demo']]` will be represented in XML as `\u003cstring\u003edemo\u003c/string\u003e`.\n\n```js\n// MicroXML\n['array', {}, [\n  ['number', {}, ['1']],\n  ['number', {}, ['2']],\n  ['number', {}, ['3']],\n]]\n```\n\n```xml\n\u003c!-- DOM --\u003e\n\u003carray\u003e\n  \u003cnumber\u003e1\u003c/number\u003e\n  \u003cnumber\u003e2\u003c/number\u003e\n  \u003cnumber\u003e3\u003c/number\u003e\n\u003c/array\u003e\n```\n\n\u003e We don't make use of attributes at all yet, but there may be opportunities in the future for computing certain attributes for things that XPath can't query, to further enhance XPath's ability to target these elements with all available information we know about the values.\n\n\u003e View the [microxml-to-dom.js](https://tomhodgins.github.io/espath/tests/microxml-to-dom.html) for more examples\n\n### Step 3: Query DOM with XPath\n\nOnce we have DOM representing our original data we can use XPath with the function provided in [index.js](./index.js) to run [`document.evaluate()`](https://developer.mozilla.org/en-US/docs/Web/API/Document/evaluate) to match XML elements in our DOM based on different criteria. XPath is more expressive and powerful than CSS selectors, so you will be able to write queries for things like:\n\n- “Find all arrays containing boolean values”\n- “Find all objects containing a string that includes the text ‘example’”\n- “Find all objects where the value associated with the ‘dob’ key is a number less than 1990”\n\nWhen you write an XPath selector to match values inside the DOM structure representing the original data, `document.evaluate()` will return the matching DOM nodes. Here in this example, lets query for all `\u003cnumber\u003e` tags with `text()` content that is a `number()` that is greater than (`\u003e`) 1. In XPath we can describe that as: `//number[text() \u003e 1]`\n\n```xml\n\u003c!-- DOM --\u003e\n\u003carray\u003e\n  \u003cnumber\u003e1\u003c/number\u003e\n  \u003cnumber\u003e2\u003c/number\u003e\n  \u003cnumber\u003e3\u003c/number\u003e\n\u003c/array\u003e\n```\n\n```jsx\n// JavaScript\n[\n  \u003cnumber\u003e2\u003c/number\u003e,\n  \u003cnumber\u003e3\u003c/number\u003e\n]\n```\n\n\u003e View the [ESpath test page](https://tomhodgins.github.io/espath/tests/espath.html) for more examples\n\n### Step 4: Convert matching DOM back into JS objects\n\nThe final step once we have an array of DOM nodes matching our XPath query, is to use the function provided in [dom-to-object.js](./dom-to-object.js) to convert the DOM nodes back into their original JavaScript objects.\n\n```js\n// JavaScript\n[2, 3]\n```\n\nBy using this process of representing JavaScript objects in an intermediate format of XML DOM we are able to combine the expressive power of XPath and put it to work to help us find the `'needle'` in `{very: ['large', ['haystacks']]}`.\n\n\u003e View the [dom-to-object.js test page](https://tomhodgins.github.io/espath/tests/dom-to-object.html) for more examples\n\n## Example\n\n### Filtering objects by the comparison of a number value it contains\n\nSuppose you have some data on a number of users in JSON like this:\n\n```js\nconst users = [\n  {\n    user: 'Johnny',\n    dob: 1995\n  },\n  {\n    user: 'Ronny',\n    dob: 1985\n  },\n  {\n    user: 'Donny',\n    dob: 1975\n  }\n]\n```\n\nYou want to filter this array to only keep the user objects for users that were born before 1990. This is a perfect use-case for an XPath query.\n\n1. We can start by finding all keys the object that have a text value of `dob`, in XPath that's `//key[. = \"dob\"]`\n\n2. Next, we want to find the associated value, in XPath that's `/following-sibling::value`\n\n3. Then we want to compare the text value of our value as a number to see if it's less than the number 1990, in XPath that's `[. \u003c 1990]`\n\n4. Finally, we want to return the whole user object containing the matching numbers, so we'll need to find the ancestor object, in XPath that's `/ancestor::object`\n\nSeparately, each of these pieces is easy to write and reason with, and the syntax is incredibly terse. All together that gives us an XPath of:\n\n```xpath\n//key[. = \"dob\"]/following-sibling::value[. \u003c 1990]/ancestor::object\n```\n\nThat's quite a mouthful. If you want to break it down further into bite-sized pieces for easier documentation and refactoring later, I would recommend that you put the individual parts of the path into an array as strings, and `join()` them together as one string before you use them:\n\n```js\n[\n  '//key',                      // Find all keys\n  '[. = \"dob\"]',                // named 'dob'\n  '/following-sibling::value',  // locate their associated value\n  '[. \u003c 1990]',                 // if the value is less than 1990\n  '/ancestor::object'           // and select their ancestor object\n].join('')\n```\n\nWhen we query our original array using this XPath selector we yield the following result:\n\n```js\n[\n  {\n    user: 'Ronny',\n    dob: 1985\n  },\n  {\n    user: 'Donny',\n    dob: 1975\n  }\n]\n```\n\n## Bonus Round: CSS Selectors\n\nThis repository also contains a function in [css-match.js](./css-match.js) that allows you to query JS object using CSS selectors. Though writing CSS selectors is simpler and more familiar for many people, it lacks most of the descriptive power that makes querying objects with XPath useful in the first place.\n\nThis function is provided here mainly as a demonstration of the difference in power between XPath and CSS selectors, but it is usable on its own, and if the information you are looking for can be described in terms of CSS selectors, it may be all you need. For most querying, XPath will give you tools to work with in the form of [functions](https://developer.mozilla.org/en-US/docs/Web/XPath/Functions) and [axes](https://developer.mozilla.org/en-US/docs/Web/XPath/Axes).\n\n```js\nimport cssMatch from './css-match.js'\n\nconsole.log(\n  cssMatch(['a', 'b', 'c', 'd'], ':last-child')\n)\n\n// ['d']\n```\n\n\u003e View the [cssmatch test page](https://tomhodgins.github.io/espath/tests/css-match.html) for more examples\n\nFor a comparison between CSS and XPath selectors, check out [this page on MDN](https://developer.mozilla.org/en-US/docs/Web/XPath/Comparison_with_CSS_selectors)\n\n## Browser Support\n\nThe [browser support for XPath](https://caniuse.com/#feat=document-evaluate-xpath\u0026search=xpath) looks pretty good, supported by all modern browsers. ESpath is written to work in the same browsers where XPath is supported natively, for some reason the current version isn't working in Edge, though [css-match.js](./css-match.js) and all of the helper functions work.\n\nIf you are interested in supporting Internet Explorer or other legacy browsers, it may be possible to polyfill support for XPath and `document.evaluate()` with [wicked-good-xpath](https://github.com/google/wicked-good-xpath).","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftomhodgins%2Fespath","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ftomhodgins%2Fespath","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftomhodgins%2Fespath/lists"}