Repository: plaid/sanctuary (js)

Issue:
Even safer parseJson
> `parseJson(Array, '{}') -> Nothing()`

I'm very fond of the `get` and `gets` style of type validation, because it forces you to do it. It would've already saved me on multiple occasions if `parseJson` (or any `x -> Any` function for that matter) had this built-in, because after doing a "safe" JSON parse, it doesn't always occur to me the return value may still be of an unwanted type.

An alternative is to extract this behaviour out to its own function, which is used solely for type validation:

```js
validate :: TypeRep -> a -> Maybe a

const parseJsonArray = compose(chain(validate(Array)), parseJson);
```

I think this second solution is more elegant in terms of modularity but it lacks the safety that comes from forcing you to do the type validation.

Side note: `TypeRep` [may be changed to a predicate](https://github.com/plaid/sanctuary/issues/149), but the idea remains the same.

Pull request description:
Change parseJson to perform type validation
Adds a second argument to the `parseJson` function: A TypeRep which the result from parsing the JSON is validated against. This closes #150.

Patch:
diff --git a/index.js b/index.js
index d9ba959..fb8ac0a 100644
--- a/index.js
+++ b/index.js
@@ -2760,24 +2760,28 @@
         )(s);
       });
 
-  //# parseJson :: String -> Maybe Any
+  //# parseJson :: TypeRep a -> String -> Maybe a
   //.
-  //. Takes a string which may or may not be valid JSON, and returns Just
-  //. the result of applying `JSON.parse` to the string if valid; Nothing
-  //. otherwise.
+  //. Takes a [type representative](#type-representatives) and a string which
+  //. may or may not be valid JSON, and returns Just the result of applying
+  //. `JSON.parse` to the string *if* the result is of the specified type
+  //. (according to [`is`](#is)); Nothing otherwise.
   //.
   //. ```javascript
-  //. > S.parseJson('["foo","bar","baz"]')
+  //. > S.parseJson(Array, '["foo","bar","baz"]')
   //. Just(['foo', 'bar', 'baz'])
   //.
-  //. > S.parseJson('[')
+  //. > S.parseJson(Array, '[')
+  //. Nothing()
+  //.
+  //. > S.parseJson(Object, '["foo","bar","baz"]')
   //. Nothing()
   //. ```
   S.parseJson =
   def('parseJson',
       {},
-      [$.String, $Maybe($.Any)],
-      encase(JSON.parse));
+      [TypeRep, $.String, $Maybe(a)],
+      function(type, s) { return filter(is(type), encase(JSON.parse, s)); });
 
   //. ### RegExp
 


Test patch:
diff --git a/test/index.js b/test/index.js
index 60f58bc..a65c7b7 100644
--- a/test/index.js
+++ b/test/index.js
@@ -5125,19 +5125,31 @@ describe('parse', function() {
 
   describe('parseJson', function() {
 
-    it('is a unary function', function() {
+    it('is a binary function', function() {
       eq(typeof S.parseJson, 'function');
-      eq(S.parseJson.length, 1);
+      eq(S.parseJson.length, 2);
     });
 
     it('type checks its arguments', function() {
-      throws(function() { S.parseJson([1, 2, 3]); },
+      throws(function() { S.parseJson('String'); },
              errorEq(TypeError,
                      'Invalid value\n' +
                      '\n' +
-                     'parseJson :: String -> Maybe Any\n' +
-                     '             ^^^^^^\n' +
-                     '               1\n' +
+                     'parseJson :: TypeRep -> String -> Maybe a\n' +
+                     '             ^^^^^^^\n' +
+                     '                1\n' +
+                     '\n' +
+                     '1)  "String" :: String\n' +
+                     '\n' +
+                     'The value at position 1 is not a member of ‘TypeRep’.\n'));
+
+      throws(function() { S.parseJson(Array, [1, 2, 3]); },
+             errorEq(TypeError,
+                     'Invalid value\n' +
+                     '\n' +
+                     'parseJson :: TypeRep -> String -> Maybe a\n' +
+                     '                        ^^^^^^\n' +
+                     '                          1\n' +
                      '\n' +
                      '1)  [1, 2, 3] :: Array Number, Array FiniteNumber, Array NonZeroFiniteNumber, Array Integer, Array ValidNumber\n' +
                      '\n' +
@@ -5145,11 +5157,15 @@ describe('parse', function() {
     });
 
     it('returns a Just when applied to a valid JSON string', function() {
-      eq(S.parseJson('["foo","bar"]'), S.Just(['foo', 'bar']));
+      eq(S.parseJson(Array, '["foo","bar"]'), S.Just(['foo', 'bar']));
     });
 
     it('returns a Nothing when applied to an invalid JSON string', function() {
-      eq(S.parseJson('[Invalid JSON]'), S.Nothing());
+      eq(S.parseJson(Object, '[Invalid JSON]'), S.Nothing());
+    });
+
+    it('returns a Nothing when the parsed result is not a member of the given type', function() {
+      eq(S.parseJson(Array, '{"foo":"bar"}'), S.Nothing());
     });
 
   });