Skip to content

Commit 1bcc534

Browse files
committed
Many changes improving interoperability and completeness
1 parent 834eb1b commit 1bcc534

File tree

11 files changed

+334
-289
lines changed

11 files changed

+334
-289
lines changed

bower.json

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,10 +24,13 @@
2424
"purescript-functions": "^6.0.0",
2525
"purescript-maybe": "^6.0.0",
2626
"purescript-either": "^6.1.0",
27-
"purescript-st": "^6.2.0",
2827
"purescript-tuples": "^7.0.0",
2928
"purescript-foldable-traversable": "^6.0.0",
3029
"purescript-gen": "^4.0.0",
31-
"purescript-strings": "^6.0.1"
30+
"purescript-strings": "^6.0.1",
31+
"purescript-unfoldable": "^6.0.0"
32+
},
33+
"devDependencies": {
34+
"purescript-assert": "^6.0.0"
3235
}
3336
}

src/JSON.js

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
const coerce = (x) => x;
2+
3+
export const _null = null;
4+
5+
export const fromBoolean = coerce;
6+
7+
export const fromInt = coerce;
8+
9+
export const fromString = coerce;
10+
11+
export const fromArray = coerce;
12+
13+
export const fromObject = coerce;
14+
15+
export const print = (j) => JSON.stringify(j);
16+
17+
export const printIndented = (j) => JSON.stringify(j, null, 2);

src/Json.purs

