1# exjsx (v4.0.0) # 2 3[json][json] for [elixir][elixir] 4 5based on [jsx][jsx] 6 7testing provided by [travis-ci][travis] 8 9[![Build Status](https://secure.travis-ci.org/talentdeficit/exjsx.png)](http://travis-ci.org/talentdeficit/exjsx) 10 11exjsx is released under the terms of the [MIT][MIT] license 12 13copyright 2013, 2014, 2015, 2016, 2017 alisdair sullivan 14 15 16## index ## 17 18* [quickstart](#quickstart) 19 - [building and running tests](#build-the-library-and-run-tests) 20 - [decoding json](#convert-a-json-string-into-an-elixir-dict) 21 - [encoding json](#convert-an-elixir-dict-into-a-json-string) 22 - [checking validity](#check-if-a-binary-or-a-term-is-valid-json) 23 - [minify](#minify-some-json) 24 - [prettify](#prettify-some-json) 25* [description](#description) 26 - [json <-> elixir mapping summary](#json---elixir-mapping) 27 - [numbers](#numbers) 28 - [strings](#strings) 29 - [true, false and null/nil](#true-false-and-nullnil) 30 - [arrays](#arrays) 31 - [objects](#objects) 32* [fma](#frequently-made-accusations) 33 - [your lib sucks and encodes my structs wrong](#your-lib-sucks-and-encodes-my-structs-wrong) 34 - [you forgot to document incompletes](#you-forgot-to-document-incompletes) 35* [options](#options) 36 - [escaped forward slashes](#escaped_forward_slashes) 37 - [escaped strings](#escaped_strings) 38 - [uescape](#uescape) 39 - [unescaped jsonp](#unescaped_jsonp) 40 - [dirty strings](#dirty_strings) 41 - [strict](#strict) 42* [exports](#exports) 43 - [decode and decode!](#decodejson-opts) 44 - [encode and encode!](#encodeterm-opts) 45 - [format and format!](#formatjson-opts) 46 - [minify and minify!](#minifyjson) 47 - [prettify and prettify!](#prettifyjson) 48 - [is_json?](#is_jsonjson-opts) 49 - [is_term?](#is_termterm-opts) 50* [acknowledgements](#acknowledgements) 51 52 53## quickstart ## 54 55#### build the library and run tests #### 56 57```bash 58$ mix compile 59$ mix test 60``` 61 62#### convert a json string into an elixir term #### 63 64```iex 65iex> JSX.decode "{\"library\": \"jsx\", \"awesome\": true}" 66{:ok, %{"awesome" => true, "library" => "jsx"}} 67iex> JSX.decode "[\"a\",\"list\",\"of\",\"words\"]" 68{:ok, ["a", "list", "of", "words"]} 69``` 70 71#### convert an elixir term into a json string #### 72 73```iex 74iex> JSX.encode %{"library" => "jsx", "awesome" => true} 75{:ok, "{\"awesome\":true,\"library\":\"jsx\"}"} 76iex> JSX.encode [library: "jsx", awesome: true] 77{:ok, "{\"library\":\"jsx\",\"awesome\":true}"} 78iex> JSX.encode ["a","list","of","words"] 79{:ok, "[\"a\",\"list\",\"of\",\"words\"]"} 80``` 81 82#### check if a binary or a term is valid json #### 83 84```iex 85iex> JSX.is_json? "[\"this is json\"]" 86true 87iex> JSX.is_json? ["this is not"] 88false 89iex> JSX.is_term? ["this is a term"] 90true 91iex> JSX.is_term? self() 92false 93``` 94 95#### minify some json #### 96 97```iex 98iex> JSX.minify "{ 99...> \"a list\": [ 100...> 1, 101...> 2, 102...> 3 103...> ] 104...> }" 105{:ok,"{\"a list\":[1,2,3]}"} 106``` 107 108#### prettify some json #### 109 110```iex 111iex> JSX.prettify "{\"a list\":[1,2,3]}" 112{:ok, "{ 113 \"a list\": [ 114 1, 115 2, 116 3 117 ] 118}"} 119``` 120 121 122## description ## 123 124exjsx is an [elixir][elixir] application for consuming, producing and manipulating 125[json][json] 126 127json has a [spec][rfc4627] but common usage deviates in a number of cases. exjsx 128attempts to address common usage while following the spirit of the spec 129 130all json produced and consumed by exjsx should be `utf8` encoded text or a 131reasonable approximation thereof. ascii works too, but anything beyond that 132i'm not going to make any promises. **especially** not latin1 133 134 135#### json <-> elixir mapping #### 136 137**json** | **elixir** 138--------------------------------|-------------------------------- 139`number` | `Float` and `Integer` 140`string` | `String` 141`true` and `false` | `true` and `false` 142`null` | `nil` 143`array` | `List` and `Enumerable` 144`object` | `Map` 145 146#### numbers #### 147 148javascript and thus json represent all numeric values with floats. as 149this is woefully insufficient for many uses, **exjsx**, just like elixir, 150supports bigints. whenever possible, this library will interpret json 151numbers that look like integers as integers. other numbers will be converted 152to elixir's floating point type, which is nearly but not quite iee754. 153negative zero is not representable in elixir (zero is unsigned in elixir and 154`0` is equivalent to `-0`) and will be interpreted as regular zero. numbers 155not representable are beyond the concern of this implementation, and will 156result in parsing errors 157 158when converting from elixir to json, numbers are represented with their 159shortest representation that will round trip without loss of precision. this 160means that some floats may be superficially dissimilar (although 161functionally equivalent). for example, `1.0000000000000001` will be 162represented by `1.0` 163 164#### strings #### 165 166the json [spec][rfc4627] is frustratingly vague on the exact details of json 167strings. json must be unicode, but no encoding is specified. javascript 168explicitly allows strings containing codepoints explicitly disallowed by 169unicode. json allows implementations to set limits on the content of 170strings. other implementations attempt to resolve this in various ways. this 171implementation, in default operation, only accepts strings that meet the 172constraints set out in the json spec (strings are sequences of unicode 173codepoints deliminated by `"` (`u+0022`) that may not contain control codes 174unless properly escaped with `\` (`u+005c`)) and that are encoded in `utf8` 175 176the utf8 restriction means improperly paired surrogates are explicitly 177disallowed. `u+d800` to `u+dfff` are allowed, but only when they form valid 178surrogate pairs. surrogates encountered otherwise result in errors 179 180json string escapes of the form `\uXXXX` will be converted to their 181equivalent codepoints during parsing. this means control characters and 182other codepoints disallowed by the json spec may be encountered in resulting 183strings, but codepoints disallowed by the unicode spec will not be. in the 184interest of pragmatism there is an [option](#options) for looser parsing 185 186all elixir strings are represented by BitStrings. the encoder will check 187strings for conformance. noncharacters (like `u+ffff`) are allowed in elixir 188utf8 encoded binaries, but not in strings passed to the encoder (although, 189again, see [options](#options)) 190 191when encoding, atoms are first converted to BitStrings 192 193this implementation performs no normalization on strings beyond that 194detailed here. be careful when comparing strings as equivalent strings 195may have different `utf8` encodings 196 197#### true, false and null/nil #### 198 199the json primitives `true`, `false` and `null` are represented by the 200elixir atoms `true`, `false` and `nil` 201 202#### arrays #### 203 204json arrays are represented with elixir lists of json values as described 205in this section. elixir enumerables like `Stream`, `Range` and `MapSet` are 206serialized to json arrays 207 208#### objects #### 209 210json objects are represented by elixir maps. keys are atoms, bitstrings or integers 211and values are valid json values. structs, keylists and dicts are serialized to objects 212automagically but there is currently no way to perform the reverse. stay tuned tho 213 214 215## frequently made accusations ## 216 217#### your lib sucks and encodes my structs wrong #### 218 219so you have this struct: 220 221```elixir 222defmodule Character do 223 defstruct name: nil, rank: nil 224end 225``` 226 227```iex 228iex> JSX.encode %Character{name: "Walder Frey", rank: "Lord"} 229{:ok, "{\"name\":\"Walder Frey\",\"rank\":\"Lord\"}"} 230``` 231 232but you don't like that encoding. ok. do this: 233 234```elixir 235defimpl JSX.Encoder, for: Character do 236 def json(record) do 237 [:start_object, "name", record.rank <> " " <> record.name, :end_object] 238 end 239end 240``` 241 242```iex 243iex> JSX.encode Character.new(name: "Walder Frey", rank: "Lord") 244{:ok, "{\"name\":\"Lord Walder Frey\"}"} 245``` 246 247apart from the [jsx][jsx] internal format you can also generate you own json 248and pass it to the encoder with `[{:raw, "{\"name\": \"Lord Walder Frey\"}"}]` 249 250someone should write a macro that does this and make a pull request 251 252#### you forgot to document incompletes #### 253 254no i didn't. they are [jsx][jsx] only for now. stay tuned tho 255 256 257## options ## 258 259**exjsx** functions all take a common set of options. not all flags have meaning 260in all contexts, but they are always valid options. functions may have 261additional options beyond these. see 262[individual function documentation](#exports) for details 263 264#### `escaped_forward_slashes` #### 265 266json strings are escaped according to the json spec. this means forward 267slashes (solidus) are only escaped when this flag is present. otherwise they 268are left unescaped. you may want to use this if you are embedding json 269directly into a html or xml document 270 271#### `escaped_strings` #### 272 273by default both the encoder and decoder return strings as utf8 binaries 274appropriate for use in elixir. escape sequences that were present in decoded 275terms are converted into the appropriate codepoint while encoded terms are 276unaltered. this flag escapes strings as if for output in json, removing 277control codes and problematic codepoints and replacing them with the 278appropriate escapes 279 280#### `uescape` #### 281 282escape all codepoints outside the ascii range for 7 bit clean output. note this 283escaping takes place even if no other string escaping is requested (via 284`escaped_strings`) 285 286#### `unescaped_jsonp` #### 287 288javascript interpreters treat the codepoints `u+2028` and `u+2029` as 289significant whitespace. json strings that contain either of these codepoints 290will be parsed incorrectly by some javascript interpreters. by default, 291these codepoints are escaped (to `\u2028` and `\u2029`, respectively) to 292retain compatibility. this option simply removes that escaping 293 294#### `dirty_strings` #### 295 296json escaping is lossy; it mutates the json string and repeated application 297can result in unwanted behaviour. if your strings are already escaped (or 298you'd like to force invalid strings into "json" you monster) use this flag 299to bypass escaping. this can also be used to read in **really** invalid json 300strings. everything between unescaped quotes are passed as is to the resulting 301string term. note that this takes precedence over any other options 302 303#### `strict` #### 304 305as mentioned [earlier](#description), **exjsx** is pragmatic. if you're more of a 306json purist or you're really into bdsm stricter adherence to the spec is 307possible. the following restrictions are available 308 309* `:comments` 310 311 comments are disabled and result in `ArgumentError` or `{:error, :badarg}` 312 313* `:utf8` 314 315 invalid codepoints and malformed unicode result in `ArgumentError` or 316 `{:error, :badarg}` 317 318* `:single_quotes` 319 320 only keys and strings delimited by double quotes (`u+0022`) are allowed. the 321 single quote (`u+0027`) results in `ArgumentError` or `{:error, :badarg}` 322 323* `trailing_commas` 324 325 trailing commas in an object or list result in `badarg` errors 326 327* `:escapes` 328 329 escape sequences not adhering to the json spec result in `ArgumentError` or 330 `{:error, :badarg}` 331 332any combination of these can be passed to **exjsx** by using `{:strict, [strict_option()]}`. 333`:strict` is equivalent to `{:strict, [:comments, :bad_utf8, :single_quotes, :escapes]}` 334 335 336## exports ## 337 338#### `decode(json, opts)` #### 339 340`decode` parses a json text (a `BitString`) and produces `{:ok, result}` or 341`{:error, reason}` 342 343`opts` has the default value `[]` and can be a list containing any of the 344standard exjsx [options](#options) plus the following 345 346* `{:labels, :binary}` 347 json object's keys will be decoded to `BitStrings`. the default 348 349* `{:labels, :atom}` 350 json object's keys will be decoded to `Atoms` 351 352* `{:labels, :existing_atom}` 353 json object's keys will be decoded to `Atoms` if they are already 354 known to the runtime, otherwise the decoder will return an error 355 356##### examples ##### 357 358```iex 359iex> JSX.decode "[true, false, null]" 360{:ok,[true,false,nil]} 361iex> JSX.decode("{\"key\": true}", [{:labels, :binary}]) 362{:ok, %{"key" => true}} 363iex> JSX.decode("{\"key\": true}", [{:labels, :atom}]) 364{:ok, %{key: true}} 365iex> JSX.decode [:a, :b, :c] 366{:error, :badarg} 367``` 368 369#### `decode!(json, opts)` #### 370 371`decode!` parses a json text (a `BitString`) and produces `result` or 372an `ArgumentError` exception 373 374see [decode](#decodejson-opts) for opts 375 376##### examples ##### 377 378```iex 379iex> JSX.decode! "[true, false, null]" 380[true, false, nil] 381iex> JSX.decode! [:a, :b, :c] 382** (ArgumentError) argument error 383``` 384 385#### `encode(term, opts)` #### 386 387`encode` produces takes an elixir term and produces `{:ok, json}` or 388`{:error, :badarg}` 389 390`opts` has the default value `[]` and can be a list containing any of the 391standard exjsx [options](#options) plus the following 392 393* `{:space, n}` 394 inserts `n` spaces after every comma and colon in your json output. 395 `:space` is an alias for `{:space, 1}`. the default is `{:space, 0}` 396 397* `{:indent, n}` 398 inserts a newline and `n` spaces for each level of indentation in your 399 json output after each comma. note that this overrides spaces inserted 400 after a comma. `:indent` is an alias for `{:indent, 1}`. the default 401 is `{:indent, 0}` 402 403##### examples ##### 404 405```iex 406iex> JSX.encode [true, false, nil] 407{:ok, "[true,false,null]"} 408iex> JSX.encode(%{:a => 1, :b => 2, :c => 3}, [{:space, 2}, :indent]) 409{:ok,"{ 410 \"a\": 1, 411 \"b\": 2, 412 \"c\": 3 413}"} 414iex> JSX.encode(%{:a => 1, :b => 2, :c => 3}, [:space, {:indent, 4}]) 415{:ok,"{ 416 \"a\": 1, 417 \"b\": 2, 418 \"c\": 3 419}"} 420``` 421 422#### `encode!(json, opts)` #### 423 424`encode!` produces takes an elixir term and produces `json` or 425an `ArgumentError` exception 426 427see [encode](#encodejson-opts) for opts 428 429##### examples ##### 430 431```iex 432iex> JSX.encode! [true, false, null] 433[true, false, nil] 434iex> JSX.encode! [self()] 435** (ArgumentError) argument error 436``` 437 438#### `format(json, opts)` #### 439 440`format` parses a json text and produces formatted `{:ok, json}` or 441`{:error, :badarg}` 442 443see [encode](#encodejson-opts) for opts 444 445##### examples ##### 446 447```iex 448iex> JSX.format "[true, false, null]" 449{:ok, "[true,false,null]"} 450iex> JSX.format("[true, false, null]", [space: 2]) 451{:ok, "[true, false, null]"} 452iex> JSX.format("[true, false, null]", [space: 4]) 453{:ok, "[true, false, null]"} 454iex> JSX.format "{\"foo\":true,\"bar\":false}" 455{:ok, "{\"foo\":true,\"bar\":false}"} 456iex> JSX.format("{\"foo\":true,\"bar\":false}", [:space]) 457{:ok, "{\"foo\": true,\"bar\": false}"} 458iex> JSX.format("{\"foo\":true,\"bar\":false}", [space: 2, indent: 4]) 459{:ok, "{ 460 \"foo\": true, 461 \"bar\": false 462}"} 463iex> JSX.format [self()] 464{:error,:badarg} 465``` 466 467#### `format!(json, opts)` #### 468 469`format!` parses a json text and produces formatted `json` or 470an `ArgumentError` exception 471 472see [encode](#encodejson-opts) for opts 473 474##### examples ##### 475 476```iex 477iex> JSX.format! "[true, false, null]" 478"[true,false,null]" 479iex> JSX.format!("{\"foo\":true,\"bar\":false}", [space: 2, indent: 4]) 480"{ 481 \"foo\": true, 482 \"bar\": false 483}" 484iex> JSX.format! [self()] 485** (ArgumentError) argument error 486``` 487 488#### `minify(json)` #### 489 490`minify` is an alias for `format(json, [space: 0, indent: 0])` 491 492##### examples ##### 493 494```iex 495iex> JSX.minify "[true, false, null]" 496{:ok,"[true,false,null]"} 497iex> JSX.minify [self()] 498{:error,:badarg} 499``` 500 501#### `minify!(json)` #### 502 503`minify!` is an alias for `format!(json, [space: 0, indent: 0])` 504 505##### examples ##### 506 507```iex 508iex> JSX.minify! "[true, false, null]" 509"[true,false,null]" 510iex> JSX.minify [self()] 511** (ArgumentError) argument error 512``` 513 514#### `prettify(json)` #### 515 516`prettify` is an alias for `format(json, [space: 1, indent: 2])` 517 518##### examples ##### 519 520```iex 521iex> JSX.prettify "[true, false, null]" 522{:ok,"[ 523 true, 524 false, 525 null 526]"} 527iex> JSX.prettify [self()] 528{:error,:badarg} 529``` 530 531#### `prettify!(json)` #### 532 533`prettify!` is an alias for `format!(json, [space: 1, indent: 2])` 534 535##### examples ##### 536 537```iex 538iex> JSX.prettify! "[true, false, null]" 539"[ 540 true, 541 false, 542 null 543]" 544iex> JSX.prettify! [self()] 545** (ArgumentError) argument error 546``` 547 548#### `is_json?(json, opts)` #### 549 550returns `true` if input is a valid json text, `false` if not 551 552`opts` has the default value `[]` and can be a list containing any of the 553standard exjsx [options](#options) 554 555what exactly constitutes valid json may be [altered](#options) 556 557##### examples ##### 558 559```iex 560iex> JSX.is_json? "[true, false, null]" 561true 562iex> JSX.is_json? [self()] 563false 564``` 565 566#### `is_term?(term, opts)` #### 567 568returns `true` if input is an elixir term that can be safely converted to json, 569`false` if not 570 571`opts` has the default value `[]` and can be a list containing any of the 572standard exjsx [options](#options) 573 574what exactly constitutes valid json may be [altered](#options) 575 576##### examples ##### 577 578```iex 579iex> JSX.is_term? [true, false, nil] 580true 581iex> JSX.is_term? [self()] 582false 583``` 584 585 586## acknowledgements ## 587 588exjsx wouldn't be what it is without the guidance and code review of 589[yurii rashkovskii](https://github.com/yrashk), [eduardo gurgel](https://github.com/edgurgel) and [devin torres](https://github.com/devinus) 590 591[json]: http://json.org 592[elixir]: https://github.com/elixir-lang/elixir 593[jsx]: https://github.com/talentdeficit/jsx 594[MIT]: http://www.opensource.org/licenses/mit-license.html 595[rfc4627]: http://tools.ietf.org/html/rfc4627 596[travis]: https://travis-ci.org/ 597