Skip to content

Commit

Permalink
Introduce , etc.
Browse files Browse the repository at this point in the history
  • Loading branch information
garyb committed May 14, 2023
1 parent b2b19cf commit 7349a7c
Show file tree
Hide file tree
Showing 10 changed files with 172 additions and 95 deletions.
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,10 @@

[![Build status](https://github.com/purescript/purescript-json/workflows/CI/badge.svg?branch=master)](https://github.com/purescript/purescript-json/actions?query=workflow%3ACI+branch%3Amaster)

Standard types for JSON and JSON objects, and basic operations for working with them.
Standard types and basic operations for working with JSON.

For efficiency and performance reasons this library provides an interface for working with JSON without using PureScript ADTs, and instead operates on the underlying representation.

## Differences from Argonaut

This library is similar to the traditionally used `argonaut-core` library, but has been implemented with an eye to making it backend agnostic. As such, it does not use `Foreign.Object` as the representation for JSON objects, and instead provides its own type.
This library is similar to the traditionally used `argonaut-core` library, but has been implemented with an eye to making it backend agnostic. As such, it does not use `Foreign.Object` as the representation for JSON objects, does not use `Array JSON`, and instead provides its own `JObject` and `JArray` types.
9 changes: 5 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,14 @@
"private": true,
"scripts": {
"clean": "rimraf output && rimraf .pulp-cache",
"build": "eslint src && purs-tidy check --config-require src/**/*.purs && pulp build -- --censor-lib --strict"
"build": "eslint src && purs-tidy check --config-require src/**/*.purs && pulp build -- --censor-lib --strict",
"test": "pulp test"
},
"devDependencies": {
"eslint": "^8.25.0",
"eslint": "^8.40.0",
"pulp": "^16.0.2",
"purescript-psa": "^0.8.2",
"purs-tidy": "^0.9.2",
"rimraf": "^3.0.2"
"purs-tidy": "^0.9.3",
"rimraf": "^5.0.0"
}
}
4 changes: 2 additions & 2 deletions src/JSON.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,9 @@ export const fromInt = coerce;

export const fromString = coerce;

export const fromArray = coerce;
export const fromJArray = coerce;

export const fromObject = coerce;
export const fromJObject = coerce;

export const print = (j) => JSON.stringify(j);

Expand Down
78 changes: 46 additions & 32 deletions src/JSON.purs
Original file line number Diff line number Diff line change
Expand Up @@ -2,65 +2,69 @@ module JSON
( parse
, null
, fromBoolean
, fromNumberWithDefault
, fromNumber
, fromNumberWithDefault
, fromInt
, fromString
, fromArray
, fromObject
, fromJArray
, fromJObject
, case_
, toNull
, toBoolean
, toNumber
, toString
, toArray
, toObject
, toJArray
, toJObject
, print
, printIndented
, module Internal
, module Exports
) where

import Prelude

import Data.Either (Either(..))
import Data.Function.Uncurried (runFn2, runFn3, runFn7)
import Data.Maybe (Maybe(..))
import JSON.Internal (JSON) as Internal
import JSON.Internal (JSON, Object, _case, _fromNumberWithDefault, _parse)
import JSON.Internal (JArray, JObject, JSON) as Exports
import JSON.Internal (JArray, JObject, JSON)
import JSON.Internal as Internal

-- | Attempts to parse a string as a JSON value. If parsing fails, an error message detailing the
-- | cause may be returned in the `Left` of the result.
parse :: String -> Either String JSON
parse j = runFn3 _parse Left Right j
parse j = runFn3 Internal._parse Left Right j

-- | The JSON `null` value.
null :: JSON
null = _null

-- | The JSON `null` value.
foreign import _null :: JSON

-- | Creates a `JSON` value from a `Boolean`.
-- | Converts a `Boolean` into `JSON`.
foreign import fromBoolean :: Boolean -> JSON

-- | Creates a `JSON` value from a `Number`, using a fallback `Int` value for cases where the
-- | PureScript number value is not valid for JSON.
fromNumberWithDefault :: Int -> Number -> JSON
fromNumberWithDefault fallback n = runFn2 _fromNumberWithDefault fallback n

-- | Creates a `JSON` value from a `Number`.
-- | Converts a `Number` into `JSON`.
-- |
-- | The PureScript `Number` type admits infinities and a `NaN` value which are not allowed in JSON,
-- | so when encountered, this function will treat those values as 0.
fromNumber :: Number -> JSON
fromNumber n = runFn2 _fromNumberWithDefault 0 n
fromNumber n = runFn2 Internal._fromNumberWithDefault 0 n

-- | Creates a `JSON` value from an `Int`.
-- | Creates a `Number` into `JSON`, using a fallback `Int` value for cases where the
-- | PureScript number value is not valid for JSON (`NaN`, `infinity`).
fromNumberWithDefault :: Int -> Number -> JSON
fromNumberWithDefault fallback n = runFn2 Internal._fromNumberWithDefault fallback n

-- | Converts an `Int` into `JSON`.
-- |
-- | There is no corresponding `toInt` as JSON doesn't have a concept of integers - this is provided
-- | as a convenience to avoid having to convert `Int` to `Number` before creating a `JSON` value.
foreign import fromInt :: Int -> JSON

-- | Creates a `JSON` value from a `String`.
-- | Converts a `String` into `JSON`.
-- |
-- | **Note**: this does not parse a string as a JSON value, it takes a PureScript string and
-- | produces the corresponding `JSON` value for that string, similar to the other functions like
Expand All @@ -70,59 +74,69 @@ foreign import fromInt :: Int -> JSON
-- | [`parse`](#v:parse).
foreign import fromString :: String -> JSON

-- | Creates a `JSON` value from an array of `JSON` values.
foreign import fromArray :: Array JSON -> JSON
-- | Converts a `JArray` into `JSON`.
foreign import fromJArray :: JArray -> JSON

-- | Converts an array of `JSON` values into `JSON`.
fromArray :: Array JSON -> JSON
fromArray js = fromJArray (Internal.fromArray js)

-- | Creates a `JSON` value from an `Object`.
foreign import fromObject :: Object -> JSON
-- | Converts a `JObject` into `JSON`.
foreign import fromJObject :: JObject -> JSON

-- | Performs case analysis on a JSON value.
-- |
-- | As the `JSON` type is not a PureScript sum type, pattern matching cannot be used to
-- | discriminate between the potential varieties of value. This function provides an equivalent
-- | mechanism by accepting functions that deal with each variety, similar to an exaustive `case`
-- | statement.
-- |
-- | The `Unit` case is for `null` values.
case_
:: forall a
. (Unit -> a)
-> (Boolean -> a)
-> (Number -> a)
-> (String -> a)
-> (Array JSON -> a)
-> (Object -> a)
-> (JArray -> a)
-> (JObject -> a)
-> JSON
-> a
case_ a b c d e f json = runFn7 _case a b c d e f json
case_ a b c d e f json = runFn7 Internal._case a b c d e f json

fail :: forall a b. a -> Maybe b
fail _ = Nothing

-- | Converts a `JSON` value to `Null` if the `JSON` is `null`.
toNull :: JSON -> Maybe Unit
toNull json = runFn7 _case Just fail fail fail fail fail json
toNull json = runFn7 Internal._case Just fail fail fail fail fail json

-- | Converts a `JSON` value to `Boolean` if the `JSON` is a boolean.
toBoolean :: JSON -> Maybe Boolean
toBoolean json = runFn7 _case fail Just fail fail fail fail json
toBoolean json = runFn7 Internal._case fail Just fail fail fail fail json

-- | Converts a `JSON` value to `Number` if the `JSON` is a number.
toNumber :: JSON -> Maybe Number
toNumber json = runFn7 _case fail fail Just fail fail fail json
toNumber json = runFn7 Internal._case fail fail Just fail fail fail json

-- | Converts a `JSON` value to `String` if the `JSON` is a string.
toString :: JSON -> Maybe String
toString json = runFn7 _case fail fail fail Just fail fail json
toString json = runFn7 Internal._case fail fail fail Just fail fail json

-- | Converts a `JSON` value to `JArray` if the `JSON` is an array.
toJArray :: JSON -> Maybe JArray
toJArray json = runFn7 Internal._case fail fail fail fail Just fail json

-- | Converts a `JSON` value to `Array JSON` if the `JSON` is an array.
toArray :: JSON -> Maybe (Array JSON)
toArray json = runFn7 _case fail fail fail fail Just fail json
toArray json = Internal.toArray <$> toJArray json

-- | Converts a `JSON` value to `Object` if the `JSON` is an object.
toObject :: JSON -> Maybe Object
toObject json = runFn7 _case fail fail fail fail fail Just json
toJObject :: JSON -> Maybe JObject
toJObject json = runFn7 Internal._case fail fail fail fail fail Just json

-- | Prints a JSON value as a compact (single line) string.
foreign import print :: JSON -> String

-- | Prints a JSON value as a "pretty" string,
-- | Prints a JSON value as a "pretty" string with newlines and indentation.
foreign import printIndented :: JSON -> String
35 changes: 35 additions & 0 deletions src/JSON/Array.purs
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
module JSON.Array
( fromFoldable
, empty
, singleton
, index
, toUnfoldable
, module Exports
) where

import Data.Array as Array
import Data.Foldable (class Foldable)
import Data.Maybe (Maybe)
import Data.Unfoldable (class Unfoldable)
import JSON.Internal (JArray, JSON, toArray, fromArray)
import JSON.Internal (JArray, toArray, fromArray) as Exports

-- | Creates a `JArray` from a `Foldable` source of `JSON`.
fromFoldable :: forall f. Foldable f => f JSON -> JArray
fromFoldable js = fromArray (Array.fromFoldable js)

-- | An empty `JArray`.
empty :: JArray
empty = fromArray []

-- | Creates a `JArray` with a single entry.
singleton :: JSON -> JArray
singleton j = fromArray [ j ]

-- | Attempts to read a value from the specified index of a `JArray`.
index :: JArray -> Int -> Maybe JSON
index js = Array.index (toArray js)

-- | Unfolds a `JArray` into `JSON` items
toUnfoldable :: forall f. Unfoldable f => JArray -> f JSON
toUnfoldable js = Array.toUnfoldable (toArray js)
7 changes: 4 additions & 3 deletions src/JSON/Gen.purs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@ import Data.NonEmpty ((:|))
import Data.String.Gen (genUnicodeString)
import Data.Tuple (Tuple(..))
import JSON as J
import JSON.Object as Object
import JSON.Array as JArray
import JSON.Object as JObject

-- | A generator for random `JSON` values of any variety.
genJSON :: forall m. MonadGen m => MonadRec m => Lazy (m J.JSON) => m J.JSON
Expand All @@ -23,15 +24,15 @@ genJSON = Gen.resize (min 5) $ Gen.sized genJSON'

-- | A generator for JSON arrays containing items based on the passed generator.
genArrayOf :: forall m. MonadGen m => MonadRec m => m J.JSON -> m J.JSON
genArrayOf inner = J.fromArray <$> Gen.unfoldable inner
genArrayOf inner = J.fromJArray <<< JArray.fromArray <$> Gen.unfoldable inner

-- | A generator for JSON arrays containing random items.
genArray :: forall m. MonadGen m => MonadRec m => Lazy (m J.JSON) => m J.JSON
genArray = genArrayOf (defer \_ -> genJSON)

-- | A generator for JSON objects containing entries based on the passed generator.
genObjectOf :: forall m. MonadGen m => MonadRec m => m (Tuple String J.JSON) -> m J.JSON
genObjectOf inner = J.fromObject <<< Object.fromEntries <$> (Gen.unfoldable inner)
genObjectOf inner = J.fromJObject <<< JObject.fromEntries <$> (Gen.unfoldable inner)

-- | A generator for JSON objects containing random entries.
genObject :: forall m. MonadGen m => MonadRec m => Lazy (m J.JSON) => m J.JSON
Expand Down
3 changes: 3 additions & 0 deletions src/JSON/Internal.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@ export const _case = (isNull, isBool, isNum, isStr, isArr, isObj, j) => {
return isObj(j);
};

export const toArray = (js) => js;
export const fromArray = (js) => js;

export const _fromEntries = (fst, snd, entries) => {
const result = {};
for (var i = 0; i < entries.length; i++) {
Expand Down
44 changes: 33 additions & 11 deletions src/JSON/Internal.purs
Original file line number Diff line number Diff line change
Expand Up @@ -44,14 +44,36 @@ _lt _ = LT
_gt :: forall a. a -> Ordering
_gt _ = GT

-- | A type that represents JSON arrays. Similar to the JSON type, this is not a PureScript type,
-- | but represents the underlying representation for JSON arrays.
foreign import data JArray :: Type

-- | Converts a `JArray` into an `Array` of `JSON` values
foreign import toArray :: JArray -> Array JSON

-- | Converts an `Array` of `JSON` values into a `JArray`.
foreign import fromArray :: Array JSON -> JArray

instance Eq JArray where
eq x y = eq (toArray x) (toArray y)

instance Ord JArray where
compare x y = compare (toArray x) (toArray y)

instance Semigroup JArray where
append x y = fromArray (append (toArray x) (toArray y))

instance Monoid JArray where
mempty = fromArray []

-- | A type that represents JSON objects. Similar to the JSON type, this is not a PureScript type,
-- | but represents the underlying representation for JSON objects.
foreign import data Object :: Type
foreign import data JObject :: Type

instance Eq Object where
instance Eq JObject where
eq x y = eq (runFn2 _entries Tuple x) (runFn2 _entries Tuple y)

instance Ord Object where
instance Ord JObject where
compare x y = compare (runFn2 _entries Tuple x) (runFn2 _entries Tuple y)

foreign import _parse
Expand All @@ -70,28 +92,28 @@ foreign import _case
(Boolean -> a)
(Number -> a)
(String -> a)
(Array JSON -> a)
(Object -> a)
(JArray -> a)
(JObject -> a)
JSON
a

foreign import _insert :: Fn3 String JSON Object Object
foreign import _insert :: Fn3 String JSON JObject JObject

foreign import _delete :: Fn2 String Object Object
foreign import _delete :: Fn2 String JObject JObject

foreign import _fromEntries
:: Fn3
(forall x y. Tuple x y -> x)
(forall x y. Tuple x y -> y)
(Array (Tuple String JSON))
Object
(Prim.Array (Tuple String JSON))
JObject

foreign import _entries :: forall c. Fn2 (String -> JSON -> c) Object (Array c)
foreign import _entries :: forall c. Fn2 (String -> JSON -> c) JObject (Prim.Array c)

foreign import _lookup
:: Fn4
(forall a. Maybe a)
(forall a. a -> Maybe a)
String
Object
JObject
(Maybe JSON)
Loading

0 comments on commit 7349a7c

Please sign in to comment.