{"id":43231810,"url":"https://github.com/restaumatic/purescript-unscramble","last_synced_at":"2026-02-01T10:08:48.857Z","repository":{"id":69943036,"uuid":"350706275","full_name":"restaumatic/purescript-unscramble","owner":"restaumatic","description":null,"archived":false,"fork":false,"pushed_at":"2024-10-22T08:31:12.000Z","size":77,"stargazers_count":2,"open_issues_count":0,"forks_count":0,"subscribers_count":10,"default_branch":"master","last_synced_at":"2024-10-23T12:26:30.396Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"","language":"PureScript","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/restaumatic.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":"2021-03-23T12:36:17.000Z","updated_at":"2024-10-22T08:31:13.000Z","dependencies_parsed_at":"2024-10-22T14:42:56.478Z","dependency_job_id":"ef842a04-f8d0-4d08-902e-726b390601d3","html_url":"https://github.com/restaumatic/purescript-unscramble","commit_stats":null,"previous_names":[],"tags_count":7,"template":false,"template_full_name":null,"purl":"pkg:github/restaumatic/purescript-unscramble","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/restaumatic%2Fpurescript-unscramble","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/restaumatic%2Fpurescript-unscramble/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/restaumatic%2Fpurescript-unscramble/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/restaumatic%2Fpurescript-unscramble/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/restaumatic","download_url":"https://codeload.github.com/restaumatic/purescript-unscramble/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/restaumatic%2Fpurescript-unscramble/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":28975288,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-02-01T09:57:52.632Z","status":"ssl_error","status_checked_at":"2026-02-01T09:57:49.143Z","response_time":56,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.6: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":[],"created_at":"2026-02-01T10:08:48.785Z","updated_at":"2026-02-01T10:08:48.850Z","avatar_url":"https://github.com/restaumatic.png","language":"PureScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Unscramble [![CI](https://github.com/restaumatic/purescript-unscramble/actions/workflows/ci.yml/badge.svg)](https://github.com/restaumatic/purescript-unscramble/actions/workflows/ci.yml)\n\n**Unscramble** (\"decode while visibly struggling\") - a hopefully faster JSON decoding library for PureScript.\n\n**Warning**: not stable yet, the API will probably change.\n\n## Why\n\nDecoding data is often on the critical path of loading a page. Other PureScript decoding libraries have too much overhead for some use cases.\n\n## Usage\n\nThis library offers typeclass-based conversion from JSON (in the form of already parsed JavaScript objects, `Data.Foreign`) to arbitrary PureScript data types.\n\nNote that it doesn't offer encoding. You have to use some other library to do that.\n\nThe encoding format is compatible with Haskell `aeson` generic encoding with default options. Note: this means it's not fully compatible with `foreign-generic`. See [Data type encoding](#Data_type_encoding) below for details.\n\nThe main entry points are:\n\n```purescript\nmodule Unscramble where\n\ntype DecodingError = String\ntype Result = Either DecodingError\n\ndecode :: forall a. Decode a =\u003e Foreign -\u003e Maybe a\n\ndecodeEither :: forall a. Decode a =\u003e Foreign -\u003e Result a\n\ndecodeJSON :: forall a. Decode a =\u003e String -\u003e Maybe a\n\ndecodeJSONEither :: forall a. Decode a =\u003e String -\u003e Result a\n```\n\nTo use it on your own data types, you need to obtain an instance of the `Decode` class.\n\n```purescript\nclass Decode a where\n  -- WARNING: Partial function, may throw a `DecodingError` JavaScript exception.\n  unsafeDecode :: Foreign -\u003e a\n```\n\nIf you are generating your types in some way, the fastest option would be to modify the generator to produce a \"hand-written\" instance.\nOtherwise, use the generic options below.\n\n### Records\n\nThere is an instance for `Record`. Records are encoded, obviously, as JavaScript objects.\n\nIf you have a nominal product type (a `Record` wrapper), just use newtype deriving:\n\n```purescript\nimport Unscramble (class Decode)\n\nnewtype Person = Person { name :: String, age :: Int }\n\nderive newtype instance Decode Person\n```\n\n### Sum types\n\nThe easiest way is to use generic deriving:\n\n```purescript\nimport Data.Generic.Rep (class Generic)\nimport Unscramble (class Decode)\nimport Unscramble.Generic as UG\n\ndata Person = NamedPerson { name :: String } | NamelessPerson\n\nderive instance Generic Person _\ninstance Decode Person where\n  unsafeDecode = UG.unsafeGenericDecode UG.defaultOptions\n```\n\nNote: The partial application of `unsafeGenericDecode` precomputes a lookup table for constructor tags.\nTo avoid reconstructing it on every decode, make sure it is not behind a lambda. For example:\n\n```purescript\ninstance Decode Person where\n  -- GOOD, `UG.unsafeGenericDecode UG.defaultOptions` is computed once\n  unsafeDecode = someFixup \u003c\u003c\u003c UG.unsafeGenericDecode UG.defaultOptions\n    where someFixup person = ...\n\ninstance Decode Person where\n  -- BAD, `UG.unsafeGenericDecode UG.defaultOptions` is computed for every decoded value\n  unsafeDecode value = someFixup (UG.unsafeGenericDecode UG.defaultOptions value)\n    where someFixup person = ...\n```\n\nIf you need more complicated logic than a simple function composition, make the sharing explicit, e.g.:\n\n```purescript\ninstance Decode Person where\n  -- GOOD, `UG.unsafeGenericDecode UG.defaultOptions` is computed once\n  unsafeDecode =\n    let decode = UG.unsafeGenericDecode UG.defaultOptions\n    in \\value -\u003e\n      if U.isString value then\n        ... some special case ...\n      else\n        decode value\n```\n\n### Enums\n\nAs a special case, sum types with no constructor arguments can use the string representation (value encoded as the constructor name).\n\n```purescript\nimport Data.Generic.Rep (class Generic)\nimport Unscramble (class Decode)\nimport Unscramble.Enum as UE\n\ndata Weather = Sunny | Rainy | Cold\n\nderive instance Generic Weather _\ninstance Decode Weather where\n  unsafeDecode = UE.unsafeGenericDecodeEnum UE.defaultEnumOptions\n```\n\n### Custom types\n\nYou may want to, for example, provide an UUID type which is encoded as String but requires additional validation.\n\nUse `unsafeDecode` to decode to the original representation. This library uses JavaScript exceptions for error reporting, so the function is partial. But the implementation you're writing also is, so that's OK. See [How it works](#How_it_works) below for details.\n\nAfter obtaining the value, you can perform additional validation, and use `decodingError :: forall a. String -\u003e a` (also, obviously, partial) to indicate failure.\n\nExample:\n\n```purescript\nimport Unscramble as U\n\nnewtype UUID = UUID String\n\ninstance Decode UUID where\n  unsafeDecode value =\n    let str = U.unsafeDecode value :: String in\n    if isValidUUID str then\n      UUID str\n    else \n      U.decodingError \"Invalid UUID\"\n\nisValidUUID :: String -\u003e Boolean\nisValidUUID = {- ... check if the format matches ... -}\n```\n\n#### Multiple alternatives\n\nYou may want to accept multiple representations (e.g. a number as String or as a number).\n\nDon't use backtracking for that - it would result in wasted work to generate the error message in the failed branch.\nInstead, use helper functions to decide which branch to take, and commit to it.\n\n```purescript\nUnscramble.isString :: Foreign -\u003e Boolean\nUnscramble.isNumber :: Foreign -\u003e Boolean\nForeign.isNull :: Foreign -\u003e Boolean\nForeign.isUndefined :: Foreign -\u003e Boolean\nForeign.isArray :: Foreign -\u003e Boolean\n```\n\n(Note: some are missing, e.g. `isObject`; if you need it, please file an issue or PR)\n\nExample:\n\n```purescript\nimport Data.Number as Number\nimport Unscramble as U\n\nnewtype Num = Num Number\n\n-- | `Num` can be encoded either as JSON Number, or JSON String.\ninstance U.Decode Num where\n  unsafeDecode value =\n    if U.isString value then\n      case Number.fromString (U.unsafeDecode value) of\n        Just n -\u003e Num n\n        Nothing -\u003e U.decodingError \"Invalid number\"\n    else\n      Num (U.unsafeDecode value)\n```\n\n## How it works\n\n`unscramble` is built on the following principles:\n\n- The goal of decoding is to succeed, not fail.\n  - The success code path should be as fast as possible.\n  - Decoding failure is a rare occurence caused by programmer error. The behavior of the library in this case (i.e. the error messages) should be only as good so that the developer can fix the problem.\n    - For example, it would be perfectly acceptable to have _no_ error messages at at all, if it improved performance in the success case. In case of decoding failure, the developer could always reproduce the issue locally with a modified version of the library which does report errors.\n- The target language (JavaScript) should be utilized properly, even if it means sacrificing some ideals of the source language (PureScript), or even some readability.\n  - For example: prefer loops over recursion.\n\nThe architecture of `unscramble` follows from these principles:\n\n- We don't use monads (`Maybe/Either`) for representing failure, because they compile to non-idiomatic JavaScript and slow down the success path. Instead, JavaScript exceptions are used for reporting failure.\n- This makes the decoding functions partial. Despite that, there is no `Partial` constraint on them, because it would add another level of currying, therefore adding more function allocations, destroying sharing, and overall resulting in slowdown of the code.\n\n### Is it worth it?\n\nShould we sacrifice functional purity for performance gains?\n\n```\n$ npm run bench-micro \"R10\"\n...\nFilters: [\"R10\"]\nR10 Unscramble                          : 0.000514ms/op\nR10 Argonaut                            : 0.007123ms/op\nR10 Foreign.Generic                     : 0.013110ms/op\nR10 Simple.JSON                         : 0.013957ms/op\n...\n```\n\n(R10 means \"decoding a `Record` with 10 fields\")\n\n14x speedup over Argonaut, 26x speedup over Foreign.Generic and Simple.JSON. It is at least worth considering :).\n\nSee [doc/Performance.md](./doc/Performance.md) for more details.\n\n### Why not use `Effect`? It has support for JS exceptions\n\nThis is an additional layer of indirection on top of JavaScript. Which in _most_ cases can be eliminated thanks to `MagicDo` and `EffectFn...`, but not always.\n\n## Stability\n\nThe library is new, the API will probably change. Most likely we'll make the primitive decoding functions look more scary by adding words like `unsafe`, `internal`, `don't` or `php`.\n\n## Contributions\n\nAll improvements are welcome, especially:\n- documentation\n- performance\n- better benchmarking (more realistic benchmarks, comparison with more alternatives etc.).\n\n## Contact\n\nIf you discover bugs, want new features, or have questions, please post an issue using the GitHub issue tracker.\nAlternatively you can use the GitHub discussion feature (enabled in this repo).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Frestaumatic%2Fpurescript-unscramble","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Frestaumatic%2Fpurescript-unscramble","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Frestaumatic%2Fpurescript-unscramble/lists"}