Skip to content

Commit cb40860

Browse files
committed
Add user-defined numeral parsing
1 parent 68da81d commit cb40860

File tree

8 files changed

+587
-178
lines changed

8 files changed

+587
-178
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,10 @@ Breaking changes:
88

99
New features:
1010

11+
* New module `Data.Argonaut.Custom` allows for user-defined handling of JSON
12+
numerals. This enables avoiding IEEE 754, which does not have infinite
13+
precision.
14+
1115
Bugfixes:
1216

1317
Other improvements:

src/Data/Argonaut/Core.js

Lines changed: 0 additions & 93 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,3 @@
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-
131
export function stringify(j) {
142
return JSON.stringify(j);
153
}
@@ -19,84 +7,3 @@ export function stringifyWithIndent(i) {
197
return JSON.stringify(j, null, i);
208
};
219
}
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-
}

src/Data/Argonaut/Core.purs

Lines changed: 42 additions & 81 deletions
Original file line numberDiff line numberDiff line change
@@ -42,32 +42,14 @@ module Data.Argonaut.Core
4242

4343
import Prelude
4444

45-
import Data.Function.Uncurried (Fn5, runFn5, Fn7, runFn7)
46-
import Data.Maybe (Maybe(..))
45+
import Data.Maybe (Maybe)
4746
import Foreign.Object (Object)
48-
import Foreign.Object as Obj
47+
import Data.Argonaut.Custom as Custom
4948

5049
-- | The type of JSON data. The underlying representation is the same as what
5150
-- | would be returned from JavaScript's `JSON.parse` function; that is,
5251
-- | 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
52+
type Json = Custom.Json Number
7153

7254
-- | Case analysis for `Json` values. See the README for more information.
7355
caseJson
@@ -80,161 +62,154 @@ caseJson
8062
-> (Object Json -> a)
8163
-> Json
8264
-> a
83-
caseJson a b c d e f json = runFn7 _caseJson a b c d e f json
65+
caseJson = Custom.caseJson
8466

8567
-- | A simpler version of `caseJson` which accepts a callback for when the
8668
-- | `Json` argument was null, and a default value for all other cases.
8769
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
70+
caseJsonNull = Custom.caseJsonNull
8971

9072
-- | A simpler version of `caseJson` which accepts a callback for when the
9173
-- | `Json` argument was a `Boolean`, and a default value for all other cases.
9274
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
75+
caseJsonBoolean = Custom.caseJsonBoolean
9476

9577
-- | A simpler version of `caseJson` which accepts a callback for when the
9678
-- | `Json` argument was a `Number`, and a default value for all other cases.
9779
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
80+
caseJsonNumber = Custom.caseJsonNumber
9981

10082
-- | A simpler version of `caseJson` which accepts a callback for when the
10183
-- | `Json` argument was a `String`, and a default value for all other cases.
10284
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
85+
caseJsonString = Custom.caseJsonString
10486

10587
-- | A simpler version of `caseJson` which accepts a callback for when the
10688
-- | `Json` argument was a `Array Json`, and a default value for all other cases.
10789
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
90+
caseJsonArray = Custom.caseJsonArray
10991

11092
-- | A simpler version of `caseJson` which accepts a callback for when the
11193
-- | `Json` argument was an `Object`, and a default value for all other cases.
11294
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
95+
caseJsonObject = Custom.caseJsonObject
11796

11897
-- Tests
11998

120-
isJsonType :: forall a. (Boolean -> (a -> Boolean) -> Json -> Boolean) -> Json -> Boolean
121-
isJsonType = verbJsonType false (const true)
122-
12399
-- | Check if the provided `Json` is the `null` value
124100
isNull :: Json -> Boolean
125-
isNull = isJsonType caseJsonNull
101+
isNull = Custom.isNull
126102

127103
-- | Check if the provided `Json` is a `Boolean`
128104
isBoolean :: Json -> Boolean
129-
isBoolean = isJsonType caseJsonBoolean
105+
isBoolean = Custom.isBoolean
130106

131107
-- | Check if the provided `Json` is a `Number`
132108
isNumber :: Json -> Boolean
133-
isNumber = isJsonType caseJsonNumber
109+
isNumber = Custom.isNumber
134110

135111
-- | Check if the provided `Json` is a `String`
136112
isString :: Json -> Boolean
137-
isString = isJsonType caseJsonString
113+
isString = Custom.isString
138114

139115
-- | Check if the provided `Json` is an `Array`
140116
isArray :: Json -> Boolean
141-
isArray = isJsonType caseJsonArray
117+
isArray = Custom.isArray
142118

143119
-- | Check if the provided `Json` is an `Object`
144120
isObject :: Json -> Boolean
145-
isObject = isJsonType caseJsonObject
121+
isObject = Custom.isObject
146122

147123
-- Decoding
148124

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-
156125
-- | Convert `Json` to the `Unit` value if the `Json` is the null value
157126
toNull :: Json -> Maybe Unit
158-
toNull = toJsonType caseJsonNull
127+
toNull = Custom.toNull
159128

