├── .gitignore ├── LICENSE ├── README.md ├── elm.json ├── src ├── Elm │ └── Kernel │ │ └── Json.js └── Json │ ├── Decode.elm │ └── Encode.elm └── tests └── Json.elm /.gitignore: -------------------------------------------------------------------------------- 1 | elm-stuff -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2014-present Evan Czaplicki 2 | 3 | Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 4 | 5 | 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 6 | 7 | 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 8 | 9 | 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. 10 | 11 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 12 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # JSON in Elm 2 | 3 | This package helps you convert between Elm values and JSON values. 4 | 5 | This package is usually used alongside [`elm/http`](http://package.elm-lang.org/packages/elm/http/latest) to talk to servers or [ports](https://guide.elm-lang.org/interop/ports.html) to talk to JavaScript. 6 | 7 | 8 | ## Example 9 | 10 | Have you seen this [causes of death](https://en.wikipedia.org/wiki/List_of_causes_of_death_by_rate) table? Did you know that in 2002, war accounted for 0.3% of global deaths whereas road traffic accidents accounted for 2.09% and diarrhea accounted for 3.15%? 11 | 12 | The table is interesting, but say we want to visualize this data in a nicer way. We will need some way to get the cause-of-death data from our server, so we create encoders and decoders: 13 | 14 | ```elm 15 | module Cause exposing (Cause, encode, decoder) 16 | 17 | import Json.Decode as D 18 | import Json.Encode as E 19 | 20 | 21 | -- CAUSE OF DEATH 22 | 23 | type alias Cause = 24 | { name : String 25 | , percent : Float 26 | , per100k : Float 27 | } 28 | 29 | 30 | -- ENCODE 31 | 32 | encode : Cause -> E.Value 33 | encode cause = 34 | E.object 35 | [ ("name", E.string cause.name) 36 | , ("percent", E.float cause.percent) 37 | , ("per100k", E.float cause.per100k) 38 | ] 39 | 40 | 41 | -- DECODER 42 | 43 | decoder : D.Decoder Cause 44 | decoder = 45 | D.map3 Cause 46 | (D.field "name" D.string) 47 | (D.field "percent" D.float) 48 | (D.field "per100k" D.float) 49 | ``` 50 | 51 | Now in some other code we can use `Cause.encode` and `Cause.decoder` as building blocks. So if we want to decode a list of causes, saying `Decode.list Cause.decoder` will handle it! 52 | 53 | Point is, the goal should be: 54 | 55 | 1. Make small JSON decoders and encoders. 56 | 2. Snap together these building blocks as needed. 57 | 58 | So say you decide to make the `name` field more precise. Instead of a `String`, you want to use codes from the [International Classification of Diseases](http://www.who.int/classifications/icd/en/) recommended by the World Health Organization. These [codes](http://apps.who.int/classifications/icd10/browse/2016/en) are used in a lot of mortality data sets. So it may make sense to make a separate `IcdCode` module with its own `IcdCode.encode` and `IcdCode.decoder` that ensure you are working with valid codes. From there, you can use them as building blocks in the `Cause` module! 59 | 60 | 61 | ## Future Plans 62 | 63 | It is easy to get focused on how to optimize the use of JSON, but I think this is missing the bigger picture. Instead, I would like to head towards [this vision](https://gist.github.com/evancz/1c5f2cf34939336ecb79b97bb89d9da6) of data interchange. 64 | -------------------------------------------------------------------------------- /elm.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "package", 3 | "name": "elm/json", 4 | "summary": "Encode and decode JSON values", 5 | "license": "BSD-3-Clause", 6 | "version": "1.1.3", 7 | "exposed-modules": [ 8 | "Json.Decode", 9 | "Json.Encode" 10 | ], 11 | "elm-version": "0.19.0 <= v < 0.20.0", 12 | "dependencies": { 13 | "elm/core": "1.0.0 <= v < 2.0.0" 14 | }, 15 | "test-dependencies": {} 16 | } -------------------------------------------------------------------------------- /src/Elm/Kernel/Json.js: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | import Array exposing (initialize) 4 | import Elm.Kernel.List exposing (Cons, Nil, fromArray) 5 | import Elm.Kernel.Utils exposing (Tuple2) 6 | import Json.Decode as Json exposing (Field, Index, OneOf, Failure, errorToString) 7 | import List exposing (reverse) 8 | import Maybe exposing (Just, Nothing) 9 | import Result exposing (Ok, Err, isOk) 10 | 11 | */ 12 | 13 | 14 | /**__DEBUG/ 15 | function _Json_errorToString(error) 16 | { 17 | return __Json_errorToString(error); 18 | } 19 | //*/ 20 | 21 | 22 | // CORE DECODERS 23 | 24 | function _Json_succeed(msg) 25 | { 26 | return { 27 | $: __1_SUCCEED, 28 | __msg: msg 29 | }; 30 | } 31 | 32 | function _Json_fail(msg) 33 | { 34 | return { 35 | $: __1_FAIL, 36 | __msg: msg 37 | }; 38 | } 39 | 40 | function _Json_decodePrim(decoder) 41 | { 42 | return { $: __1_PRIM, __decoder: decoder }; 43 | } 44 | 45 | var _Json_decodeInt = _Json_decodePrim(function(value) { 46 | return (typeof value !== 'number') 47 | ? _Json_expecting('an INT', value) 48 | : 49 | (-2147483647 < value && value < 2147483647 && (value | 0) === value) 50 | ? __Result_Ok(value) 51 | : 52 | (isFinite(value) && !(value % 1)) 53 | ? __Result_Ok(value) 54 | : _Json_expecting('an INT', value); 55 | }); 56 | 57 | var _Json_decodeBool = _Json_decodePrim(function(value) { 58 | return (typeof value === 'boolean') 59 | ? __Result_Ok(value) 60 | : _Json_expecting('a BOOL', value); 61 | }); 62 | 63 | var _Json_decodeFloat = _Json_decodePrim(function(value) { 64 | return (typeof value === 'number') 65 | ? __Result_Ok(value) 66 | : _Json_expecting('a FLOAT', value); 67 | }); 68 | 69 | var _Json_decodeValue = _Json_decodePrim(function(value) { 70 | return __Result_Ok(_Json_wrap(value)); 71 | }); 72 | 73 | var _Json_decodeString = _Json_decodePrim(function(value) { 74 | return (typeof value === 'string') 75 | ? __Result_Ok(value) 76 | : (value instanceof String) 77 | ? __Result_Ok(value + '') 78 | : _Json_expecting('a STRING', value); 79 | }); 80 | 81 | function _Json_decodeList(decoder) { return { $: __1_LIST, __decoder: decoder }; } 82 | function _Json_decodeArray(decoder) { return { $: __1_ARRAY, __decoder: decoder }; } 83 | 84 | function _Json_decodeNull(value) { return { $: __1_NULL, __value: value }; } 85 | 86 | var _Json_decodeField = F2(function(field, decoder) 87 | { 88 | return { 89 | $: __1_FIELD, 90 | __field: field, 91 | __decoder: decoder 92 | }; 93 | }); 94 | 95 | var _Json_decodeIndex = F2(function(index, decoder) 96 | { 97 | return { 98 | $: __1_INDEX, 99 | __index: index, 100 | __decoder: decoder 101 | }; 102 | }); 103 | 104 | function _Json_decodeKeyValuePairs(decoder) 105 | { 106 | return { 107 | $: __1_KEY_VALUE, 108 | __decoder: decoder 109 | }; 110 | } 111 | 112 | function _Json_mapMany(f, decoders) 113 | { 114 | return { 115 | $: __1_MAP, 116 | __func: f, 117 | __decoders: decoders 118 | }; 119 | } 120 | 121 | var _Json_andThen = F2(function(callback, decoder) 122 | { 123 | return { 124 | $: __1_AND_THEN, 125 | __decoder: decoder, 126 | __callback: callback 127 | }; 128 | }); 129 | 130 | function _Json_oneOf(decoders) 131 | { 132 | return { 133 | $: __1_ONE_OF, 134 | __decoders: decoders 135 | }; 136 | } 137 | 138 | 139 | // DECODING OBJECTS 140 | 141 | var _Json_map1 = F2(function(f, d1) 142 | { 143 | return _Json_mapMany(f, [d1]); 144 | }); 145 | 146 | var _Json_map2 = F3(function(f, d1, d2) 147 | { 148 | return _Json_mapMany(f, [d1, d2]); 149 | }); 150 | 151 | var _Json_map3 = F4(function(f, d1, d2, d3) 152 | { 153 | return _Json_mapMany(f, [d1, d2, d3]); 154 | }); 155 | 156 | var _Json_map4 = F5(function(f, d1, d2, d3, d4) 157 | { 158 | return _Json_mapMany(f, [d1, d2, d3, d4]); 159 | }); 160 | 161 | var _Json_map5 = F6(function(f, d1, d2, d3, d4, d5) 162 | { 163 | return _Json_mapMany(f, [d1, d2, d3, d4, d5]); 164 | }); 165 | 166 | var _Json_map6 = F7(function(f, d1, d2, d3, d4, d5, d6) 167 | { 168 | return _Json_mapMany(f, [d1, d2, d3, d4, d5, d6]); 169 | }); 170 | 171 | var _Json_map7 = F8(function(f, d1, d2, d3, d4, d5, d6, d7) 172 | { 173 | return _Json_mapMany(f, [d1, d2, d3, d4, d5, d6, d7]); 174 | }); 175 | 176 | var _Json_map8 = F9(function(f, d1, d2, d3, d4, d5, d6, d7, d8) 177 | { 178 | return _Json_mapMany(f, [d1, d2, d3, d4, d5, d6, d7, d8]); 179 | }); 180 | 181 | 182 | // DECODE 183 | 184 | var _Json_runOnString = F2(function(decoder, string) 185 | { 186 | try 187 | { 188 | var value = JSON.parse(string); 189 | return _Json_runHelp(decoder, value); 190 | } 191 | catch (e) 192 | { 193 | return __Result_Err(A2(__Json_Failure, 'This is not valid JSON! ' + e.message, _Json_wrap(string))); 194 | } 195 | }); 196 | 197 | var _Json_run = F2(function(decoder, value) 198 | { 199 | return _Json_runHelp(decoder, _Json_unwrap(value)); 200 | }); 201 | 202 | function _Json_runHelp(decoder, value) 203 | { 204 | switch (decoder.$) 205 | { 206 | case __1_PRIM: 207 | return decoder.__decoder(value); 208 | 209 | case __1_NULL: 210 | return (value === null) 211 | ? __Result_Ok(decoder.__value) 212 | : _Json_expecting('null', value); 213 | 214 | case __1_LIST: 215 | if (!_Json_isArray(value)) 216 | { 217 | return _Json_expecting('a LIST', value); 218 | } 219 | return _Json_runArrayDecoder(decoder.__decoder, value, __List_fromArray); 220 | 221 | case __1_ARRAY: 222 | if (!_Json_isArray(value)) 223 | { 224 | return _Json_expecting('an ARRAY', value); 225 | } 226 | return _Json_runArrayDecoder(decoder.__decoder, value, _Json_toElmArray); 227 | 228 | case __1_FIELD: 229 | var field = decoder.__field; 230 | if (typeof value !== 'object' || value === null || !(field in value)) 231 | { 232 | return _Json_expecting('an OBJECT with a field named `' + field + '`', value); 233 | } 234 | var result = _Json_runHelp(decoder.__decoder, value[field]); 235 | return (__Result_isOk(result)) ? result : __Result_Err(A2(__Json_Field, field, result.a)); 236 | 237 | case __1_INDEX: 238 | var index = decoder.__index; 239 | if (!_Json_isArray(value)) 240 | { 241 | return _Json_expecting('an ARRAY', value); 242 | } 243 | if (index >= value.length) 244 | { 245 | return _Json_expecting('a LONGER array. Need index ' + index + ' but only see ' + value.length + ' entries', value); 246 | } 247 | var result = _Json_runHelp(decoder.__decoder, value[index]); 248 | return (__Result_isOk(result)) ? result : __Result_Err(A2(__Json_Index, index, result.a)); 249 | 250 | case __1_KEY_VALUE: 251 | if (typeof value !== 'object' || value === null || _Json_isArray(value)) 252 | { 253 | return _Json_expecting('an OBJECT', value); 254 | } 255 | 256 | var keyValuePairs = __List_Nil; 257 | // TODO test perf of Object.keys and switch when support is good enough 258 | for (var key in value) 259 | { 260 | if (value.hasOwnProperty(key)) 261 | { 262 | var result = _Json_runHelp(decoder.__decoder, value[key]); 263 | if (!__Result_isOk(result)) 264 | { 265 | return __Result_Err(A2(__Json_Field, key, result.a)); 266 | } 267 | keyValuePairs = __List_Cons(__Utils_Tuple2(key, result.a), keyValuePairs); 268 | } 269 | } 270 | return __Result_Ok(__List_reverse(keyValuePairs)); 271 | 272 | case __1_MAP: 273 | var answer = decoder.__func; 274 | var decoders = decoder.__decoders; 275 | for (var i = 0; i < decoders.length; i++) 276 | { 277 | var result = _Json_runHelp(decoders[i], value); 278 | if (!__Result_isOk(result)) 279 | { 280 | return result; 281 | } 282 | answer = answer(result.a); 283 | } 284 | return __Result_Ok(answer); 285 | 286 | case __1_AND_THEN: 287 | var result = _Json_runHelp(decoder.__decoder, value); 288 | return (!__Result_isOk(result)) 289 | ? result 290 | : _Json_runHelp(decoder.__callback(result.a), value); 291 | 292 | case __1_ONE_OF: 293 | var errors = __List_Nil; 294 | for (var temp = decoder.__decoders; temp.b; temp = temp.b) // WHILE_CONS 295 | { 296 | var result = _Json_runHelp(temp.a, value); 297 | if (__Result_isOk(result)) 298 | { 299 | return result; 300 | } 301 | errors = __List_Cons(result.a, errors); 302 | } 303 | return __Result_Err(__Json_OneOf(__List_reverse(errors))); 304 | 305 | case __1_FAIL: 306 | return __Result_Err(A2(__Json_Failure, decoder.__msg, _Json_wrap(value))); 307 | 308 | case __1_SUCCEED: 309 | return __Result_Ok(decoder.__msg); 310 | } 311 | } 312 | 313 | function _Json_runArrayDecoder(decoder, value, toElmValue) 314 | { 315 | var len = value.length; 316 | var array = new Array(len); 317 | for (var i = 0; i < len; i++) 318 | { 319 | var result = _Json_runHelp(decoder, value[i]); 320 | if (!__Result_isOk(result)) 321 | { 322 | return __Result_Err(A2(__Json_Index, i, result.a)); 323 | } 324 | array[i] = result.a; 325 | } 326 | return __Result_Ok(toElmValue(array)); 327 | } 328 | 329 | function _Json_isArray(value) 330 | { 331 | return Array.isArray(value) || (typeof FileList !== 'undefined' && value instanceof FileList); 332 | } 333 | 334 | function _Json_toElmArray(array) 335 | { 336 | return A2(__Array_initialize, array.length, function(i) { return array[i]; }); 337 | } 338 | 339 | function _Json_expecting(type, value) 340 | { 341 | return __Result_Err(A2(__Json_Failure, 'Expecting ' + type, _Json_wrap(value))); 342 | } 343 | 344 | 345 | // EQUALITY 346 | 347 | function _Json_equality(x, y) 348 | { 349 | if (x === y) 350 | { 351 | return true; 352 | } 353 | 354 | if (x.$ !== y.$) 355 | { 356 | return false; 357 | } 358 | 359 | switch (x.$) 360 | { 361 | case __1_SUCCEED: 362 | case __1_FAIL: 363 | return x.__msg === y.__msg; 364 | 365 | case __1_PRIM: 366 | return x.__decoder === y.__decoder; 367 | 368 | case __1_NULL: 369 | return x.__value === y.__value; 370 | 371 | case __1_LIST: 372 | case __1_ARRAY: 373 | case __1_KEY_VALUE: 374 | return _Json_equality(x.__decoder, y.__decoder); 375 | 376 | case __1_FIELD: 377 | return x.__field === y.__field && _Json_equality(x.__decoder, y.__decoder); 378 | 379 | case __1_INDEX: 380 | return x.__index === y.__index && _Json_equality(x.__decoder, y.__decoder); 381 | 382 | case __1_MAP: 383 | return x.__func === y.__func && _Json_listEquality(x.__decoders, y.__decoders); 384 | 385 | case __1_AND_THEN: 386 | return x.__callback === y.__callback && _Json_equality(x.__decoder, y.__decoder); 387 | 388 | case __1_ONE_OF: 389 | return _Json_listEquality(x.__decoders, y.__decoders); 390 | } 391 | } 392 | 393 | function _Json_listEquality(aDecoders, bDecoders) 394 | { 395 | var len = aDecoders.length; 396 | if (len !== bDecoders.length) 397 | { 398 | return false; 399 | } 400 | for (var i = 0; i < len; i++) 401 | { 402 | if (!_Json_equality(aDecoders[i], bDecoders[i])) 403 | { 404 | return false; 405 | } 406 | } 407 | return true; 408 | } 409 | 410 | 411 | // ENCODE 412 | 413 | var _Json_encode = F2(function(indentLevel, value) 414 | { 415 | return JSON.stringify(_Json_unwrap(value), null, indentLevel) + ''; 416 | }); 417 | 418 | function _Json_wrap__DEBUG(value) { return { $: __0_JSON, a: value }; } 419 | function _Json_unwrap__DEBUG(value) { return value.a; } 420 | 421 | function _Json_wrap__PROD(value) { return value; } 422 | function _Json_unwrap__PROD(value) { return value; } 423 | 424 | function _Json_emptyArray() { return []; } 425 | function _Json_emptyObject() { return {}; } 426 | 427 | var _Json_addField = F3(function(key, value, object) 428 | { 429 | object[key] = _Json_unwrap(value); 430 | return object; 431 | }); 432 | 433 | function _Json_addEntry(func) 434 | { 435 | return F2(function(entry, array) 436 | { 437 | array.push(_Json_unwrap(func(entry))); 438 | return array; 439 | }); 440 | } 441 | 442 | var _Json_encodeNull = _Json_wrap(null); 443 | -------------------------------------------------------------------------------- /src/Json/Decode.elm: -------------------------------------------------------------------------------- 1 | module Json.Decode exposing 2 | ( Decoder, string, bool, int, float 3 | , nullable, list, array, dict, keyValuePairs, oneOrMore 4 | , field, at, index 5 | , maybe, oneOf 6 | , decodeString, decodeValue, Value, Error(..), errorToString 7 | , map, map2, map3, map4, map5, map6, map7, map8 8 | , lazy, value, null, succeed, fail, andThen 9 | ) 10 | 11 | {-| Turn JSON values into Elm values. Definitely check out this [intro to 12 | JSON decoders][guide] to get a feel for how this library works! 13 | 14 | [guide]: https://guide.elm-lang.org/effects/json.html 15 | 16 | # Primitives 17 | @docs Decoder, string, bool, int, float 18 | 19 | # Data Structures 20 | @docs nullable, list, array, dict, keyValuePairs, oneOrMore 21 | 22 | # Object Primitives 23 | @docs field, at, index 24 | 25 | # Inconsistent Structure 26 | @docs maybe, oneOf 27 | 28 | # Run Decoders 29 | @docs decodeString, decodeValue, Value, Error, errorToString 30 | 31 | # Mapping 32 | 33 | **Note:** If you run out of map functions, take a look at [elm-json-decode-pipeline][pipe] 34 | which makes it easier to handle large objects, but produces lower quality type 35 | errors. 36 | 37 | [pipe]: /packages/NoRedInk/elm-json-decode-pipeline/latest 38 | 39 | @docs map, map2, map3, map4, map5, map6, map7, map8 40 | 41 | # Fancy Decoding 42 | @docs lazy, value, null, succeed, fail, andThen 43 | -} 44 | 45 | 46 | import Array exposing (Array) 47 | import Dict exposing (Dict) 48 | import Json.Encode 49 | import Elm.Kernel.Json 50 | 51 | 52 | 53 | -- PRIMITIVES 54 | 55 | 56 | {-| A value that knows how to decode JSON values. 57 | 58 | There is a whole section in `guide.elm-lang.org` about decoders, so [check it 59 | out](https://guide.elm-lang.org/interop/json.html) for a more comprehensive 60 | introduction! 61 | -} 62 | type Decoder a = Decoder 63 | 64 | 65 | {-| Decode a JSON string into an Elm `String`. 66 | 67 | decodeString string "true" == Err ... 68 | decodeString string "42" == Err ... 69 | decodeString string "3.14" == Err ... 70 | decodeString string "\"hello\"" == Ok "hello" 71 | decodeString string "{ \"hello\": 42 }" == Err ... 72 | -} 73 | string : Decoder String 74 | string = 75 | Elm.Kernel.Json.decodeString 76 | 77 | 78 | {-| Decode a JSON boolean into an Elm `Bool`. 79 | 80 | decodeString bool "true" == Ok True 81 | decodeString bool "42" == Err ... 82 | decodeString bool "3.14" == Err ... 83 | decodeString bool "\"hello\"" == Err ... 84 | decodeString bool "{ \"hello\": 42 }" == Err ... 85 | -} 86 | bool : Decoder Bool 87 | bool = 88 | Elm.Kernel.Json.decodeBool 89 | 90 | 91 | {-| Decode a JSON number into an Elm `Int`. 92 | 93 | decodeString int "true" == Err ... 94 | decodeString int "42" == Ok 42 95 | decodeString int "3.14" == Err ... 96 | decodeString int "\"hello\"" == Err ... 97 | decodeString int "{ \"hello\": 42 }" == Err ... 98 | -} 99 | int : Decoder Int 100 | int = 101 | Elm.Kernel.Json.decodeInt 102 | 103 | 104 | {-| Decode a JSON number into an Elm `Float`. 105 | 106 | decodeString float "true" == Err .. 107 | decodeString float "42" == Ok 42 108 | decodeString float "3.14" == Ok 3.14 109 | decodeString float "\"hello\"" == Err ... 110 | decodeString float "{ \"hello\": 42 }" == Err ... 111 | -} 112 | float : Decoder Float 113 | float = 114 | Elm.Kernel.Json.decodeFloat 115 | 116 | 117 | 118 | -- DATA STRUCTURES 119 | 120 | 121 | {-| Decode a nullable JSON value into an Elm value. 122 | 123 | decodeString (nullable int) "13" == Ok (Just 13) 124 | decodeString (nullable int) "42" == Ok (Just 42) 125 | decodeString (nullable int) "null" == Ok Nothing 126 | decodeString (nullable int) "true" == Err .. 127 | -} 128 | nullable : Decoder a -> Decoder (Maybe a) 129 | nullable decoder = 130 | oneOf 131 | [ null Nothing 132 | , map Just decoder 133 | ] 134 | 135 | 136 | {-| Decode a JSON array into an Elm `List`. 137 | 138 | decodeString (list int) "[1,2,3]" == Ok [1,2,3] 139 | decodeString (list bool) "[true,false]" == Ok [True,False] 140 | -} 141 | list : Decoder a -> Decoder (List a) 142 | list = 143 | Elm.Kernel.Json.decodeList 144 | 145 | 146 | {-| Decode a JSON array into an Elm `Array`. 147 | 148 | decodeString (array int) "[1,2,3]" == Ok (Array.fromList [1,2,3]) 149 | decodeString (array bool) "[true,false]" == Ok (Array.fromList [True,False]) 150 | -} 151 | array : Decoder a -> Decoder (Array a) 152 | array = 153 | Elm.Kernel.Json.decodeArray 154 | 155 | 156 | {-| Decode a JSON object into an Elm `Dict`. 157 | 158 | decodeString (dict int) "{ \"alice\": 42, \"bob\": 99 }" 159 | == Ok (Dict.fromList [("alice", 42), ("bob", 99)]) 160 | 161 | If you need the keys (like `"alice"` and `"bob"`) available in the `Dict` 162 | values as well, I recommend using a (private) intermediate data structure like 163 | `Info` in this example: 164 | 165 | module User exposing (User, decoder) 166 | 167 | import Dict 168 | import Json.Decode exposing (..) 169 | 170 | type alias User = 171 | { name : String 172 | , height : Float 173 | , age : Int 174 | } 175 | 176 | decoder : Decoder (Dict.Dict String User) 177 | decoder = 178 | map (Dict.map infoToUser) (dict infoDecoder) 179 | 180 | type alias Info = 181 | { height : Float 182 | , age : Int 183 | } 184 | 185 | infoDecoder : Decoder Info 186 | infoDecoder = 187 | map2 Info 188 | (field "height" float) 189 | (field "age" int) 190 | 191 | infoToUser : String -> Info -> User 192 | infoToUser name { height, age } = 193 | User name height age 194 | 195 | So now JSON like `{ "alice": { height: 1.6, age: 33 }}` are turned into 196 | dictionary values like `Dict.singleton "alice" (User "alice" 1.6 33)` if 197 | you need that. 198 | -} 199 | dict : Decoder a -> Decoder (Dict String a) 200 | dict decoder = 201 | map Dict.fromList (keyValuePairs decoder) 202 | 203 | 204 | {-| Decode a JSON object into an Elm `List` of pairs. 205 | 206 | decodeString (keyValuePairs int) "{ \"alice\": 42, \"bob\": 99 }" 207 | == Ok [("alice", 42), ("bob", 99)] 208 | -} 209 | keyValuePairs : Decoder a -> Decoder (List (String, a)) 210 | keyValuePairs = 211 | Elm.Kernel.Json.decodeKeyValuePairs 212 | 213 | 214 | {-| Decode a JSON array that has one or more elements. This comes up if you 215 | want to enable drag-and-drop of files into your application. You would pair 216 | this function with [`elm/file`]() to write a `dropDecoder` like this: 217 | 218 | import File exposing (File) 219 | import Json.Decoder as D 220 | 221 | type Msg 222 | = GotFiles File (List Files) 223 | 224 | inputDecoder : D.Decoder Msg 225 | inputDecoder = 226 | D.at ["dataTransfer","files"] (D.oneOrMore GotFiles File.decoder) 227 | 228 | This captures the fact that you can never drag-and-drop zero files. 229 | -} 230 | oneOrMore : (a -> List a -> value) -> Decoder a -> Decoder value 231 | oneOrMore toValue decoder = 232 | list decoder 233 | |> andThen (oneOrMoreHelp toValue) 234 | 235 | 236 | oneOrMoreHelp : (a -> List a -> value) -> List a -> Decoder value 237 | oneOrMoreHelp toValue xs = 238 | case xs of 239 | [] -> 240 | fail "a ARRAY with at least ONE element" 241 | 242 | y :: ys -> 243 | succeed (toValue y ys) 244 | 245 | 246 | 247 | -- OBJECT PRIMITIVES 248 | 249 | 250 | {-| Decode a JSON object, requiring a particular field. 251 | 252 | decodeString (field "x" int) "{ \"x\": 3 }" == Ok 3 253 | decodeString (field "x" int) "{ \"x\": 3, \"y\": 4 }" == Ok 3 254 | decodeString (field "x" int) "{ \"x\": true }" == Err ... 255 | decodeString (field "x" int) "{ \"y\": 4 }" == Err ... 256 | 257 | decodeString (field "name" string) "{ \"name\": \"tom\" }" == Ok "tom" 258 | 259 | The object *can* have other fields. Lots of them! The only thing this decoder 260 | cares about is if `x` is present and that the value there is an `Int`. 261 | 262 | Check out [`map2`](#map2) to see how to decode multiple fields! 263 | -} 264 | field : String -> Decoder a -> Decoder a 265 | field = 266 | Elm.Kernel.Json.decodeField 267 | 268 | 269 | {-| Decode a nested JSON object, requiring certain fields. 270 | 271 | json = """{ "person": { "name": "tom", "age": 42 } }""" 272 | 273 | decodeString (at ["person", "name"] string) json == Ok "tom" 274 | decodeString (at ["person", "age" ] int ) json == Ok 42 275 | 276 | This is really just a shorthand for saying things like: 277 | 278 | field "person" (field "name" string) == at ["person","name"] string 279 | -} 280 | at : List String -> Decoder a -> Decoder a 281 | at fields decoder = 282 | List.foldr field decoder fields 283 | 284 | 285 | {-| Decode a JSON array, requiring a particular index. 286 | 287 | json = """[ "alice", "bob", "chuck" ]""" 288 | 289 | decodeString (index 0 string) json == Ok "alice" 290 | decodeString (index 1 string) json == Ok "bob" 291 | decodeString (index 2 string) json == Ok "chuck" 292 | decodeString (index 3 string) json == Err ... 293 | -} 294 | index : Int -> Decoder a -> Decoder a 295 | index = 296 | Elm.Kernel.Json.decodeIndex 297 | 298 | 299 | 300 | -- WEIRD STRUCTURE 301 | 302 | 303 | {-| Helpful for dealing with optional fields. Here are a few slightly different 304 | examples: 305 | 306 | json = """{ "name": "tom", "age": 42 }""" 307 | 308 | decodeString (maybe (field "age" int )) json == Ok (Just 42) 309 | decodeString (maybe (field "name" int )) json == Ok Nothing 310 | decodeString (maybe (field "height" float)) json == Ok Nothing 311 | 312 | decodeString (field "age" (maybe int )) json == Ok (Just 42) 313 | decodeString (field "name" (maybe int )) json == Ok Nothing 314 | decodeString (field "height" (maybe float)) json == Err ... 315 | 316 | Notice the last example! It is saying we *must* have a field named `height` and 317 | the content *may* be a float. There is no `height` field, so the decoder fails. 318 | 319 | Point is, `maybe` will make exactly what it contains conditional. For optional 320 | fields, this means you probably want it *outside* a use of `field` or `at`. 321 | -} 322 | maybe : Decoder a -> Decoder (Maybe a) 323 | maybe decoder = 324 | oneOf 325 | [ map Just decoder 326 | , succeed Nothing 327 | ] 328 | 329 | 330 | {-| Try a bunch of different decoders. This can be useful if the JSON may come 331 | in a couple different formats. For example, say you want to read an array of 332 | numbers, but some of them are `null`. 333 | 334 | import String 335 | 336 | badInt : Decoder Int 337 | badInt = 338 | oneOf [ int, null 0 ] 339 | 340 | -- decodeString (list badInt) "[1,2,null,4]" == Ok [1,2,0,4] 341 | 342 | Why would someone generate JSON like this? Questions like this are not good 343 | for your health. The point is that you can use `oneOf` to handle situations 344 | like this! 345 | 346 | You could also use `oneOf` to help version your data. Try the latest format, 347 | then a few older ones that you still support. You could use `andThen` to be 348 | even more particular if you wanted. 349 | -} 350 | oneOf : List (Decoder a) -> Decoder a 351 | oneOf = 352 | Elm.Kernel.Json.oneOf 353 | 354 | 355 | 356 | -- MAPPING 357 | 358 | 359 | {-| Transform a decoder. Maybe you just want to know the length of a string: 360 | 361 | import String 362 | 363 | stringLength : Decoder Int 364 | stringLength = 365 | map String.length string 366 | 367 | It is often helpful to use `map` with `oneOf`, like when defining `nullable`: 368 | 369 | nullable : Decoder a -> Decoder (Maybe a) 370 | nullable decoder = 371 | oneOf 372 | [ null Nothing 373 | , map Just decoder 374 | ] 375 | -} 376 | map : (a -> value) -> Decoder a -> Decoder value 377 | map = 378 | Elm.Kernel.Json.map1 379 | 380 | 381 | {-| Try two decoders and then combine the result. We can use this to decode 382 | objects with many fields: 383 | 384 | type alias Point = { x : Float, y : Float } 385 | 386 | point : Decoder Point 387 | point = 388 | map2 Point 389 | (field "x" float) 390 | (field "y" float) 391 | 392 | -- decodeString point """{ "x": 3, "y": 4 }""" == Ok { x = 3, y = 4 } 393 | 394 | It tries each individual decoder and puts the result together with the `Point` 395 | constructor. 396 | -} 397 | map2 : (a -> b -> value) -> Decoder a -> Decoder b -> Decoder value 398 | map2 = 399 | Elm.Kernel.Json.map2 400 | 401 | 402 | {-| Try three decoders and then combine the result. We can use this to decode 403 | objects with many fields: 404 | 405 | type alias Person = { name : String, age : Int, height : Float } 406 | 407 | person : Decoder Person 408 | person = 409 | map3 Person 410 | (at ["name"] string) 411 | (at ["info","age"] int) 412 | (at ["info","height"] float) 413 | 414 | -- json = """{ "name": "tom", "info": { "age": 42, "height": 1.8 } }""" 415 | -- decodeString person json == Ok { name = "tom", age = 42, height = 1.8 } 416 | 417 | Like `map2` it tries each decoder in order and then give the results to the 418 | `Person` constructor. That can be any function though! 419 | -} 420 | map3 : (a -> b -> c -> value) -> Decoder a -> Decoder b -> Decoder c -> Decoder value 421 | map3 = 422 | Elm.Kernel.Json.map3 423 | 424 | 425 | {-|-} 426 | map4 : (a -> b -> c -> d -> value) -> Decoder a -> Decoder b -> Decoder c -> Decoder d -> Decoder value 427 | map4 = 428 | Elm.Kernel.Json.map4 429 | 430 | 431 | {-|-} 432 | map5 : (a -> b -> c -> d -> e -> value) -> Decoder a -> Decoder b -> Decoder c -> Decoder d -> Decoder e -> Decoder value 433 | map5 = 434 | Elm.Kernel.Json.map5 435 | 436 | 437 | {-|-} 438 | map6 : (a -> b -> c -> d -> e -> f -> value) -> Decoder a -> Decoder b -> Decoder c -> Decoder d -> Decoder e -> Decoder f -> Decoder value 439 | map6 = 440 | Elm.Kernel.Json.map6 441 | 442 | 443 | {-|-} 444 | map7 : (a -> b -> c -> d -> e -> f -> g -> value) -> Decoder a -> Decoder b -> Decoder c -> Decoder d -> Decoder e -> Decoder f -> Decoder g -> Decoder value 445 | map7 = 446 | Elm.Kernel.Json.map7 447 | 448 | 449 | {-|-} 450 | map8 : (a -> b -> c -> d -> e -> f -> g -> h -> value) -> Decoder a -> Decoder b -> Decoder c -> Decoder d -> Decoder e -> Decoder f -> Decoder g -> Decoder h -> Decoder value 451 | map8 = 452 | Elm.Kernel.Json.map8 453 | 454 | 455 | 456 | -- RUN DECODERS 457 | 458 | 459 | {-| Parse the given string into a JSON value and then run the `Decoder` on it. 460 | This will fail if the string is not well-formed JSON or if the `Decoder` 461 | fails for some reason. 462 | 463 | decodeString int "4" == Ok 4 464 | decodeString int "1 + 2" == Err ... 465 | -} 466 | decodeString : Decoder a -> String -> Result Error a 467 | decodeString = 468 | Elm.Kernel.Json.runOnString 469 | 470 | 471 | {-| Run a `Decoder` on some JSON `Value`. You can send these JSON values 472 | through ports, so that is probably the main time you would use this function. 473 | -} 474 | decodeValue : Decoder a -> Value -> Result Error a 475 | decodeValue = 476 | Elm.Kernel.Json.run 477 | 478 | 479 | {-| Represents a JavaScript value. 480 | -} 481 | type alias Value = Json.Encode.Value 482 | 483 | 484 | {-| A structured error describing exactly how the decoder failed. You can use 485 | this to create more elaborate visualizations of a decoder problem. For example, 486 | you could show the entire JSON object and show the part causing the failure in 487 | red. 488 | -} 489 | type Error 490 | = Field String Error 491 | | Index Int Error 492 | | OneOf (List Error) 493 | | Failure String Value 494 | 495 | 496 | {-| Convert a decoding error into a `String` that is nice for debugging. 497 | 498 | It produces multiple lines of output, so you may want to peek at it with 499 | something like this: 500 | 501 | import Html 502 | import Json.Decode as Decode 503 | 504 | errorToHtml : Decode.Error -> Html.Html msg 505 | errorToHtml error = 506 | Html.pre [] [ Html.text (Decode.errorToString error) ] 507 | 508 | **Note:** It would be cool to do nicer coloring and fancier HTML, but I wanted 509 | to avoid having an `elm/html` dependency for now. It is totally possible to 510 | crawl the `Error` structure and create this separately though! 511 | -} 512 | errorToString : Error -> String 513 | errorToString error = 514 | errorToStringHelp error [] 515 | 516 | 517 | errorToStringHelp : Error -> List String -> String 518 | errorToStringHelp error context = 519 | case error of 520 | Field f err -> 521 | let 522 | isSimple = 523 | case String.uncons f of 524 | Nothing -> 525 | False 526 | 527 | Just (char, rest) -> 528 | Char.isAlpha char && String.all Char.isAlphaNum rest 529 | 530 | fieldName = 531 | if isSimple then "." ++ f else "['" ++ f ++ "']" 532 | in 533 | errorToStringHelp err (fieldName :: context) 534 | 535 | Index i err -> 536 | let 537 | indexName = 538 | "[" ++ String.fromInt i ++ "]" 539 | in 540 | errorToStringHelp err (indexName :: context) 541 | 542 | OneOf errors -> 543 | case errors of 544 | [] -> 545 | "Ran into a Json.Decode.oneOf with no possibilities" ++ 546 | case context of 547 | [] -> 548 | "!" 549 | _ -> 550 | " at json" ++ String.join "" (List.reverse context) 551 | 552 | [err] -> 553 | errorToStringHelp err context 554 | 555 | _ -> 556 | let 557 | starter = 558 | case context of 559 | [] -> 560 | "Json.Decode.oneOf" 561 | _ -> 562 | "The Json.Decode.oneOf at json" ++ String.join "" (List.reverse context) 563 | 564 | introduction = 565 | starter ++ " failed in the following " ++ String.fromInt (List.length errors) ++ " ways:" 566 | in 567 | String.join "\n\n" (introduction :: List.indexedMap errorOneOf errors) 568 | 569 | Failure msg json -> 570 | let 571 | introduction = 572 | case context of 573 | [] -> 574 | "Problem with the given value:\n\n" 575 | _ -> 576 | "Problem with the value at json" ++ String.join "" (List.reverse context) ++ ":\n\n " 577 | in 578 | introduction ++ indent (Json.Encode.encode 4 json) ++ "\n\n" ++ msg 579 | 580 | 581 | errorOneOf : Int -> Error -> String 582 | errorOneOf i error = 583 | "\n\n(" ++ String.fromInt (i + 1) ++ ") " ++ indent (errorToString error) 584 | 585 | 586 | indent : String -> String 587 | indent str = 588 | String.join "\n " (String.split "\n" str) 589 | 590 | 591 | 592 | -- FANCY PRIMITIVES 593 | 594 | 595 | {-| Ignore the JSON and produce a certain Elm value. 596 | 597 | decodeString (succeed 42) "true" == Ok 42 598 | decodeString (succeed 42) "[1,2,3]" == Ok 42 599 | decodeString (succeed 42) "hello" == Err ... -- this is not a valid JSON string 600 | 601 | This is handy when used with `oneOf` or `andThen`. 602 | -} 603 | succeed : a -> Decoder a 604 | succeed = 605 | Elm.Kernel.Json.succeed 606 | 607 | 608 | {-| Ignore the JSON and make the decoder fail. This is handy when used with 609 | `oneOf` or `andThen` where you want to give a custom error message in some 610 | case. 611 | 612 | See the [`andThen`](#andThen) docs for an example. 613 | -} 614 | fail : String -> Decoder a 615 | fail = 616 | Elm.Kernel.Json.fail 617 | 618 | 619 | {-| Create decoders that depend on previous results. If you are creating 620 | versioned data, you might do something like this: 621 | 622 | info : Decoder Info 623 | info = 624 | field "version" int 625 | |> andThen infoHelp 626 | 627 | infoHelp : Int -> Decoder Info 628 | infoHelp version = 629 | case version of 630 | 4 -> 631 | infoDecoder4 632 | 633 | 3 -> 634 | infoDecoder3 635 | 636 | _ -> 637 | fail <| 638 | "Trying to decode info, but version " 639 | ++ toString version ++ " is not supported." 640 | 641 | -- infoDecoder4 : Decoder Info 642 | -- infoDecoder3 : Decoder Info 643 | -} 644 | andThen : (a -> Decoder b) -> Decoder a -> Decoder b 645 | andThen = 646 | Elm.Kernel.Json.andThen 647 | 648 | 649 | {-| Sometimes you have JSON with recursive structure, like nested comments. 650 | You can use `lazy` to make sure your decoder unrolls lazily. 651 | 652 | type alias Comment = 653 | { message : String 654 | , responses : Responses 655 | } 656 | 657 | type Responses = Responses (List Comment) 658 | 659 | comment : Decoder Comment 660 | comment = 661 | map2 Comment 662 | (field "message" string) 663 | (field "responses" (map Responses (list (lazy (\_ -> comment))))) 664 | 665 | If we had said `list comment` instead, we would start expanding the value 666 | infinitely. What is a `comment`? It is a decoder for objects where the 667 | `responses` field contains comments. What is a `comment` though? Etc. 668 | 669 | By using `list (lazy (\_ -> comment))` we make sure the decoder only expands 670 | to be as deep as the JSON we are given. You can read more about recursive data 671 | structures [here][]. 672 | 673 | [here]: https://github.com/elm/compiler/blob/master/hints/recursive-alias.md 674 | -} 675 | lazy : (() -> Decoder a) -> Decoder a 676 | lazy thunk = 677 | andThen thunk (succeed ()) 678 | 679 | 680 | {-| Do not do anything with a JSON value, just bring it into Elm as a `Value`. 681 | This can be useful if you have particularly complex data that you would like to 682 | deal with later. Or if you are going to send it out a port and do not care 683 | about its structure. 684 | -} 685 | value : Decoder Value 686 | value = 687 | Elm.Kernel.Json.decodeValue 688 | 689 | 690 | {-| Decode a `null` value into some Elm value. 691 | 692 | decodeString (null False) "null" == Ok False 693 | decodeString (null 42) "null" == Ok 42 694 | decodeString (null 42) "42" == Err .. 695 | decodeString (null 42) "false" == Err .. 696 | 697 | So if you ever see a `null`, this will return whatever value you specified. 698 | -} 699 | null : a -> Decoder a 700 | null = 701 | Elm.Kernel.Json.decodeNull 702 | -------------------------------------------------------------------------------- /src/Json/Encode.elm: -------------------------------------------------------------------------------- 1 | module Json.Encode exposing 2 | ( Value 3 | , encode 4 | , string, int, float, bool, null 5 | , list, array, set 6 | , object, dict 7 | ) 8 | 9 | {-| Library for turning Elm values into Json values. 10 | 11 | # Encoding 12 | @docs encode, Value 13 | 14 | # Primitives 15 | @docs string, int, float, bool, null 16 | 17 | # Arrays 18 | @docs list, array, set 19 | 20 | # Objects 21 | @docs object, dict 22 | -} 23 | 24 | 25 | import Array exposing (Array) 26 | import Dict exposing (Dict) 27 | import Set exposing (Set) 28 | import Elm.Kernel.Json 29 | 30 | 31 | 32 | -- ENCODE 33 | 34 | 35 | {-| Represents a JavaScript value. 36 | -} 37 | type Value = Value 38 | 39 | 40 | {-| Convert a `Value` into a prettified string. The first argument specifies 41 | the amount of indentation in the resulting string. 42 | 43 | import Json.Encode as Encode 44 | 45 | tom : Encode.Value 46 | tom = 47 | Encode.object 48 | [ ( "name", Encode.string "Tom" ) 49 | , ( "age", Encode.int 42 ) 50 | ] 51 | 52 | compact = Encode.encode 0 tom 53 | -- {"name":"Tom","age":42} 54 | 55 | readable = Encode.encode 4 tom 56 | -- { 57 | -- "name": "Tom", 58 | -- "age": 42 59 | -- } 60 | -} 61 | encode : Int -> Value -> String 62 | encode = 63 | Elm.Kernel.Json.encode 64 | 65 | 66 | 67 | -- PRIMITIVES 68 | 69 | 70 | {-| Turn a `String` into a JSON string. 71 | 72 | import Json.Encode exposing (encode, string) 73 | 74 | -- encode 0 (string "") == "\"\"" 75 | -- encode 0 (string "abc") == "\"abc\"" 76 | -- encode 0 (string "hello") == "\"hello\"" 77 | -} 78 | string : String -> Value 79 | string = 80 | Elm.Kernel.Json.wrap 81 | 82 | 83 | {-| Turn an `Int` into a JSON number. 84 | 85 | import Json.Encode exposing (encode, int) 86 | 87 | -- encode 0 (int 42) == "42" 88 | -- encode 0 (int -7) == "-7" 89 | -- encode 0 (int 0) == "0" 90 | -} 91 | int : Int -> Value 92 | int = 93 | Elm.Kernel.Json.wrap 94 | 95 | 96 | {-| Turn a `Float` into a JSON number. 97 | 98 | import Json.Encode exposing (encode, float) 99 | 100 | -- encode 0 (float 3.14) == "3.14" 101 | -- encode 0 (float 1.618) == "1.618" 102 | -- encode 0 (float -42) == "-42" 103 | -- encode 0 (float NaN) == "null" 104 | -- encode 0 (float Infinity) == "null" 105 | 106 | **Note:** Floating point numbers are defined in the [IEEE 754 standard][ieee] 107 | which is hardcoded into almost all CPUs. This standard allows `Infinity` and 108 | `NaN`. [The JSON spec][json] does not include these values, so we encode them 109 | both as `null`. 110 | 111 | [ieee]: https://en.wikipedia.org/wiki/IEEE_754 112 | [json]: https://www.json.org/ 113 | -} 114 | float : Float -> Value 115 | float = 116 | Elm.Kernel.Json.wrap 117 | 118 | 119 | {-| Turn a `Bool` into a JSON boolean. 120 | 121 | import Json.Encode exposing (encode, bool) 122 | 123 | -- encode 0 (bool True) == "true" 124 | -- encode 0 (bool False) == "false" 125 | -} 126 | bool : Bool -> Value 127 | bool = 128 | Elm.Kernel.Json.wrap 129 | 130 | 131 | 132 | -- NULLS 133 | 134 | 135 | {-| Create a JSON `null` value. 136 | 137 | import Json.Encode exposing (encode, null) 138 | 139 | -- encode 0 null == "null" 140 | -} 141 | null : Value 142 | null = 143 | Elm.Kernel.Json.encodeNull 144 | 145 | 146 | 147 | -- ARRAYS 148 | 149 | 150 | {-| Turn a `List` into a JSON array. 151 | 152 | import Json.Encode as Encode exposing (bool, encode, int, list, string) 153 | 154 | -- encode 0 (list int [1,3,4]) == "[1,3,4]" 155 | -- encode 0 (list bool [True,False]) == "[true,false]" 156 | -- encode 0 (list string ["a","b"]) == """["a","b"]""" 157 | 158 | -} 159 | list : (a -> Value) -> List a -> Value 160 | list func entries = 161 | Elm.Kernel.Json.wrap 162 | (List.foldl (Elm.Kernel.Json.addEntry func) (Elm.Kernel.Json.emptyArray ()) entries) 163 | 164 | 165 | {-| Turn an `Array` into a JSON array. 166 | -} 167 | array : (a -> Value) -> Array a -> Value 168 | array func entries = 169 | Elm.Kernel.Json.wrap 170 | (Array.foldl (Elm.Kernel.Json.addEntry func) (Elm.Kernel.Json.emptyArray ()) entries) 171 | 172 | 173 | {-| Turn an `Set` into a JSON array. 174 | -} 175 | set : (a -> Value) -> Set a -> Value 176 | set func entries = 177 | Elm.Kernel.Json.wrap 178 | (Set.foldl (Elm.Kernel.Json.addEntry func) (Elm.Kernel.Json.emptyArray ()) entries) 179 | 180 | 181 | 182 | -- OBJECTS 183 | 184 | 185 | {-| Create a JSON object. 186 | 187 | import Json.Encode as Encode 188 | 189 | tom : Encode.Value 190 | tom = 191 | Encode.object 192 | [ ( "name", Encode.string "Tom" ) 193 | , ( "age", Encode.int 42 ) 194 | ] 195 | 196 | -- Encode.encode 0 tom == """{"name":"Tom","age":42}""" 197 | -} 198 | object : List (String, Value) -> Value 199 | object pairs = 200 | Elm.Kernel.Json.wrap ( 201 | List.foldl 202 | (\(k,v) obj -> Elm.Kernel.Json.addField k v obj) 203 | (Elm.Kernel.Json.emptyObject ()) 204 | pairs 205 | ) 206 | 207 | 208 | {-| Turn a `Dict` into a JSON object. 209 | 210 | import Dict exposing (Dict) 211 | import Json.Encode as Encode 212 | 213 | people : Dict String Int 214 | people = 215 | Dict.fromList [ ("Tom",42), ("Sue", 38) ] 216 | 217 | -- Encode.encode 0 (Encode.dict identity Encode.int people) 218 | -- == """{"Tom":42,"Sue":38}""" 219 | -} 220 | dict : (k -> String) -> (v -> Value) -> Dict k v -> Value 221 | dict toKey toValue dictionary = 222 | Elm.Kernel.Json.wrap ( 223 | Dict.foldl 224 | (\key value obj -> Elm.Kernel.Json.addField (toKey key) (toValue value) obj) 225 | (Elm.Kernel.Json.emptyObject ()) 226 | dictionary 227 | ) 228 | -------------------------------------------------------------------------------- /tests/Json.elm: -------------------------------------------------------------------------------- 1 | module Test.Json exposing (tests) 2 | 3 | import Basics exposing (..) 4 | import Result exposing (..) 5 | import Json.Decode as Json 6 | import String 7 | import Test exposing (..) 8 | import Expect 9 | 10 | 11 | tests : Test 12 | tests = 13 | describe "Json decode" 14 | [ intTests 15 | , customTests 16 | ] 17 | 18 | 19 | intTests : Test 20 | intTests = 21 | let 22 | testInt val str = 23 | case Json.decodeString Json.int str of 24 | Ok _ -> 25 | Expect.equal val True 26 | 27 | Err _ -> 28 | Expect.equal val False 29 | in 30 | describe "Json decode int" 31 | [ test "whole int" <| \() -> testInt True "4" 32 | , test "-whole int" <| \() -> testInt True "-4" 33 | , test "whole float" <| \() -> testInt True "4.0" 34 | , test "-whole float" <| \() -> testInt True "-4.0" 35 | , test "large int" <| \() -> testInt True "1801439850948" 36 | , test "-large int" <| \() -> testInt True "-1801439850948" 37 | , test "float" <| \() -> testInt False "4.2" 38 | , test "-float" <| \() -> testInt False "-4.2" 39 | , test "Infinity" <| \() -> testInt False "Infinity" 40 | , test "-Infinity" <| \() -> testInt False "-Infinity" 41 | , test "NaN" <| \() -> testInt False "NaN" 42 | , test "-NaN" <| \() -> testInt False "-NaN" 43 | , test "true" <| \() -> testInt False "true" 44 | , test "false" <| \() -> testInt False "false" 45 | , test "string" <| \() -> testInt False "\"string\"" 46 | , test "object" <| \() -> testInt False "{}" 47 | , test "null" <| \() -> testInt False "null" 48 | , test "undefined" <| \() -> testInt False "undefined" 49 | , test "Decoder expects object finds array, was crashing runtime." <| 50 | \() -> 51 | Expect.equal 52 | (Err "Expecting an object but instead got: []") 53 | (Json.decodeString (Json.dict Json.float) "[]") 54 | ] 55 | 56 | 57 | customTests : Test 58 | customTests = 59 | let 60 | jsonString = 61 | """{ "foo": "bar" }""" 62 | 63 | customErrorMessage = 64 | "I want to see this message!" 65 | 66 | myDecoder = 67 | Json.field "foo" Json.string |> Json.andThen (\_ -> Json.fail customErrorMessage) 68 | 69 | assertion = 70 | case Json.decodeString myDecoder jsonString of 71 | Ok _ -> 72 | Expect.fail "expected `customDecoder` to produce a value of type Err, but got Ok" 73 | 74 | Err message -> 75 | if String.contains customErrorMessage message then 76 | Expect.pass 77 | else 78 | Expect.fail <| 79 | "expected `customDecoder` to preserve user's error message '" 80 | ++ customErrorMessage 81 | ++ "', but instead got: " 82 | ++ message 83 | in 84 | test "customDecoder preserves user error messages" <| \() -> assertion 85 | --------------------------------------------------------------------------------