Lines changed: 120 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,128 @@
1-
module Json (module Json.Internal) where
2-
3-
import Json.Internal
4-
( Json
5-
6-
, parse
7-
, print
8-
, printIndented
9-
1+
module JSON
2+
( parse
3+
, null
4+
, fromBoolean
5+
, fromNumberWithDefault
6+
, fromNumber
7+
, fromInt
8+
, fromString
9+
, fromArray
10+
, fromObject
1011
, case_
1112
, toNull
1213
, toBoolean
1314
, toNumber
1415
, toString
1516
, toArray
1617
, toObject
18+
, print
19+
, printIndented
20+
, module Internal
21+
) where
1722

18-
, null
19-
, fromNumber
20-
, fromNumberWithDefault
21-
, fromInt
22-
, fromBoolean
23-
, fromString
24-
, fromArray
25-
, fromObject
26-
)
23+
import Prelude
24+
25+
import Data.Either (Either(..))
26+
import Data.Function.Uncurried (runFn2, runFn3, runFn7)
27+
import Data.Maybe (Maybe(..))
28+
import JSON.Internal (JSON) as Internal
29+
import JSON.Internal (JSON, Object, _case, _fromNumberWithDefault, _parse)
30+
31+
-- | Attempts to parse a string as a JSON value. If parsing fails, an error message detailing the
32+
-- | cause may be returned in the `Left` of the result.
33+
parse :: String -> Either String JSON
34+
parse j = runFn3 _parse Left Right j
35+
36+
-- | The JSON `null` value.
37+
null :: JSON
38+
null = _null
39+
40+
foreign import _null :: JSON
41+
42+
-- | Creates a `JSON` value from a `Boolean`.
43+
foreign import fromBoolean :: Boolean -> JSON
44+
45+
-- | Creates a `JSON` value from a `Number`, using a fallback `Int` value for cases where the
46+
-- | PureScript number value is not valid for JSON.
47+
fromNumberWithDefault :: Int -> Number -> JSON
48+
fromNumberWithDefault fallback n = runFn2 _fromNumberWithDefault fallback n
49+
50+
-- | Creates a `JSON` value from a `Number`.
51+
-- |
52+
-- | The PureScript `Number` type admits infinities and a `NaN` value which are not allowed in JSON,
53+
-- | so when encountered, this function will treat those values as 0.
54+
fromNumber :: Number -> JSON
55+
fromNumber n = runFn2 _fromNumberWithDefault 0 n
56+
57+
-- | Creates a `JSON` value from an `Int`.
58+
-- |
59+
-- | There is no corresponding `toInt` as JSON doesn't have a concept of integers - this is provided
60+
-- | as a convenience to avoid having to convert `Int` to `Number` before creating a `JSON` value.
61+
foreign import fromInt :: Int -> JSON
62+
63+
-- | Creates a `JSON` value from a `String`.
64+
-- |
65+
-- | **Note**: this does not parse a string as a JSON value, it takes a PureScript string and
66+
-- | produces the corresponding `JSON` value for that string, similar to the other functions like
67+
-- | `fromBoolean` and `fromNumber`.
68+
-- |
69+
-- | To take a string that contains printed JSON and turn it into a `JSON` value, see
70+
-- | [`parse`](#v:parse).
71+
foreign import fromString :: String -> JSON
72+
73+
-- | Creates a `JSON` value from an array of `JSON` values.
74+
foreign import fromArray :: Array JSON -> JSON
75+
76+
-- | Creates a `JSON` value from an `Object`.
77+
foreign import fromObject :: Object -> JSON
78+
79+
-- | Performs case analysis on a JSON value.
80+
-- |
81+
-- | As the `JSON` type is not a PureScript sum type, pattern matching cannot be used to
82+
-- | discriminate between the potential varieties of value. This function provides an equivalent
83+
-- | mechanism by accepting functions that deal with each variety, similar to an exaustive `case`
84+
-- | statement.
85+
case_
86+
:: forall a
87+
. (Unit -> a)
88+
-> (Boolean -> a)
89+
-> (Number -> a)
90+
-> (String -> a)
91+
-> (Array JSON -> a)
92+
-> (Object -> a)
93+
-> JSON
94+
-> a
95+
case_ a b c d e f json = runFn7 _case a b c d e f json
96+
97+
fail :: forall a b. a -> Maybe b
98+
fail _ = Nothing
99+
100+
-- | Converts a `JSON` value to `Null` if the `JSON` is `null`.
101+
toNull :: JSON -> Maybe Unit
102+
toNull json = runFn7 _case Just fail fail fail fail fail json
103+
104+
-- | Converts a `JSON` value to `Boolean` if the `JSON` is a boolean.
105+
toBoolean :: JSON -> Maybe Boolean
106+
toBoolean json = runFn7 _case fail Just fail fail fail fail json
107+
108+
-- | Converts a `JSON` value to `Number` if the `JSON` is a number.
109+
toNumber :: JSON -> Maybe Number
110+
toNumber json = runFn7 _case fail fail Just fail fail fail json
111+
112+
-- | Converts a `JSON` value to `String` if the `JSON` is a string.
113+
toString :: JSON -> Maybe String
114+
toString json = runFn7 _case fail fail fail Just fail fail json
115+
116+
-- | Converts a `JSON` value to `Array JSON` if the `JSON` is an array.
117+
toArray :: JSON -> Maybe (Array JSON)
118+
toArray json = runFn7 _case fail fail fail fail Just fail json
119+
120+
-- | Converts a `JSON` value to `Object` if the `JSON` is an object.
121+
toObject :: JSON -> Maybe Object
122+
toObject json = runFn7 _case fail fail fail fail fail Just json
123+
124+
-- | Prints a JSON value as a compact (single line) string.
125+
foreign import print :: JSON -> String
126+
127+
-- | Prints a JSON value as a "pretty" string,
128+
foreign import printIndented :: JSON -> String

src/Json/Gen.purs

Lines changed: 37 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
module Json.Gen where
1+
module JSON.Gen where
22

33
import Prelude
44

@@ -9,35 +9,50 @@ import Control.Monad.Rec.Class (class MonadRec)
99
import Data.NonEmpty ((:|))
1010
import Data.String.Gen (genUnicodeString)
1111
import Data.Tuple (Tuple(..))
12-
import Json as J
13-
import Json.Object as Object
12+
import JSON as J
13+
import JSON.Object as Object
1414

15-
-- | A generator for `Json` values. Especially useful for writing property-based tests.
16-
genJson :: forall m. MonadGen m => MonadRec m => Lazy (m J.Json) => m J.Json
17-
genJson = Gen.resize (min 5) $ Gen.sized genJson'
15+
-- | A generator for random `JSON` values of any variety.
16+
genJSON :: forall m. MonadGen m => MonadRec m => Lazy (m J.JSON) => m J.JSON
17+
genJSON = Gen.resize (min 5) $ Gen.sized genJSON'
1818
where
19-
genJson' :: Int -> m J.Json
20-
genJson' size
19+
genJSON' :: Int -> m J.JSON
20+
genJSON' size
2121
| size > 1 = Gen.resize (_ - 1) (Gen.choose genArray genObject)
2222
| otherwise = genLeaf
2323

24-
genArray :: m J.Json
25-
genArray = J.fromArray <$> Gen.unfoldable (defer \_ -> genJson)
24+
-- | A generator for JSON arrays containing items based on the passed generator.
25+
genArrayOf :: forall m. MonadGen m => MonadRec m => m J.JSON -> m J.JSON
26+
genArrayOf inner = J.fromArray <$> Gen.unfoldable inner
2627

27-
genObject :: m J.Json
28-
genObject = J.fromObject <<< Object.fromFoldable <$> genObjectEntries
28+
-- | A generator for JSON arrays containing random items.
29+
genArray :: forall m. MonadGen m => MonadRec m => Lazy (m J.JSON) => m J.JSON
30+
genArray = genArrayOf (defer \_ -> genJSON)
2931

30-
genObjectEntries :: m (Array (Tuple String J.Json))
31-
genObjectEntries = Gen.unfoldable (Tuple <$> genUnicodeString <*> (defer \_ -> genJson))
32+
-- | A generator for JSON objects containing entries based on the passed generator.
33+
genObjectOf :: forall m. MonadGen m => MonadRec m => m (Tuple String J.JSON) -> m J.JSON
34+
genObjectOf inner = J.fromObject <<< Object.fromEntries <$> (Gen.unfoldable inner)
3235

33-
genLeaf :: m J.Json
34-
genLeaf = Gen.oneOf $ pure J.null :| [ genBoolean, genNumber, genString ]
36+
-- | A generator for JSON objects containing random entries.
37+
genObject :: forall m. MonadGen m => MonadRec m => Lazy (m J.JSON) => m J.JSON
38+
genObject = genObjectOf (Tuple <$> genUnicodeString <*> defer \_ -> genJSON)
3539

36-
genBoolean :: m J.Json
37-
genBoolean = J.fromBoolean <$> Gen.chooseBool
40+
-- | A generator for JSON leaf (null, boolean, number, string) values.
41+
genLeaf :: forall m. MonadGen m => MonadRec m => m J.JSON
42+
genLeaf = Gen.oneOf $ pure J.null :| [ genBoolean, genNumber, genString ]
3843

39-
genNumber :: m J.Json
40-
genNumber = J.fromNumber <$> Gen.chooseFloat (-1000000.0) 1000000.0
44+
-- | A generator for JSON booleans.
45+
genBoolean :: forall m. MonadGen m => m J.JSON
46+
genBoolean = J.fromBoolean <$> Gen.chooseBool
4147

42-
genString :: m J.Json
43-
genString = J.fromString <$> genUnicodeString
48+
-- | A generator for JSON numbers.
49+
genNumber :: forall m. MonadGen m => m J.JSON
50+
genNumber = J.fromNumber <$> Gen.chooseFloat (-1000000.0) 1000000.0
51+
52+
-- | A generator for JSON integers.
53+
genInt :: forall m. MonadGen m => m J.JSON
54+
genInt = J.fromInt <$> Gen.chooseInt (-1000000) 1000000
55+
56+
-- | A generator for JSON strings.
57+
genString :: forall m. MonadGen m => MonadRec m => m J.JSON
58+
genString = J.fromString <$> genUnicodeString

src/Json/Internal.js

Lines changed: 16 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
const toString = Object.prototype.toString;
22
const hasOwnProperty = Object.prototype.hasOwnProperty;
3-
const coerce = (x) => x;
43

54
export const _parse = (left, right, s) => {
65
try {
@@ -11,9 +10,7 @@ export const _parse = (left, right, s) => {
1110
}
1211
};
1312

14-
export const print = (j) => JSON.stringify(j);
15-
16-
export const printIndented = (j) => JSON.stringify(j, null, 2);
13+
export const _fromNumberWithDefault = (fallback, n) => isNaN(n) || !isFinite(n) ? fallback : n;
1714

1815
export const _case = (isNull, isBool, isNum, isStr, isArr, isObj, j) => {
1916
if (j == null) return isNull(null);
@@ -25,19 +22,23 @@ export const _case = (isNull, isBool, isNum, isStr, isArr, isObj, j) => {
2522
return isObj(j);
2623
};
2724

28-
export const _null = null;
29-
30-
export const fromBoolean = coerce;
31-
32-
export const fromNumberWithDefault = (fallback) => (n) => isNaN(n) || !isFinite(n) ? fallback : n;
33-
34-
export const fromInt = coerce;
35-
36-
export const fromString = coerce;
25+
export const _fromEntries = (fst, snd, entries) => {
26+
const result = {};
27+
for (var i = 0; i < entries.length; i++) {
28+
result[fst(entries[i])] = snd(entries[i]);
29+
}
30+
return result;
31+
};
3732

38-
export const fromArray = coerce;
33+
export const _insert = (k, v, obj) =>
34+
Object.assign({ [k]: v }, obj);
3935

40-
export const fromObject = coerce;
36+
export const _delete = (k, obj) => {
37+
if (!Object.hasOwn(obj, k)) return obj;
38+
const result = Object.assign({}, obj);
39+
delete result[k];
40+
return result;
41+
};
4142

4243
export const _entries = (tuple, obj) =>
4344
Object.entries(obj).map(([k, v]) => tuple(k)(v));

0 commit comments

Comments
 (0)