160129
-- | Convert `Json` to a `Boolean` value, if the `Json` is a boolean.
161130
toBoolean :: Json -> Maybe Boolean
162-
toBoolean = toJsonType caseJsonBoolean
131+
toBoolean = Custom.toBoolean
163132

164133
-- | Convert `Json` to a `Number` value, if the `Json` is a number.
165134
toNumber :: Json -> Maybe Number
166-
toNumber = toJsonType caseJsonNumber
135+
toNumber = Custom.toNumber
167136

168137
-- | Convert `Json` to a `String` value, if the `Json` is a string. To write a
169138
-- | `Json` value to a JSON string, see `stringify`.
170139
toString :: Json -> Maybe String
171-
toString = toJsonType caseJsonString
140+
toString = Custom.toString
172141

173142
-- | Convert `Json` to an `Array` of `Json` values, if the `Json` is an array.
174143
toArray :: Json -> Maybe (Array Json)
175-
toArray = toJsonType caseJsonArray
144+
toArray = Custom.toArray
176145

177146
-- | Convert `Json` to an `Object` of `Json` values, if the `Json` is an object.
178147
toObject :: Json -> Maybe (Object Json)
179-
toObject = toJsonType caseJsonObject
148+
toObject = Custom.toObject
180149

181150
-- Encoding
182151

183152
-- | Construct `Json` from a `Boolean` value
184-
foreign import fromBoolean :: Boolean -> Json
153+
fromBoolean :: Boolean -> Json
154+
fromBoolean = Custom.fromBoolean
185155

186156
-- | Construct `Json` from a `Number` value
187-
foreign import fromNumber :: Number -> Json
157+
fromNumber :: Number -> Json
158+
fromNumber = Custom.fromNumber
188159

189160
-- | Construct the `Json` representation of a `String` value.
190161
-- | Note that this function only produces `Json` containing a single piece of `String`
191162
-- | data (similar to `fromBoolean`, `fromNumber`, etc.).
192163
-- | This function does NOT convert the `String` encoding of a JSON value to `Json` - For that
193164
-- | purpose, you'll need to use `jsonParser`.
194-
foreign import fromString :: String -> Json
165+
fromString :: String -> Json
166+
fromString = Custom.fromString
195167

196168
-- | Construct `Json` from an array of `Json` values
197-
foreign import fromArray :: Array Json -> Json
169+
fromArray :: Array Json -> Json
170+
fromArray = Custom.fromArray
198171

199172
-- | Construct `Json` from an object with `Json` values
200-
foreign import fromObject :: Object Json -> Json
173+
fromObject :: Object Json -> Json
174+
fromObject = Custom.fromObject
201175

202176
-- Defaults
203177

204178
-- | The JSON null value represented as `Json`
205-
foreign import jsonNull :: Json
179+
jsonNull :: Json
180+
jsonNull = Custom.jsonNull
206181

207182
-- | The true boolean value represented as `Json`
208183
jsonTrue :: Json
209-
jsonTrue = fromBoolean true
184+
jsonTrue = Custom.jsonTrue
210185

211186
-- | The false boolean value represented as `Json`
212187
jsonFalse :: Json
213-
jsonFalse = fromBoolean false
188+
jsonFalse = Custom.jsonFalse
214189

215190
-- | The number zero represented as `Json`
216191
jsonZero :: Json
217-
jsonZero = fromNumber 0.0
192+
jsonZero = Custom.fromNumber 0.0
218193

219194
-- | An empty string represented as `Json`
220195
jsonEmptyString :: Json
221-
jsonEmptyString = fromString ""
196+
jsonEmptyString = Custom.jsonEmptyString
222197

223198
-- | An empty array represented as `Json`
224199
jsonEmptyArray :: Json
225-
jsonEmptyArray = fromArray []
200+
jsonEmptyArray = Custom.jsonEmptyArray
226201

227202
-- | An empty object represented as `Json`
228203
jsonEmptyObject :: Json
229-
jsonEmptyObject = fromObject Obj.empty
204+
jsonEmptyObject = Custom.jsonEmptyObject
230205

231206
-- | Constructs a `Json` array value containing only the provided value
232207
jsonSingletonArray :: Json -> Json
233-
jsonSingletonArray j = fromArray [ j ]
208+
jsonSingletonArray = Custom.jsonSingletonArray
234209

235210
-- | Constructs a `Json` object value containing only the provided key and value
236211
jsonSingletonObject :: String -> Json -> Json
237-
jsonSingletonObject key val = fromObject (Obj.singleton key val)
212+
jsonSingletonObject = Custom.jsonSingletonObject
238213

239214
-- | Converts a `Json` value to a JSON string. To retrieve a string from a `Json`
240215
-- | string value, see `fromString`.
@@ -244,17 +219,3 @@ foreign import stringify :: Json -> String
244219
-- | The first `Int` argument specifies the amount of white space characters to use as indentation.
245220
-- | 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.
246221
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

0 commit comments

Comments
 (0)