{"id":44325189,"url":"https://github.com/floraison/dense","last_synced_at":"2026-02-11T07:33:58.187Z","repository":{"id":59152831,"uuid":"99451277","full_name":"floraison/dense","owner":"floraison","description":"fetching deep in a dense structure","archived":false,"fork":false,"pushed_at":"2026-02-04T23:27:26.000Z","size":156,"stargazers_count":10,"open_issues_count":0,"forks_count":0,"subscribers_count":3,"default_branch":"master","last_synced_at":"2026-02-05T11:40:08.658Z","etag":null,"topics":["path","ruby"],"latest_commit_sha":null,"homepage":null,"language":"Ruby","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/floraison.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE.txt","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2017-08-05T22:35:26.000Z","updated_at":"2026-02-04T23:27:30.000Z","dependencies_parsed_at":"2022-09-13T11:01:14.518Z","dependency_job_id":null,"html_url":"https://github.com/floraison/dense","commit_stats":null,"previous_names":[],"tags_count":9,"template":false,"template_full_name":null,"purl":"pkg:github/floraison/dense","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/floraison%2Fdense","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/floraison%2Fdense/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/floraison%2Fdense/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/floraison%2Fdense/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/floraison","download_url":"https://codeload.github.com/floraison/dense/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/floraison%2Fdense/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":29329493,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-02-11T06:13:03.264Z","status":"ssl_error","status_checked_at":"2026-02-11T06:12:55.843Z","response_time":97,"last_error":"SSL_read: 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":["path","ruby"],"created_at":"2026-02-11T07:33:57.459Z","updated_at":"2026-02-11T07:33:58.178Z","avatar_url":"https://github.com/floraison.png","language":"Ruby","readme":"\n# dense\n\n[![Gem Version](https://badge.fury.io/rb/dense.svg)](http://badge.fury.io/rb/dense)\n\nFetching deep in a dense structure. A kind of bastard of [JSONPath](http://goessner.net/articles/JsonPath/).\n\n## usage\n\nLet\n```ruby\n  data = # taken from http://goessner.net/articles/JsonPath/\n    { 'store' =\u003e {\n        'book' =\u003e [\n          { 'category' =\u003e 'reference',\n            'author' =\u003e 'Nigel Rees',\n            'title' =\u003e 'Sayings of the Century',\n            'price' =\u003e 8.95\n          },\n          { 'category' =\u003e 'fiction',\n            'author' =\u003e 'Evelyn Waugh',\n            'title' =\u003e 'Sword of Honour',\n            'price' =\u003e 12.99\n          },\n          { 'category' =\u003e 'fiction',\n            'author' =\u003e 'Herman Melville',\n            'title' =\u003e 'Moby Dick',\n            'isbn' =\u003e '0-553-21311-3',\n            'price' =\u003e 8.99\n          },\n          { 'category' =\u003e 'fiction',\n            'author' =\u003e 'J. R. R. Tolkien',\n            'title' =\u003e 'The Lord of the Rings',\n            'isbn' =\u003e '0-395-19395-8',\n            'price' =\u003e 22.99\n          }\n        ],\n        'bicycle' =\u003e {\n          'color' =\u003e 'red',\n          'price' =\u003e 19.95,\n          '7' =\u003e 'seven'\n        }\n      }\n    }\n```\n\n### paths\n\n```ruby\n\"store.book.1.title\"            # the title of the second book in the store\n\"store.book[1].title\"           # the title of the second book in the store\n\"store.book.1['french title']\"  # the french title of the 2nd book\n\"store.book.1[title,author]\"    # the title and the author of the 2nd book\n\"store.book[1,3].title\"         # the titles of the 2nd and 4th books\n\"store.book[1:8:2].title\"       # titles of books at offset 1, 3, 5, 7\n\"store.book[::3].title\"         # titles of books at offset 0, 3, 6, 9, ...\n\"store.book[:3].title\"          # titles of books at offset 0, 1, 2, 3\n\"store.*.price\"                 # the price of everything directly in the store\n\"store..price\"                  # the price of everything in the store\n# ...\n```\n\n### `Dense.get(collection, path)`\n\n```ruby\nDense.get(data, 'store.book.1.title')\n  # =\u003e \"Sword of Honour\"\n\nDense.get(data, 'store.book.*.title')\n  # =\u003e [\n  #  'Sayings of the Century',\n  #  'Sword of Honour',\n  #  'Moby Dick',\n  #  'The Lord of the Rings' ]\n\nDense.get(data, 'store.bicycle.7')\n  # =\u003e \"seven\"\n```\n\nWhen `Dense.get(collection, path)` doesn't find, it returns `nil`.\n\nAs seen above `Dense.get` might return a single value or an array of values. A \"single\" path like `\"store.book.1.title\"` will return a single value, while a \"multiple\" path like `\"store.book.*.title\"` or `\"store.book[1,2].title\"` will return an array of values.\n\n\n### `Dense.has_key?(collection, path)`\n\n```ruby\nDense.has_key?(data, 'store.book.1.title')\n  # =\u003e true\nDense.has_key?(data, 'store.book.1[\"social security number\"]')\n  # =\u003e false\n```\n\n\n### `Dense.fetch(collection, path)`\n\n`Dense.fetch` is modelled after `Hash.fetch`.\n\n```ruby\nDense.fetch(data, 'store.book.1.title')\n  # =\u003e 'Sword of Honour'\n\nDense.fetch(data, 'store.book.*.title')\n  # =\u003e [ 'Sayings of the Century', 'Sword of Honour', 'Moby Dick',\n  #      'The Lord of the Rings' ]\n\nDense.fetch(data, 'store.bicycle.7')\n  # =\u003e 'seven'\n\nDense.fetch(data, 'store.bicycle[7]')\n  # =\u003e 'seven'\n```\n\nWhen it doesn't find, it raises an instance of `KeyError`:\n\n```ruby\nDense.fetch({}, 'a.0.b')\n  # raises\n  #   KeyError: found nothing at \"a\" (\"0.b\" remains)\n```\n\nIt might instead raise an instance of `TypeError` if a non-integer key is requested of an array:\n\n```ruby\nDense.fetch({ 'a' =\u003e [] }, 'a.k.b')\n  # raises\n  #   TypeError: no key \"k\" for Array at \"a\"\n```\n\nSee `KeyError` and `TypeError` below for more details.\n\n`Dense.fetch(collection, path)` raises when it doesn't find, while `Dense.get(collection, path)` returns `nil`.\n\n\n### `Dense.fetch(collection, path, default)`\n\n`Dense.fetch` is modelled after `Hash.fetch` so it features a `default` optional argument.\n\nIf `fetch` doesn't find, it will return the provided default value.\n\n```ruby\nDense.fetch(data, 'store.book.1.title', -1)\n  # =\u003e \"Sword of Honour\" (found)\nDense.fetch(data, 'a.0.b', -1)\n  # =\u003e -1\nDense.fetch(data, 'store.nada', 'x')\n  # =\u003e \"x\"\nDense.fetch(data, 'store.bicycle.seven', false)\n  # =\u003e false\n```\n\n\n### `Dense.fetch(collection, path) { block }`\n\n`Dense.fetch` is modelled after `Hash.fetch` so it features a 'default' optional block.\n\n```ruby\nDense.fetch(data, 'store.book.1.title') do |coll, path|\n  \"len:#{coll.length},path:#{path}\"\nend\n  # =\u003e \"Sword of Honour\" (found)\n\nDense.fetch(@data, 'store.bicycle.otto') do |coll, path|\n  \"len:#{coll.length},path:#{path}\"\nend\n  # =\u003e \"len:18,path:store.bicycle.otto\" (not found)\n\nnot_found = lambda { |coll, path| \"not found!\" }\n  #\nDense.fetch(@data, 'store.bicycle.otto', not_found)\n  # =\u003e \"not found!\"\nDense.fetch(@data, 'store.bicycle.sept', not_found)\n  # =\u003e \"not found!\"\n```\n\n\n### `Dense.set(collection, path, value)`\n\nSets a value \"deep\" in a collection. Returns the value if successful.\n\n```ruby\nc = {}\nr = Dense.set(c, 'a', 1)\nc   # =\u003e { 'a' =\u003e 1 }\nr   # =\u003e 1\n\nc = { 'h' =\u003e {} }\nr = Dense.set(c, 'h.i', 1)\nc   # =\u003e { 'h' =\u003e { 'i' =\u003e 1 } }\nr   # =\u003e 1\n\nc = { 'a' =\u003e [ 1, 2, 3 ] }\nr = Dense.set(c, 'a.1', 1)\nc   # =\u003e { 'a' =\u003e [ 1, 1, 3 ] }\nr   # =\u003e 1\n\nc = { 'h' =\u003e { 'a' =\u003e [ 1, 2, 3 ] } }\nr = Dense.set(c, 'h.a.first', 'one')\nc   # =\u003e { 'h' =\u003e { 'a' =\u003e [ \"one\", 2, 3 ] } }\nr   # =\u003e 'one'\n\nc = { 'h' =\u003e { 'a' =\u003e [ 1, 2, 3 ] } }\nr = Dense.set(c, 'h.a.last', 'three')\nc   # =\u003e { 'h' =\u003e { 'a' =\u003e [ 1, 2, 'three' ] } }\nr   # =\u003e 'three'\n\nc = { 'a' =\u003e [] }\nDense.set(c, 'a.b', 1)\n  # =\u003e TypeError: no key \"b\" for Array at \"a\"\n\n\nc = { 'a' =\u003e {} }\nr = Dense.set(c, 'a.1', 1)\nc   # =\u003e { 'a' =\u003e { '1' =\u003e 1 } }\nr   # =\u003e 1\n\nc = {}\nDense.set(c, 'a.0', 1)\n  # =\u003e KeyError: found nothing at \"a\" (\"0\" remains)\n```\n\nSetting at multiple places in one go is possible:\n```ruby\nc = { 'h' =\u003e {} }\nDense.set(c, 'h[k0,k1,k2]', 123)\nc\n  # =\u003e { 'h' =\u003e { 'k0' =\u003e 123, 'k1' =\u003e 123, 'k2' =\u003e 123 } }\n```\n\n### `Dense.force_set(collection, path, value)`\n\nCreates the necessary collections on the way. A bit like `mkdir -f x/y/z/`\n\n```ruby\nc = {}\nr = Dense.force_set(c, 'a', 1)\nr # =\u003e 1\nc # =\u003e { 'a' =\u003e 1 }\n\nc = {}\nr = Dense.force_set(c, 'a.b.3.d.0', 1)\nr # =\u003e 1\nc # =\u003e { 'a' =\u003e { 'b' =\u003e [ nil, nil, nil, { 'd' =\u003e [ 1 ] } ] } }\n\nc = { 'a' =\u003e [] }\nDense.force_set(c, 'a.b', 1)\n  # =\u003e TypeError: no key \"b\" for Array at \"a\"\n```\n\n\n### `Dense.insert(collection, path, value)`\n\n```ruby\nc = { 'a' =\u003e [ 0, 1, 2, 3 ] }\nr = Dense.insert(c, 'b', 1234)\nc\n  # =\u003e { \"a\" =\u003e [ 0, 1, 2, 3 ], \"b\" =\u003e 1234 }\n\nc = { 'a' =\u003e [ 0, 1, 2, 3 ] }\nr = Dense.insert(c, 'a.1', 'ONE')\nc\n  # =\u003e { \"a\" =\u003e [ 0, \"ONE\", 1, 2, 3 ] }\n\nc = { 'a' =\u003e [ 0, 1, 2, 3 ], 'a1' =\u003e [ 0, 1 ] }\nr = Dense.insert(c, '.1', 'ONE')\nc\n  # =\u003e { \"a\" =\u003e [ 0, \"ONE\", 1, 2, 3 ], \"a1\" =\u003e [ 0, \"ONE\", 1 ] }\n```\n\n\n### `Dense.unset(collection, path)`\n\nRemoves an element deep in a collection.\n```ruby\nc = { 'a' =\u003e 1 }\nr = Dense.unset(c, 'a')\nc   # =\u003e {}\nr   # =\u003e 1\n\nc = { 'h' =\u003e { 'i' =\u003e 1 } }\nr = Dense.unset(c, 'h.i')\nc   # =\u003e { 'h' =\u003e {} }\nr   # =\u003e 1\n\nc = { 'a' =\u003e [ 1, 2, 3 ] }\nr = Dense.unset(c, 'a.1')\nc   # =\u003e { 'a' =\u003e [ 1, 3 ] }\nr   # =\u003e 2\n\nc = { 'h' =\u003e { 'a' =\u003e [ 1, 2, 3 ] } }\nr = Dense.unset(c, 'h.a.first')\nc   # =\u003e { 'h' =\u003e { 'a' =\u003e [ 2, 3 ] } }\nr   # =\u003e 1\n\nc = { 'h' =\u003e { 'a' =\u003e [ 1, 2, 3 ] } }\nr = Dense.unset(c, 'h.a.last')\nc   # =\u003e { 'h' =\u003e { 'a' =\u003e [ 1, 2 ] } }\nr   # =\u003e 3\n```\n\nIt fails with a `KeyError` or a `TypeError` if it cannot unset.\n```ruby\nDense.unset({}, 'a')\n  # =\u003e KeyError: found nothing at \"a\"\nDense.unset([], 'a')\n  # =\u003e TypeError: no key \"a\" for Array at root\nDense.unset([], '1')\n  # =\u003e KeyError: found nothing at \"1\"\n```\n\nUnsetting multiple values is OK:\n\n```ruby\nc = { 'h' =\u003e { 'a' =\u003e [ 1, 2, 3, 4, 5 ] } }\nr = Dense.unset(c, 'h.a[2,3]')\nc\n  # =\u003e { 'h' =\u003e { 'a' =\u003e [ 1, 2, 5 ] } }\n```\n\n\n### `Dense.list(collection, path)`\n\nLike `Dense.get` but returns an array of match. Returns an empty array if no match.\n\n\n### `Dense.paths(collection, path)`\n\nGiven a \"glob\" path, returns the list of paths to the matching leaves (or an empty array instead).\n\n```ruby\nDense.paths(data, '..author')\n  # =\u003e %w[\n  #   store.book.0.author store.book.1.author store.book.2.author\n  #   store.book.3.author ]\n\nDense.paths(data, '..price')\n  # =\u003e %w[\n  #  store.book.0.price store.book.1.price store.book.2.price\n  #  store.book.3.price store.bicycle.price ]\n\nDense.paths(data, '..nada')\n  # =\u003e []\n```\n\n\n### KeyError and TypeError\n\nDense might raise instances of `KeyError` and `TypeError`. Those instances have extra `#full_path` and `#miss` methods.\n\n```ruby\ne =\n  begin\n    Dense.fetch({}, 'a.b')\n  rescue =\u003e err\n    err\n  end\n  # =\u003e #\u003cKeyError: found nothing at \"a\" (\"b\" remains)\u003e\ne.full_path\n  # =\u003e \"a\"\ne.miss\n  # =\u003e [false, [], {}, \"a\", [ \"b\" ]]\n```\n\nThe \"miss\" is an array `[ false, path-to-miss, collection-at-miss, key-at-miss, path-post-miss ]`.\n\n\n## LICENSE\n\nMIT, see [LICENSE.txt](LICENSE.txt)\n\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ffloraison%2Fdense","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ffloraison%2Fdense","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ffloraison%2Fdense/lists"}