├── .editorconfig ├── .eslintrc.json ├── .github ├── ISSUE_TEMPLATE │ ├── bug-report.md │ ├── change-request.md │ └── config.yml ├── PULL_REQUEST_TEMPLATE.md └── workflows │ └── ci.yml ├── .gitignore ├── .tidyrc.json ├── CHANGELOG.md ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── bower.json ├── docs └── README.md ├── package.json ├── packages.dhall ├── spago.dhall ├── src └── Data │ └── Argonaut │ ├── Core.js │ ├── Core.purs │ ├── Gen.purs │ ├── Parser.js │ └── Parser.purs └── test └── Test ├── Main.js └── Main.purs /.editorconfig: -------------------------------------------------------------------------------- 1 | # http://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | indent_style = space 6 | indent_size = 2 7 | end_of_line = lf 8 | charset = utf-8 9 | trim_trailing_whitespace = true 10 | insert_final_newline = true 11 | 12 | [*.md] 13 | trim_trailing_whitespace = false 14 | -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "env": { "browser": true }, 3 | "extends": "eslint:recommended", 4 | "parserOptions": { "ecmaVersion": 6, "sourceType": "module" }, 5 | "rules": { 6 | "block-scoped-var": "error", 7 | "consistent-return": "error", 8 | "eqeqeq": "error", 9 | "guard-for-in": "error", 10 | "no-bitwise": "error", 11 | "no-caller": "error", 12 | "no-extra-parens": "off", 13 | "no-extend-native": "error", 14 | "no-loop-func": "error", 15 | "no-new": "error", 16 | "no-param-reassign": "error", 17 | "no-return-assign": "error", 18 | "no-sequences": "error", 19 | "no-unused-expressions": "error", 20 | "no-use-before-define": "error", 21 | "no-undef": "error", 22 | "no-eq-null": "error", 23 | "radix": ["error", "always"], 24 | "indent": ["error", 2, { "SwitchCase": 1 }], 25 | "quotes": ["error", "double"], 26 | "semi": ["error", "always"], 27 | "strict": ["error", "global"] 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug-report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Report an issue 4 | title: "" 5 | labels: bug 6 | assignees: "" 7 | --- 8 | 9 | **Describe the bug** 10 | A clear and concise description of the bug. 11 | 12 | **To Reproduce** 13 | A minimal code example (preferably a runnable example on [Try PureScript](https://try.purescript.org)!) or steps to reproduce the issue. 14 | 15 | **Expected behavior** 16 | A clear and concise description of what you expected to happen. 17 | 18 | **Additional context** 19 | Add any other context about the problem here. 20 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/change-request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Change request 3 | about: Propose an improvement to this library 4 | title: "" 5 | labels: "" 6 | assignees: "" 7 | --- 8 | 9 | **Is your change request related to a problem? Please describe.** 10 | A clear and concise description of the problem. 11 | 12 | Examples: 13 | 14 | - It's frustrating to have to [...] 15 | - I was looking for a function to [...] 16 | 17 | **Describe the solution you'd like** 18 | A clear and concise description of what a good solution to you looks like, including any solutions you've already considered. 19 | 20 | **Additional context** 21 | Add any other context about the change request here. 22 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: false 2 | contact_links: 3 | - name: PureScript Discourse 4 | url: https://discourse.purescript.org/ 5 | about: Ask and answer questions on the PureScript discussion forum. 6 | - name: PureScript Discord 7 | url: https://purescript.org/chat 8 | about: Ask and answer questions on the PureScript chat. 9 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | **Description of the change** 2 | Clearly and concisely describe the purpose of the pull request. If this PR relates to an existing issue or change proposal, please link to it. Include any other background context that would help reviewers understand the motivation for this PR. 3 | 4 | --- 5 | 6 | **Checklist:** 7 | 8 | - [ ] Added the change to the changelog's "Unreleased" section with a link to this PR and your username 9 | - [ ] Linked any existing issues or proposals that this pull request should close 10 | - [ ] Updated or added relevant documentation in the README and/or documentation directory 11 | - [ ] Added a test for the contribution (if applicable) 12 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | branches: [main] 6 | pull_request: 7 | branches: [main] 8 | 9 | jobs: 10 | build: 11 | runs-on: ubuntu-latest 12 | 13 | steps: 14 | - uses: actions/checkout@v2 15 | 16 | - name: Set up PureScript toolchain 17 | uses: purescript-contrib/setup-purescript@main 18 | with: 19 | purescript: "unstable" 20 | purs-tidy: "latest" 21 | 22 | - name: Cache PureScript dependencies 23 | uses: actions/cache@v2 24 | with: 25 | key: ${{ runner.os }}-spago-${{ hashFiles('**/*.dhall') }} 26 | path: | 27 | .spago 28 | output 29 | 30 | - name: Set up Node toolchain 31 | uses: actions/setup-node@v2 32 | with: 33 | node-version: "14.x" 34 | 35 | - name: Cache NPM dependencies 36 | uses: actions/cache@v2 37 | env: 38 | cache-name: cache-node-modules 39 | with: 40 | path: ~/.npm 41 | key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ hashFiles('**/package.json') }} 42 | restore-keys: | 43 | ${{ runner.os }}-build-${{ env.cache-name }}- 44 | ${{ runner.os }}-build- 45 | ${{ runner.os }}- 46 | 47 | - name: Install NPM dependencies 48 | run: npm install 49 | 50 | - name: Build the project 51 | run: npm run build 52 | 53 | - name: Run tests 54 | run: npm run test 55 | 56 | - name: Check formatting 57 | run: purs-tidy check src test 58 | 59 | - name: Verify Bower & Pulp 60 | run: | 61 | npm install bower pulp@16.0.0-0 62 | npx bower install 63 | npx pulp build -- --censor-lib --strict 64 | npx pulp test 65 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .* 2 | !.gitignore 3 | !.github 4 | !.editorconfig 5 | !.tidyrc.json 6 | !.eslintrc.json 7 | 8 | output 9 | generated-docs 10 | bower_components 11 | 12 | node_modules 13 | *.lock 14 | package-lock.json 15 | -------------------------------------------------------------------------------- /.tidyrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "importSort": "source", 3 | "importWrap": "source", 4 | "indent": 2, 5 | "operatorsFile": null, 6 | "ribbon": 1, 7 | "typeArrowPlacement": "first", 8 | "unicode": "never", 9 | "width": null 10 | } 11 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | Notable changes to this project are documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). 4 | 5 | ## [Unreleased] 6 | 7 | Breaking changes: 8 | 9 | New features: 10 | 11 | Bugfixes: 12 | 13 | Other improvements: 14 | 15 | ## [v7.0.0](https://github.com/purescript-contrib/purescript-argonaut-core/releases/tag/v7.0.0) - 2022-04-27 16 | 17 | Breaking changes: 18 | - Migrate FFI to ES modules (#57 by @JordanMartinez) 19 | 20 | New features: 21 | 22 | Bugfixes: 23 | 24 | Other improvements: 25 | - Added `purs-tidy` formatter (#53 by @thomashoneyman) 26 | * Fixed readme bug where `jsonParser` was imported from `Data.Argonaut.Core` instead of `Data.Argonaut.Parser` (#50 by @flip111) 27 | 28 | ## [v6.0.0](https://github.com/purescript-contrib/purescript-argonaut-core/releases/tag/v6.0.0) - 2021-02-26 29 | 30 | Breaking changes: 31 | - Added support for PureScript 0.14 and dropped support for all previous versions (#46) 32 | 33 | New features: 34 | 35 | Bugfixes: 36 | 37 | Other improvements: 38 | - Removed extra spaces in README examples (#44) 39 | - Added more details contrasting `fromString` and `jsonParser` (#45) 40 | - Changed default branch to `main` from `master` 41 | - Backfilled the CHANGELOG using prior releases (#43) 42 | 43 | ## [v5.1.0](https://github.com/purescript-contrib/purescript-argonaut-core/releases/tag/v5.1.0) - 2020-09-09 44 | 45 | - Added `stringifyWithIndent` function to enable pretty-printing JSON. 46 | - Updated to comply with Contributors library guidelines by adding new issue and pull request templates, updating documentation, and migrating to Spago for local development and CI (#42) 47 | 48 | ## [v5.0.2](https://github.com/purescript-contrib/purescript-argonaut-core/releases/tag/v5.0.2) - 2020-03-05 49 | 50 | - Added documentation comments to public functions (@thomashoneyman) 51 | 52 | ## [v5.0.1](https://github.com/purescript-contrib/purescript-argonaut-core/releases/tag/v5.0.1) - 2019-09-03 53 | 54 | - Fixed `purs bundle` issue caused by `objToString` and `objKeys` functions in the FFI file (@joneshf) 55 | - Updated tests for compatibility with PureScript 0.13 (@joneshf) 56 | 57 | ## [v5.0.0](https://github.com/purescript-contrib/purescript-argonaut-core/releases/tag/v5.0.0) - 2019-03-04 58 | 59 | - Updated dependencies 60 | 61 | ## [v4.0.1](https://github.com/purescript-contrib/purescript-argonaut-core/releases/tag/v4.0.1) - 2018-06-23 62 | 63 | - Added metadata including contributor guidelines 64 | - Pushed latest release to Pursuit 65 | 66 | ## [v4.0.0](https://github.com/purescript-contrib/purescript-argonaut-core/releases/tag/v4.0.0) - 2018-05-25 67 | 68 | - Updated for 0.12 69 | 70 | **Breaking changes:** 71 | - Removed the J\* aliases (like `JArray`) 72 | - `foldJson`, `fold*` functions are now called `caseJson`, `case*` 73 | 74 | ## [v3.1.1](https://github.com/purescript-contrib/purescript-argonaut-core/releases/tag/v3.1.1) - 2018-01-24 75 | 76 | - Reduced size in `genJson` to prevent excessively large structures being built up 77 | 78 | ## [v3.1.0](https://github.com/purescript-contrib/purescript-argonaut-core/releases/tag/v3.1.0) - 2017-04-08 79 | 80 | - Added `JNull` value export 81 | 82 | ## [v3.0.0](https://github.com/purescript-contrib/purescript-argonaut-core/releases/tag/v3.0.0) - 2017-04-07 83 | 84 | - Updated for PureScript 0.11 85 | 86 | ## [v2.0.1](https://github.com/purescript-contrib/purescript-argonaut-core/releases/tag/v2.0.1) - 2016-10-27 87 | 88 | - Added workaround for bug in DCE in `psc-bundle` (#14, @menelaos) 89 | 90 | ## [v2.0.0](https://github.com/purescript-contrib/purescript-argonaut-core/releases/tag/v2.0.0) - 2016-10-17 91 | 92 | - Updated dependencies 93 | 94 | ## [v1.1.0](https://github.com/purescript-contrib/purescript-argonaut-core/releases/tag/v1.1.0) - 2016-08-09 95 | 96 | - `stringify` is now exported explicitly, to be used to better express intent than the current `show` (@charleso) 97 | 98 | ## [v1.0.0](https://github.com/purescript-contrib/purescript-argonaut-core/releases/tag/v1.0.0) - 2016-06-06 99 | 100 | - Updated for 1.0 core libraries. 101 | 102 | ## [v0.2.3](https://github.com/purescript-contrib/purescript-argonaut-core/releases/tag/v0.2.3) - 2015-12-15 103 | 104 | - Functionally equivalent to v0.2.2, this release improves the documentation for Pursuit 105 | 106 | ## [v0.2.2](https://github.com/purescript-contrib/purescript-argonaut-core/releases/tag/v0.2.2) - 2015-11-20 107 | 108 | - Removed unused import 109 | 110 | ## [v0.2.1](https://github.com/purescript-contrib/purescript-argonaut-core/releases/tag/v0.2.1) - 2015-10-29 111 | 112 | - Updated strongcheck dependency and fixed compiler warnings (#5) 113 | ## [v0.2.0](https://github.com/purescript-contrib/purescript-argonaut-core/releases/tag/v0.2.0) - 2015-08-19 114 | 115 | - Updated dependencies for PureScript 0.7.3 (@zudov) 116 | 117 | ## [v0.1.0](https://github.com/purescript-contrib/purescript-argonaut-core/releases/tag/v0.1.0) - 2015-07-11 118 | 119 | - Initial release 120 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to Argonaut Core 2 | 3 | Thanks for your interest in contributing to `argonaut-core`! We welcome new contributions regardless of your level of experience or familiarity with PureScript. 4 | 5 | Every library in the Contributors organization shares a simple handbook that helps new contributors get started. With that in mind, please [read the short contributing guide on purescript-contrib/governance](https://github.com/purescript-contrib/governance/blob/main/contributing.md) before contributing to this library. 6 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 PureScript Contrib 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Argonaut Core 2 | 3 | [![CI](https://github.com/purescript-contrib/purescript-argonaut-core/workflows/CI/badge.svg?branch=main)](https://github.com/purescript-contrib/purescript-argonaut-core/actions?query=workflow%3ACI+branch%3Amain) 4 | [![Release](http://img.shields.io/github/release/purescript-contrib/purescript-argonaut-core.svg)](https://github.com/purescript-contrib/purescript-argonaut-core/releases) 5 | [![Pursuit](http://pursuit.purescript.org/packages/purescript-argonaut-core/badge)](http://pursuit.purescript.org/packages/purescript-argonaut-core) 6 | [![Maintainer: garyb](https://img.shields.io/badge/maintainer-garyb-teal.svg)](http://github.com/garyb) 7 | 8 | The core `Json` type for the [Argonaut](https://github.com/purescript-contrib/purescript-argonaut) libraries, along with basic parsing, printing, and folding functions which operate on it. 9 | 10 | ## Installation 11 | 12 | Install `argonaut-core` with [Spago](https://github.com/purescript/spago): 13 | 14 | ```sh 15 | spago install argonaut-core 16 | ``` 17 | 18 | or install it as part of the [Argonaut](https://github.com/purescript-contrib/purescript-argonaut) bundle: 19 | 20 | ```sh 21 | spago install argonaut 22 | ``` 23 | 24 | ## Quick start 25 | 26 | Use the `Json` type to represent JSON data in PureScript. You can produce a value of `Json` via the FFI or by functions from `Data.Argonaut.Core`. 27 | 28 | For example, via the FFI: 29 | 30 | ```js 31 | // In an FFI module 32 | exports.someNumber = 23.6; 33 | exports.someObject = { people: [{ name: "John" }, { name: "Jane" }] }; 34 | ``` 35 | 36 | ```purs 37 | foreign import someNumber :: Json 38 | foreign import someObject :: Json 39 | ``` 40 | 41 | In general, if a JavaScript value could be returned from a call to `JSON.parse` then you can import it via the FFI as `Json`. That includes objects, booleans, numbers, strings, and arrays (but not things like functions). 42 | 43 | You can also use the construction functions which follow the naming convention `fromX` or `jsonX`: 44 | 45 | ```purs 46 | import Data.Tuple (Tuple(..)) 47 | import Foreign.Object as Object 48 | import Data.Argonaut.Core as A 49 | 50 | someNumber :: Json 51 | someNumber = A.fromNumber 23.6 52 | 53 | someObject :: Json 54 | someObject = 55 | A.fromObject 56 | ( Object.fromFoldable 57 | [ Tuple "people" 58 | ( A.fromArray 59 | [ A.jsonSingletonObject "name" (A.fromString "John") 60 | , A.jsonSingletonObject "name" (A.fromString "Jane") 61 | ] 62 | ) 63 | ] 64 | ) 65 | ``` 66 | 67 | Finally, you can parse JSON from a string using the `jsonParser` function. However, this isn't guaranteed to produce a correct value, so it returns an `Either` value, where a parsing error is represented with `Left` containing an error message. 68 | 69 | ```purs 70 | import Data.Argonaut.Parser (jsonParser) 71 | import Data.Maybe (Maybe(..)) 72 | 73 | someObject :: Either String Json 74 | someObject = jsonParser 75 | """ 76 | { people: [{ name: "John" }, { name: "Jane" }] }; 77 | """ 78 | ``` 79 | 80 | See the [docs](./docs) for an in-depth overview of the rest of the Argonaut Core library. You may also be interested in other libraries in the Argonaut ecosystem: 81 | 82 | - [purescript-argonaut-codecs](https://github.com/purescript-contrib/purescript-argonaut-codecs) provides codecs based on `EncodeJson` and `DecodeJson` type classes, along with instances for common data types and combinators for encoding and decoding `Json` values. 83 | - [purescript-codec-argonaut](https://github.com/garyb/purescript-codec-argonaut) supports an alternative approach for codecs, which are based on profunctors instead of type classes. 84 | - [purescript-argonaut-traversals](https://github.com/purescript-contrib/purescript-argonaut-traversals) defines prisms, traversals, and zippers for the `Json` type. 85 | - [purescript-argonaut-generic](https://github.com/purescript-contrib/purescript-argonaut-generic) supports generic encoding and decoding for any type with a `Generic` instance. 86 | 87 | ## Documentation 88 | 89 | `argonaut-core` documentation is stored in a few places: 90 | 91 | 1. Module documentation is [published on Pursuit](https://pursuit.purescript.org/packages/purescript-argonaut-core). 92 | 2. Written documentation is kept in [the docs directory](./docs). 93 | 3. Usage examples can be found in [the test suite](./test). 94 | 95 | If you get stuck, there are several ways to get help: 96 | 97 | - [Open an issue](https://github.com/purescript-contrib/purescript-argonaut-core/issues) if you have encountered a bug or problem. 98 | - Ask general questions on the [PureScript Discourse](https://discourse.purescript.org) forum or the [PureScript Discord](https://purescript.org/chat) chat. 99 | 100 | ## Contributing 101 | 102 | You can contribute to `argonaut-core` in several ways: 103 | 104 | 1. If you encounter a problem or have a question, please [open an issue](https://github.com/purescript-contrib/purescript-argonaut-core/issues). We'll do our best to work with you to resolve or answer it. 105 | 106 | 2. If you would like to contribute code, tests, or documentation, please [read the contributor guide](./CONTRIBUTING.md). It's a short, helpful introduction to contributing to this library, including development instructions. 107 | 108 | 3. If you have written a library, tutorial, guide, or other resource based on this package, please share it on the [PureScript Discourse](https://discourse.purescript.org)! Writing libraries and learning resources are a great way to help this library succeed. 109 | -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "purescript-argonaut-core", 3 | "homepage": "https://github.com/purescript-contrib/purescript-argonaut-core", 4 | "description": "Core of the purescript-argonaut library, providing basic types, folds, and combinators for `Json`", 5 | "license": "MIT", 6 | "authors": [ 7 | "Maxim Zimaliev ", 8 | "Hardy Jones <>", 9 | "John A. De Goes " 10 | ], 11 | "keywords": [ 12 | "purescript", 13 | "argonaut", 14 | "json" 15 | ], 16 | "repository": { 17 | "type": "git", 18 | "url": "https://github.com/purescript-contrib/purescript-argonaut-core.git" 19 | }, 20 | "ignore": [ 21 | "**/.*", 22 | "bower_components", 23 | "node_modules", 24 | "output", 25 | "test", 26 | "bower.json", 27 | "package.json" 28 | ], 29 | "dependencies": { 30 | "purescript-arrays": "^7.0.0", 31 | "purescript-control": "^6.0.0", 32 | "purescript-either": "^6.0.0", 33 | "purescript-foreign-object": "^4.0.0", 34 | "purescript-functions": "^6.0.0", 35 | "purescript-gen": "^4.0.0", 36 | "purescript-maybe": "^6.0.0", 37 | "purescript-nonempty": "^7.0.0", 38 | "purescript-prelude": "^6.0.0", 39 | "purescript-strings": "^6.0.0", 40 | "purescript-tailrec": "^6.0.0" 41 | }, 42 | "devDependencies": { 43 | "purescript-quickcheck": "^8.0.1" 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /docs/README.md: -------------------------------------------------------------------------------- 1 | # Argonaut Core Documentation 2 | 3 | This directory contains documentation for `argonaut-core`. If you are interested in contributing new documentation, please read the [contributor guidelines](../CONTRIBUTING.md) and [What Nobody Tells You About Documentation](https://documentation.divio.com) for help getting started. 4 | 5 | ## Tutorial 6 | 7 | Some of Argonaut's functions might seem a bit arcane at first, so it can help to understand the underlying design decisions which make it the way it is. 8 | 9 | One approach for modelling JSON values would be to define an algebraic data type, like this: 10 | 11 | ```purs 12 | data Json 13 | = JNull 14 | | JString String 15 | | JNumber Number 16 | | JBoolean Boolean 17 | | JArray (Array Json) 18 | | JObject (Object Json) 19 | ``` 20 | 21 | And indeed, some might even say this is the obvious approach. 22 | 23 | Because Argonaut is written with the compilation target of JavaScript in mind, it takes a slightly different approach, which is to reuse the existing data types which JavaScript already provides. This way, the result of JavaScript's `JSON.parse` function is already a `Json` value, and no extra processing is needed before you can start operating on it. This ought to help your program both in terms of speed and memory churn. 24 | 25 | Much of the design of Argonaut follows naturally from this design decision. 26 | 27 | ### Introducing Json values 28 | 29 | (Or, where do `Json` values come from?) 30 | 31 | If your program is receiving JSON data as a string, you probably want the `jsonParser` function in `Data.Argonaut.Parser`, which is a very simple wrapper around JavaScript's `JSON.parse`. 32 | 33 | Otherwise, `Json` values can be introduced into your program via the FFI or via the construction functions in `Data.Argonaut.Core`. Here are some examples: 34 | 35 | ```js 36 | // In an FFI module. 37 | exports.someNumber = 23.6; 38 | exports.someBoolean = false; 39 | exports.someObject = { 40 | people: [{ name: "john" }, { name: "jane" }], 41 | common_interests: [], 42 | }; 43 | ``` 44 | 45 | ```purs 46 | foreign import someNumber :: Json 47 | foreign import someBoolean :: Json 48 | foreign import someObject :: Json 49 | ``` 50 | 51 | Generally, if a JavaScript value could be returned from a call to `JSON.parse`, it's fine to import it from the FFI as `Json`. So, for example, objects, booleans, numbers, strings, and arrays are all fine, but functions are not. 52 | 53 | The construction functions (that is, `fromX`, or `jsonX`) can be used as follows: 54 | 55 | ```purs 56 | import Data.Tuple (Tuple(..)) 57 | import Foreign.Object as Object 58 | import Data.Argonaut.Core as A 59 | 60 | someNumber = A.fromNumber 23.6 61 | 62 | someBoolean = A.fromBoolean false 63 | 64 | someObject = 65 | A.fromObject 66 | ( Object.fromFoldable 67 | [ Tuple "people" 68 | ( A.fromArray 69 | [ A.jsonSingletonObject "name" (A.fromString "john") 70 | , A.jsonSingletonObject "name" (A.fromString "jane") 71 | ] 72 | ) 73 | , Tuple "common_interests" A.jsonEmptyArray 74 | ] 75 | ) 76 | ``` 77 | 78 | ### Eliminating/matching on `Json` values 79 | 80 | We can perform case analysis for `Json` values using the `caseJson` function. This function is necessary because `Json` is not an algebraic data type. If `Json` were an algebraic data type, we would not have as much need for this function, because we could perform pattern matching with a `case ... of` expression instead. 81 | 82 | The type of `caseJson` is: 83 | 84 | ```purs 85 | caseJson 86 | :: forall a 87 | . (Unit -> a) 88 | -> (Boolean -> a) 89 | -> (Number -> a) 90 | -> (String -> a) 91 | -> (Array Json -> a) 92 | -> (Object Json -> a) 93 | -> Json 94 | -> a 95 | ``` 96 | 97 | That is, `caseJson` takes six functions, which all must return values of some particular type `a`, together with one `Json` value. `caseJson` itself also returns a value of the same type `a`. 98 | 99 | A use of `caseJson` is very similar to a `case ... of` expression, as it allows you to handle each of the six possibilities for the `Json` value you passed in. Thinking of it this way, each of the six function arguments is like one of the case alternatives. 100 | 101 | The function that takes `Unit` as an argument is for matching `null` values. As there is only one possible `null` value, we use the PureScript `Unit` type, as correspondingly there is only one possible `Unit` value. 102 | 103 | Just like in a `case ... of` expression, the final value that the whole expression evaluates to comes from evaluating exactly one of the 'alternatives' (functions) that you pass in. In fact, you can tell that this is the case just by looking at the type signature of `caseJson`, because of a property called _parametricity_ (although a deeper explanation of parametricity is outside the scope of this tutorial). 104 | 105 | For example, imagine we had the following values defined in JavaScript and imported via the FFI: 106 | 107 | ```js 108 | exports.anotherNumber = 0.0; 109 | exports.anotherArray = [0.0, { foo: "bar" }, false]; 110 | exports.anotherObject = { foo: 1, bar: [2, 2] }; 111 | ``` 112 | 113 | Then we can match on them in PureScript using `caseJson`: 114 | 115 | ```purs 116 | foreign import anotherNumber :: Json 117 | foreign import anotherArray :: Json 118 | foreign import anotherObject :: Json 119 | 120 | basicInfo :: Json -> String 121 | basicInfo = caseJson 122 | (const "It was null") 123 | (\b -> "Got a boolean: " <> if b then "it was true!" else "It was false.") 124 | (\x -> "Got a number: " <> show x) 125 | (\s -> "Got a string, which was " <> Data.String.length s <> " characters long.") 126 | (\xs -> "Got an array, which had " <> Data.Array.length xs <> " items.") 127 | (\obj -> "Got an object, which had " <> Foreign.Object.size obj <> " items.") 128 | ``` 129 | 130 | ```purs 131 | basicInfo anotherNumber -- => "Got a number: 0.0" 132 | basicInfo anotherArray -- => "Got an array, which had 3 items." 133 | basicInfo anotherObject -- => "Got an object, which had 2 items." 134 | ``` 135 | 136 | `caseJson` is the fundamental function for pattern matching on `Json` values; any kind of pattern matching you might want to do can be done with `caseJson`. 137 | 138 | However, `caseJson` is not always comfortable to use, so Argonaut provides a few other simpler versions for convenience. For example, the `caseJsonX` functions can be used to match on a specific type. The first argument acts as a default value, to be used if the `Json` value turned out not to be that type. For example, we can write a function which tests whether a JSON value is the string "lol" like this: 139 | 140 | ```purs 141 | caseJsonString :: forall a. a -> (String -> a) -> Json -> a 142 | 143 | isJsonLol = caseJsonString false (_ == "lol") 144 | ``` 145 | 146 | If the `Json` value is not a string, the default `false` is used. Otherwise, we test whether the string is equal to "lol". 147 | 148 | The `toX` functions also occupy a similar role: they attempt to convert `Json` values into a specific type. If the json value you provide is of the right type, you'll get a `Just` value. Otherwise, you'll get `Nothing`. For example, we could have written `isJsonLol` like this, too: 149 | 150 | ```purs 151 | toString :: Json -> Maybe String 152 | 153 | isJsonLol json = 154 | case toString json of 155 | Just str -> str == "lol" 156 | Nothing -> false 157 | ``` 158 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "scripts": { 4 | "build": "eslint src && spago build --purs-args '--censor-lib --strict'", 5 | "test": "spago test --no-install" 6 | }, 7 | "devDependencies": { 8 | "eslint": "^7.6.0" 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /packages.dhall: -------------------------------------------------------------------------------- 1 | let upstream = 2 | https://raw.githubusercontent.com/purescript/package-sets/prepare-0.15/src/packages.dhall 3 | 4 | in upstream 5 | -------------------------------------------------------------------------------- /spago.dhall: -------------------------------------------------------------------------------- 1 | { name = "argonaut-core" 2 | , dependencies = 3 | [ "arrays" 4 | , "console" 5 | , "control" 6 | , "effect" 7 | , "either" 8 | , "foreign-object" 9 | , "functions" 10 | , "gen" 11 | , "maybe" 12 | , "nonempty" 13 | , "partial" 14 | , "prelude" 15 | , "quickcheck" 16 | , "strings" 17 | , "tailrec" 18 | , "tuples" 19 | ] 20 | , packages = ./packages.dhall 21 | , sources = [ "src/**/*.purs", "test/**/*.purs" ] 22 | } 23 | -------------------------------------------------------------------------------- /src/Data/Argonaut/Core.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-eq-null, eqeqeq */ 2 | function id(x) { 3 | return x; 4 | } 5 | 6 | export {id as fromBoolean}; 7 | export {id as fromNumber}; 8 | export {id as fromString}; 9 | export {id as fromArray}; 10 | export {id as fromObject}; 11 | export const jsonNull = null; 12 | 13 | export function stringify(j) { 14 | return JSON.stringify(j); 15 | } 16 | 17 | export function stringifyWithIndent(i) { 18 | return function (j) { 19 | return JSON.stringify(j, null, i); 20 | }; 21 | } 22 | 23 | function isArray(a) { 24 | return Object.prototype.toString.call(a) === "[object Array]"; 25 | } 26 | 27 | export function _caseJson(isNull, isBool, isNum, isStr, isArr, isObj, j) { 28 | if (j == null) return isNull(); 29 | else if (typeof j === "boolean") return isBool(j); 30 | else if (typeof j === "number") return isNum(j); 31 | else if (typeof j === "string") return isStr(j); 32 | else if (Object.prototype.toString.call(j) === "[object Array]") 33 | return isArr(j); 34 | else return isObj(j); 35 | } 36 | 37 | export function _compare(EQ, GT, LT, a, b) { 38 | if (a == null) { 39 | if (b == null) return EQ; 40 | else return LT; 41 | } else if (typeof a === "boolean") { 42 | if (typeof b === "boolean") { 43 | // boolean / boolean 44 | if (a === b) return EQ; 45 | else if (a === false) return LT; 46 | else return GT; 47 | } else if (b == null) return GT; 48 | else return LT; 49 | } else if (typeof a === "number") { 50 | if (typeof b === "number") { 51 | if (a === b) return EQ; 52 | else if (a < b) return LT; 53 | else return GT; 54 | } else if (b == null) return GT; 55 | else if (typeof b === "boolean") return GT; 56 | else return LT; 57 | } else if (typeof a === "string") { 58 | if (typeof b === "string") { 59 | if (a === b) return EQ; 60 | else if (a < b) return LT; 61 | else return GT; 62 | } else if (b == null) return GT; 63 | else if (typeof b === "boolean") return GT; 64 | else if (typeof b === "number") return GT; 65 | else return LT; 66 | } else if (isArray(a)) { 67 | if (isArray(b)) { 68 | for (var i = 0; i < Math.min(a.length, b.length); i++) { 69 | var ca = _compare(EQ, GT, LT, a[i], b[i]); 70 | if (ca !== EQ) return ca; 71 | } 72 | if (a.length === b.length) return EQ; 73 | else if (a.length < b.length) return LT; 74 | else return GT; 75 | } else if (b == null) return GT; 76 | else if (typeof b === "boolean") return GT; 77 | else if (typeof b === "number") return GT; 78 | else if (typeof b === "string") return GT; 79 | else return LT; 80 | } else { 81 | if (b == null) return GT; 82 | else if (typeof b === "boolean") return GT; 83 | else if (typeof b === "number") return GT; 84 | else if (typeof b === "string") return GT; 85 | else if (isArray(b)) return GT; 86 | else { 87 | var akeys = Object.keys(a); 88 | var bkeys = Object.keys(b); 89 | if (akeys.length < bkeys.length) return LT; 90 | else if (akeys.length > bkeys.length) return GT; 91 | var keys = akeys.concat(bkeys).sort(); 92 | for (var j = 0; j < keys.length; j++) { 93 | var k = keys[j]; 94 | if (a[k] === undefined) return LT; 95 | else if (b[k] === undefined) return GT; 96 | var ck = _compare(EQ, GT, LT, a[k], b[k]); 97 | if (ck !== EQ) return ck; 98 | } 99 | return EQ; 100 | } 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /src/Data/Argonaut/Core.purs: -------------------------------------------------------------------------------- 1 | -- | This module defines a data type and various functions for creating and 2 | -- | manipulating JSON values. The README contains additional documentation 3 | -- | for this module. 4 | module Data.Argonaut.Core 5 | ( Json 6 | , caseJson 7 | , caseJsonNull 8 | , caseJsonBoolean 9 | , caseJsonNumber 10 | , caseJsonString 11 | , caseJsonArray 12 | , caseJsonObject 13 | , isNull 14 | , isBoolean 15 | , isNumber 16 | , isString 17 | , isArray 18 | , isObject 19 | , fromBoolean 20 | , fromNumber 21 | , fromString 22 | , fromArray 23 | , fromObject 24 | , toNull 25 | , toBoolean 26 | , toNumber 27 | , toString 28 | , toArray 29 | , toObject 30 | , jsonNull 31 | , jsonTrue 32 | , jsonFalse 33 | , jsonZero 34 | , jsonEmptyString 35 | , jsonEmptyArray 36 | , jsonSingletonArray 37 | , jsonEmptyObject 38 | , jsonSingletonObject 39 | , stringify 40 | , stringifyWithIndent 41 | ) where 42 | 43 | import Prelude 44 | 45 | import Data.Function.Uncurried (Fn5, runFn5, Fn7, runFn7) 46 | import Data.Maybe (Maybe(..)) 47 | import Foreign.Object (Object) 48 | import Foreign.Object as Obj 49 | 50 | -- | The type of JSON data. The underlying representation is the same as what 51 | -- | would be returned from JavaScript's `JSON.parse` function; that is, 52 | -- | ordinary JavaScript booleans, strings, arrays, objects, etc. 53 | foreign import data Json :: Type 54 | 55 | instance eqJson :: Eq Json where 56 | eq j1 j2 = compare j1 j2 == EQ 57 | 58 | instance ordJson :: Ord Json where 59 | compare a b = runFn5 _compare EQ GT LT a b 60 | 61 | -- | The type of null values inside JSON data. There is exactly one value of 62 | -- | this type: in JavaScript, it is written `null`. This module exports this 63 | -- | value as `jsonNull`. 64 | foreign import data JNull :: Type 65 | 66 | instance eqJNull :: Eq JNull where 67 | eq _ _ = true 68 | 69 | instance ordJNull :: Ord JNull where 70 | compare _ _ = EQ 71 | 72 | -- | Case analysis for `Json` values. See the README for more information. 73 | caseJson 74 | :: forall a 75 | . (Unit -> a) 76 | -> (Boolean -> a) 77 | -> (Number -> a) 78 | -> (String -> a) 79 | -> (Array Json -> a) 80 | -> (Object Json -> a) 81 | -> Json 82 | -> a 83 | caseJson a b c d e f json = runFn7 _caseJson a b c d e f json 84 | 85 | -- | A simpler version of `caseJson` which accepts a callback for when the 86 | -- | `Json` argument was null, and a default value for all other cases. 87 | caseJsonNull :: forall a. a -> (Unit -> a) -> Json -> a 88 | caseJsonNull d f j = runFn7 _caseJson f (const d) (const d) (const d) (const d) (const d) j 89 | 90 | -- | A simpler version of `caseJson` which accepts a callback for when the 91 | -- | `Json` argument was a `Boolean`, and a default value for all other cases. 92 | caseJsonBoolean :: forall a. a -> (Boolean -> a) -> Json -> a 93 | caseJsonBoolean d f j = runFn7 _caseJson (const d) f (const d) (const d) (const d) (const d) j 94 | 95 | -- | A simpler version of `caseJson` which accepts a callback for when the 96 | -- | `Json` argument was a `Number`, and a default value for all other cases. 97 | caseJsonNumber :: forall a. a -> (Number -> a) -> Json -> a 98 | caseJsonNumber d f j = runFn7 _caseJson (const d) (const d) f (const d) (const d) (const d) j 99 | 100 | -- | A simpler version of `caseJson` which accepts a callback for when the 101 | -- | `Json` argument was a `String`, and a default value for all other cases. 102 | caseJsonString :: forall a. a -> (String -> a) -> Json -> a 103 | caseJsonString d f j = runFn7 _caseJson (const d) (const d) (const d) f (const d) (const d) j 104 | 105 | -- | A simpler version of `caseJson` which accepts a callback for when the 106 | -- | `Json` argument was a `Array Json`, and a default value for all other cases. 107 | caseJsonArray :: forall a. a -> (Array Json -> a) -> Json -> a 108 | caseJsonArray d f j = runFn7 _caseJson (const d) (const d) (const d) (const d) f (const d) j 109 | 110 | -- | A simpler version of `caseJson` which accepts a callback for when the 111 | -- | `Json` argument was an `Object`, and a default value for all other cases. 112 | caseJsonObject :: forall a. a -> (Object Json -> a) -> Json -> a 113 | caseJsonObject d f j = runFn7 _caseJson (const d) (const d) (const d) (const d) (const d) f j 114 | 115 | verbJsonType :: forall a b. b -> (a -> b) -> (b -> (a -> b) -> Json -> b) -> Json -> b 116 | verbJsonType def f g = g def f 117 | 118 | -- Tests 119 | 120 | isJsonType :: forall a. (Boolean -> (a -> Boolean) -> Json -> Boolean) -> Json -> Boolean 121 | isJsonType = verbJsonType false (const true) 122 | 123 | -- | Check if the provided `Json` is the `null` value 124 | isNull :: Json -> Boolean 125 | isNull = isJsonType caseJsonNull 126 | 127 | -- | Check if the provided `Json` is a `Boolean` 128 | isBoolean :: Json -> Boolean 129 | isBoolean = isJsonType caseJsonBoolean 130 | 131 | -- | Check if the provided `Json` is a `Number` 132 | isNumber :: Json -> Boolean 133 | isNumber = isJsonType caseJsonNumber 134 | 135 | -- | Check if the provided `Json` is a `String` 136 | isString :: Json -> Boolean 137 | isString = isJsonType caseJsonString 138 | 139 | -- | Check if the provided `Json` is an `Array` 140 | isArray :: Json -> Boolean 141 | isArray = isJsonType caseJsonArray 142 | 143 | -- | Check if the provided `Json` is an `Object` 144 | isObject :: Json -> Boolean 145 | isObject = isJsonType caseJsonObject 146 | 147 | -- Decoding 148 | 149 | toJsonType 150 | :: forall a 151 | . (Maybe a -> (a -> Maybe a) -> Json -> Maybe a) 152 | -> Json 153 | -> Maybe a 154 | toJsonType = verbJsonType Nothing Just 155 | 156 | -- | Convert `Json` to the `Unit` value if the `Json` is the null value 157 | toNull :: Json -> Maybe Unit 158 | toNull = toJsonType caseJsonNull 159 | 160 | -- | Convert `Json` to a `Boolean` value, if the `Json` is a boolean. 161 | toBoolean :: Json -> Maybe Boolean 162 | toBoolean = toJsonType caseJsonBoolean 163 | 164 | -- | Convert `Json` to a `Number` value, if the `Json` is a number. 165 | toNumber :: Json -> Maybe Number 166 | toNumber = toJsonType caseJsonNumber 167 | 168 | -- | Convert `Json` to a `String` value, if the `Json` is a string. To write a 169 | -- | `Json` value to a JSON string, see `stringify`. 170 | toString :: Json -> Maybe String 171 | toString = toJsonType caseJsonString 172 | 173 | -- | Convert `Json` to an `Array` of `Json` values, if the `Json` is an array. 174 | toArray :: Json -> Maybe (Array Json) 175 | toArray = toJsonType caseJsonArray 176 | 177 | -- | Convert `Json` to an `Object` of `Json` values, if the `Json` is an object. 178 | toObject :: Json -> Maybe (Object Json) 179 | toObject = toJsonType caseJsonObject 180 | 181 | -- Encoding 182 | 183 | -- | Construct `Json` from a `Boolean` value 184 | foreign import fromBoolean :: Boolean -> Json 185 | 186 | -- | Construct `Json` from a `Number` value 187 | foreign import fromNumber :: Number -> Json 188 | 189 | -- | Construct the `Json` representation of a `String` value. 190 | -- | Note that this function only produces `Json` containing a single piece of `String` 191 | -- | data (similar to `fromBoolean`, `fromNumber`, etc.). 192 | -- | This function does NOT convert the `String` encoding of a JSON value to `Json` - For that 193 | -- | purpose, you'll need to use `jsonParser`. 194 | foreign import fromString :: String -> Json 195 | 196 | -- | Construct `Json` from an array of `Json` values 197 | foreign import fromArray :: Array Json -> Json 198 | 199 | -- | Construct `Json` from an object with `Json` values 200 | foreign import fromObject :: Object Json -> Json 201 | 202 | -- Defaults 203 | 204 | -- | The JSON null value represented as `Json` 205 | foreign import jsonNull :: Json 206 | 207 | -- | The true boolean value represented as `Json` 208 | jsonTrue :: Json 209 | jsonTrue = fromBoolean true 210 | 211 | -- | The false boolean value represented as `Json` 212 | jsonFalse :: Json 213 | jsonFalse = fromBoolean false 214 | 215 | -- | The number zero represented as `Json` 216 | jsonZero :: Json 217 | jsonZero = fromNumber 0.0 218 | 219 | -- | An empty string represented as `Json` 220 | jsonEmptyString :: Json 221 | jsonEmptyString = fromString "" 222 | 223 | -- | An empty array represented as `Json` 224 | jsonEmptyArray :: Json 225 | jsonEmptyArray = fromArray [] 226 | 227 | -- | An empty object represented as `Json` 228 | jsonEmptyObject :: Json 229 | jsonEmptyObject = fromObject Obj.empty 230 | 231 | -- | Constructs a `Json` array value containing only the provided value 232 | jsonSingletonArray :: Json -> Json 233 | jsonSingletonArray j = fromArray [ j ] 234 | 235 | -- | Constructs a `Json` object value containing only the provided key and value 236 | jsonSingletonObject :: String -> Json -> Json 237 | jsonSingletonObject key val = fromObject (Obj.singleton key val) 238 | 239 | -- | Converts a `Json` value to a JSON string. To retrieve a string from a `Json` 240 | -- | string value, see `fromString`. 241 | foreign import stringify :: Json -> String 242 | 243 | -- | Converts a `Json` value to a JSON string. 244 | -- | The first `Int` argument specifies the amount of white space characters to use as indentation. 245 | -- | This number is capped at 10 (if it is greater, the value is just 10). Values less than 1 indicate that no space should be used. 246 | foreign import stringifyWithIndent :: Int -> Json -> String 247 | 248 | foreign import _caseJson 249 | :: forall z 250 | . Fn7 251 | (Unit -> z) 252 | (Boolean -> z) 253 | (Number -> z) 254 | (String -> z) 255 | (Array Json -> z) 256 | (Object Json -> z) 257 | Json 258 | z 259 | 260 | foreign import _compare :: Fn5 Ordering Ordering Ordering Json Json Ordering 261 | -------------------------------------------------------------------------------- /src/Data/Argonaut/Gen.purs: -------------------------------------------------------------------------------- 1 | module Data.Argonaut.Gen where 2 | 3 | import Prelude 4 | 5 | import Control.Lazy (class Lazy, defer) 6 | import Control.Monad.Gen (class MonadGen) 7 | import Control.Monad.Gen as Gen 8 | import Control.Monad.Rec.Class (class MonadRec) 9 | import Data.Argonaut.Core as J 10 | import Data.Array as A 11 | import Data.NonEmpty ((:|)) 12 | import Data.String.Gen (genUnicodeString) 13 | import Foreign.Object as Obj 14 | 15 | -- | A generator for `Json` values. Especially useful for writing property-based 16 | -- | tests. 17 | genJson :: forall m. MonadGen m => MonadRec m => Lazy (m J.Json) => m J.Json 18 | genJson = Gen.resize (min 5) $ Gen.sized genJson' 19 | where 20 | genJson' :: Int -> m J.Json 21 | genJson' size 22 | | size > 1 = Gen.resize (_ - 1) (Gen.choose genJArray genJObject) 23 | | otherwise = genLeaf 24 | 25 | genLeaf :: m J.Json 26 | genLeaf = Gen.oneOf $ pure J.jsonNull :| [ genJBoolean, genJNumber, genJString ] 27 | 28 | genJArray :: m J.Json 29 | genJArray = J.fromArray <$> Gen.unfoldable (defer \_ -> genJson) 30 | 31 | genJObject :: m J.Json 32 | genJObject = A.foldM extendJObj J.jsonEmptyObject =<< Gen.unfoldable genUnicodeString 33 | 34 | extendJObj :: J.Json -> String -> m J.Json 35 | extendJObj obj k = do 36 | v <- genJson 37 | pure $ 38 | J.caseJsonObject 39 | (J.jsonSingletonObject k v) 40 | (J.fromObject <<< Obj.insert k v) 41 | obj 42 | 43 | genJBoolean :: m J.Json 44 | genJBoolean = J.fromBoolean <$> Gen.chooseBool 45 | 46 | genJNumber :: m J.Json 47 | genJNumber = J.fromNumber <$> Gen.chooseFloat (-1000000.0) 1000000.0 48 | 49 | genJString :: m J.Json 50 | genJString = J.fromString <$> genUnicodeString 51 | -------------------------------------------------------------------------------- /src/Data/Argonaut/Parser.js: -------------------------------------------------------------------------------- 1 | export function _jsonParser(fail, succ, s) { 2 | try { 3 | return succ(JSON.parse(s)); 4 | } 5 | catch (e) { 6 | return fail(e.message); 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /src/Data/Argonaut/Parser.purs: -------------------------------------------------------------------------------- 1 | module Data.Argonaut.Parser (jsonParser) where 2 | 3 | import Data.Argonaut.Core (Json) 4 | import Data.Either (Either(..)) 5 | import Data.Function.Uncurried (Fn3, runFn3) 6 | 7 | foreign import _jsonParser :: forall a. Fn3 (String -> a) (Json -> a) String a 8 | 9 | -- | Parse a JSON string, constructing the `Json` value described by the string. 10 | -- | To convert a string into a `Json` string, see `fromString`. 11 | jsonParser :: String -> Either String Json 12 | jsonParser j = runFn3 _jsonParser Left Right j 13 | -------------------------------------------------------------------------------- /test/Test/Main.js: -------------------------------------------------------------------------------- 1 | export const thisIsNull = null; 2 | export const thisIsBoolean = true; 3 | export const thisIsNumber = 12; 4 | export const thisIsString = "foobar"; 5 | export const thisIsArray = ["foo", "bar"]; 6 | export const thisIsObject = { foo: "bar" }; 7 | export const thisIsInvalidString = "\\\ffff"; 8 | -------------------------------------------------------------------------------- /test/Test/Main.purs: -------------------------------------------------------------------------------- 1 | module Test.Main where 2 | 3 | import Prelude 4 | 5 | import Control.Monad.Gen as Gen 6 | import Data.Argonaut.Core (Json, caseJson, caseJsonArray, caseJsonBoolean, caseJsonNull, caseJsonNumber, caseJsonObject, caseJsonString, fromArray, fromBoolean, fromNumber, fromObject, fromString, isArray, isBoolean, isNull, isNumber, isObject, isString, jsonNull, stringify, toArray, toBoolean, toNull, toNumber, toObject, toString) 7 | import Data.Argonaut.Gen (genJson) 8 | import Data.Argonaut.Parser (jsonParser) 9 | import Data.Array as A 10 | import Data.Either (isLeft, Either(..)) 11 | import Data.Maybe (Maybe(..), fromJust) 12 | import Data.Tuple (Tuple(..)) 13 | import Effect (Effect) 14 | import Effect.Console (log) 15 | import Foreign.Object as Obj 16 | import Partial.Unsafe (unsafePartial) 17 | import Test.QuickCheck (class Testable, Result, quickCheck, quickCheck', ()) 18 | import Test.QuickCheck.Gen (Gen) 19 | 20 | foreign import thisIsNull :: Json 21 | foreign import thisIsBoolean :: Json 22 | foreign import thisIsNumber :: Json 23 | foreign import thisIsString :: Json 24 | foreign import thisIsArray :: Json 25 | foreign import thisIsObject :: Json 26 | foreign import thisIsInvalidString :: String 27 | 28 | isTest :: Effect Unit 29 | isTest = do 30 | assert (isNull thisIsNull "Error in null test") 31 | assert (isBoolean thisIsBoolean "Error in boolean test") 32 | assert (isNumber thisIsNumber "Error in number test") 33 | assert (isString thisIsString "Error in string test") 34 | assert (isArray thisIsArray "Error in array test") 35 | assert (isObject thisIsObject "Error in object test") 36 | 37 | foldTest :: Effect Unit 38 | foldTest = do 39 | assert (foldFn thisIsNull == "null" "Error in caseJson null") 40 | assert (foldFn thisIsBoolean == "boolean" "Error in caseJson boolean") 41 | assert (foldFn thisIsNumber == "number" "Error in caseJson number") 42 | assert (foldFn thisIsString == "string" "Error in caseJson string") 43 | assert (foldFn thisIsArray == "array" "Error in caseJson array") 44 | assert (foldFn thisIsObject == "object" "Error in caseJson object") 45 | 46 | foldFn :: Json -> String 47 | foldFn = caseJson 48 | (const "null") 49 | (const "boolean") 50 | (const "number") 51 | (const "string") 52 | (const "array") 53 | (const "object") 54 | 55 | cases :: Array Json 56 | cases = 57 | [ thisIsNull 58 | , thisIsBoolean 59 | , thisIsNumber 60 | , thisIsString 61 | , thisIsArray 62 | , thisIsObject 63 | ] 64 | 65 | foldXXX :: Effect Unit 66 | foldXXX = do 67 | assert 68 | ( (caseJsonNull "not null" (const "null") <$> cases) == 69 | [ "null", "not null", "not null", "not null", "not null", "not null" ] 70 | "Error in caseJsonNull" 71 | ) 72 | assert 73 | ( (caseJsonBoolean "not boolean" (const "boolean") <$> cases) == 74 | [ "not boolean", "boolean", "not boolean", "not boolean", "not boolean", "not boolean" ] 75 | "Error in caseJsonBoolean" 76 | ) 77 | assert 78 | ( (caseJsonNumber "not number" (const "number") <$> cases) == 79 | [ "not number", "not number", "number", "not number", "not number", "not number" ] 80 | "Error in caseJsonNumber" 81 | ) 82 | 83 | assert 84 | ( (caseJsonString "not string" (const "string") <$> cases) == 85 | [ "not string", "not string", "not string", "string", "not string", "not string" ] 86 | "Error in caseJsonString" 87 | ) 88 | 89 | assert 90 | ( (caseJsonArray "not array" (const "array") <$> cases) == 91 | [ "not array", "not array", "not array", "not array", "array", "not array" ] 92 | "Error in caseJsonArray" 93 | ) 94 | assert 95 | ( (caseJsonObject "not object" (const "object") <$> cases) == 96 | [ "not object", "not object", "not object", "not object", "not object", "object" ] 97 | "Error in caseJsonObject" 98 | ) 99 | 100 | fromTest :: Effect Unit 101 | fromTest = do 102 | assert ((caseJsonNull false (const true) jsonNull) "Error in fromNull") 103 | quickCheck (\bool -> caseJsonBoolean Nothing Just (fromBoolean bool) == Just bool "Error in fromBoolean") 104 | quickCheck (\num -> caseJsonNumber Nothing Just (fromNumber num) == Just num "Error in fromNumber") 105 | quickCheck (\str -> caseJsonString Nothing Just (fromString str) == Just str "Error in fromString") 106 | quickCheck 107 | ( \num -> 108 | let 109 | arr :: Array Json 110 | arr = A.singleton (fromNumber num) 111 | in 112 | (caseJsonArray Nothing Just (fromArray arr) == Just arr) 113 | "Error in fromArray" 114 | ) 115 | quickCheck 116 | ( \(Tuple str num) -> 117 | let 118 | sm :: Obj.Object Json 119 | sm = Obj.singleton str (fromNumber num) 120 | in 121 | (caseJsonObject Nothing Just (fromObject sm) == Just sm) 122 | "Error in fromObject" 123 | ) 124 | 125 | toTest :: Effect Unit 126 | toTest = do 127 | assert (assertion toNull thisIsNull "Error in toNull") 128 | assert (assertion toBoolean thisIsBoolean "Error in toBoolean") 129 | assert (assertion toNumber thisIsNumber "Error in toNumber") 130 | assert (assertion toString thisIsString "Error in toString") 131 | assert (assertion toArray thisIsArray "Error in toArray") 132 | assert (assertion toObject thisIsObject "Error in toObject") 133 | where 134 | assertion :: forall a. (Eq a) => (Json -> Maybe a) -> Json -> String -> Result 135 | assertion fn j msg = 136 | let 137 | forCases = A.catMaybes (fn <$> cases) 138 | exact = A.singleton $ unsafePartial fromJust $ fn j 139 | in 140 | forCases == exact msg 141 | 142 | parserTest :: Effect Unit 143 | parserTest = do 144 | assert ((isLeft (jsonParser thisIsInvalidString)) "Error in jsonParser") 145 | quickCheck' 10 roundtripTest 146 | where 147 | roundtripTest :: Gen Result 148 | roundtripTest = do 149 | json <- Gen.resize (const 5) genJson 150 | let parsed = jsonParser (stringify json) 151 | pure $ parsed == Right json show (stringify <$> parsed) <> " /= " <> stringify json 152 | 153 | assert :: forall prop. Testable prop => prop -> Effect Unit 154 | assert = quickCheck' 1 155 | 156 | main :: Effect Unit 157 | main = do 158 | log "isXxx tests" 159 | isTest 160 | log "caseJson tests" 161 | foldTest 162 | log "caseJsonXxx tests" 163 | foldXXX 164 | log "fromXxx tests" 165 | fromTest 166 | log "toXxx tests" 167 | toTest 168 | log "jsonParser tests" 169 | parserTest 170 | --------------------------------------------------------------------------------