├── .credo.exs ├── .dialyzer_ignore.exs ├── .dockerignore ├── .editorconfig ├── .formatter.exs ├── .gitattributes ├── .github └── workflows │ └── ci.yml ├── .gitignore ├── .gitmodules ├── .tool-versions ├── .vscode └── settings.json ├── CHANGELOG.md ├── Dockerfile ├── LICENSE ├── README.md ├── VERSION ├── bench ├── data │ ├── benchee.json │ ├── blockchain.json │ ├── geojson.json │ ├── giphy.json │ ├── github.json │ ├── govtrack.json │ ├── issue-90.json │ ├── json-generator-pretty.json │ ├── json-generator.json │ ├── pokedex.json │ ├── reddit.json │ ├── utf-8-escaped.json │ └── utf-8-unescaped.json └── run.exs ├── docker-compose.yml ├── lib ├── poison.ex └── poison │ ├── decoder.ex │ ├── encoder.ex │ └── parser.ex ├── mix.exs ├── mix.lock ├── profile └── profiler.ex └── test ├── fixtures └── unparsable.json ├── poison ├── decoder_test.exs ├── encoder_test.exs └── parser_test.exs ├── poison_test.exs └── test_helper.exs /.credo.exs: -------------------------------------------------------------------------------- 1 | # This file contains the configuration for Credo and you are probably reading 2 | # this after creating it with `mix credo.gen.config`. 3 | # 4 | # If you find anything wrong or unclear in this file, please report an 5 | # issue on GitHub: https://github.com/rrrene/credo/issues 6 | # 7 | %{ 8 | # 9 | # You can have as many configs as you like in the `configs:` field. 10 | configs: [ 11 | %{ 12 | # 13 | # Run any config using `mix credo -C `. If no config name is given 14 | # "default" is used. 15 | # 16 | name: "default", 17 | # 18 | # These are the files included in the analysis: 19 | files: %{ 20 | # 21 | # You can give explicit globs or simply directories. 22 | # In the latter case `**/*.{ex,exs}` will be used. 23 | # 24 | included: [ 25 | "{.credo,.dialyzer_ignore,.formatter,mix}.exs", 26 | "config/", 27 | "bench/", 28 | "lib/", 29 | "profile/", 30 | "test/" 31 | ], 32 | excluded: [~r"/_build/", ~r"/deps/"] 33 | }, 34 | # 35 | # Load and configure plugins here: 36 | # 37 | plugins: [], 38 | # 39 | # If you create your own checks, you must specify the source files for 40 | # them here, so they can be loaded by Credo before running the analysis. 41 | # 42 | requires: [], 43 | # 44 | # If you want to enforce a style guide and need a more traditional linting 45 | # experience, you can change `strict` to `true` below: 46 | # 47 | strict: true, 48 | # 49 | # To modify the timeout for parsing files, change this value: 50 | # 51 | parse_timeout: 5000, 52 | # 53 | # If you want to use uncolored output by default, you can change `color` 54 | # to `false` below: 55 | # 56 | color: true, 57 | # 58 | # You can customize the parameters of any check by adding a second element 59 | # to the tuple. 60 | # 61 | # To disable a check put `false` as second element: 62 | # 63 | # {Credo.Check.Design.DuplicatedCode, false} 64 | # 65 | checks: %{ 66 | enabled: [ 67 | # 68 | ## Consistency Checks 69 | # 70 | {Credo.Check.Consistency.ExceptionNames, []}, 71 | {Credo.Check.Consistency.LineEndings, []}, 72 | {Credo.Check.Consistency.MultiAliasImportRequireUse, []}, 73 | {Credo.Check.Consistency.ParameterPatternMatching, []}, 74 | {Credo.Check.Consistency.SpaceAroundOperators, []}, 75 | {Credo.Check.Consistency.SpaceInParentheses, []}, 76 | {Credo.Check.Consistency.TabsOrSpaces, []}, 77 | 78 | # 79 | ## Design Checks 80 | # 81 | # You can customize the priority of any check 82 | # Priority values are: `low, normal, high, higher` 83 | # 84 | {Credo.Check.Design.AliasUsage, 85 | [priority: :low, if_nested_deeper_than: 2, if_called_more_often_than: 0]}, 86 | {Credo.Check.Design.DuplicatedCode, []}, 87 | {Credo.Check.Design.SkipTestWithoutComment, []}, 88 | {Credo.Check.Design.TagFIXME, []}, 89 | # You can also customize the exit_status of each check. 90 | # If you don't want TODO comments to cause `mix credo` to fail, just 91 | # set this value to 0 (zero). 92 | # 93 | {Credo.Check.Design.TagTODO, [exit_status: 2]}, 94 | 95 | # 96 | ## Readability Checks 97 | # 98 | {Credo.Check.Readability.AliasAs, []}, 99 | {Credo.Check.Readability.AliasOrder, []}, 100 | {Credo.Check.Readability.BlockPipe, []}, 101 | {Credo.Check.Readability.FunctionNames, []}, 102 | {Credo.Check.Readability.ImplTrue, []}, 103 | {Credo.Check.Readability.LargeNumbers, []}, 104 | {Credo.Check.Readability.MaxLineLength, [priority: :low, max_length: 120]}, 105 | {Credo.Check.Readability.ModuleAttributeNames, []}, 106 | {Credo.Check.Readability.ModuleDoc, []}, 107 | {Credo.Check.Readability.ModuleNames, []}, 108 | {Credo.Check.Readability.OneArityFunctionInPipe, []}, 109 | {Credo.Check.Readability.ParenthesesInCondition, []}, 110 | {Credo.Check.Readability.ParenthesesOnZeroArityDefs, []}, 111 | {Credo.Check.Readability.PipeIntoAnonymousFunctions, []}, 112 | {Credo.Check.Readability.PredicateFunctionNames, []}, 113 | {Credo.Check.Readability.PreferImplicitTry, []}, 114 | {Credo.Check.Readability.RedundantBlankLines, []}, 115 | {Credo.Check.Readability.Semicolons, []}, 116 | {Credo.Check.Readability.SeparateAliasRequire, []}, 117 | {Credo.Check.Readability.SingleFunctionToBlockPipe, []}, 118 | {Credo.Check.Readability.SinglePipe, []}, 119 | {Credo.Check.Readability.SpaceAfterCommas, []}, 120 | {Credo.Check.Readability.StrictModuleLayout, []}, 121 | {Credo.Check.Readability.StringSigils, []}, 122 | {Credo.Check.Readability.TrailingBlankLine, []}, 123 | {Credo.Check.Readability.TrailingWhiteSpace, []}, 124 | {Credo.Check.Readability.UnnecessaryAliasExpansion, []}, 125 | {Credo.Check.Readability.VariableNames, []}, 126 | {Credo.Check.Readability.WithCustomTaggedTuple, []}, 127 | {Credo.Check.Readability.WithSingleClause, []}, 128 | 129 | # 130 | ## Refactoring Opportunities 131 | # 132 | {Credo.Check.Refactor.ABCSize, []}, 133 | {Credo.Check.Refactor.AppendSingleItem, []}, 134 | {Credo.Check.Refactor.Apply, []}, 135 | {Credo.Check.Refactor.CondStatements, []}, 136 | {Credo.Check.Refactor.CyclomaticComplexity, []}, 137 | {Credo.Check.Refactor.DoubleBooleanNegation, []}, 138 | {Credo.Check.Refactor.FilterCount, []}, 139 | {Credo.Check.Refactor.FilterFilter, []}, 140 | {Credo.Check.Refactor.FilterReject, []}, 141 | {Credo.Check.Refactor.FunctionArity, []}, 142 | {Credo.Check.Refactor.IoPuts, []}, 143 | {Credo.Check.Refactor.LongQuoteBlocks, []}, 144 | {Credo.Check.Refactor.MapJoin, []}, 145 | {Credo.Check.Refactor.MapMap, []}, 146 | {Credo.Check.Refactor.MatchInCondition, []}, 147 | {Credo.Check.Refactor.NegatedConditionsInUnless, []}, 148 | {Credo.Check.Refactor.NegatedConditionsWithElse, []}, 149 | {Credo.Check.Refactor.NegatedIsNil, []}, 150 | {Credo.Check.Refactor.Nesting, []}, 151 | {Credo.Check.Refactor.PassAsyncInTestCases, []}, 152 | {Credo.Check.Refactor.PipeChainStart, []}, 153 | {Credo.Check.Refactor.RedundantWithClauseResult, []}, 154 | {Credo.Check.Refactor.RejectFilter, []}, 155 | {Credo.Check.Refactor.RejectReject, []}, 156 | {Credo.Check.Refactor.UnlessWithElse, []}, 157 | {Credo.Check.Refactor.UtcNowTruncate, []}, 158 | {Credo.Check.Refactor.WithClauses, []}, 159 | 160 | # 161 | ## Warnings 162 | # 163 | {Credo.Check.Consistency.UnusedVariableNames, []}, 164 | {Credo.Check.Warning.ApplicationConfigInModuleAttribute, []}, 165 | {Credo.Check.Warning.BoolOperationOnSameValues, []}, 166 | {Credo.Check.Warning.Dbg, []}, 167 | {Credo.Check.Warning.ExpensiveEmptyEnumCheck, []}, 168 | {Credo.Check.Warning.IExPry, []}, 169 | {Credo.Check.Warning.IoInspect, []}, 170 | {Credo.Check.Warning.LazyLogging, false}, 171 | {Credo.Check.Warning.LeakyEnvironment, []}, 172 | {Credo.Check.Warning.MapGetUnsafePass, []}, 173 | {Credo.Check.Warning.MissedMetadataKeyInLoggerConfig, []}, 174 | {Credo.Check.Warning.MixEnv, []}, 175 | {Credo.Check.Warning.OperationOnSameValues, []}, 176 | {Credo.Check.Warning.OperationWithConstantResult, []}, 177 | {Credo.Check.Warning.RaiseInsideRescue, []}, 178 | {Credo.Check.Warning.SpecWithStruct, []}, 179 | {Credo.Check.Warning.UnsafeExec, []}, 180 | {Credo.Check.Warning.UnsafeToAtom, []}, 181 | {Credo.Check.Warning.UnusedEnumOperation, []}, 182 | {Credo.Check.Warning.UnusedFileOperation, []}, 183 | {Credo.Check.Warning.UnusedKeywordOperation, []}, 184 | {Credo.Check.Warning.UnusedListOperation, []}, 185 | {Credo.Check.Warning.UnusedPathOperation, []}, 186 | {Credo.Check.Warning.UnusedRegexOperation, []}, 187 | {Credo.Check.Warning.UnusedStringOperation, []}, 188 | {Credo.Check.Warning.UnusedTupleOperation, []}, 189 | {Credo.Check.Warning.WrongTestFileExtension, []} 190 | ], 191 | disabled: [ 192 | # 193 | # Controversial and experimental checks (opt-in, just move the check to `:enabled` 194 | # and be sure to use `mix credo --strict` to see low priority checks) 195 | # 196 | {Credo.Check.Readability.MultiAlias, []}, 197 | {Credo.Check.Readability.NestedFunctionCalls, []}, 198 | {Credo.Check.Readability.OnePipePerLine, []}, 199 | {Credo.Check.Readability.Specs, []}, 200 | {Credo.Check.Refactor.ModuleDependencies, []}, 201 | {Credo.Check.Refactor.VariableRebinding, []} 202 | 203 | # {Credo.Check.Refactor.MapInto, []}, 204 | 205 | # 206 | # Custom checks can be created using `mix credo.gen.check`. 207 | # 208 | ] 209 | } 210 | } 211 | ] 212 | } 213 | -------------------------------------------------------------------------------- /.dialyzer_ignore.exs: -------------------------------------------------------------------------------- 1 | [ 2 | {"lib/poison/parser.ex", :improper_list_constr} 3 | ] 4 | -------------------------------------------------------------------------------- /.dockerignore: -------------------------------------------------------------------------------- 1 | .gitignore -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig is awesome: http://EditorConfig.org 2 | 3 | # top-most EditorConfig file 4 | root = true 5 | 6 | # Unix-style newlines with a newline ending every file 7 | [*] 8 | end_of_line = lf 9 | insert_final_newline = true 10 | 11 | # 2 space indentation 12 | [*.{ex,exs,json,yml}] 13 | indent_style = space 14 | indent_size = 2 15 | -------------------------------------------------------------------------------- /.formatter.exs: -------------------------------------------------------------------------------- 1 | # Used by "mix format" 2 | [ 3 | import_deps: [:stream_data], 4 | inputs: [ 5 | "{.credo,.dialyzer_ignore,.formatter,mix}.exs", 6 | "{config,bench,lib,profile,test}/**/*.{ex,exs}" 7 | ] 8 | ] 9 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto eol=lf 2 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: ci 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | - develop 8 | pull_request: 9 | branches: 10 | - master 11 | 12 | env: 13 | MIX_ENV: test 14 | 15 | jobs: 16 | test: 17 | runs-on: ${{ matrix.os }} 18 | name: ${{ matrix.os }} / OTP ${{matrix.otp}} / Elixir ${{matrix.elixir}} 19 | strategy: 20 | matrix: 21 | os: 22 | - ubuntu-22.04 23 | - windows-2022 24 | otp: 25 | - "26.2.2" 26 | - "25.3.2" 27 | - "24.3.4" 28 | elixir: 29 | - "1.16.3" 30 | - "1.15.7" 31 | - "1.14.5" 32 | - "1.13.4" 33 | - "1.12.3" 34 | exclude: 35 | - otp: "26.2.2" 36 | elixir: "1.14.5" 37 | - otp: "26.2.2" 38 | elixir: "1.13.4" 39 | - otp: "26.2.2" 40 | elixir: "1.12.3" 41 | - otp: "25.3.2" 42 | elixir: "1.13.4" 43 | - otp: "25.3.2" 44 | elixir: "1.12.3" 45 | 46 | steps: 47 | - uses: actions/checkout@v4 48 | with: 49 | fetch-depth: 0 50 | submodules: true 51 | 52 | - uses: erlef/setup-beam@v1 53 | with: 54 | otp-version: ${{matrix.otp}} 55 | elixir-version: ${{matrix.elixir}} 56 | 57 | - uses: actions/cache@v4 58 | id: cache 59 | with: 60 | path: | 61 | _build 62 | deps 63 | vendor 64 | key: ${{ runner.os }}-${{ matrix.otp }}-${{ matrix.elixir }}-${{ hashFiles('mix.lock') }} 65 | 66 | - if: steps.cache.outputs.cache-hit != 'true' 67 | run: mix deps.get 68 | 69 | - run: mix coveralls.github 70 | env: 71 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 72 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # The directory Mix will write compiled artifacts to. 2 | /_build/ 3 | 4 | # If you run "mix test --cover", coverage assets end up here. 5 | /cover/ 6 | 7 | # The directory Mix downloads your dependencies sources to. 8 | /deps/ 9 | 10 | # Where third-party dependencies like ExDoc output generated docs. 11 | /doc/ 12 | 13 | # Ignore .fetch files in case you like to edit your project deps locally. 14 | /.fetch 15 | 16 | # If the VM crashes, it generates a dump, let's ignore it too. 17 | erl_crash.dump 18 | 19 | # Also ignore archive artifacts (built via "mix archive.build"). 20 | *.ez 21 | 22 | # Ignore package tarball (built via "mix hex.build"). 23 | poison-*.tar 24 | 25 | # Temporary files, for example, from tests. 26 | /tmp/ 27 | 28 | .elixir_ls 29 | *.benchee 30 | /bench/output/ 31 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "JSONTestSuite"] 2 | path = vendor/JSONTestSuite 3 | url = https://github.com/nst/JSONTestSuite.git 4 | shallow = true 5 | -------------------------------------------------------------------------------- /.tool-versions: -------------------------------------------------------------------------------- 1 | erlang 27.0 2 | elixir 1.17.0-rc.1-otp-27 3 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "editor.formatOnSave": true, 3 | "elixirLS.enableTestLenses": true, 4 | "elixirLS.dialyzerWarnOpts": ["no_improper_lists"] 5 | } 6 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Poison Changelog 2 | 3 | ## v6.0.0 4 | 5 | ### Features 6 | 7 | * Support Erlang 27 and Elixir 1.17 8 | [#214](https://github.com/devinus/poison/issues/214) 9 | [#222](https://github.com/devinus/poison/issues/222) 10 | * Reintroduce `Poison.encode_to_iodata!/1` for Phoenix compatibility 11 | [#172](https://github.com/devinus/poison/issues/172) 12 | [#206](https://github.com/devinus/poison/pull/206) 13 | * Make [`:html_safe`](`t:Poison.Encoder.escape/0`) encode option follow OWASP 14 | recommended HTML escaping 15 | [#194](https://github.com/devinus/poison/issues/194) 16 | * Add `Date.Range` encoding 17 | * Allow [`:as`](`t:Poison.Decoder.as/0`) decode option to be a function 18 | [#207](https://github.com/devinus/poison/pull/207) 19 | * Add a [CHANGELOG](CHANGELOG.md) 20 | [#105](https://github.com/devinus/poison/issues/105) 21 | 22 | ### Bug Fixes 23 | 24 | * Stop double decoding structs 25 | [#191](https://github.com/devinus/poison/issues/191) 26 | * Fix various typespecs 27 | [#199](https://github.com/devinus/poison/issues/199) 28 | * Correctly encode some UTF-8 surrogate pairs 29 | [#217](https://github.com/devinus/poison/issues/217) 30 | 31 | ### Performance Improvements 32 | 33 | * Significantly improve performance 34 | ([2024-06-06](https://gist.github.com/devinus/afb351ae45194a6b93b6db9bf2d4c163)) 35 | 36 | ### Breaking Changes 37 | 38 | * Remove deprecated `HashSet` encoding 39 | * Minimum supported versions are now Erlang 24 and Elixir 1.12 40 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM elixir:1.17.0-rc.0-otp-27-alpine AS base 2 | ARG MIX_ENV 3 | ENV MIX_ENV ${MIX_ENV:-test} 4 | RUN apk --no-cache add git build-base 5 | RUN adduser -D user 6 | RUN mkdir -p /usr/src/project 7 | RUN chown user:user /usr/src/project 8 | USER user 9 | WORKDIR /usr/src/project 10 | RUN mix local.hex --force 11 | RUN mix local.rebar --force 12 | 13 | 14 | FROM base AS build 15 | COPY --chown=user VERSION . 16 | COPY --chown=user mix.exs . 17 | COPY --chown=user mix.lock . 18 | RUN mix deps.get 19 | RUN mix deps.compile 20 | RUN mix compile 21 | 22 | 23 | FROM base AS dev 24 | COPY --chown=user --from=build /usr/src/project/deps deps 25 | COPY --chown=user --from=build /usr/src/project/_build _build 26 | COPY --chown=user . . 27 | RUN git submodule update --init 28 | CMD ["iex", "-S", "mix"] 29 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | BSD Zero Clause License 2 | 3 | Copyright (C) 2024 Devin Alexander Torres 4 | 5 | Permission to use, copy, modify, and/or distribute this software for any 6 | purpose with or without fee is hereby granted. 7 | 8 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH 9 | REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY 10 | AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, 11 | INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM 12 | LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR 13 | OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR 14 | PERFORMANCE OF THIS SOFTWARE. 15 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Poison 2 | 3 | [![Build Status](https://img.shields.io/github/actions/workflow/status/devinus/poison/ci.yml)](https://github.com/devinus/poison/actions/workflows/ci.yml) 4 | [![Coverage Status](https://img.shields.io/coverallsCoverage/github/devinus/poison)](https://coveralls.io/github/devinus/poison?branch=master)) 5 | [![Hex.pm Version](https://img.shields.io/hexpm/v/poison)](https://hex.pm/packages/poison) 6 | [![Hex.pm Download Total](https://img.shields.io/hexpm/dt/poison)](https://hex.pm/packages/poison) 7 | [![Hex.pm Dependents](https://img.shields.io/librariesio/dependents/hex/poison)](https://hex.pm/packages/poison) 8 | 9 | Poison is a new JSON library for Elixir focusing on wicked-fast **speed** 10 | without sacrificing **simplicity**, **completeness**, or **correctness**. 11 | 12 | Poison takes several approaches to be the fastest JSON library for Elixir. 13 | 14 | Poison uses extensive [sub binary matching][1], a **hand-rolled parser** using 15 | several techniques that are [known to benefit BeamAsm][2] for JIT compilation, 16 | [IO list][3] encoding and **single-pass** decoding. 17 | 18 | Poison benchmarks sometimes puts Poison's performance close to `jiffy` and 19 | usually faster than other Erlang/Elixir libraries. 20 | 21 | Poison fully conforms to [RFC 8259][4], [ECMA 404][5], and fully passes the 22 | [JSONTestSuite][6]. 23 | 24 | ## Installation 25 | 26 | First, add Poison to your `mix.exs` dependencies: 27 | 28 | ```elixir 29 | def deps do 30 | [{:poison, "~> 6.0"}] 31 | end 32 | ``` 33 | 34 | Then, update your dependencies: 35 | 36 | ```sh 37 | mix deps.get 38 | ``` 39 | 40 | ## Usage 41 | 42 | ```elixir 43 | Poison.encode!(%{"age" => 27, "name" => "Devin Torres"}) 44 | #=> "{\"name\":\"Devin Torres\",\"age\":27}" 45 | 46 | Poison.decode!(~s({"name": "Devin Torres", "age": 27})) 47 | #=> %{"age" => 27, "name" => "Devin Torres"} 48 | 49 | defmodule Person do 50 | @derive [Poison.Encoder] 51 | defstruct [:name, :age] 52 | end 53 | 54 | Poison.encode!(%Person{name: "Devin Torres", age: 27}) 55 | #=> "{\"name\":\"Devin Torres\",\"age\":27}" 56 | 57 | Poison.decode!(~s({"name": "Devin Torres", "age": 27}), as: %Person{}) 58 | #=> %Person{name: "Devin Torres", age: 27} 59 | 60 | Poison.decode!(~s({"people": [{"name": "Devin Torres", "age": 27}]}), 61 | as: %{"people" => [%Person{}]}) 62 | #=> %{"people" => [%Person{age: 27, name: "Devin Torres"}]} 63 | ``` 64 | 65 | Every component of Poison (encoder, decoder, and parser) are all usable on 66 | their own without buying into other functionality. For example, if you were 67 | interested purely in the speed of parsing JSON without a decoding step, you 68 | could simply call `Poison.Parser.parse`. 69 | 70 | ## Parser 71 | 72 | ```iex 73 | iex> Poison.Parser.parse!(~s({"name": "Devin Torres", "age": 27}), %{}) 74 | %{"name" => "Devin Torres", "age" => 27} 75 | iex> Poison.Parser.parse!(~s({"name": "Devin Torres", "age": 27}), %{keys: :atoms!}) 76 | %{name: "Devin Torres", age: 27} 77 | ``` 78 | 79 | Note that `keys: :atoms!` reuses existing atoms, i.e. if `:name` was not 80 | allocated before the call, you will encounter an `argument error` message. 81 | 82 | You can use the `keys: :atoms` variant to make sure all atoms are created as 83 | needed. However, unless you absolutely know what you're doing, do **not** do 84 | it. Atoms are not garbage-collected, see 85 | [Erlang Efficiency Guide](http://www.erlang.org/doc/efficiency_guide/commoncaveats.html) 86 | for more info: 87 | 88 | > Atoms are not garbage-collected. Once an atom is created, it will never be 89 | > removed. The emulator will terminate if the limit for the number of atoms 90 | > (1048576 by default) is reached. 91 | 92 | ## Encoder 93 | 94 | ```iex 95 | iex> Poison.Encoder.encode([1, 2, 3], %{}) |> IO.iodata_to_binary 96 | "[1,2,3]" 97 | ``` 98 | 99 | Anything implementing the Encoder protocol is expected to return an 100 | [IO list][7] to be embedded within any other Encoder's implementation and 101 | passable to any IO subsystem without conversion. 102 | 103 | ```elixir 104 | defimpl Poison.Encoder, for: Person do 105 | def encode(%{name: name, age: age}, options) do 106 | Poison.Encoder.BitString.encode("#{name} (#{age})", options) 107 | end 108 | end 109 | ``` 110 | 111 | For maximum performance, make sure you `@derive [Poison.Encoder]` for any 112 | struct you plan on encoding. 113 | 114 | ### Encoding only some attributes 115 | 116 | When deriving structs for encoding, it is possible to select or exclude 117 | specific attributes. This is achieved by deriving `Poison.Encoder` with the 118 | `:only` or `:except` options set: 119 | 120 | ```elixir 121 | defmodule PersonOnlyName do 122 | @derive {Poison.Encoder, only: [:name]} 123 | defstruct [:name, :age] 124 | end 125 | 126 | defmodule PersonWithoutName do 127 | @derive {Poison.Encoder, except: [:name]} 128 | defstruct [:name, :age] 129 | end 130 | ``` 131 | 132 | In case both `:only` and `:except` keys are defined, the `:except` option is 133 | ignored. 134 | 135 | ### Key Validation 136 | 137 | According to [RFC 8259][4] keys in a JSON object should be unique. This is 138 | enforced and resolved in different ways in other libraries. In the Ruby JSON 139 | library for example, the output generated from encoding a hash with a duplicate 140 | key (say one is a string, the other an atom) will include both keys. When 141 | parsing JSON of this type, Chromium will override all previous values with the 142 | final one. 143 | 144 | Poison will generate JSON with duplicate keys if you attempt to encode a map 145 | with atom and string keys whose encoded names would clash. If you'd like to 146 | ensure that your generated JSON doesn't have this issue, you can pass the 147 | `strict_keys: true` option when encoding. This will force the encoding to fail. 148 | 149 | _Note:_ Validating keys can cause a small performance hit. 150 | 151 | ```iex 152 | iex> Poison.encode!(%{:foo => "foo1", "foo" => "foo2"}, strict_keys: true) 153 | ** (Poison.EncodeError) duplicate key found: :foo 154 | ``` 155 | 156 | ## Benchmarking 157 | 158 | ```sh 159 | MIX_ENV=bench mix run bench/run.exs 160 | ``` 161 | 162 | ### Current Benchmarks 163 | 164 | As of 2024-06-06: 165 | 166 | - Amazon EC2 c6i.2xlarge instance running Ubuntu Server 22.04: 167 | 168 | 169 | ## License 170 | 171 | Poison is released under the [public-domain-equivalent][8] [0BSD][9] license. 172 | 173 | [1]: https://erlang.org/euc/07/papers/1700Gustafsson.pdf 174 | [2]: https://erlang.org/documentation/doc-12.0-rc1/erts-12.0/doc/html/BeamAsm.html 175 | [3]: https://jlouisramblings.blogspot.com/2013/07/problematic-traits-in-erlang.html 176 | [4]: https://datatracker.ietf.org/doc/html/rfc8259 177 | [5]: https://ecma-international.org/wp-content/uploads/ECMA-404_2nd_edition_december_2017.pdf 178 | [6]: https://github.com/nst/JSONTestSuite 179 | [7]: https://prog21.dadgum.com/70.html 180 | [8]: https://en.wikipedia.org/wiki/Public-domain-equivalent_license 181 | [9]: https://opensource.org/licenses/0BSD 182 | -------------------------------------------------------------------------------- /VERSION: -------------------------------------------------------------------------------- 1 | 6.0.0 2 | -------------------------------------------------------------------------------- /bench/data/blockchain.json: -------------------------------------------------------------------------------- 1 | { 2 | "txs":[ 3 | 4 | { 5 | "lock_time":0, 6 | "ver":1, 7 | "size":224, 8 | "inputs":[ 9 | { 10 | "sequence":4294967295, 11 | "prev_out":{ 12 | "spent":true, 13 | "tx_index":251046142, 14 | "type":0, 15 | "addr":"1Cz2o3kcCzWUME4yaTakC78ZmAuGmnkARX", 16 | "value":2327000, 17 | "n":0, 18 | "script":"76a914837297476f9f22b1b63aef82560b3ae0610c980388ac" 19 | }, 20 | "script":"483045022100b0f9f874293d3ea9d214411a20ce094b819ac564d3dfeeeaef651bf9b9bfb9410220665470d6c58ad5ab77ce81179efdc45a1629b592f98e93f7b45dd7d7a3591c06012103da87165824fb8bd4face2ea4cf97e3ffe8359110141096f750db876289d66223" 21 | } 22 | ], 23 | "double_spend":false, 24 | "time":1494882215, 25 | "tx_index":251067906, 26 | "vin_sz":1, 27 | "hash":"a7fd5cf3901ffd4410dd730942366658648b64119cfc8240feb3c07806703540", 28 | "vout_sz":2, 29 | "relayed_by":"127.0.0.1", 30 | "out":[ 31 | { 32 | "spent":false, 33 | "tx_index":251067906, 34 | "type":0, 35 | "addr":"1Bf226wi388ax87yCFxcijYP8iz1KCxasU", 36 | "value":29880, 37 | "n":0, 38 | "script":"76a91474e1ea3e27554771e289045c7a082045246ae86a88ac" 39 | }, 40 | { 41 | "spent":false, 42 | "tx_index":251067906, 43 | "type":0, 44 | "addr":"37McuYxfwo97fiFUwxUpj8FG3McWrWvfk2", 45 | "value":2270000, 46 | "n":1, 47 | "script":"a9143e25a34198abcbcae06cf4e235ea8de65fafba6387" 48 | } 49 | ] 50 | }, 51 | 52 | { 53 | "lock_time":0, 54 | "ver":1, 55 | "size":473, 56 | "inputs":[ 57 | { 58 | "sequence":4294967295, 59 | "prev_out":{ 60 | "spent":true, 61 | "tx_index":251057821, 62 | "type":0, 63 | "addr":"365VPevZ8zpnzvDg9e2XEufLJpDXunrL3p", 64 | "value":13386793, 65 | "n":1, 66 | "script":"a9143020953c35bd255bcfc98bdf8565e6e8486793ce87" 67 | }, 68 | "script":"0047304402200a56722de364cd46c2a863f9899f6ed668ed0a0d50e1f7795e36af7e94d9a2f202202949a294cb20bafc0b5a83d6f363d13f971a9b78898de6e10403f953ca88def101473044022049ad29cf209fbef3003a4270f2ff00bd2a992693c895483dbe96e675f1a89f0002206d310e61682c1334f46d0ca7cb4c4aed6a76b5af094592b2485d66161f329935014ccf522102ad8f2010b1d6ca1bf6d0289de8c5706dca174bfff9b551fa0a3e9961fd7896a121032cfca753d90effe3a35cb01d50bdbad05de439a28cd022f49e9108dc9a5b28fc210370a034d6ba2745681236f54334b023be6f84076fac8b3573c70e2780371e23502103844e246b4ba7e517cbaa247b10e29b59866409a8b6f97cb2d9a6fa346f472e0e210390afcc527e0b5ee39f2ac8ef5931796eaadbc32ecdd64d8fb2fbea7d7ec96cf42103b3dfce7aa0d2f4b29367076a5d2301d321a367d8fb6777080a98657bbab2c9ca56ae" 69 | } 70 | ], 71 | "double_spend":false, 72 | "time":1494882215, 73 | "tx_index":251069422, 74 | "vin_sz":1, 75 | "hash":"b56687718789d9468cd240e584d385817006c10cea4d18e9afe9b7557df2f33f", 76 | "vout_sz":2, 77 | "relayed_by":"78.68.155.43", 78 | "out":[ 79 | { 80 | "spent":false, 81 | "tx_index":251069422, 82 | "type":0, 83 | "addr":"1GDTiq92XgXaocw9yyFx1pjJBHwjd2Si1T", 84 | "value":6116700, 85 | "n":0, 86 | "script":"76a914a6e52f1d1c522aafcf2b0726a7621c5358d896a888ac" 87 | }, 88 | { 89 | "spent":false, 90 | "tx_index":251069422, 91 | "type":0, 92 | "addr":"3KsoXsw4FZN9Hm1fwQjHkcTNeDQXWYPRMu", 93 | "value":7111025, 94 | "n":1, 95 | "script":"a914c77c7f1387da8129df42715fdeeeb5f300b6bcca87" 96 | } 97 | ] 98 | }, 99 | 100 | { 101 | "lock_time":0, 102 | "ver":1, 103 | "size":1259, 104 | "inputs":[ 105 | { 106 | "sequence":4294967295, 107 | "prev_out":{ 108 | "spent":true, 109 | "tx_index":246301202, 110 | "type":0, 111 | "addr":"14rLNtnfRndYvGhuNqpRpUtPQLaeheKjHk", 112 | "value":4444444, 113 | "n":118, 114 | "script":"76a9142a3cef5a41ca75f7fdb4259938491bcee9e99b2588ac" 115 | }, 116 | "script":"483045022100cfd7ac622d25cb54304d68461049a696477f758f4fd493d1d60df1e08e6c280a02204c1712c6d7625194b0e947acefa9fdffc904b6e6c769cd47deeb78d3e3119df30121038ef6a69cb98448bda6e3e6a2b514168c0e36a7c10e88414531c4072965eff063" 117 | }, 118 | { 119 | "sequence":4294967295, 120 | "prev_out":{ 121 | "spent":true, 122 | "tx_index":247736773, 123 | "type":0, 124 | "addr":"14rLNtnfRndYvGhuNqpRpUtPQLaeheKjHk", 125 | "value":4444444, 126 | "n":91, 127 | "script":"76a9142a3cef5a41ca75f7fdb4259938491bcee9e99b2588ac" 128 | }, 129 | "script":"483045022100dcd341ac99292e4198c8c695a3cce6db1181b912093196ce217515f5272306640220011afff35bc63db46d772cd72be874658a54d3e0fc1ed0026a71edfc617a21d20121038ef6a69cb98448bda6e3e6a2b514168c0e36a7c10e88414531c4072965eff063" 130 | }, 131 | { 132 | "sequence":4294967295, 133 | "prev_out":{ 134 | "spent":true, 135 | "tx_index":247002242, 136 | "type":0, 137 | "addr":"14rLNtnfRndYvGhuNqpRpUtPQLaeheKjHk", 138 | "value":4444444, 139 | "n":107, 140 | "script":"76a9142a3cef5a41ca75f7fdb4259938491bcee9e99b2588ac" 141 | }, 142 | "script":"4830450221009cd60022558a45d341acef08b8d078e14138d6ad12259c57fa39737979194763022000a97fd165f294bda58fbbb66d03f833d8ca52644ee071d04e4e237d725d29670121038ef6a69cb98448bda6e3e6a2b514168c0e36a7c10e88414531c4072965eff063" 143 | }, 144 | { 145 | "sequence":4294967295, 146 | "prev_out":{ 147 | "spent":true, 148 | "tx_index":249742437, 149 | "type":0, 150 | "addr":"14rLNtnfRndYvGhuNqpRpUtPQLaeheKjHk", 151 | "value":4444444, 152 | "n":111, 153 | "script":"76a9142a3cef5a41ca75f7fdb4259938491bcee9e99b2588ac" 154 | }, 155 | "script":"473044022009e2d273ba3e2f319431ad32b51628e4ea365778384d07f956ee4742c0afb6a502200acea9de021de43ddd4105cbbf9501775bec4c29dd73a1a795d881e8c68cbf470121038ef6a69cb98448bda6e3e6a2b514168c0e36a7c10e88414531c4072965eff063" 156 | }, 157 | { 158 | "sequence":4294967295, 159 | "prev_out":{ 160 | "spent":true, 161 | "tx_index":249038984, 162 | "type":0, 163 | "addr":"14rLNtnfRndYvGhuNqpRpUtPQLaeheKjHk", 164 | "value":4444444, 165 | "n":121, 166 | "script":"76a9142a3cef5a41ca75f7fdb4259938491bcee9e99b2588ac" 167 | }, 168 | "script":"483045022100eb1c0e4be87609c659bc9eab60f52bc39e8e485ce571dec3efc840d66d64a1560220016a1f40fa8d06fd1577f13656c3079870a8b07903cfb01c14ba699df22c395f0121038ef6a69cb98448bda6e3e6a2b514168c0e36a7c10e88414531c4072965eff063" 169 | }, 170 | { 171 | "sequence":4294967295, 172 | "prev_out":{ 173 | "spent":true, 174 | "tx_index":245680316, 175 | "type":0, 176 | "addr":"14rLNtnfRndYvGhuNqpRpUtPQLaeheKjHk", 177 | "value":4444444, 178 | "n":125, 179 | "script":"76a9142a3cef5a41ca75f7fdb4259938491bcee9e99b2588ac" 180 | }, 181 | "script":"4830450221008682ac4ccbdb36a7b123f793e3057c99b37710a582dc446b931f5b3b4175efb502203f9d41a9130a65cee46e08f8de568d21f359d2dce05d0a26be4bc8f1c3e4396d0121038ef6a69cb98448bda6e3e6a2b514168c0e36a7c10e88414531c4072965eff063" 182 | }, 183 | { 184 | "sequence":4294967295, 185 | "prev_out":{ 186 | "spent":true, 187 | "tx_index":250412310, 188 | "type":0, 189 | "addr":"14rLNtnfRndYvGhuNqpRpUtPQLaeheKjHk", 190 | "value":4444444, 191 | "n":107, 192 | "script":"76a9142a3cef5a41ca75f7fdb4259938491bcee9e99b2588ac" 193 | }, 194 | "script":"47304402204a5c440a348b9f8ac1bba105edec0e8e6e064c93f68c4a0fcd2f7e8d12fbc524022061a62702cb7b9e01901f23c5cff0f7bd56d1bc0bc689343fff9f259782ec131b0121038ef6a69cb98448bda6e3e6a2b514168c0e36a7c10e88414531c4072965eff063" 195 | }, 196 | { 197 | "sequence":4294967295, 198 | "prev_out":{ 199 | "spent":true, 200 | "tx_index":248289092, 201 | "type":0, 202 | "addr":"14rLNtnfRndYvGhuNqpRpUtPQLaeheKjHk", 203 | "value":4444444, 204 | "n":106, 205 | "script":"76a9142a3cef5a41ca75f7fdb4259938491bcee9e99b2588ac" 206 | }, 207 | "script":"47304402201074ca23132c2c1c23f445176307195f65d045adf193f2d0704dac5dce47e28702201cb2fb0c20460a4de3f419920a810d4059b5c92fba17bde1ffe900e59e3b413f0121038ef6a69cb98448bda6e3e6a2b514168c0e36a7c10e88414531c4072965eff063" 208 | } 209 | ], 210 | "double_spend":false, 211 | "time":1494882215, 212 | "tx_index":251067907, 213 | "vin_sz":8, 214 | "hash":"9380e2a725ce0ecdf1dc3125cb2e60242daad08a75ba14407ad88757e2b7cb1d", 215 | "vout_sz":2, 216 | "relayed_by":"127.0.0.1", 217 | "out":[ 218 | { 219 | "spent":false, 220 | "tx_index":251067907, 221 | "type":0, 222 | "addr":"1MyjAo3QMCew6d7Cy1bj9j5C9gracQW8LH", 223 | "value":151468, 224 | "n":0, 225 | "script":"76a914e61d02a4c1a0ff3d8c7a577f431e77f81c6f5a6788ac" 226 | }, 227 | { 228 | "spent":false, 229 | "tx_index":251067907, 230 | "type":0, 231 | "addr":"1B21L6VCqaLy9HRHYu76vQ4Y2PbamNXSGj", 232 | "value":35252644, 233 | "n":1, 234 | "script":"76a9146de1f2d8820969f9dbd0310d900e3ec9d9245c4888ac" 235 | } 236 | ] 237 | }, 238 | 239 | { 240 | "lock_time":0, 241 | "ver":1, 242 | "size":300, 243 | "inputs":[ 244 | { 245 | "sequence":4294967295, 246 | "prev_out":{ 247 | "spent":true, 248 | "tx_index":251018984, 249 | "type":0, 250 | "addr":"33dPjT2DrjRBa6hL8xNdDS1anh9CGnc2JP", 251 | "value":300000, 252 | "n":1, 253 | "script":"a9141540caefd7eb26de1fc56c7ffa8c4784f4737eec87" 254 | }, 255 | "script":"0047304402202e848d5d10f302bfcdc443a36f46d2d31fd12daea6bd527327d094a2ce0299f8022008b9deb9611f4a43bf52845fdfc8bfc46b7088d3a3cd79b944e2ae029b641afa0147304402205abf36e332edffb30cb73d0d812f3bb5cf7a9738ec3dcd268b2b9f797337c064022054ce915bc28fdc8d3a8ecce422c94d74b9ea23372ee05957015632e1aadfb9f201475221020ac26563884b7a94892cf4d455470cb926531d2d5fb9926d6d477027303165c221025866ea732eef3017fa944a16af48316e2441e4a3a8bbac521300226f8f4cfb0c52ae" 256 | } 257 | ], 258 | "double_spend":false, 259 | "time":1494882215, 260 | "tx_index":251071134, 261 | "vin_sz":1, 262 | "hash":"79827d7745d8dfa78aa7928e172d4a1c176c8f8a32109f4eace632868ba366b1", 263 | "vout_sz":1, 264 | "relayed_by":"88.99.144.222", 265 | "out":[ 266 | { 267 | "spent":false, 268 | "tx_index":251071134, 269 | "type":0, 270 | "addr":"3HEGhkgoBPnFEe4RqCbnCGKp2yJsTQrNf7", 271 | "value":239300, 272 | "n":0, 273 | "script":"a914aa731e6e109224f382dbe39851b7662186abe2eb87" 274 | } 275 | ] 276 | }, 277 | 278 | { 279 | "lock_time":0, 280 | "ver":1, 281 | "size":225, 282 | "inputs":[ 283 | { 284 | "sequence":4294967295, 285 | "prev_out":{ 286 | "spent":true, 287 | "tx_index":250470010, 288 | "type":0, 289 | "addr":"17RQE4ZDQxFndCZGacBkMv2hXoz6Ao4P6n", 290 | "value":7125000, 291 | "n":1, 292 | "script":"76a914466e200d3b7e281b2e14d43c864d7f8fc24ea7c088ac" 293 | }, 294 | "script":"47304402203dd8b230a5a1ae87e2fe0e2987a25198fd38953beeca59e108aa5362198e398b02207ce3acc7401e11ffd2087da4e98ce6112bc9e80d19f71bc1c06b5ca76a79594d012103726e04f1a6641f30dbe49cf526e191daa0ed91b79cdca86f3b4177b63a1fbbde" 295 | } 296 | ], 297 | "double_spend":false, 298 | "time":1494882215, 299 | "tx_index":251071135, 300 | "vin_sz":1, 301 | "hash":"297bbdba8fe56bf407b62c8e71edafa7bebe3ac082b8b1c55bd434f94e106eed", 302 | "vout_sz":2, 303 | "relayed_by":"88.99.144.222", 304 | "out":[ 305 | { 306 | "spent":false, 307 | "tx_index":251071135, 308 | "type":0, 309 | "addr":"1Fpa6F3xzGC9CV9YBEt1KiNwuacd8Yuxyg", 310 | "value":5797100, 311 | "n":0, 312 | "script":"76a914a290ef02ebdfd1fee11679e6f4646db334b54abb88ac" 313 | }, 314 | { 315 | "spent":false, 316 | "tx_index":251071135, 317 | "type":0, 318 | "addr":"11PagoqBp9FiLHE3wWJtnG47Y41jvoYxc", 319 | "value":1268156, 320 | "n":1, 321 | "script":"76a9140012d978f0aaf679c908a1c9d9b3664e021d18ed88ac" 322 | } 323 | ] 324 | }, 325 | 326 | { 327 | "lock_time":0, 328 | "ver":1, 329 | "size":192, 330 | "inputs":[ 331 | { 332 | "sequence":4294967295, 333 | "prev_out":{ 334 | "spent":true, 335 | "tx_index":249639876, 336 | "type":0, 337 | "addr":"1CKAaswJseRbYwhNQ8GTbfyJV4xYYetorz", 338 | "value":26210, 339 | "n":0, 340 | "script":"76a9147c18df105dde3e540f080a50539f94d94a8c524b88ac" 341 | }, 342 | "script":"483045022100c36ab33ba8607e2f096cd5bb87de02d33a8adae01eea5a14e7d048986b1da6b202201bb6e7914065d7e297a9874635a4d94ca72dd4109ac75d300dcfdb3112b7112a0121033df928eded9fe1b41467cd931cabaebeeee61578de28302f3bfb9721149c0bf9" 343 | } 344 | ], 345 | "double_spend":false, 346 | "time":1494882215, 347 | "tx_index":251067908, 348 | "vin_sz":1, 349 | "hash":"ed1f583eda4cd2f125053cc52f6dcda4bf090d514ed14731ffd105b4576cd0e0", 350 | "vout_sz":1, 351 | "relayed_by":"51.15.77.78", 352 | "out":[ 353 | { 354 | "spent":false, 355 | "tx_index":251067908, 356 | "type":0, 357 | "addr":"1DwZpY6ESxaRvNvq5RCCDkVFQrbvPAtESb", 358 | "value":16210, 359 | "n":0, 360 | "script":"76a9148df35e251612fd6d07a2752bcbb2af36e1e2655288ac" 361 | } 362 | ] 363 | }, 364 | 365 | { 366 | "lock_time":0, 367 | "ver":1, 368 | "size":191, 369 | "inputs":[ 370 | { 371 | "sequence":4294967295, 372 | "prev_out":{ 373 | "spent":true, 374 | "tx_index":249642709, 375 | "type":0, 376 | "addr":"1CKAaswJseRbYwhNQ8GTbfyJV4xYYetorz", 377 | "value":42301, 378 | "n":1, 379 | "script":"76a9147c18df105dde3e540f080a50539f94d94a8c524b88ac" 380 | }, 381 | "script":"4730440220237a3998b55cb1015abc26196abcc5b267990bba1a955a5ee14e9159ca80bdfa02204b966b9bee759e626c41fb11f9e3d7e16c99d4a2c621f26170383fbe9e5736a70121033df928eded9fe1b41467cd931cabaebeeee61578de28302f3bfb9721149c0bf9" 382 | } 383 | ], 384 | "double_spend":false, 385 | "time":1494882215, 386 | "tx_index":251069423, 387 | "vin_sz":1, 388 | "hash":"550bdd658800e39bbcf05d53cd3059a61f0a7bdb2e7cfe5e7966fed511609760", 389 | "vout_sz":1, 390 | "relayed_by":"51.15.77.78", 391 | "out":[ 392 | { 393 | "spent":false, 394 | "tx_index":251069423, 395 | "type":0, 396 | "addr":"1DwZpY6ESxaRvNvq5RCCDkVFQrbvPAtESb", 397 | "value":32301, 398 | "n":0, 399 | "script":"76a9148df35e251612fd6d07a2752bcbb2af36e1e2655288ac" 400 | } 401 | ] 402 | }, 403 | 404 | { 405 | "lock_time":0, 406 | "ver":1, 407 | "size":192, 408 | "inputs":[ 409 | { 410 | "sequence":4294967295, 411 | "prev_out":{ 412 | "spent":true, 413 | "tx_index":249640878, 414 | "type":0, 415 | "addr":"1CKAaswJseRbYwhNQ8GTbfyJV4xYYetorz", 416 | "value":5527309, 417 | "n":1, 418 | "script":"76a9147c18df105dde3e540f080a50539f94d94a8c524b88ac" 419 | }, 420 | "script":"483045022100c1e2e936e4a9aad6b230d2fa93e7ff2463413b4008d831402f219d1a1517abce02200deadab123fd21ed27daeb6c9adbad99ad5b5cbda56ee1c41fe4eb2a847915e10121033df928eded9fe1b41467cd931cabaebeeee61578de28302f3bfb9721149c0bf9" 421 | } 422 | ], 423 | "double_spend":false, 424 | "time":1494882215, 425 | "tx_index":251069424, 426 | "vin_sz":1, 427 | "hash":"717e26e31490419c179b5df69eef2804fcf1c0df0b33d8acb19b9e585398c3be", 428 | "vout_sz":1, 429 | "relayed_by":"51.15.77.78", 430 | "out":[ 431 | { 432 | "spent":false, 433 | "tx_index":251069424, 434 | "type":0, 435 | "addr":"1DwZpY6ESxaRvNvq5RCCDkVFQrbvPAtESb", 436 | "value":5517309, 437 | "n":0, 438 | "script":"76a9148df35e251612fd6d07a2752bcbb2af36e1e2655288ac" 439 | } 440 | ] 441 | }, 442 | 443 | { 444 | "lock_time":466583, 445 | "ver":2, 446 | "size":226, 447 | "inputs":[ 448 | { 449 | "sequence":4294967294, 450 | "prev_out":{ 451 | "spent":true, 452 | "tx_index":251037372, 453 | "type":0, 454 | "addr":"1DhJkf66tyWhgk97eticf3u5serhYugCrT", 455 | "value":290000000, 456 | "n":0, 457 | "script":"76a9148b40f792f03cc13d1314dcf50260fffc8b1f92e688ac" 458 | }, 459 | "script":"483045022100bf4854c7d657fc194272dc1b18227660666d5e164c2a55d90ee72e1fb9cd6ea60220417f805e3f6d56b370fbc606a89492479a0edfdc3c71528a18e247b3db1d18100121030c041f7ef29a50edd9cbb8b7d4f0f39648eb45b36504d0f8e030ccd3484e2046" 460 | } 461 | ], 462 | "double_spend":false, 463 | "time":1494882215, 464 | "tx_index":251067909, 465 | "vin_sz":1, 466 | "hash":"70c21386a9eb45b6c5c1c9b9d8f85f697262370acbd812d0d242e59687466265", 467 | "vout_sz":2, 468 | "relayed_by":"138.201.31.13", 469 | "out":[ 470 | { 471 | "spent":false, 472 | "tx_index":251067909, 473 | "type":0, 474 | "addr":"1E5XAF3SX1GCx1RokrnvNceUGmZWP8QMu1", 475 | "value":11326740, 476 | "n":0, 477 | "script":"76a9148f74798be706ce097f4dd4df36b9b23737efbe2288ac" 478 | }, 479 | { 480 | "spent":false, 481 | "tx_index":251067909, 482 | "type":0, 483 | "addr":"13Kk45oEGt9rFJZFqSXxU3NsUDqXTQP4wb", 484 | "value":278622410, 485 | "n":1, 486 | "script":"76a914197bad86e5b8f23f0dfd32136cb9e9445c7b651788ac" 487 | } 488 | ] 489 | }, 490 | 491 | { 492 | "lock_time":0, 493 | "ver":1, 494 | "size":373, 495 | "inputs":[ 496 | { 497 | "sequence":4294967295, 498 | "prev_out":{ 499 | "spent":true, 500 | "tx_index":251031230, 501 | "type":0, 502 | "addr":"3PG8xdNEYw3SvH84htLvaNPV2V5NaJPE9N", 503 | "value":57994243, 504 | "n":1, 505 | "script":"a914ec9e3dd2dcc70ae6bc00ddb101b3769c6d68503b87" 506 | }, 507 | "script":"00483045022100bc347fa68409ff4c93b8d344b0775c8d6ce36168d6cbbec402371659171319d7022045babb00bc4f8b027839475d78034c18b2b7c5ce157c185497c1a04bc0c283d401483045022100ef0c44e5ad98f164d555e0594d7b180d8948171408228562869b9bd8a1b9e8960220778309f70793f8d33f2013fdccb0478b97bf23328d6fb8b6188857df50868a49014c69522102bf69b532e65cd381850aff1e5dd30d8ffaf75b7c97049c4018e2a1c65f7e8da22102e69156861a946dcbc3ad91d8723a9ac7bc56110378f10472ea76a2a7f1ff39602103db33c172b067a82b573e8977cb7f4727555c104031ef4c43effe90aca59453ff53ae" 508 | } 509 | ], 510 | "double_spend":false, 511 | "time":1494882216, 512 | "tx_index":251069425, 513 | "vin_sz":1, 514 | "hash":"3ff294998cd2dc356110c86a84f1daa624b34dd5f03a2cca81d366410479ee89", 515 | "vout_sz":2, 516 | "relayed_by":"72.234.155.29", 517 | "out":[ 518 | { 519 | "spent":false, 520 | "tx_index":251069425, 521 | "type":0, 522 | "addr":"1FpzCX4tkCWCprMVoCvwRvTJcoUofHMMN8", 523 | "value":650000, 524 | "n":0, 525 | "script":"76a914a2a50ee370ab1b2e8bec4f1c545fa74f0bf0275788ac" 526 | }, 527 | { 528 | "spent":false, 529 | "tx_index":251069425, 530 | "type":0, 531 | "addr":"3JXBX8M9iDF6S8kborg8Lx2jzJEQ2f1fze", 532 | "value":57272008, 533 | "n":1, 534 | "script":"a914b89dfd40d18420a4800df0d7b5bfc62a9021223787" 535 | } 536 | ] 537 | }] 538 | } -------------------------------------------------------------------------------- /bench/data/utf-8-escaped.json: -------------------------------------------------------------------------------- 1 | "\"\\nUTF-8 encoded sample plain-text file\\n\u203E\u203E\u203E\u203E\u203E\u203E\u203E\u203E\u203E\u203E\u203E\u203E\u203E\u203E\u203E\u203E\u203E\u203E\u203E\u203E\u203E\u203E\u203E\u203E\u203E\u203E\u203E\u203E\u203E\u203E\u203E\u203E\u203E\u203E\u203E\u203E\\n\\nMarkus Kuhn [\u02C8ma\u02B3k\u028As ku\u02D0n] \u2014 2002-07-25\\n\\n\\nThe ASCII compatible UTF-8 encoding used in this plain-text file\\nis defined in Unicode, ISO 10646-1, and RFC 2279.\\n\\n\\nUsing Unicode/UTF-8, you can write in emails and source code things such as\\n\\nMathematics and sciences:\\n\\n \u222E E\u22C5da = Q, n \u2192 \u221E, \u2211 f(i) = \u220F g(i), \u23A7\u23A1\u239B\u250C\u2500\u2500\u2500\u2500\u2500\u2510\u239E\u23A4\u23AB\\n \u23AA\u23A2\u239C\u2502a\u00B2+b\u00B3 \u239F\u23A5\u23AA\\n \u2200x\u2208\u211D: \u2308x\u2309 = \u2212\u230A\u2212x\u230B, \u03B1 \u2227 \u00AC\u03B2 = \u00AC(\u00AC\u03B1 \u2228 \u03B2), \u23AA\u23A2\u239C\u2502\u2500\u2500\u2500\u2500\u2500 \u239F\u23A5\u23AA\\n \u23AA\u23A2\u239C\u23B7 c\u2088 \u239F\u23A5\u23AA\\n \u2115 \u2286 \u2115\u2080 \u2282 \u2124 \u2282 \u211A \u2282 \u211D \u2282 \u2102, \u23A8\u23A2\u239C \u239F\u23A5\u23AC\\n \u23AA\u23A2\u239C \u221E \u239F\u23A5\u23AA\\n \u22A5 < a \u2260 b \u2261 c \u2264 d \u226A \u22A4 \u21D2 (\u27E6A\u27E7 \u21D4 \u27EAB\u27EB), \u23AA\u23A2\u239C \u23B2 \u239F\u23A5\u23AA\\n \u23AA\u23A2\u239C \u23B3a\u2071-b\u2071\u239F\u23A5\u23AA\\n 2H\u2082 + O\u2082 \u21CC 2H\u2082O, R = 4.7 k\u03A9, \u2300 200 mm \u23A9\u23A3\u239Di=1 \u23A0\u23A6\u23AD\\n\\nLinguistics and dictionaries:\\n\\n \u00F0i \u0131nt\u0259\u02C8n\u00E6\u0283\u0259n\u0259l f\u0259\u02C8n\u025Bt\u0131k \u0259so\u028Asi\u02C8e\u0131\u0283n\\n Y [\u02C8\u028Fpsil\u0254n], Yen [j\u025Bn], Yoga [\u02C8jo\u02D0g\u0251]\\n\\nAPL:\\n\\n ((V\u2373V)=\u2373\u2374V)/V\u2190,V \u2337\u2190\u2373\u2192\u2374\u2206\u2207\u2283\u203E\u234E\u2355\u2308\\n\\nNicer typography in plain text files:\\n\\n \u2554\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2557\\n \u2551 \u2551\\n \u2551 \u2022 \u2018single\u2019 and \u201Cdouble\u201D quotes \u2551\\n \u2551 \u2551\\n \u2551 \u2022 Curly apostrophes: \u201CWe\u2019ve been here\u201D \u2551\\n \u2551 \u2551\\n \u2551 \u2022 Latin-1 apostrophe and accents: '\u00B4` \u2551\\n \u2551 \u2551\\n \u2551 \u2022 \u201Adeutsche\u2018 \u201EAnf\u00FChrungszeichen\u201C \u2551\\n \u2551 \u2551\\n \u2551 \u2022 \u2020, \u2021, \u2030, \u2022, 3\u20134, \u2014, \u22125/+5, \u2122, \u2026 \u2551\\n \u2551 \u2551\\n \u2551 \u2022 ASCII safety test: 1lI|, 0OD, 8B \u2551\\n \u2551 \u256D\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u256E \u2551\\n \u2551 \u2022 the euro symbol: \u2502 14.95 \u20AC \u2502 \u2551\\n \u2551 \u2570\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u256F \u2551\\n \u255A\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255D\\n\\nCombining characters:\\n\\n STARG\u039B\u030ATE SG-1, a = v\u0307 = r\u0308, a\u20D1 \u22A5 b\u20D1\\n\\nGreek (in Polytonic):\\n\\n The Greek anthem:\\n\\n \u03A3\u1F72 \u03B3\u03BD\u03C9\u03C1\u1F77\u03B6\u03C9 \u1F00\u03C0\u1F78 \u03C4\u1F74\u03BD \u03BA\u1F79\u03C8\u03B7\\n \u03C4\u03BF\u1FE6 \u03C3\u03C0\u03B1\u03B8\u03B9\u03BF\u1FE6 \u03C4\u1F74\u03BD \u03C4\u03C1\u03BF\u03BC\u03B5\u03C1\u1F75,\\n \u03C3\u1F72 \u03B3\u03BD\u03C9\u03C1\u1F77\u03B6\u03C9 \u1F00\u03C0\u1F78 \u03C4\u1F74\u03BD \u1F44\u03C8\u03B7\\n \u03C0\u03BF\u1F7A \u03BC\u1F72 \u03B2\u1F77\u03B1 \u03BC\u03B5\u03C4\u03C1\u1F71\u03B5\u03B9 \u03C4\u1F74 \u03B3\u1FC6.\\n\\n \u1FBF\u0391\u03C0\u1FBF \u03C4\u1F70 \u03BA\u1F79\u03BA\u03BA\u03B1\u03BB\u03B1 \u03B2\u03B3\u03B1\u03BB\u03BC\u1F73\u03BD\u03B7\\n \u03C4\u1FF6\u03BD \u1FFE\u0395\u03BB\u03BB\u1F75\u03BD\u03C9\u03BD \u03C4\u1F70 \u1F31\u03B5\u03C1\u1F71\\n \u03BA\u03B1\u1F76 \u03C3\u1F70\u03BD \u03C0\u03C1\u1FF6\u03C4\u03B1 \u1F00\u03BD\u03B4\u03C1\u03B5\u03B9\u03C9\u03BC\u1F73\u03BD\u03B7\\n \u03C7\u03B1\u1FD6\u03C1\u03B5, \u1F66 \u03C7\u03B1\u1FD6\u03C1\u03B5, \u1FBF\u0395\u03BB\u03B5\u03C5\u03B8\u03B5\u03C1\u03B9\u1F71!\\n\\n From a speech of Demosthenes in the 4th century BC:\\n\\n \u039F\u1F50\u03C7\u1F76 \u03C4\u03B1\u1F50\u03C4\u1F70 \u03C0\u03B1\u03C1\u1F77\u03C3\u03C4\u03B1\u03C4\u03B1\u1F77 \u03BC\u03BF\u03B9 \u03B3\u03B9\u03B3\u03BD\u1F7D\u03C3\u03BA\u03B5\u03B9\u03BD, \u1F66 \u1F04\u03BD\u03B4\u03C1\u03B5\u03C2 \u1FBF\u0391\u03B8\u03B7\u03BD\u03B1\u1FD6\u03BF\u03B9,\\n \u1F45\u03C4\u03B1\u03BD \u03C4\u1FBF \u03B5\u1F30\u03C2 \u03C4\u1F70 \u03C0\u03C1\u1F71\u03B3\u03BC\u03B1\u03C4\u03B1 \u1F00\u03C0\u03BF\u03B2\u03BB\u1F73\u03C8\u03C9 \u03BA\u03B1\u1F76 \u1F45\u03C4\u03B1\u03BD \u03C0\u03C1\u1F78\u03C2 \u03C4\u03BF\u1F7A\u03C2\\n \u03BB\u1F79\u03B3\u03BF\u03C5\u03C2 \u03BF\u1F53\u03C2 \u1F00\u03BA\u03BF\u1F7B\u03C9\u0387 \u03C4\u03BF\u1F7A\u03C2 \u03BC\u1F72\u03BD \u03B3\u1F70\u03C1 \u03BB\u1F79\u03B3\u03BF\u03C5\u03C2 \u03C0\u03B5\u03C1\u1F76 \u03C4\u03BF\u1FE6\\n \u03C4\u03B9\u03BC\u03C9\u03C1\u1F75\u03C3\u03B1\u03C3\u03B8\u03B1\u03B9 \u03A6\u1F77\u03BB\u03B9\u03C0\u03C0\u03BF\u03BD \u1F41\u03C1\u1FF6 \u03B3\u03B9\u03B3\u03BD\u03BF\u03BC\u1F73\u03BD\u03BF\u03C5\u03C2, \u03C4\u1F70 \u03B4\u1F72 \u03C0\u03C1\u1F71\u03B3\u03BC\u03B1\u03C4\u1FBF\\n \u03B5\u1F30\u03C2 \u03C4\u03BF\u1FE6\u03C4\u03BF \u03C0\u03C1\u03BF\u1F75\u03BA\u03BF\u03BD\u03C4\u03B1, \u1F65\u03C3\u03B8\u1FBF \u1F45\u03C0\u03C9\u03C2 \u03BC\u1F74 \u03C0\u03B5\u03B9\u03C3\u1F79\u03BC\u03B5\u03B8\u1FBF \u03B1\u1F50\u03C4\u03BF\u1F76\\n \u03C0\u03C1\u1F79\u03C4\u03B5\u03C1\u03BF\u03BD \u03BA\u03B1\u03BA\u1FF6\u03C2 \u03C3\u03BA\u1F73\u03C8\u03B1\u03C3\u03B8\u03B1\u03B9 \u03B4\u1F73\u03BF\u03BD. \u03BF\u1F50\u03B4\u1F73\u03BD \u03BF\u1F56\u03BD \u1F04\u03BB\u03BB\u03BF \u03BC\u03BF\u03B9 \u03B4\u03BF\u03BA\u03BF\u1FE6\u03C3\u03B9\u03BD\\n \u03BF\u1F31 \u03C4\u1F70 \u03C4\u03BF\u03B9\u03B1\u1FE6\u03C4\u03B1 \u03BB\u1F73\u03B3\u03BF\u03BD\u03C4\u03B5\u03C2 \u1F22 \u03C4\u1F74\u03BD \u1F51\u03C0\u1F79\u03B8\u03B5\u03C3\u03B9\u03BD, \u03C0\u03B5\u03C1\u1F76 \u1F27\u03C2 \u03B2\u03BF\u03C5\u03BB\u03B5\u1F7B\u03B5\u03C3\u03B8\u03B1\u03B9,\\n \u03BF\u1F50\u03C7\u1F76 \u03C4\u1F74\u03BD \u03BF\u1F56\u03C3\u03B1\u03BD \u03C0\u03B1\u03C1\u03B9\u03C3\u03C4\u1F71\u03BD\u03C4\u03B5\u03C2 \u1F51\u03BC\u1FD6\u03BD \u1F01\u03BC\u03B1\u03C1\u03C4\u1F71\u03BD\u03B5\u03B9\u03BD. \u1F10\u03B3\u1F7C \u03B4\u1F73, \u1F45\u03C4\u03B9 \u03BC\u1F73\u03BD\\n \u03C0\u03BF\u03C4\u1FBF \u1F10\u03BE\u1FC6\u03BD \u03C4\u1FC7 \u03C0\u1F79\u03BB\u03B5\u03B9 \u03BA\u03B1\u1F76 \u03C4\u1F70 \u03B1\u1F51\u03C4\u1FC6\u03C2 \u1F14\u03C7\u03B5\u03B9\u03BD \u1F00\u03C3\u03C6\u03B1\u03BB\u1FF6\u03C2 \u03BA\u03B1\u1F76 \u03A6\u1F77\u03BB\u03B9\u03C0\u03C0\u03BF\u03BD\\n \u03C4\u03B9\u03BC\u03C9\u03C1\u1F75\u03C3\u03B1\u03C3\u03B8\u03B1\u03B9, \u03BA\u03B1\u1F76 \u03BC\u1F71\u03BB\u1FBF \u1F00\u03BA\u03C1\u03B9\u03B2\u1FF6\u03C2 \u03BF\u1F36\u03B4\u03B1\u0387 \u1F10\u03C0\u1FBF \u1F10\u03BC\u03BF\u1FE6 \u03B3\u1F71\u03C1, \u03BF\u1F50 \u03C0\u1F71\u03BB\u03B1\u03B9\\n \u03B3\u1F73\u03B3\u03BF\u03BD\u03B5\u03BD \u03C4\u03B1\u1FE6\u03C4\u1FBF \u1F00\u03BC\u03C6\u1F79\u03C4\u03B5\u03C1\u03B1\u0387 \u03BD\u1FE6\u03BD \u03BC\u1F73\u03BD\u03C4\u03BF\u03B9 \u03C0\u1F73\u03C0\u03B5\u03B9\u03C3\u03BC\u03B1\u03B9 \u03C4\u03BF\u1FE6\u03B8\u1FBF \u1F31\u03BA\u03B1\u03BD\u1F78\u03BD\\n \u03C0\u03C1\u03BF\u03BB\u03B1\u03B2\u03B5\u1FD6\u03BD \u1F21\u03BC\u1FD6\u03BD \u03B5\u1F36\u03BD\u03B1\u03B9 \u03C4\u1F74\u03BD \u03C0\u03C1\u1F7D\u03C4\u03B7\u03BD, \u1F45\u03C0\u03C9\u03C2 \u03C4\u03BF\u1F7A\u03C2 \u03C3\u03C5\u03BC\u03BC\u1F71\u03C7\u03BF\u03C5\u03C2\\n \u03C3\u1F7D\u03C3\u03BF\u03BC\u03B5\u03BD. \u1F10\u1F70\u03BD \u03B3\u1F70\u03C1 \u03C4\u03BF\u1FE6\u03C4\u03BF \u03B2\u03B5\u03B2\u03B1\u1F77\u03C9\u03C2 \u1F51\u03C0\u1F71\u03C1\u03BE\u1FC3, \u03C4\u1F79\u03C4\u03B5 \u03BA\u03B1\u1F76 \u03C0\u03B5\u03C1\u1F76 \u03C4\u03BF\u1FE6\\n \u03C4\u1F77\u03BD\u03B1 \u03C4\u03B9\u03BC\u03C9\u03C1\u1F75\u03C3\u03B5\u03C4\u03B1\u1F77 \u03C4\u03B9\u03C2 \u03BA\u03B1\u1F76 \u1F43\u03BD \u03C4\u03C1\u1F79\u03C0\u03BF\u03BD \u1F10\u03BE\u1F73\u03C3\u03C4\u03B1\u03B9 \u03C3\u03BA\u03BF\u03C0\u03B5\u1FD6\u03BD\u0387 \u03C0\u03C1\u1F76\u03BD \u03B4\u1F72\\n \u03C4\u1F74\u03BD \u1F00\u03C1\u03C7\u1F74\u03BD \u1F40\u03C1\u03B8\u1FF6\u03C2 \u1F51\u03C0\u03BF\u03B8\u1F73\u03C3\u03B8\u03B1\u03B9, \u03BC\u1F71\u03C4\u03B1\u03B9\u03BF\u03BD \u1F21\u03B3\u03BF\u1FE6\u03BC\u03B1\u03B9 \u03C0\u03B5\u03C1\u1F76 \u03C4\u1FC6\u03C2\\n \u03C4\u03B5\u03BB\u03B5\u03C5\u03C4\u1FC6\u03C2 \u1F41\u03BD\u03C4\u03B9\u03BD\u03BF\u1FE6\u03BD \u03C0\u03BF\u03B9\u03B5\u1FD6\u03C3\u03B8\u03B1\u03B9 \u03BB\u1F79\u03B3\u03BF\u03BD.\\n\\n \u0394\u03B7\u03BC\u03BF\u03C3\u03B8\u1F73\u03BD\u03BF\u03C5\u03C2, \u0393\u1FFD \u1FBF\u039F\u03BB\u03C5\u03BD\u03B8\u03B9\u03B1\u03BA\u1F78\u03C2\\n\\nGeorgian:\\n\\n From a Unicode conference invitation:\\n\\n \u10D2\u10D7\u10EE\u10DD\u10D5\u10D7 \u10D0\u10EE\u10DA\u10D0\u10D5\u10D4 \u10D2\u10D0\u10D8\u10D0\u10E0\u10DD\u10D7 \u10E0\u10D4\u10D2\u10D8\u10E1\u10E2\u10E0\u10D0\u10EA\u10D8\u10D0 Unicode-\u10D8\u10E1 \u10DB\u10D4\u10D0\u10D7\u10D4 \u10E1\u10D0\u10D4\u10E0\u10D7\u10D0\u10E8\u10DD\u10E0\u10D8\u10E1\u10DD\\n \u10D9\u10DD\u10DC\u10E4\u10D4\u10E0\u10D4\u10DC\u10EA\u10D8\u10D0\u10D6\u10D4 \u10D3\u10D0\u10E1\u10D0\u10E1\u10EC\u10E0\u10D4\u10D1\u10D0\u10D3, \u10E0\u10DD\u10DB\u10D4\u10DA\u10D8\u10EA \u10D2\u10D0\u10D8\u10DB\u10D0\u10E0\u10D7\u10D4\u10D1\u10D0 10-12 \u10DB\u10D0\u10E0\u10E2\u10E1,\\n \u10E5. \u10DB\u10D0\u10D8\u10DC\u10EA\u10E8\u10D8, \u10D2\u10D4\u10E0\u10DB\u10D0\u10DC\u10D8\u10D0\u10E8\u10D8. \u10D9\u10DD\u10DC\u10E4\u10D4\u10E0\u10D4\u10DC\u10EA\u10D8\u10D0 \u10E8\u10D4\u10F0\u10D9\u10E0\u10D4\u10D1\u10E1 \u10D4\u10E0\u10D7\u10D0\u10D3 \u10DB\u10E1\u10DD\u10E4\u10DA\u10D8\u10DD\u10E1\\n \u10D4\u10E5\u10E1\u10DE\u10D4\u10E0\u10E2\u10D4\u10D1\u10E1 \u10D8\u10E1\u10D4\u10D7 \u10D3\u10D0\u10E0\u10D2\u10D4\u10D1\u10E8\u10D8 \u10E0\u10DD\u10D2\u10DD\u10E0\u10D8\u10EA\u10D0\u10D0 \u10D8\u10DC\u10E2\u10D4\u10E0\u10DC\u10D4\u10E2\u10D8 \u10D3\u10D0 Unicode-\u10D8,\\n \u10D8\u10DC\u10E2\u10D4\u10E0\u10DC\u10D0\u10EA\u10D8\u10DD\u10DC\u10D0\u10DA\u10D8\u10D6\u10D0\u10EA\u10D8\u10D0 \u10D3\u10D0 \u10DA\u10DD\u10D9\u10D0\u10DA\u10D8\u10D6\u10D0\u10EA\u10D8\u10D0, Unicode-\u10D8\u10E1 \u10D2\u10D0\u10DB\u10DD\u10E7\u10D4\u10DC\u10D4\u10D1\u10D0\\n \u10DD\u10DE\u10D4\u10E0\u10D0\u10EA\u10D8\u10E3\u10DA \u10E1\u10D8\u10E1\u10E2\u10D4\u10DB\u10D4\u10D1\u10E1\u10D0, \u10D3\u10D0 \u10D2\u10D0\u10DB\u10DD\u10E7\u10D4\u10DC\u10D4\u10D1\u10D8\u10D7 \u10DE\u10E0\u10DD\u10D2\u10E0\u10D0\u10DB\u10D4\u10D1\u10E8\u10D8, \u10E8\u10E0\u10D8\u10E4\u10E2\u10D4\u10D1\u10E8\u10D8,\\n \u10E2\u10D4\u10E5\u10E1\u10E2\u10D4\u10D1\u10D8\u10E1 \u10D3\u10D0\u10DB\u10E3\u10E8\u10D0\u10D5\u10D4\u10D1\u10D0\u10E1\u10D0 \u10D3\u10D0 \u10DB\u10E0\u10D0\u10D5\u10D0\u10DA\u10D4\u10DC\u10DD\u10D5\u10D0\u10DC \u10D9\u10DD\u10DB\u10DE\u10D8\u10E3\u10E2\u10D4\u10E0\u10E3\u10DA \u10E1\u10D8\u10E1\u10E2\u10D4\u10DB\u10D4\u10D1\u10E8\u10D8.\\n\\nRussian:\\n\\n From a Unicode conference invitation:\\n\\n \u0417\u0430\u0440\u0435\u0433\u0438\u0441\u0442\u0440\u0438\u0440\u0443\u0439\u0442\u0435\u0441\u044C \u0441\u0435\u0439\u0447\u0430\u0441 \u043D\u0430 \u0414\u0435\u0441\u044F\u0442\u0443\u044E \u041C\u0435\u0436\u0434\u0443\u043D\u0430\u0440\u043E\u0434\u043D\u0443\u044E \u041A\u043E\u043D\u0444\u0435\u0440\u0435\u043D\u0446\u0438\u044E \u043F\u043E\\n Unicode, \u043A\u043E\u0442\u043E\u0440\u0430\u044F \u0441\u043E\u0441\u0442\u043E\u0438\u0442\u0441\u044F 10-12 \u043C\u0430\u0440\u0442\u0430 1997 \u0433\u043E\u0434\u0430 \u0432 \u041C\u0430\u0439\u043D\u0446\u0435 \u0432 \u0413\u0435\u0440\u043C\u0430\u043D\u0438\u0438.\\n \u041A\u043E\u043D\u0444\u0435\u0440\u0435\u043D\u0446\u0438\u044F \u0441\u043E\u0431\u0435\u0440\u0435\u0442 \u0448\u0438\u0440\u043E\u043A\u0438\u0439 \u043A\u0440\u0443\u0433 \u044D\u043A\u0441\u043F\u0435\u0440\u0442\u043E\u0432 \u043F\u043E \u0432\u043E\u043F\u0440\u043E\u0441\u0430\u043C \u0433\u043B\u043E\u0431\u0430\u043B\u044C\u043D\u043E\u0433\u043E\\n \u0418\u043D\u0442\u0435\u0440\u043D\u0435\u0442\u0430 \u0438 Unicode, \u043B\u043E\u043A\u0430\u043B\u0438\u0437\u0430\u0446\u0438\u0438 \u0438 \u0438\u043D\u0442\u0435\u0440\u043D\u0430\u0446\u0438\u043E\u043D\u0430\u043B\u0438\u0437\u0430\u0446\u0438\u0438, \u0432\u043E\u043F\u043B\u043E\u0449\u0435\u043D\u0438\u044E \u0438\\n \u043F\u0440\u0438\u043C\u0435\u043D\u0435\u043D\u0438\u044E Unicode \u0432 \u0440\u0430\u0437\u043B\u0438\u0447\u043D\u044B\u0445 \u043E\u043F\u0435\u0440\u0430\u0446\u0438\u043E\u043D\u043D\u044B\u0445 \u0441\u0438\u0441\u0442\u0435\u043C\u0430\u0445 \u0438 \u043F\u0440\u043E\u0433\u0440\u0430\u043C\u043C\u043D\u044B\u0445\\n \u043F\u0440\u0438\u043B\u043E\u0436\u0435\u043D\u0438\u044F\u0445, \u0448\u0440\u0438\u0444\u0442\u0430\u0445, \u0432\u0435\u0440\u0441\u0442\u043A\u0435 \u0438 \u043C\u043D\u043E\u0433\u043E\u044F\u0437\u044B\u0447\u043D\u044B\u0445 \u043A\u043E\u043C\u043F\u044C\u044E\u0442\u0435\u0440\u043D\u044B\u0445 \u0441\u0438\u0441\u0442\u0435\u043C\u0430\u0445.\\n\\nThai (UCS Level 2):\\n\\n Excerpt from a poetry on The Romance of The Three Kingdoms (a Chinese\\n classic 'San Gua'):\\n\\n [----------------------------|------------------------]\\n \u0E4F \u0E41\u0E1C\u0E48\u0E19\u0E14\u0E34\u0E19\u0E2E\u0E31\u0E48\u0E19\u0E40\u0E2A\u0E37\u0E48\u0E2D\u0E21\u0E42\u0E17\u0E23\u0E21\u0E41\u0E2A\u0E19\u0E2A\u0E31\u0E07\u0E40\u0E27\u0E0A \u0E1E\u0E23\u0E30\u0E1B\u0E01\u0E40\u0E01\u0E28\u0E01\u0E2D\u0E07\u0E1A\u0E39\u0E4A\u0E01\u0E39\u0E49\u0E02\u0E36\u0E49\u0E19\u0E43\u0E2B\u0E21\u0E48\\n \u0E2A\u0E34\u0E1A\u0E2A\u0E2D\u0E07\u0E01\u0E29\u0E31\u0E15\u0E23\u0E34\u0E22\u0E4C\u0E01\u0E48\u0E2D\u0E19\u0E2B\u0E19\u0E49\u0E32\u0E41\u0E25\u0E16\u0E31\u0E14\u0E44\u0E1B \u0E2A\u0E2D\u0E07\u0E2D\u0E07\u0E04\u0E4C\u0E44\u0E0B\u0E23\u0E49\u0E42\u0E07\u0E48\u0E40\u0E02\u0E25\u0E32\u0E40\u0E1A\u0E32\u0E1B\u0E31\u0E0D\u0E0D\u0E32\\n \u0E17\u0E23\u0E07\u0E19\u0E31\u0E1A\u0E16\u0E37\u0E2D\u0E02\u0E31\u0E19\u0E17\u0E35\u0E40\u0E1B\u0E47\u0E19\u0E17\u0E35\u0E48\u0E1E\u0E36\u0E48\u0E07 \u0E1A\u0E49\u0E32\u0E19\u0E40\u0E21\u0E37\u0E2D\u0E07\u0E08\u0E36\u0E07\u0E27\u0E34\u0E1B\u0E23\u0E34\u0E15\u0E40\u0E1B\u0E47\u0E19\u0E19\u0E31\u0E01\u0E2B\u0E19\u0E32\\n \u0E42\u0E2E\u0E08\u0E34\u0E4B\u0E19\u0E40\u0E23\u0E35\u0E22\u0E01\u0E17\u0E31\u0E1E\u0E17\u0E31\u0E48\u0E27\u0E2B\u0E31\u0E27\u0E40\u0E21\u0E37\u0E2D\u0E07\u0E21\u0E32 \u0E2B\u0E21\u0E32\u0E22\u0E08\u0E30\u0E06\u0E48\u0E32\u0E21\u0E14\u0E0A\u0E31\u0E48\u0E27\u0E15\u0E31\u0E27\u0E2A\u0E33\u0E04\u0E31\u0E0D\\n \u0E40\u0E2B\u0E21\u0E37\u0E2D\u0E19\u0E02\u0E31\u0E1A\u0E44\u0E2A\u0E44\u0E25\u0E48\u0E40\u0E2A\u0E37\u0E2D\u0E08\u0E32\u0E01\u0E40\u0E04\u0E2B\u0E32 \u0E23\u0E31\u0E1A\u0E2B\u0E21\u0E32\u0E1B\u0E48\u0E32\u0E40\u0E02\u0E49\u0E32\u0E21\u0E32\u0E40\u0E25\u0E22\u0E2D\u0E32\u0E2A\u0E31\u0E0D\\n \u0E1D\u0E48\u0E32\u0E22\u0E2D\u0E49\u0E2D\u0E07\u0E2D\u0E38\u0E49\u0E19\u0E22\u0E38\u0E41\u0E22\u0E01\u0E43\u0E2B\u0E49\u0E41\u0E15\u0E01\u0E01\u0E31\u0E19 \u0E43\u0E0A\u0E49\u0E2A\u0E32\u0E27\u0E19\u0E31\u0E49\u0E19\u0E40\u0E1B\u0E47\u0E19\u0E0A\u0E19\u0E27\u0E19\u0E0A\u0E37\u0E48\u0E19\u0E0A\u0E27\u0E19\u0E43\u0E08\\n \u0E1E\u0E25\u0E31\u0E19\u0E25\u0E34\u0E09\u0E38\u0E22\u0E01\u0E38\u0E22\u0E01\u0E35\u0E01\u0E25\u0E31\u0E1A\u0E01\u0E48\u0E2D\u0E40\u0E2B\u0E15\u0E38 \u0E0A\u0E48\u0E32\u0E07\u0E2D\u0E32\u0E40\u0E1E\u0E28\u0E08\u0E23\u0E34\u0E07\u0E2B\u0E19\u0E32\u0E1F\u0E49\u0E32\u0E23\u0E49\u0E2D\u0E07\u0E44\u0E2B\u0E49\\n \u0E15\u0E49\u0E2D\u0E07\u0E23\u0E1A\u0E23\u0E32\u0E06\u0E48\u0E32\u0E1F\u0E31\u0E19\u0E08\u0E19\u0E1A\u0E23\u0E23\u0E25\u0E31\u0E22 \u0E24\u0E45\u0E2B\u0E32\u0E43\u0E04\u0E23\u0E04\u0E49\u0E33\u0E0A\u0E39\u0E01\u0E39\u0E49\u0E1A\u0E23\u0E23\u0E25\u0E31\u0E07\u0E01\u0E4C \u0E2F\\n\\n (The above is a two-column text. If combining characters are handled\\n correctly, the lines of the second column should be aligned with the\\n | character above.)\\n\\nEthiopian:\\n\\n Proverbs in the Amharic language:\\n\\n \u1230\u121B\u12ED \u12A0\u12ED\u1273\u1228\u1235 \u1295\u1309\u1225 \u12A0\u12ED\u12A8\u1230\u1235\u1362\\n \u1265\u120B \u12AB\u1208\u129D \u12A5\u1295\u12F0\u12A0\u1263\u1274 \u1260\u1246\u1218\u1320\u129D\u1362\\n \u130C\u1325 \u12EB\u1208\u1264\u1271 \u1241\u121D\u1325\u1293 \u1290\u12CD\u1362\\n \u12F0\u1200 \u1260\u1215\u120D\u1219 \u1245\u1264 \u1263\u12ED\u1320\u1323 \u1295\u1323\u1275 \u1260\u1308\u12F0\u1208\u12CD\u1362\\n \u12E8\u12A0\u134D \u12C8\u1208\u121D\u1273 \u1260\u1245\u1264 \u12A0\u12ED\u1273\u123D\u121D\u1362\\n \u12A0\u12ED\u1325 \u1260\u1260\u120B \u12F3\u12CB \u1270\u1218\u1273\u1362\\n \u1232\u1270\u1228\u1309\u1219 \u12ED\u12F0\u1228\u130D\u1219\u1362\\n \u1240\u1235 \u1260\u1240\u1235\u1365 \u12D5\u1295\u1241\u120B\u120D \u1260\u12A5\u130D\u1229 \u12ED\u1204\u12F3\u120D\u1362\\n \u12F5\u122D \u1262\u12EB\u1265\u122D \u12A0\u1295\u1260\u1233 \u12EB\u1235\u122D\u1362\\n \u1230\u12CD \u12A5\u1295\u12F0\u1264\u1271 \u12A5\u1295\u1305 \u12A5\u1295\u12F0 \u1309\u1228\u1264\u1271 \u12A0\u12ED\u1270\u12F3\u12F0\u122D\u121D\u1362\\n \u12A5\u130D\u12DC\u122D \u12E8\u12A8\u1348\u1270\u12CD\u1295 \u1309\u122E\u122E \u1233\u12ED\u12D8\u130B\u12CD \u12A0\u12ED\u12F5\u122D\u121D\u1362\\n \u12E8\u130E\u1228\u1264\u1275 \u120C\u1263\u1365 \u1262\u12EB\u12E9\u1275 \u12ED\u1235\u1245 \u1263\u12EB\u12E9\u1275 \u12EB\u1320\u120D\u1245\u1362\\n \u1225\u122B \u12A8\u1218\u134D\u1273\u1275 \u120D\u1304\u1295 \u120B\u134B\u1273\u1275\u1362\\n \u12D3\u1263\u12ED \u121B\u12F0\u122A\u12EB \u12E8\u1208\u12CD\u1365 \u130D\u1295\u12F5 \u12ED\u12DE \u12ED\u12DE\u122B\u120D\u1362\\n \u12E8\u12A5\u1235\u120B\u121D \u12A0\u1308\u1229 \u1218\u12AB \u12E8\u12A0\u121E\u122B \u12A0\u1308\u1229 \u12CB\u122D\u12AB\u1362\\n \u1270\u1295\u130B\u120E \u1262\u1270\u1349 \u1270\u1218\u120D\u1236 \u1263\u1349\u1362\\n \u12C8\u12F3\u1305\u1205 \u121B\u122D \u1262\u1206\u1295 \u1328\u122D\u1235\u1205 \u12A0\u1275\u120B\u1230\u12CD\u1362\\n \u12A5\u130D\u122D\u1205\u1295 \u1260\u134D\u122B\u123D\u1205 \u120D\u12AD \u12D8\u122D\u130B\u1362\\n\\nRunes:\\n\\n \u16BB\u16D6 \u16B3\u16B9\u16AB\u16A6 \u16A6\u16AB\u16CF \u16BB\u16D6 \u16D2\u16A2\u16DE\u16D6 \u16A9\u16BE \u16A6\u16AB\u16D7 \u16DA\u16AA\u16BE\u16DE\u16D6 \u16BE\u16A9\u16B1\u16A6\u16B9\u16D6\u16AA\u16B1\u16DE\u16A2\u16D7 \u16B9\u16C1\u16A6 \u16A6\u16AA \u16B9\u16D6\u16E5\u16AB\\n\\n (Old English, which transcribed into Latin reads 'He cwaeth that he\\n bude thaem lande northweardum with tha Westsae.' and means 'He said\\n that he lived in the northern land near the Western Sea.')\\n\\nBraille:\\n\\n \u284C\u2801\u2827\u2811 \u283C\u2801\u2812 \u284D\u281C\u2807\u2811\u2839\u2830\u280E \u2863\u2815\u280C\\n\\n \u284D\u281C\u2807\u2811\u2839 \u283A\u2801\u280E \u2819\u2811\u2801\u2819\u2812 \u281E\u2815 \u2803\u2811\u281B\u2814 \u283A\u280A\u2839\u2832 \u2879\u283B\u2811 \u280A\u280E \u281D\u2815 \u2819\u2833\u2803\u281E\\n \u2831\u2801\u281E\u2811\u2827\u283B \u2801\u2803\u2833\u281E \u2839\u2801\u281E\u2832 \u2879\u2811 \u2817\u2811\u281B\u280A\u280C\u283B \u2815\u280B \u2819\u280A\u280E \u2803\u2825\u2817\u280A\u2801\u2807 \u283A\u2801\u280E\\n \u280E\u280A\u281B\u281D\u282B \u2803\u2839 \u2839\u2811 \u280A\u2807\u283B\u281B\u2839\u280D\u2801\u281D\u2802 \u2839\u2811 \u280A\u2807\u283B\u2805\u2802 \u2839\u2811 \u2825\u281D\u2819\u283B\u281E\u2801\u2805\u283B\u2802\\n \u2801\u281D\u2819 \u2839\u2811 \u2821\u280A\u2811\u280B \u280D\u2833\u2817\u281D\u283B\u2832 \u284E\u280A\u2817\u2815\u2815\u281B\u2811 \u280E\u280A\u281B\u281D\u282B \u280A\u281E\u2832 \u2841\u281D\u2819\\n \u284E\u280A\u2817\u2815\u2815\u281B\u2811\u2830\u280E \u281D\u2801\u280D\u2811 \u283A\u2801\u280E \u281B\u2815\u2815\u2819 \u2825\u280F\u2815\u281D \u2830\u2861\u2801\u281D\u281B\u2811\u2802 \u280B\u2815\u2817 \u2801\u281D\u2839\u2839\u2814\u281B \u2819\u2811\\n \u2821\u2815\u280E\u2811 \u281E\u2815 \u280F\u2825\u281E \u2819\u280A\u280E \u2819\u2801\u281D\u2819 \u281E\u2815\u2832\\n\\n \u2855\u2807\u2819 \u284D\u281C\u2807\u2811\u2839 \u283A\u2801\u280E \u2801\u280E \u2819\u2811\u2801\u2819 \u2801\u280E \u2801 \u2819\u2815\u2815\u2817\u2824\u281D\u2801\u280A\u2807\u2832\\n\\n \u284D\u2814\u2819\u2816 \u284A \u2819\u2815\u281D\u2830\u281E \u280D\u2811\u2801\u281D \u281E\u2815 \u280E\u2801\u2839 \u2839\u2801\u281E \u284A \u2805\u281D\u282A\u2802 \u2815\u280B \u280D\u2839\\n \u282A\u281D \u2805\u281D\u282A\u2807\u282B\u281B\u2811\u2802 \u2831\u2801\u281E \u2839\u283B\u2811 \u280A\u280E \u280F\u281C\u281E\u280A\u280A\u2825\u2807\u281C\u2807\u2839 \u2819\u2811\u2801\u2819 \u2801\u2803\u2833\u281E\\n \u2801 \u2819\u2815\u2815\u2817\u2824\u281D\u2801\u280A\u2807\u2832 \u284A \u280D\u280A\u2823\u281E \u2819\u2801\u2827\u2811 \u2803\u2811\u2832 \u2814\u280A\u2807\u2814\u282B\u2802 \u280D\u2839\u280E\u2811\u2807\u280B\u2802 \u281E\u2815\\n \u2817\u2811\u281B\u281C\u2819 \u2801 \u280A\u2815\u280B\u280B\u2814\u2824\u281D\u2801\u280A\u2807 \u2801\u280E \u2839\u2811 \u2819\u2811\u2801\u2819\u2811\u280C \u280F\u280A\u2811\u280A\u2811 \u2815\u280B \u280A\u2817\u2815\u281D\u280D\u2815\u281D\u281B\u283B\u2839\\n \u2814 \u2839\u2811 \u281E\u2817\u2801\u2819\u2811\u2832 \u2843\u2825\u281E \u2839\u2811 \u283A\u280A\u280E\u2819\u2815\u280D \u2815\u280B \u2833\u2817 \u2801\u281D\u280A\u2811\u280C\u2815\u2817\u280E\\n \u280A\u280E \u2814 \u2839\u2811 \u280E\u280A\u280D\u280A\u2807\u2811\u2806 \u2801\u281D\u2819 \u280D\u2839 \u2825\u281D\u2819\u2801\u2807\u2807\u282A\u282B \u2819\u2801\u281D\u2819\u280E\\n \u2829\u2801\u2807\u2807 \u281D\u2815\u281E \u2819\u280A\u280C\u2825\u2817\u2803 \u280A\u281E\u2802 \u2815\u2817 \u2839\u2811 \u284A\u2833\u281D\u281E\u2817\u2839\u2830\u280E \u2819\u2815\u281D\u2811 \u280B\u2815\u2817\u2832 \u2879\u2833\\n \u283A\u280A\u2807\u2807 \u2839\u283B\u2811\u280B\u2815\u2817\u2811 \u280F\u283B\u280D\u280A\u281E \u280D\u2811 \u281E\u2815 \u2817\u2811\u280F\u2811\u2801\u281E\u2802 \u2811\u280D\u280F\u2819\u2801\u281E\u280A\u280A\u2801\u2807\u2807\u2839\u2802 \u2839\u2801\u281E\\n \u284D\u281C\u2807\u2811\u2839 \u283A\u2801\u280E \u2801\u280E \u2819\u2811\u2801\u2819 \u2801\u280E \u2801 \u2819\u2815\u2815\u2817\u2824\u281D\u2801\u280A\u2807\u2832\\n\\n (The first couple of paragraphs of \\\"A Christmas Carol\\\" by Dickens)\\n\\nCompact font selection example text:\\n\\n ABCDEFGHIJKLMNOPQRSTUVWXYZ /0123456789\\n abcdefghijklmnopqrstuvwxyz \u00A3\u00A9\u00B5\u00C0\u00C6\u00D6\u00DE\u00DF\u00E9\u00F6\u00FF\\n \u2013\u2014\u2018\u201C\u201D\u201E\u2020\u2022\u2026\u2030\u2122\u0153\u0160\u0178\u017E\u20AC \u0391\u0392\u0393\u0394\u03A9\u03B1\u03B2\u03B3\u03B4\u03C9 \u0410\u0411\u0412\u0413\u0414\u0430\u0431\u0432\u0433\u0434\\n \u2200\u2202\u2208\u211D\u2227\u222A\u2261\u221E \u2191\u2197\u21A8\u21BB\u21E3 \u2510\u253C\u2554\u2558\u2591\u25BA\u263A\u2640 \uFB01\uFFFD\u2440\u2082\u1F20\u1E02\u04E5\u1E84\u0250\u02D0\u234E\u05D0\u0531\u10D0\\n\\nGreetings in various languages:\\n\\n Hello world, \u039A\u03B1\u03BB\u03B7\u03BC\u1F73\u03C1\u03B1 \u03BA\u1F79\u03C3\u03BC\u03B5, \u30B3\u30F3\u30CB\u30C1\u30CF\\n\\nBox drawing alignment tests: \u2588\\n \u2589\\n \u2554\u2550\u2550\u2566\u2550\u2550\u2557 \u250C\u2500\u2500\u252C\u2500\u2500\u2510 \u256D\u2500\u2500\u252C\u2500\u2500\u256E \u256D\u2500\u2500\u252C\u2500\u2500\u256E \u250F\u2501\u2501\u2533\u2501\u2501\u2513 \u250E\u2512\u250F\u2511 \u2577 \u257B \u250F\u252F\u2513 \u250C\u2530\u2510 \u258A \u2571\u2572\u2571\u2572\u2573\u2573\u2573\\n \u2551\u250C\u2500\u2568\u2500\u2510\u2551 \u2502\u2554\u2550\u2567\u2550\u2557\u2502 \u2502\u2552\u2550\u256A\u2550\u2555\u2502 \u2502\u2553\u2500\u2541\u2500\u2556\u2502 \u2503\u250C\u2500\u2542\u2500\u2510\u2503 \u2517\u2543\u2544\u2519 \u2576\u253C\u2574\u257A\u254B\u2578\u2520\u253C\u2528 \u251D\u254B\u2525 \u258B \u2572\u2571\u2572\u2571\u2573\u2573\u2573\\n \u2551\u2502\u2572 \u2571\u2502\u2551 \u2502\u2551 \u2551\u2502 \u2502\u2502 \u2502 \u2502\u2502 \u2502\u2551 \u2503 \u2551\u2502 \u2503\u2502 \u257F \u2502\u2503 \u250D\u2545\u2546\u2513 \u2575 \u2579 \u2517\u2537\u251B \u2514\u2538\u2518 \u258C \u2571\u2572\u2571\u2572\u2573\u2573\u2573\\n \u2560\u2561 \u2573 \u255E\u2563 \u251C\u2562 \u255F\u2524 \u251C\u253C\u2500\u253C\u2500\u253C\u2524 \u251C\u256B\u2500\u2542\u2500\u256B\u2524 \u2523\u253F\u257E\u253C\u257C\u253F\u252B \u2515\u251B\u2516\u251A \u250C\u2504\u2504\u2510 \u254E \u250F\u2505\u2505\u2513 \u250B \u258D \u2572\u2571\u2572\u2571\u2573\u2573\u2573\\n \u2551\u2502\u2571 \u2572\u2502\u2551 \u2502\u2551 \u2551\u2502 \u2502\u2502 \u2502 \u2502\u2502 \u2502\u2551 \u2503 \u2551\u2502 \u2503\u2502 \u257D \u2502\u2503 \u2591\u2591\u2592\u2592\u2593\u2593\u2588\u2588 \u250A \u2506 \u254E \u254F \u2507 \u250B \u258E\\n \u2551\u2514\u2500\u2565\u2500\u2518\u2551 \u2502\u255A\u2550\u2564\u2550\u255D\u2502 \u2502\u2558\u2550\u256A\u2550\u255B\u2502 \u2502\u2559\u2500\u2540\u2500\u255C\u2502 \u2503\u2514\u2500\u2542\u2500\u2518\u2503 \u2591\u2591\u2592\u2592\u2593\u2593\u2588\u2588 \u250A \u2506 \u254E \u254F \u2507 \u250B \u258F\\n \u255A\u2550\u2550\u2569\u2550\u2550\u255D \u2514\u2500\u2500\u2534\u2500\u2500\u2518 \u2570\u2500\u2500\u2534\u2500\u2500\u256F \u2570\u2500\u2500\u2534\u2500\u2500\u256F \u2517\u2501\u2501\u253B\u2501\u2501\u251B \u2597\u2584\u2596\u259B\u2580\u259C \u2514\u254C\u254C\u2518 \u254E \u2517\u254D\u254D\u251B \u250B \u2581\u2582\u2583\u2584\u2585\u2586\u2587\u2588\\n \u259D\u2580\u2598\u2599\u2584\u259F\\n\"" -------------------------------------------------------------------------------- /bench/data/utf-8-unescaped.json: -------------------------------------------------------------------------------- 1 | "\nUTF-8 encoded sample plain-text file\n‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾\n\nMarkus Kuhn [ˈmaʳkʊs kuːn] — 2002-07-25\n\n\nThe ASCII compatible UTF-8 encoding used in this plain-text file\nis defined in Unicode, ISO 10646-1, and RFC 2279.\n\n\nUsing Unicode/UTF-8, you can write in emails and source code things such as\n\nMathematics and sciences:\n\n ∮ E⋅da = Q, n → ∞, ∑ f(i) = ∏ g(i), ⎧⎡⎛┌─────┐⎞⎤⎫\n ⎪⎢⎜│a²+b³ ⎟⎥⎪\n ∀x∈ℝ: ⌈x⌉ = −⌊−x⌋, α ∧ ¬β = ¬(¬α ∨ β), ⎪⎢⎜│───── ⎟⎥⎪\n ⎪⎢⎜⎷ c₈ ⎟⎥⎪\n ℕ ⊆ ℕ₀ ⊂ ℤ ⊂ ℚ ⊂ ℝ ⊂ ℂ, ⎨⎢⎜ ⎟⎥⎬\n ⎪⎢⎜ ∞ ⎟⎥⎪\n ⊥ < a ≠ b ≡ c ≤ d ≪ ⊤ ⇒ (⟦A⟧ ⇔ ⟪B⟫), ⎪⎢⎜ ⎲ ⎟⎥⎪\n ⎪⎢⎜ ⎳aⁱ-bⁱ⎟⎥⎪\n 2H₂ + O₂ ⇌ 2H₂O, R = 4.7 kΩ, ⌀ 200 mm ⎩⎣⎝i=1 ⎠⎦⎭\n\nLinguistics and dictionaries:\n\n ði ıntəˈnæʃənəl fəˈnɛtık əsoʊsiˈeıʃn\n Y [ˈʏpsilɔn], Yen [jɛn], Yoga [ˈjoːgɑ]\n\nAPL:\n\n ((V⍳V)=⍳⍴V)/V←,V ⌷←⍳→⍴∆∇⊃‾⍎⍕⌈\n\nNicer typography in plain text files:\n\n ╔══════════════════════════════════════════╗\n ║ ║\n ║ • ‘single’ and “double” quotes ║\n ║ ║\n ║ • Curly apostrophes: “We’ve been here” ║\n ║ ║\n ║ • Latin-1 apostrophe and accents: '´` ║\n ║ ║\n ║ • ‚deutsche‘ „Anführungszeichen“ ║\n ║ ║\n ║ • †, ‡, ‰, •, 3–4, —, −5/+5, ™, … ║\n ║ ║\n ║ • ASCII safety test: 1lI|, 0OD, 8B ║\n ║ ╭─────────╮ ║\n ║ • the euro symbol: │ 14.95 € │ ║\n ║ ╰─────────╯ ║\n ╚══════════════════════════════════════════╝\n\nCombining characters:\n\n STARGΛ̊TE SG-1, a = v̇ = r̈, a⃑ ⊥ b⃑\n\nGreek (in Polytonic):\n\n The Greek anthem:\n\n Σὲ γνωρίζω ἀπὸ τὴν κόψη\n τοῦ σπαθιοῦ τὴν τρομερή,\n σὲ γνωρίζω ἀπὸ τὴν ὄψη\n ποὺ μὲ βία μετράει τὴ γῆ.\n\n ᾿Απ᾿ τὰ κόκκαλα βγαλμένη\n τῶν ῾Ελλήνων τὰ ἱερά\n καὶ σὰν πρῶτα ἀνδρειωμένη\n χαῖρε, ὦ χαῖρε, ᾿Ελευθεριά!\n\n From a speech of Demosthenes in the 4th century BC:\n\n Οὐχὶ ταὐτὰ παρίσταταί μοι γιγνώσκειν, ὦ ἄνδρες ᾿Αθηναῖοι,\n ὅταν τ᾿ εἰς τὰ πράγματα ἀποβλέψω καὶ ὅταν πρὸς τοὺς\n λόγους οὓς ἀκούω· τοὺς μὲν γὰρ λόγους περὶ τοῦ\n τιμωρήσασθαι Φίλιππον ὁρῶ γιγνομένους, τὰ δὲ πράγματ᾿\n εἰς τοῦτο προήκοντα, ὥσθ᾿ ὅπως μὴ πεισόμεθ᾿ αὐτοὶ\n πρότερον κακῶς σκέψασθαι δέον. οὐδέν οὖν ἄλλο μοι δοκοῦσιν\n οἱ τὰ τοιαῦτα λέγοντες ἢ τὴν ὑπόθεσιν, περὶ ἧς βουλεύεσθαι,\n οὐχὶ τὴν οὖσαν παριστάντες ὑμῖν ἁμαρτάνειν. ἐγὼ δέ, ὅτι μέν\n ποτ᾿ ἐξῆν τῇ πόλει καὶ τὰ αὑτῆς ἔχειν ἀσφαλῶς καὶ Φίλιππον\n τιμωρήσασθαι, καὶ μάλ᾿ ἀκριβῶς οἶδα· ἐπ᾿ ἐμοῦ γάρ, οὐ πάλαι\n γέγονεν ταῦτ᾿ ἀμφότερα· νῦν μέντοι πέπεισμαι τοῦθ᾿ ἱκανὸν\n προλαβεῖν ἡμῖν εἶναι τὴν πρώτην, ὅπως τοὺς συμμάχους\n σώσομεν. ἐὰν γὰρ τοῦτο βεβαίως ὑπάρξῃ, τότε καὶ περὶ τοῦ\n τίνα τιμωρήσεταί τις καὶ ὃν τρόπον ἐξέσται σκοπεῖν· πρὶν δὲ\n τὴν ἀρχὴν ὀρθῶς ὑποθέσθαι, μάταιον ἡγοῦμαι περὶ τῆς\n τελευτῆς ὁντινοῦν ποιεῖσθαι λόγον.\n\n Δημοσθένους, Γ´ ᾿Ολυνθιακὸς\n\nGeorgian:\n\n From a Unicode conference invitation:\n\n გთხოვთ ახლავე გაიაროთ რეგისტრაცია Unicode-ის მეათე საერთაშორისო\n კონფერენციაზე დასასწრებად, რომელიც გაიმართება 10-12 მარტს,\n ქ. მაინცში, გერმანიაში. კონფერენცია შეჰკრებს ერთად მსოფლიოს\n ექსპერტებს ისეთ დარგებში როგორიცაა ინტერნეტი და Unicode-ი,\n ინტერნაციონალიზაცია და ლოკალიზაცია, Unicode-ის გამოყენება\n ოპერაციულ სისტემებსა, და გამოყენებით პროგრამებში, შრიფტებში,\n ტექსტების დამუშავებასა და მრავალენოვან კომპიუტერულ სისტემებში.\n\nRussian:\n\n From a Unicode conference invitation:\n\n Зарегистрируйтесь сейчас на Десятую Международную Конференцию по\n Unicode, которая состоится 10-12 марта 1997 года в Майнце в Германии.\n Конференция соберет широкий круг экспертов по вопросам глобального\n Интернета и Unicode, локализации и интернационализации, воплощению и\n применению Unicode в различных операционных системах и программных\n приложениях, шрифтах, верстке и многоязычных компьютерных системах.\n\nThai (UCS Level 2):\n\n Excerpt from a poetry on The Romance of The Three Kingdoms (a Chinese\n classic 'San Gua'):\n\n [----------------------------|------------------------]\n ๏ แผ่นดินฮั่นเสื่อมโทรมแสนสังเวช พระปกเกศกองบู๊กู้ขึ้นใหม่\n สิบสองกษัตริย์ก่อนหน้าแลถัดไป สององค์ไซร้โง่เขลาเบาปัญญา\n ทรงนับถือขันทีเป็นที่พึ่ง บ้านเมืองจึงวิปริตเป็นนักหนา\n โฮจิ๋นเรียกทัพทั่วหัวเมืองมา หมายจะฆ่ามดชั่วตัวสำคัญ\n เหมือนขับไสไล่เสือจากเคหา รับหมาป่าเข้ามาเลยอาสัญ\n ฝ่ายอ้องอุ้นยุแยกให้แตกกัน ใช้สาวนั้นเป็นชนวนชื่นชวนใจ\n พลันลิฉุยกุยกีกลับก่อเหตุ ช่างอาเพศจริงหนาฟ้าร้องไห้\n ต้องรบราฆ่าฟันจนบรรลัย ฤๅหาใครค้ำชูกู้บรรลังก์ ฯ\n\n (The above is a two-column text. If combining characters are handled\n correctly, the lines of the second column should be aligned with the\n | character above.)\n\nEthiopian:\n\n Proverbs in the Amharic language:\n\n ሰማይ አይታረስ ንጉሥ አይከሰስ።\n ብላ ካለኝ እንደአባቴ በቆመጠኝ።\n ጌጥ ያለቤቱ ቁምጥና ነው።\n ደሀ በሕልሙ ቅቤ ባይጠጣ ንጣት በገደለው።\n የአፍ ወለምታ በቅቤ አይታሽም።\n አይጥ በበላ ዳዋ ተመታ።\n ሲተረጉሙ ይደረግሙ።\n ቀስ በቀስ፥ ዕንቁላል በእግሩ ይሄዳል።\n ድር ቢያብር አንበሳ ያስር።\n ሰው እንደቤቱ እንጅ እንደ ጉረቤቱ አይተዳደርም።\n እግዜር የከፈተውን ጉሮሮ ሳይዘጋው አይድርም።\n የጎረቤት ሌባ፥ ቢያዩት ይስቅ ባያዩት ያጠልቅ።\n ሥራ ከመፍታት ልጄን ላፋታት።\n ዓባይ ማደሪያ የለው፥ ግንድ ይዞ ይዞራል።\n የእስላም አገሩ መካ የአሞራ አገሩ ዋርካ።\n ተንጋሎ ቢተፉ ተመልሶ ባፉ።\n ወዳጅህ ማር ቢሆን ጨርስህ አትላሰው።\n እግርህን በፍራሽህ ልክ ዘርጋ።\n\nRunes:\n\n ᚻᛖ ᚳᚹᚫᚦ ᚦᚫᛏ ᚻᛖ ᛒᚢᛞᛖ ᚩᚾ ᚦᚫᛗ ᛚᚪᚾᛞᛖ ᚾᚩᚱᚦᚹᛖᚪᚱᛞᚢᛗ ᚹᛁᚦ ᚦᚪ ᚹᛖᛥᚫ\n\n (Old English, which transcribed into Latin reads 'He cwaeth that he\n bude thaem lande northweardum with tha Westsae.' and means 'He said\n that he lived in the northern land near the Western Sea.')\n\nBraille:\n\n ⡌⠁⠧⠑ ⠼⠁⠒ ⡍⠜⠇⠑⠹⠰⠎ ⡣⠕⠌\n\n ⡍⠜⠇⠑⠹ ⠺⠁⠎ ⠙⠑⠁⠙⠒ ⠞⠕ ⠃⠑⠛⠔ ⠺⠊⠹⠲ ⡹⠻⠑ ⠊⠎ ⠝⠕ ⠙⠳⠃⠞\n ⠱⠁⠞⠑⠧⠻ ⠁⠃⠳⠞ ⠹⠁⠞⠲ ⡹⠑ ⠗⠑⠛⠊⠌⠻ ⠕⠋ ⠙⠊⠎ ⠃⠥⠗⠊⠁⠇ ⠺⠁⠎\n ⠎⠊⠛⠝⠫ ⠃⠹ ⠹⠑ ⠊⠇⠻⠛⠹⠍⠁⠝⠂ ⠹⠑ ⠊⠇⠻⠅⠂ ⠹⠑ ⠥⠝⠙⠻⠞⠁⠅⠻⠂\n ⠁⠝⠙ ⠹⠑ ⠡⠊⠑⠋ ⠍⠳⠗⠝⠻⠲ ⡎⠊⠗⠕⠕⠛⠑ ⠎⠊⠛⠝⠫ ⠊⠞⠲ ⡁⠝⠙\n ⡎⠊⠗⠕⠕⠛⠑⠰⠎ ⠝⠁⠍⠑ ⠺⠁⠎ ⠛⠕⠕⠙ ⠥⠏⠕⠝ ⠰⡡⠁⠝⠛⠑⠂ ⠋⠕⠗ ⠁⠝⠹⠹⠔⠛ ⠙⠑\n ⠡⠕⠎⠑ ⠞⠕ ⠏⠥⠞ ⠙⠊⠎ ⠙⠁⠝⠙ ⠞⠕⠲\n\n ⡕⠇⠙ ⡍⠜⠇⠑⠹ ⠺⠁⠎ ⠁⠎ ⠙⠑⠁⠙ ⠁⠎ ⠁ ⠙⠕⠕⠗⠤⠝⠁⠊⠇⠲\n\n ⡍⠔⠙⠖ ⡊ ⠙⠕⠝⠰⠞ ⠍⠑⠁⠝ ⠞⠕ ⠎⠁⠹ ⠹⠁⠞ ⡊ ⠅⠝⠪⠂ ⠕⠋ ⠍⠹\n ⠪⠝ ⠅⠝⠪⠇⠫⠛⠑⠂ ⠱⠁⠞ ⠹⠻⠑ ⠊⠎ ⠏⠜⠞⠊⠊⠥⠇⠜⠇⠹ ⠙⠑⠁⠙ ⠁⠃⠳⠞\n ⠁ ⠙⠕⠕⠗⠤⠝⠁⠊⠇⠲ ⡊ ⠍⠊⠣⠞ ⠙⠁⠧⠑ ⠃⠑⠲ ⠔⠊⠇⠔⠫⠂ ⠍⠹⠎⠑⠇⠋⠂ ⠞⠕\n ⠗⠑⠛⠜⠙ ⠁ ⠊⠕⠋⠋⠔⠤⠝⠁⠊⠇ ⠁⠎ ⠹⠑ ⠙⠑⠁⠙⠑⠌ ⠏⠊⠑⠊⠑ ⠕⠋ ⠊⠗⠕⠝⠍⠕⠝⠛⠻⠹\n ⠔ ⠹⠑ ⠞⠗⠁⠙⠑⠲ ⡃⠥⠞ ⠹⠑ ⠺⠊⠎⠙⠕⠍ ⠕⠋ ⠳⠗ ⠁⠝⠊⠑⠌⠕⠗⠎\n ⠊⠎ ⠔ ⠹⠑ ⠎⠊⠍⠊⠇⠑⠆ ⠁⠝⠙ ⠍⠹ ⠥⠝⠙⠁⠇⠇⠪⠫ ⠙⠁⠝⠙⠎\n ⠩⠁⠇⠇ ⠝⠕⠞ ⠙⠊⠌⠥⠗⠃ ⠊⠞⠂ ⠕⠗ ⠹⠑ ⡊⠳⠝⠞⠗⠹⠰⠎ ⠙⠕⠝⠑ ⠋⠕⠗⠲ ⡹⠳\n ⠺⠊⠇⠇ ⠹⠻⠑⠋⠕⠗⠑ ⠏⠻⠍⠊⠞ ⠍⠑ ⠞⠕ ⠗⠑⠏⠑⠁⠞⠂ ⠑⠍⠏⠙⠁⠞⠊⠊⠁⠇⠇⠹⠂ ⠹⠁⠞\n ⡍⠜⠇⠑⠹ ⠺⠁⠎ ⠁⠎ ⠙⠑⠁⠙ ⠁⠎ ⠁ ⠙⠕⠕⠗⠤⠝⠁⠊⠇⠲\n\n (The first couple of paragraphs of \"A Christmas Carol\" by Dickens)\n\nCompact font selection example text:\n\n ABCDEFGHIJKLMNOPQRSTUVWXYZ /0123456789\n abcdefghijklmnopqrstuvwxyz £©µÀÆÖÞßéöÿ\n –—‘“”„†•…‰™œŠŸž€ ΑΒΓΔΩαβγδω АБВГДабвгд\n ∀∂∈ℝ∧∪≡∞ ↑↗↨↻⇣ ┐┼╔╘░►☺♀ fi�⑀₂ἠḂӥẄɐː⍎אԱა\n\nGreetings in various languages:\n\n Hello world, Καλημέρα κόσμε, コンニチハ\n\nBox drawing alignment tests: █\n ▉\n ╔══╦══╗ ┌──┬──┐ ╭──┬──╮ ╭──┬──╮ ┏━━┳━━┓ ┎┒┏┑ ╷ ╻ ┏┯┓ ┌┰┐ ▊ ╱╲╱╲╳╳╳\n ║┌─╨─┐║ │╔═╧═╗│ │╒═╪═╕│ │╓─╁─╖│ ┃┌─╂─┐┃ ┗╃╄┙ ╶┼╴╺╋╸┠┼┨ ┝╋┥ ▋ ╲╱╲╱╳╳╳\n ║│╲ ╱│║ │║ ║│ ││ │ ││ │║ ┃ ║│ ┃│ ╿ │┃ ┍╅╆┓ ╵ ╹ ┗┷┛ └┸┘ ▌ ╱╲╱╲╳╳╳\n ╠╡ ╳ ╞╣ ├╢ ╟┤ ├┼─┼─┼┤ ├╫─╂─╫┤ ┣┿╾┼╼┿┫ ┕┛┖┚ ┌┄┄┐ ╎ ┏┅┅┓ ┋ ▍ ╲╱╲╱╳╳╳\n ║│╱ ╲│║ │║ ║│ ││ │ ││ │║ ┃ ║│ ┃│ ╽ │┃ ░░▒▒▓▓██ ┊ ┆ ╎ ╏ ┇ ┋ ▎\n ║└─╥─┘║ │╚═╤═╝│ │╘═╪═╛│ │╙─╀─╜│ ┃└─╂─┘┃ ░░▒▒▓▓██ ┊ ┆ ╎ ╏ ┇ ┋ ▏\n ╚══╩══╝ └──┴──┘ ╰──┴──╯ ╰──┴──╯ ┗━━┻━━┛ ▗▄▖▛▀▜ └╌╌┘ ╎ ┗╍╍┛ ┋ ▁▂▃▄▅▆▇█\n ▝▀▘▙▄▟\n" -------------------------------------------------------------------------------- /bench/run.exs: -------------------------------------------------------------------------------- 1 | defmodule Bench do 2 | alias Benchee.Formatters.{Console, HTML, Markdown} 3 | 4 | def run_decode do 5 | Benchee.run(decode_jobs(), 6 | title: "Decode", 7 | parallel: 4, 8 | warmup: 1, 9 | time: 10, 10 | memory_time: 1, 11 | reduction_time: 1, 12 | pre_check: true, 13 | measure_function_call_overhead: true, 14 | load: Path.join(__DIR__, "decode.benchee"), 15 | save: [path: Path.join(__DIR__, "decode.benchee")], 16 | inputs: 17 | for name <- decode_inputs(), into: %{} do 18 | name 19 | |> read_data() 20 | |> (&{name, &1}).() 21 | end, 22 | formatters: [ 23 | {Console, extended_statistics: true}, 24 | {Markdown, file: Path.expand("output/decode.md", __DIR__)}, 25 | {HTML, auto_open: false, file: Path.expand("output/decode.html", __DIR__)} 26 | ] 27 | ) 28 | end 29 | 30 | def run_encode do 31 | Benchee.run(encode_jobs(), 32 | title: "Encode", 33 | parallel: 4, 34 | warmup: 1, 35 | time: 10, 36 | memory_time: 1, 37 | reduction_time: 1, 38 | pre_check: true, 39 | measure_function_call_overhead: true, 40 | load: Path.join(__DIR__, "encode.benchee"), 41 | save: [path: Path.join(__DIR__, "encode.benchee")], 42 | inputs: 43 | for name <- encode_inputs(), into: %{} do 44 | name 45 | |> read_data() 46 | |> Poison.decode!() 47 | |> (&{name, &1}).() 48 | end, 49 | formatters: [ 50 | {Console, extended_statistics: true}, 51 | {Markdown, file: Path.expand("output/encode.md", __DIR__)}, 52 | {HTML, auto_open: false, file: Path.expand("output/encode.html", __DIR__)} 53 | ] 54 | ) 55 | end 56 | 57 | defp read_data(name) do 58 | name 59 | |> String.downcase() 60 | |> String.replace(~r/([^\w]|-|_)+/, "-") 61 | |> String.trim("-") 62 | |> (&"data/#{&1}.json").() 63 | |> Path.expand(__DIR__) 64 | |> File.read!() 65 | end 66 | 67 | defp decode_jobs do 68 | jobs = %{ 69 | "Jason" => &Jason.decode!/1, 70 | "jiffy" => &:jiffy.decode(&1, [:return_maps, :use_nil]), 71 | "jsone" => &:jsone.decode(&1, [:reject_invalid_utf8, duplicate_map_keys: :last]), 72 | "JSX" => &JSX.decode!(&1, [:strict]), 73 | "Poison" => &Poison.Parser.parse!/1, 74 | "Tiny" => &Tiny.decode!/1, 75 | "Thoas" => &:thoas.decode/1 76 | } 77 | 78 | if Code.ensure_loaded?(:json) do 79 | Map.put(jobs, "json", &:json.decode/1) 80 | else 81 | jobs 82 | end 83 | end 84 | 85 | defp encode_jobs do 86 | jobs = %{ 87 | "Jason" => &Jason.encode_to_iodata!/1, 88 | "jiffy" => &:jiffy.encode(&1, [:use_nil]), 89 | "jsone" => &:jsone.encode/1, 90 | "JSX" => &JSX.encode!(&1, [:strict]), 91 | "Poison" => &Poison.encode_to_iodata!/1, 92 | "Tiny" => &Tiny.encode_to_iodata!/1, 93 | "Thoas" => &:thoas.encode_to_iodata/1 94 | } 95 | 96 | if Code.ensure_loaded?(:json) do 97 | Map.put(jobs, "json", &:json.encode/1) 98 | else 99 | jobs 100 | end 101 | end 102 | 103 | defp decode_inputs do 104 | [ 105 | "Benchee", 106 | "Blockchain", 107 | "GeoJSON", 108 | "Giphy", 109 | "GitHub", 110 | "GovTrack", 111 | "Issue 90", 112 | "JSON Generator (Pretty)", 113 | "JSON Generator", 114 | "Pokedex", 115 | "Reddit", 116 | "UTF-8 escaped", 117 | "UTF-8 unescaped" 118 | ] 119 | end 120 | 121 | defp encode_inputs do 122 | decode_inputs() -- ["JSON Generator (Pretty)"] 123 | end 124 | end 125 | 126 | Bench.run_decode() 127 | Bench.run_encode() 128 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | services: 2 | project: 3 | build: 4 | context: . 5 | args: 6 | MIX_ENV: test 7 | volumes: 8 | - .:/usr/src/project:cached 9 | - /usr/src/project/deps 10 | - /usr/src/project/_build 11 | init: true 12 | -------------------------------------------------------------------------------- /lib/poison.ex: -------------------------------------------------------------------------------- 1 | defmodule Poison do 2 | readme_path = [__DIR__, "..", "README.md"] |> Path.join() |> Path.expand() 3 | 4 | @external_resource readme_path 5 | @moduledoc readme_path |> File.read!() |> String.trim() 6 | 7 | alias Poison.{Decode, DecodeError, Decoder} 8 | alias Poison.{EncodeError, Encoder} 9 | alias Poison.{ParseError, Parser} 10 | 11 | @doc """ 12 | Encode a value to JSON IO data. 13 | 14 | iex> Poison.encode_to_iodata([1, 2, 3]) 15 | {:ok, [91, ["1", 44, "2", 44, "3"], 93]} 16 | 17 | iex> Poison.encode_to_iodata({}) 18 | {:error, %Poison.EncodeError{message: nil, value: {}}} 19 | """ 20 | @spec encode_to_iodata(Encoder.t()) :: {:ok, iodata} | {:error, EncodeError.t()} 21 | @spec encode_to_iodata(Encoder.t(), Encoder.options() | [Encoder.option()]) :: 22 | {:ok, iodata} | {:error, EncodeError.t()} 23 | def encode_to_iodata(value, options \\ %{}) do 24 | {:ok, encode_to_iodata!(value, options)} 25 | rescue 26 | exception in [EncodeError] -> 27 | {:error, exception} 28 | end 29 | 30 | @doc """ 31 | Encode a value to JSON IO data. Raises an exception on error. 32 | 33 | iex> Poison.encode_to_iodata!([1, 2, 3]) 34 | [91, ["1", 44, "2", 44, "3"], 93] 35 | 36 | iex> Poison.encode_to_iodata!({}) 37 | ** (Poison.EncodeError) unable to encode value: {} 38 | """ 39 | @spec encode_to_iodata!(Encoder.t()) :: iodata 40 | @spec encode_to_iodata!(Encoder.t(), Encoder.options() | [Encoder.option()]) :: iodata 41 | def encode_to_iodata!(value, options \\ %{}) 42 | 43 | def encode_to_iodata!(value, options) when is_list(options) do 44 | encode_to_iodata!(value, Map.new(options)) 45 | end 46 | 47 | def encode_to_iodata!(value, options) do 48 | Encoder.encode(value, options) 49 | end 50 | 51 | @doc """ 52 | Encode a value to JSON. 53 | 54 | iex> Poison.encode([1, 2, 3]) 55 | {:ok, "[1,2,3]"} 56 | 57 | iex> Poison.encode({}) 58 | {:error, %Poison.EncodeError{message: nil, value: {}}} 59 | """ 60 | @spec encode(Encoder.t()) :: {:ok, iodata} | {:error, EncodeError.t()} 61 | @spec encode(Encoder.t(), Encoder.options() | [Encoder.option()]) :: 62 | {:ok, iodata} | {:error, EncodeError.t()} 63 | def encode(value, options \\ %{}) do 64 | {:ok, encode!(value, options)} 65 | rescue 66 | exception in [EncodeError] -> 67 | {:error, exception} 68 | end 69 | 70 | @doc """ 71 | Encode a value to JSON. Raises an exception on error. 72 | 73 | iex> Poison.encode!([1, 2, 3]) 74 | "[1,2,3]" 75 | 76 | iex> Poison.encode!({}) 77 | ** (Poison.EncodeError) unable to encode value: {} 78 | """ 79 | @spec encode!(Encoder.t()) :: binary 80 | @spec encode!(Encoder.t(), Encoder.options() | [Encoder.option()]) :: binary 81 | def encode!(value, options \\ %{}) 82 | 83 | def encode!(value, options) when is_list(options) do 84 | encode!(value, Map.new(options)) 85 | end 86 | 87 | def encode!(value, options) do 88 | value |> encode_to_iodata!(options) |> IO.iodata_to_binary() 89 | end 90 | 91 | @doc """ 92 | Decode JSON to a value. 93 | 94 | iex> Poison.decode("[1,2,3]") 95 | {:ok, [1, 2, 3]} 96 | 97 | iex> Poison.decode("[") 98 | {:error, %Poison.ParseError{data: "[", skip: 1, value: nil}} 99 | """ 100 | @spec decode(iodata) :: {:ok, Parser.t()} | {:error, ParseError.t() | DecodeError.t()} 101 | @spec decode(iodata, Decoder.options() | [Decoder.option()]) :: 102 | {:ok, Decoder.t()} | {:error, ParseError.t() | DecodeError.t()} 103 | def decode(iodata, options \\ %{}) do 104 | {:ok, decode!(iodata, options)} 105 | rescue 106 | exception in [ParseError, DecodeError] -> 107 | {:error, exception} 108 | end 109 | 110 | @doc """ 111 | Decode JSON to a value. Raises an exception on error. 112 | 113 | iex> Poison.decode!("[1,2,3]") 114 | [1, 2, 3] 115 | 116 | iex> Poison.decode!("[") 117 | ** (Poison.ParseError) unexpected end of input at position 1 118 | """ 119 | @spec decode!(iodata) :: Parser.t() 120 | @spec decode!(iodata, Decoder.options() | [Decoder.option()]) :: Decoder.t() 121 | def decode!(value, options \\ %{}) 122 | 123 | def decode!(value, options) when is_list(options) do 124 | decode!(value, Map.new(options)) 125 | end 126 | 127 | def decode!(value, %{as: as} = options) when as != nil do 128 | value 129 | |> Parser.parse!(options) 130 | |> Decode.transform(options) 131 | end 132 | 133 | def decode!(value, options) do 134 | Parser.parse!(value, options) 135 | end 136 | end 137 | -------------------------------------------------------------------------------- /lib/poison/decoder.ex: -------------------------------------------------------------------------------- 1 | defmodule Poison.DecodeError do 2 | @type t :: %__MODULE__{message: String.t(), value: any} 3 | 4 | defexception message: nil, value: nil 5 | 6 | def message(%{message: nil, value: value}) do 7 | "unable to decode value: #{inspect(value)}" 8 | end 9 | 10 | def message(%{message: message}) do 11 | message 12 | end 13 | end 14 | 15 | defmodule Poison.Decode do 16 | @moduledoc false 17 | 18 | alias Poison.Decoder 19 | 20 | @compile :inline 21 | @compile :inline_list_funcs 22 | 23 | def transform(value, options) when is_map(value) or is_list(value) do 24 | case Map.get(options, :as) do 25 | nil -> value 26 | as -> transform(value, Map.get(options, :keys), as, options) 27 | end 28 | end 29 | 30 | def transform(value, _options) do 31 | value 32 | end 33 | 34 | defp transform(nil, _keys, _as, _options), do: nil 35 | 36 | defp transform(value, keys, %{__struct__: _} = as, options) do 37 | transform_struct(value, keys, as, options) 38 | end 39 | 40 | defp transform(value, keys, as, options) when is_map(as) do 41 | transform_map(value, keys, as, options) 42 | end 43 | 44 | defp transform(value, keys, [as], options) do 45 | for v <- value, do: transform(v, keys, as, options) 46 | end 47 | 48 | defp transform(value, keys, as, options) when is_function(as, 1) do 49 | transform(value, keys, as.(value), options) 50 | end 51 | 52 | defp transform(value, _keys, _as, _options) do 53 | value 54 | end 55 | 56 | defp transform_map(value, keys, as, options) do 57 | Enum.reduce(as, value, fn {key, as}, acc -> 58 | case Map.get(acc, key) do 59 | value when is_map(value) or is_list(value) -> 60 | Map.put(acc, key, transform(value, keys, as, options)) 61 | 62 | _value -> 63 | acc 64 | end 65 | end) 66 | end 67 | 68 | defp transform_struct(value, keys, as, options) 69 | when keys in [:atoms, :atoms!] do 70 | as 71 | |> Map.from_struct() 72 | |> Map.merge(value) 73 | |> do_transform_struct(keys, as, options) 74 | end 75 | 76 | defp transform_struct(value, keys, as, options) do 77 | as 78 | |> Map.from_struct() 79 | |> Enum.reduce(%{}, fn {key, default}, acc -> 80 | Map.put(acc, key, Map.get(value, Atom.to_string(key), default)) 81 | end) 82 | |> do_transform_struct(keys, as, options) 83 | end 84 | 85 | defp do_transform_struct(value, keys, as, options) do 86 | default = struct(as.__struct__) 87 | 88 | as 89 | |> Map.from_struct() 90 | |> Enum.reduce(%{}, fn {key, as}, acc -> 91 | new_value = 92 | case Map.fetch(value, key) do 93 | {:ok, ^as} when is_map(as) or is_list(as) -> 94 | Map.get(default, key) 95 | 96 | {:ok, value} when is_map(value) or is_list(value) -> 97 | transform(value, keys, as, options) 98 | 99 | {:ok, value} -> 100 | value 101 | 102 | :error -> 103 | Map.get(default, key) 104 | end 105 | 106 | Map.put(acc, key, new_value) 107 | end) 108 | |> Map.put(:__struct__, as.__struct__) 109 | |> Decoder.decode(options) 110 | end 111 | end 112 | 113 | defprotocol Poison.Decoder do 114 | @fallback_to_any true 115 | 116 | @type keys :: :atoms | :atoms! 117 | @type decimal :: boolean 118 | @type as :: map | struct | [as] | (t -> as) 119 | 120 | @type option :: {:keys, keys} | {:decimal, decimal} | {:as, as} 121 | @type options :: %{ 122 | optional(:keys) => keys, 123 | optional(:decimal) => decimal, 124 | optional(:as) => as 125 | } 126 | 127 | @spec decode(t, options) :: any 128 | def decode(value, options) 129 | end 130 | 131 | defimpl Poison.Decoder, for: Any do 132 | def decode(value, _options) do 133 | value 134 | end 135 | end 136 | -------------------------------------------------------------------------------- /lib/poison/encoder.ex: -------------------------------------------------------------------------------- 1 | defmodule Poison.EncodeError do 2 | @type t :: %__MODULE__{message: String.t(), value: any} 3 | 4 | defexception message: nil, value: nil 5 | 6 | def message(%{message: nil, value: value}) do 7 | "unable to encode value: #{inspect(value)}" 8 | end 9 | 10 | def message(%{message: message}) do 11 | message 12 | end 13 | end 14 | 15 | defmodule Poison.Encode do 16 | @moduledoc false 17 | 18 | alias Poison.{EncodeError, Encoder} 19 | 20 | defmacro __using__(_) do 21 | quote do 22 | alias Poison.EncodeError 23 | alias String.Chars 24 | 25 | @compile {:inline, encode_name: 1} 26 | 27 | # Fast path encoding string keys 28 | defp encode_name(value) when is_binary(value) do 29 | value 30 | end 31 | 32 | defp encode_name(value) do 33 | case Chars.impl_for(value) do 34 | nil -> 35 | raise EncodeError, 36 | value: value, 37 | message: "expected a String.Chars encodable value, got: #{inspect(value)}" 38 | 39 | impl -> 40 | impl.to_string(value) 41 | end 42 | end 43 | end 44 | end 45 | end 46 | 47 | defmodule Poison.Pretty do 48 | @moduledoc false 49 | 50 | defmacro __using__(_) do 51 | quote do 52 | @default_indent 2 53 | @default_offset 0 54 | 55 | @compile {:inline, pretty: 1, indent: 1, offset: 1, offset: 2, spaces: 1} 56 | 57 | defp pretty(options) do 58 | Map.get(options, :pretty) == true 59 | end 60 | 61 | defp indent(options) do 62 | Map.get(options, :indent, @default_indent) 63 | end 64 | 65 | defp offset(options) do 66 | Map.get(options, :offset, @default_offset) 67 | end 68 | 69 | defp offset(options, value) do 70 | Map.put(options, :offset, value) 71 | end 72 | 73 | defp spaces(count) do 74 | :binary.copy(" ", count) 75 | end 76 | end 77 | end 78 | end 79 | 80 | defprotocol Poison.Encoder do 81 | @fallback_to_any true 82 | 83 | @type escape :: :unicode | :javascript | :html_safe 84 | @type pretty :: boolean 85 | @type indent :: non_neg_integer 86 | @type offset :: non_neg_integer 87 | @type strict_keys :: boolean 88 | 89 | @type option :: 90 | {:escape, escape} 91 | | {:pretty, pretty} 92 | | {:indent, indent} 93 | | {:offset, offset} 94 | | {:strict_keys, strict_keys} 95 | 96 | @type options :: %{ 97 | optional(:escape) => escape, 98 | optional(:pretty) => pretty, 99 | optional(:indent) => indent, 100 | optional(:offset) => offset, 101 | optional(:strict_keys) => strict_keys 102 | } 103 | 104 | @spec encode(t, options) :: iodata 105 | def encode(value, options) 106 | end 107 | 108 | defimpl Poison.Encoder, for: Atom do 109 | def encode(nil, _options), do: "null" 110 | def encode(true, _options), do: "true" 111 | def encode(false, _options), do: "false" 112 | 113 | def encode(atom, options) do 114 | Poison.Encoder.BitString.encode(Atom.to_string(atom), options) 115 | end 116 | end 117 | 118 | defimpl Poison.Encoder, for: BitString do 119 | import Bitwise 120 | 121 | @compile :inline 122 | @compile :inline_list_funcs 123 | 124 | def encode("", _mode), do: "\"\"" 125 | 126 | def encode(string, options) do 127 | [?", escape(string, Map.get(options, :escape)), ?"] 128 | end 129 | 130 | @compile {:inline, escape: 2} 131 | 132 | defp escape("", _mode), do: [] 133 | 134 | for {char, seq} <- Enum.zip(~c("\\\n\t\r\f\b), ~C("\ntrfb)) do 135 | defp escape(<>, mode) do 136 | [unquote("\\#{<>}") | escape(rest, mode)] 137 | end 138 | end 139 | 140 | # https://en.wikipedia.org/wiki/Unicode_control_characters 141 | defp escape(<>, mode) when char <= 0x1F or char == 0x7F do 142 | [seq(char) | escape(rest, mode)] 143 | end 144 | 145 | defp escape(<>, mode) when char in 0x80..0x9F do 146 | [seq(char) | escape(rest, mode)] 147 | end 148 | 149 | defp escape(<>, mode) when char in 0xD800..0xDFFF do 150 | [seq(char) | escape(rest, mode)] 151 | end 152 | 153 | defp escape(<>, :unicode) when char in 0xA0..0xFFFF do 154 | [seq(char) | escape(rest, :unicode)] 155 | end 156 | 157 | # https://en.wikipedia.org/wiki/UTF-16#U+D800_to_U+DFFF_(surrogates) 158 | # https://unicodebook.readthedocs.io/unicode_encodings.html 159 | defp escape(<>, :unicode) when char > 0xFFFF do 160 | code = char - 0x10000 161 | 162 | [ 163 | seq(0xD800 ||| code >>> 10), 164 | seq(0xDC00 ||| (code &&& 0x3FF)) 165 | | escape(rest, :unicode) 166 | ] 167 | end 168 | 169 | # https://cheatsheetseries.owasp.org/cheatsheets/Cross_Site_Scripting_Prevention_Cheat_Sheet.html 170 | # https://rubydoc.info/docs/rails/ActiveSupport%2FJSON%2FEncoding%2Eescape_html_entities_in_json= 171 | defp escape(<>, :html_safe) do 172 | ["\\u0026" | escape(rest, :html_safe)] 173 | end 174 | 175 | defp escape(<>, :html_safe) do 176 | ["\\u003C" | escape(rest, :html_safe)] 177 | end 178 | 179 | defp escape(<::utf8, rest::bits>>, :html_safe) do 180 | ["\\u003E" | escape(rest, :html_safe)] 181 | end 182 | 183 | defp escape(<<"\u2028"::utf8, rest::bits>>, mode) 184 | when mode in [:html_safe, :javascript] do 185 | ["\\u2028" | escape(rest, mode)] 186 | end 187 | 188 | defp escape(<<"\u2029"::utf8, rest::bits>>, mode) 189 | when mode in [:html_safe, :javascript] do 190 | ["\\u2029" | escape(rest, mode)] 191 | end 192 | 193 | defp escape(string, mode) do 194 | skip = chunk_size(string, mode, 0) 195 | <> = string 196 | [chunk | escape(rest, mode)] 197 | end 198 | 199 | @compile {:inline, chunk_size: 3} 200 | 201 | defp chunk_size(<>, _mode, acc) 202 | when char <= 0x1F or char in ~c("\\) do 203 | acc 204 | end 205 | 206 | defp chunk_size(<>, :html_safe, acc) when char in ~c(&<>) do 207 | acc 208 | end 209 | 210 | defp chunk_size(<>, mode, acc) when char >= 0x20 do 211 | chunk_size(rest, mode, acc + 1) 212 | end 213 | 214 | defp chunk_size(<<_codepoint::utf8, _rest::bits>>, :unicode, acc) do 215 | acc 216 | end 217 | 218 | defp chunk_size(<>, mode, acc) 219 | when mode in [:html_safe, :javascript] and char in [0x2028, 0x2029] do 220 | acc 221 | end 222 | 223 | defp chunk_size(<>, mode, acc) do 224 | chunk_size(rest, mode, acc + codepoint_size(codepoint)) 225 | end 226 | 227 | defp chunk_size(<<>>, _mode, acc), do: acc 228 | 229 | defp chunk_size(other, _mode, _acc) do 230 | raise Poison.EncodeError, value: other 231 | end 232 | 233 | @compile {:inline, seq: 1} 234 | 235 | defp seq(char) do 236 | case Integer.to_charlist(char, 16) do 237 | s when length(s) < 2 -> ["\\u000" | s] 238 | s when length(s) < 3 -> ["\\u00" | s] 239 | s when length(s) < 4 -> ["\\u0" | s] 240 | s -> ["\\u" | s] 241 | end 242 | end 243 | 244 | @compile {:inline, codepoint_size: 1} 245 | 246 | defp codepoint_size(codepoint) do 247 | cond do 248 | codepoint <= 0x7FF -> 2 249 | codepoint <= 0xFFFF -> 3 250 | true -> 4 251 | end 252 | end 253 | end 254 | 255 | defimpl Poison.Encoder, for: Integer do 256 | def encode(integer, _options) do 257 | Integer.to_string(integer) 258 | end 259 | end 260 | 261 | defimpl Poison.Encoder, for: Float do 262 | def encode(float, _options) do 263 | Float.to_string(float) 264 | end 265 | end 266 | 267 | defimpl Poison.Encoder, for: Map do 268 | alias Poison.{Encoder, EncodeError} 269 | 270 | use Poison.{Encode, Pretty} 271 | 272 | @compile :inline 273 | @compile :inline_list_funcs 274 | 275 | def encode(map, _options) when map_size(map) < 1, do: "{}" 276 | 277 | def encode(map, options) do 278 | map 279 | |> strict_keys(Map.get(options, :strict_keys, false)) 280 | |> encode(pretty(options), options) 281 | end 282 | 283 | def encode(map, true, options) do 284 | indent = indent(options) 285 | offset = offset(options) + indent 286 | options = offset(options, offset) 287 | 288 | [ 289 | "{\n", 290 | tl( 291 | :lists.foldl( 292 | &[ 293 | ",\n", 294 | spaces(offset), 295 | Encoder.BitString.encode(encode_name(&1), options), 296 | ": ", 297 | Encoder.encode(Map.fetch!(map, &1), options) | &2 298 | ], 299 | [], 300 | Map.keys(map) 301 | ) 302 | ), 303 | ?\n, 304 | spaces(offset - indent), 305 | ?} 306 | ] 307 | end 308 | 309 | def encode(map, _pretty, options) do 310 | [ 311 | ?{, 312 | tl( 313 | :lists.foldl( 314 | &[ 315 | ?,, 316 | Encoder.BitString.encode(encode_name(&1), options), 317 | ?:, 318 | Encoder.encode(Map.fetch!(map, &1), options) | &2 319 | ], 320 | [], 321 | Map.keys(map) 322 | ) 323 | ), 324 | ?} 325 | ] 326 | end 327 | 328 | @compile {:inline, strict_keys: 2} 329 | 330 | defp strict_keys(map, false), do: map 331 | 332 | defp strict_keys(map, true) do 333 | map 334 | |> Map.keys() 335 | |> Enum.each(fn key -> 336 | name = encode_name(key) 337 | 338 | if Map.has_key?(map, name) do 339 | raise EncodeError, 340 | value: name, 341 | message: "duplicate key found: #{inspect(key)}" 342 | end 343 | end) 344 | 345 | map 346 | end 347 | end 348 | 349 | defimpl Poison.Encoder, for: List do 350 | alias Poison.{Encoder, Pretty} 351 | 352 | use Pretty 353 | 354 | @compile :inline 355 | @compile :inline_list_funcs 356 | 357 | def encode([], _options), do: "[]" 358 | 359 | def encode(list, options) do 360 | encode(list, pretty(options), options) 361 | end 362 | 363 | def encode(list, false, options) do 364 | [?[, tl(:lists.foldr(&[?,, Encoder.encode(&1, options) | &2], [], list)), ?]] 365 | end 366 | 367 | def encode(list, true, options) do 368 | indent = indent(options) 369 | offset = offset(options) + indent 370 | options = offset(options, offset) 371 | 372 | [ 373 | "[\n", 374 | tl(:lists.foldr(&[",\n", spaces(offset), Encoder.encode(&1, options) | &2], [], list)), 375 | ?\n, 376 | spaces(offset - indent), 377 | ?] 378 | ] 379 | end 380 | end 381 | 382 | defimpl Poison.Encoder, for: [Range, Stream, MapSet, Date.Range] do 383 | alias Poison.{Encoder, Pretty} 384 | 385 | use Pretty 386 | 387 | @compile :inline 388 | @compile :inline_list_funcs 389 | 390 | def encode(collection, options) do 391 | encode(collection, pretty(options), options) 392 | end 393 | 394 | def encode(collection, false, options) do 395 | case Enum.flat_map(collection, &[?,, Encoder.encode(&1, options)]) do 396 | [] -> "[]" 397 | [_ | tail] -> [?[, tail, ?]] 398 | end 399 | end 400 | 401 | def encode(collection, true, options) do 402 | indent = indent(options) 403 | offset = offset(options) + indent 404 | options = offset(options, offset) 405 | 406 | case Enum.flat_map(collection, &[",\n", spaces(offset), Encoder.encode(&1, options)]) do 407 | [] -> "[]" 408 | [_ | tail] -> ["[\n", tail, ?\n, spaces(offset - indent), ?]] 409 | end 410 | end 411 | end 412 | 413 | defimpl Poison.Encoder, for: [Date, Time, NaiveDateTime, DateTime] do 414 | def encode(value, options) do 415 | Poison.Encoder.BitString.encode(@for.to_iso8601(value), options) 416 | end 417 | end 418 | 419 | defimpl Poison.Encoder, for: URI do 420 | def encode(value, options) do 421 | Poison.Encoder.BitString.encode(@for.to_string(value), options) 422 | end 423 | end 424 | 425 | if Code.ensure_loaded?(Decimal) do 426 | defimpl Poison.Encoder, for: Decimal do 427 | def encode(value, _options) do 428 | Decimal.to_string(value) 429 | end 430 | end 431 | end 432 | 433 | defimpl Poison.Encoder, for: Any do 434 | alias Poison.{Encoder, EncodeError} 435 | 436 | defmacro __deriving__(module, struct, options) do 437 | deriving(module, struct, options) 438 | end 439 | 440 | def deriving(module, _struct, options) do 441 | only = options[:only] 442 | except = options[:except] 443 | 444 | extractor = 445 | cond do 446 | only -> 447 | quote(do: Map.take(struct, unquote(only))) 448 | 449 | except -> 450 | except = [:__struct__ | except] 451 | quote(do: Map.drop(struct, unquote(except))) 452 | 453 | true -> 454 | quote(do: Map.delete(struct, :__struct__)) 455 | end 456 | 457 | quote do 458 | defimpl Encoder, for: unquote(module) do 459 | def encode(struct, options) do 460 | Encoder.Map.encode(unquote(extractor), options) 461 | end 462 | end 463 | end 464 | end 465 | 466 | def encode(%{__struct__: _} = struct, options) do 467 | Encoder.Map.encode(Map.from_struct(struct), options) 468 | end 469 | 470 | def encode(value, _options) do 471 | raise EncodeError, value: value 472 | end 473 | end 474 | -------------------------------------------------------------------------------- /lib/poison/parser.ex: -------------------------------------------------------------------------------- 1 | defmodule Poison.MissingDependencyError do 2 | @type t :: %__MODULE__{name: String.t()} 3 | 4 | defexception name: nil 5 | 6 | def message(%{name: name}) do 7 | "missing optional dependency: #{name}" 8 | end 9 | end 10 | 11 | defmodule Poison.ParseError do 12 | alias Code.Identifier 13 | alias Poison.Parser 14 | 15 | @type t :: %__MODULE__{data: String.t(), skip: non_neg_integer, value: Parser.t()} 16 | 17 | defexception data: "", skip: 0, value: nil 18 | 19 | def message(%{data: data, skip: skip, value: value}) when value != nil do 20 | <> = data 21 | pos = String.length(head) 22 | "cannot parse value at position #{pos}: #{inspect(value)}" 23 | end 24 | 25 | def message(%{data: data, skip: skip}) when is_bitstring(data) do 26 | <> = data 27 | pos = String.length(head) 28 | 29 | case rest do 30 | <<>> -> 31 | "unexpected end of input at position #{pos}" 32 | 33 | <> -> 34 | "unexpected token at position #{pos}: #{escape(token)}" 35 | 36 | _rest -> 37 | "cannot parse value at position #{pos}: #{inspect(<>)}" 38 | end 39 | end 40 | 41 | def message(%{data: data}) do 42 | "unsupported value: #{inspect(data)}" 43 | end 44 | 45 | defp escape(token) do 46 | {value, _} = Identifier.escape(<>, ?\\) 47 | value 48 | end 49 | end 50 | 51 | defmodule Poison.Parser do 52 | @moduledoc """ 53 | An RFC 8259 and ECMA 404 conforming JSON parser. 54 | 55 | See: https://datatracker.ietf.org/doc/html/rfc8259 56 | See: https://ecma-international.org/wp-content/uploads/ECMA-404_2nd_edition_december_2017.pdf 57 | """ 58 | 59 | @compile :inline 60 | @compile :inline_list_funcs 61 | 62 | import Bitwise 63 | 64 | alias Poison.{Decoder, ParseError} 65 | 66 | @type scalar :: nil | true | false | float | integer | String.t() 67 | 68 | if Code.ensure_loaded?(Decimal) do 69 | @type t :: scalar | Decimal.t() | [t] | %{optional(String.t()) => t} 70 | else 71 | @type t :: scalar | [t] | %{optional(String.t()) => t} 72 | end 73 | 74 | whitespace = ~c"\s\t\n\r" 75 | digits = ?0..?9 76 | 77 | defmacrop syntax_error(skip) do 78 | quote do 79 | raise ParseError, skip: unquote(skip) 80 | end 81 | end 82 | 83 | @spec parse!(iodata, Decoder.options()) :: t 84 | def parse!(value, options \\ %{}) 85 | 86 | def parse!(data, options) when is_bitstring(data) do 87 | [value | skip] = 88 | value(data, data, :maps.get(:keys, options, nil), :maps.get(:decimal, options, nil), 0) 89 | 90 | <<_skip::binary-size(skip), rest::bits>> = data 91 | skip_whitespace(rest, skip, value) 92 | rescue 93 | exception in ParseError -> 94 | reraise ParseError, 95 | [data: data, skip: exception.skip, value: exception.value], 96 | __STACKTRACE__ 97 | end 98 | 99 | def parse!(iodata, options) do 100 | iodata |> IO.iodata_to_binary() |> parse!(options) 101 | end 102 | 103 | @compile {:inline, value: 5} 104 | 105 | defp value(<<"null", _rest::bits>>, _data, _keys, _decimal, skip) do 106 | [nil | skip + 4] 107 | end 108 | 109 | defp value(<<"true", _rest::bits>>, _data, _keys, _decimal, skip) do 110 | [true | skip + 4] 111 | end 112 | 113 | defp value(<<"false", _rest::bits>>, _data, _keys, _decimal, skip) do 114 | [false | skip + 5] 115 | end 116 | 117 | defp value(<>, _data, _keys, decimal, skip) do 118 | number_neg(rest, decimal, skip + 1) 119 | end 120 | 121 | defp value(<>, _data, _keys, decimal, skip) do 122 | number_frac(rest, decimal, skip + 1, 1, 0, 0) 123 | end 124 | 125 | for digit <- ?1..?9 do 126 | coef = digit - ?0 127 | 128 | defp value(<>, _data, _keys, decimal, skip) do 129 | number_int(rest, decimal, skip + 1, 1, unquote(coef), 0) 130 | end 131 | end 132 | 133 | defp value(<>, data, _keys, _decimal, skip) do 134 | string_continue(rest, data, skip + 1) 135 | end 136 | 137 | defp value(<>, data, keys, decimal, skip) do 138 | array_values(rest, data, keys, decimal, skip + 1, []) 139 | end 140 | 141 | defp value(<>, data, keys, decimal, skip) do 142 | object_pairs(rest, data, keys, decimal, skip + 1, []) 143 | end 144 | 145 | for char <- whitespace do 146 | defp value(<>, data, keys, decimal, skip) do 147 | value(rest, data, keys, decimal, skip + 1) 148 | end 149 | end 150 | 151 | defp value(_rest, _data, _keys, _decimal, skip) do 152 | syntax_error(skip) 153 | end 154 | 155 | ## Objects 156 | 157 | defmacrop object_name(keys, skip, name) do 158 | quote bind_quoted: [keys: keys, skip: skip, name: name] do 159 | case keys do 160 | :atoms! -> 161 | try do 162 | String.to_existing_atom(name) 163 | rescue 164 | ArgumentError -> 165 | reraise ParseError, [skip: skip, value: name], __STACKTRACE__ 166 | end 167 | 168 | :atoms -> 169 | # credo:disable-for-next-line Credo.Check.Warning.UnsafeToAtom 170 | String.to_atom(name) 171 | 172 | _keys -> 173 | name 174 | end 175 | end 176 | end 177 | 178 | @compile {:inline, object_pairs: 6} 179 | 180 | defp object_pairs(<>, data, keys, decimal, skip, acc) do 181 | start = skip + 1 182 | [name | skip] = string_continue(rest, data, start) 183 | <<_skip::binary-size(skip), rest::bits>> = data 184 | 185 | [value | skip] = object_value(rest, data, keys, decimal, skip) 186 | <<_skip::binary-size(skip), rest::bits>> = data 187 | 188 | object_pairs_continue(rest, data, keys, decimal, skip, [ 189 | {object_name(keys, start, name), value} | acc 190 | ]) 191 | end 192 | 193 | defp object_pairs(<>, _data, _keys, _decimal, skip, []) do 194 | [%{} | skip + 1] 195 | end 196 | 197 | for char <- whitespace do 198 | defp object_pairs(<>, data, keys, decimal, skip, acc) do 199 | object_pairs(rest, data, keys, decimal, skip + 1, acc) 200 | end 201 | end 202 | 203 | defp object_pairs(_rest, _data, _keys, _decimal, skip, _acc) do 204 | syntax_error(skip) 205 | end 206 | 207 | @compile {:inline, object_pairs_continue: 6} 208 | 209 | defp object_pairs_continue(<>, data, keys, decimal, skip, acc) do 210 | object_pairs(rest, data, keys, decimal, skip + 1, acc) 211 | end 212 | 213 | defp object_pairs_continue(<>, _data, _keys, _decimal, skip, acc) do 214 | [:maps.from_list(acc) | skip + 1] 215 | end 216 | 217 | for char <- whitespace do 218 | defp object_pairs_continue(<>, data, keys, decimal, skip, acc) do 219 | object_pairs_continue(rest, data, keys, decimal, skip + 1, acc) 220 | end 221 | end 222 | 223 | defp object_pairs_continue(_rest, _data, _keys, _decimal, skip, _acc) do 224 | syntax_error(skip) 225 | end 226 | 227 | @compile {:inline, object_value: 5} 228 | 229 | defp object_value(<>, data, keys, decimal, skip) do 230 | value(rest, data, keys, decimal, skip + 1) 231 | end 232 | 233 | for char <- whitespace do 234 | defp object_value(<>, data, keys, decimal, skip) do 235 | object_value(rest, data, keys, decimal, skip + 1) 236 | end 237 | end 238 | 239 | defp object_value(_rest, _data, _keys, _decimal, skip) do 240 | syntax_error(skip) 241 | end 242 | 243 | ## Arrays 244 | 245 | @compile {:inline, array_values: 6} 246 | 247 | defp array_values(<>, _data, _keys, _decimal, skip, acc) do 248 | [acc | skip + 1] 249 | end 250 | 251 | for char <- whitespace do 252 | defp array_values(<>, data, keys, decimal, skip, acc) do 253 | array_values(rest, data, keys, decimal, skip + 1, acc) 254 | end 255 | end 256 | 257 | defp array_values(rest, data, keys, decimal, skip, acc) do 258 | [value | skip] = value(rest, data, keys, decimal, skip) 259 | <<_skip::binary-size(skip), rest::bits>> = data 260 | array_values_continue(rest, data, keys, decimal, skip, [value | acc]) 261 | end 262 | 263 | @compile {:inline, array_values_continue: 6} 264 | 265 | defp array_values_continue(<>, data, keys, decimal, skip, acc) do 266 | [value | skip] = value(rest, data, keys, decimal, skip + 1) 267 | <<_skip::binary-size(skip), rest::bits>> = data 268 | array_values_continue(rest, data, keys, decimal, skip, [value | acc]) 269 | end 270 | 271 | defp array_values_continue(<>, _data, _keys, _decimal, skip, acc) do 272 | [:lists.reverse(acc) | skip + 1] 273 | end 274 | 275 | for char <- whitespace do 276 | defp array_values_continue(<>, data, keys, decimal, skip, acc) do 277 | array_values_continue(rest, data, keys, decimal, skip + 1, acc) 278 | end 279 | end 280 | 281 | defp array_values_continue(_rest, _data, _keys, _decimal, skip, _acc) do 282 | syntax_error(skip) 283 | end 284 | 285 | ## Numbers 286 | 287 | @compile {:inline, number_neg: 3} 288 | 289 | defp number_neg(<>, decimal, skip) do 290 | number_frac(rest, decimal, skip + 1, -1, 0, 0) 291 | end 292 | 293 | for char <- ?1..?9 do 294 | defp number_neg(<>, decimal, skip) do 295 | number_int(rest, decimal, skip + 1, -1, unquote(char - ?0), 0) 296 | end 297 | end 298 | 299 | defp number_neg(_rest, _decimal, skip) do 300 | syntax_error(skip) 301 | end 302 | 303 | @compile {:inline, number_int: 6} 304 | 305 | for char <- digits do 306 | defp number_int(<>, decimal, skip, sign, coef, exp) do 307 | number_int(rest, decimal, skip + 1, sign, coef * 10 + unquote(char - ?0), exp) 308 | end 309 | end 310 | 311 | defp number_int(rest, decimal, skip, sign, coef, exp) do 312 | number_frac(rest, decimal, skip, sign, coef, exp) 313 | end 314 | 315 | @compile {:inline, number_frac: 6} 316 | 317 | defp number_frac(<>, decimal, skip, sign, coef, exp) do 318 | number_frac_continue(rest, decimal, skip + 1, sign, coef, exp) 319 | end 320 | 321 | defp number_frac(rest, decimal, skip, sign, coef, exp) do 322 | number_exp(rest, decimal, skip, sign, coef, exp) 323 | end 324 | 325 | @compile {:inline, number_frac_continue: 6} 326 | 327 | for char <- digits do 328 | defp number_frac_continue(<>, decimal, skip, sign, coef, exp) do 329 | number_frac_continue(rest, decimal, skip + 1, sign, coef * 10 + unquote(char - ?0), exp - 1) 330 | end 331 | end 332 | 333 | defp number_frac_continue(_rest, _decimal, skip, _sign, _coef, 0) do 334 | syntax_error(skip) 335 | end 336 | 337 | defp number_frac_continue(rest, decimal, skip, sign, coef, exp) do 338 | number_exp(rest, decimal, skip, sign, coef, exp) 339 | end 340 | 341 | @compile {:inline, number_exp: 6} 342 | 343 | for e <- ~c(eE) do 344 | defp number_exp(<>, decimal, skip, sign, coef, exp) do 345 | [value | skip] = number_exp_continue(rest, skip + 1) 346 | number_complete(decimal, skip, sign, coef, exp + value) 347 | end 348 | end 349 | 350 | defp number_exp(_rest, decimal, skip, sign, coef, exp) do 351 | number_complete(decimal, skip, sign, coef, exp) 352 | end 353 | 354 | @compile {:inline, number_exp_continue: 2} 355 | 356 | defp number_exp_continue(<>, skip) do 357 | [exp | skip] = number_exp_digits(rest, skip + 1) 358 | [-exp | skip] 359 | end 360 | 361 | defp number_exp_continue(<>, skip) do 362 | number_exp_digits(rest, skip + 1) 363 | end 364 | 365 | defp number_exp_continue(rest, skip) do 366 | number_exp_digits(rest, skip) 367 | end 368 | 369 | @compile {:inline, number_exp_digits: 2} 370 | 371 | defp number_exp_digits(<>, skip) do 372 | case number_digits(rest, skip, 0) do 373 | [_exp | ^skip] -> 374 | syntax_error(skip) 375 | 376 | other -> 377 | other 378 | end 379 | end 380 | 381 | defp number_exp_digits(<<>>, skip), do: syntax_error(skip) 382 | 383 | @compile {:inline, number_digits: 3} 384 | 385 | for char <- digits do 386 | defp number_digits(<>, skip, acc) do 387 | number_digits(rest, skip + 1, acc * 10 + unquote(char - ?0)) 388 | end 389 | end 390 | 391 | defp number_digits(_rest, skip, acc) do 392 | [acc | skip] 393 | end 394 | 395 | @compile {:inline, number_complete: 5} 396 | 397 | if Code.ensure_loaded?(Decimal) do 398 | defp number_complete(true, skip, sign, coef, exp) do 399 | [%Decimal{sign: sign, coef: coef, exp: exp} | skip] 400 | end 401 | else 402 | defp number_complete(true, _skip, _sign, _coef, _exp) do 403 | raise Poison.MissingDependencyError, name: "Decimal" 404 | end 405 | end 406 | 407 | defp number_complete(_decimal, skip, sign, coef, 0) do 408 | [coef * sign | skip] 409 | end 410 | 411 | max_sig = 1 <<< 53 412 | 413 | # See: https://arxiv.org/pdf/2101.11408.pdf 414 | defp number_complete(_decimal, skip, sign, coef, exp) 415 | when exp in -22..22 and coef <= unquote(max_sig) do 416 | if exp < 0 do 417 | [coef / pow10(-exp) * sign | skip] 418 | else 419 | [coef * pow10(exp) * sign | skip] 420 | end 421 | end 422 | 423 | defp number_complete(_decimal, skip, sign, coef, exp) do 424 | [ 425 | String.to_float( 426 | <> 427 | ) 428 | | skip 429 | ] 430 | rescue 431 | ArgumentError -> 432 | reraise ParseError, [skip: skip, value: "#{coef * sign}e#{exp}"], __STACKTRACE__ 433 | end 434 | 435 | @compile {:inline, pow10: 1} 436 | 437 | for n <- 1..10 do 438 | defp pow10(unquote(n)), do: unquote(:math.pow(10, n)) 439 | end 440 | 441 | defp pow10(n), do: 1.0e10 * pow10(n - 10) 442 | 443 | ## Strings 444 | 445 | defmacrop string_codepoint_size(codepoint) do 446 | quote bind_quoted: [codepoint: codepoint] do 447 | cond do 448 | codepoint <= 0x7FF -> 2 449 | codepoint <= 0xFFFF -> 3 450 | true -> 4 451 | end 452 | end 453 | end 454 | 455 | @compile {:inline, string_continue: 3} 456 | 457 | defp string_continue(<>, _data, skip) do 458 | ["" | skip + 1] 459 | end 460 | 461 | defp string_continue(rest, data, skip) do 462 | string_continue(rest, data, skip, false, 0, []) 463 | end 464 | 465 | @compile {:inline, string_continue: 6} 466 | 467 | defp string_continue(<>, data, skip, unicode, len, acc) do 468 | cond do 469 | acc == [] -> 470 | if len > 0 do 471 | [binary_part(data, skip, len) | skip + len + 1] 472 | else 473 | ["" | skip + 1] 474 | end 475 | 476 | unicode -> 477 | case :unicode.characters_to_binary([acc | binary_part(data, skip, len)], :utf8) do 478 | string when is_binary(string) -> 479 | [string | skip + len + 1] 480 | 481 | _other -> 482 | syntax_error(skip + len) 483 | end 484 | 485 | true -> 486 | [IO.iodata_to_binary([acc | binary_part(data, skip, len)]) | skip + len + 1] 487 | end 488 | end 489 | 490 | defp string_continue(<>, data, skip, unicode, len, acc) do 491 | string_escape(rest, data, skip + len + 1, unicode, [acc | binary_part(data, skip, len)]) 492 | end 493 | 494 | defp string_continue(<>, data, skip, unicode, len, acc) when char >= 0x20 do 495 | string_continue(rest, data, skip, unicode, len + 1, acc) 496 | end 497 | 498 | defp string_continue(<>, data, skip, _unicode, len, acc) 499 | when codepoint > 0x80 do 500 | string_continue(rest, data, skip, true, len + string_codepoint_size(codepoint), acc) 501 | end 502 | 503 | defp string_continue(_other, _data, skip, _unicode, len, _acc) do 504 | syntax_error(skip + len) 505 | end 506 | 507 | @compile {:inline, string_escape: 5} 508 | 509 | defp string_escape(<>, data, skip, _unicode, acc) do 510 | string_escape_unicode(rest, data, skip, acc) 511 | end 512 | 513 | for {seq, char} <- Enum.zip(~C("\ntr/fb), ~c("\\\n\t\r/\f\b)) do 514 | defp string_escape(<>, data, skip, unicode, acc) do 515 | string_continue(rest, data, skip + 1, unicode, 0, [acc | [unquote(char)]]) 516 | end 517 | end 518 | 519 | defp string_escape(_rest, _data, skip, _unicode, _acc), do: syntax_error(skip) 520 | 521 | # https://www.ietf.org/rfc/rfc2781.txt 522 | # https://perldoc.perl.org/Encode::Unicode#Surrogate-Pairs 523 | # https://mathiasbynens.be/notes/javascript-encoding#surrogate-pairs 524 | defguardp is_hi_surrogate(cp) when cp in 0xD800..0xDBFF 525 | defguardp is_lo_surrogate(cp) when cp in 0xDC00..0xDFFF 526 | 527 | defmacrop get_codepoint(seq, skip) do 528 | quote bind_quoted: [seq: seq, skip: skip] do 529 | try do 530 | String.to_integer(seq, 16) 531 | rescue 532 | ArgumentError -> 533 | reraise ParseError, [skip: skip, value: "\\u#{seq}"], __STACKTRACE__ 534 | end 535 | end 536 | end 537 | 538 | @compile {:inline, string_escape_unicode: 4} 539 | 540 | defp string_escape_unicode(<>, data, skip, acc) do 541 | case get_codepoint(seq1, skip) do 542 | hi when is_hi_surrogate(hi) -> 543 | string_escape_surrogate_pair(rest, data, skip, acc, seq1, hi) 544 | 545 | lo when is_lo_surrogate(lo) -> 546 | raise ParseError, skip: skip, value: "\\u#{seq1}" 547 | 548 | codepoint -> 549 | string_continue(rest, data, skip + 5, true, 0, [acc | [codepoint]]) 550 | end 551 | end 552 | 553 | defp string_escape_unicode(_rest, _data, skip, _acc), do: syntax_error(skip + 1) 554 | 555 | @compile {:inline, string_escape_surrogate_pair: 6} 556 | 557 | defp string_escape_surrogate_pair( 558 | <<"\\u", seq2::binary-size(4), rest::bits>>, 559 | data, 560 | skip, 561 | acc, 562 | seq1, 563 | hi 564 | ) do 565 | case get_codepoint(seq2, skip + 6) do 566 | lo when is_lo_surrogate(lo) -> 567 | codepoint = 0x10000 + ((hi &&& 0x03FF) <<< 10) + (lo &&& 0x03FF) 568 | string_continue(rest, data, skip + 11, true, 0, [acc | [codepoint]]) 569 | 570 | _other -> 571 | raise ParseError, skip: skip, value: "\\u#{seq1}\\u#{seq2}" 572 | end 573 | end 574 | 575 | defp string_escape_surrogate_pair(_rest, _data, skip, _acc, seq1, _hi) do 576 | raise ParseError, skip: skip, value: "\\u#{seq1}" 577 | end 578 | 579 | ## Whitespace 580 | 581 | @compile {:inline, skip_whitespace: 3} 582 | 583 | defp skip_whitespace(<<>>, _skip, value) do 584 | value 585 | end 586 | 587 | for char <- whitespace do 588 | defp skip_whitespace(<>, skip, value) do 589 | skip_whitespace(rest, skip + 1, value) 590 | end 591 | end 592 | 593 | defp skip_whitespace(_rest, skip, _value) do 594 | syntax_error(skip) 595 | end 596 | end 597 | -------------------------------------------------------------------------------- /mix.exs: -------------------------------------------------------------------------------- 1 | defmodule Poison.Mixfile do 2 | use Mix.Project 3 | 4 | version_path = Path.join([__DIR__, "VERSION"]) 5 | 6 | @external_resource version_path 7 | @version version_path |> File.read!() |> String.trim() 8 | 9 | def project do 10 | [ 11 | app: :poison, 12 | name: "Poison", 13 | version: @version, 14 | elixir: "~> 1.12", 15 | description: "An incredibly fast, pure Elixir JSON library", 16 | source_url: "https://github.com/devinus/poison", 17 | start_permanent: Mix.env() == :prod, 18 | consolidate_protocols: Mix.env() not in [:dev, :test], 19 | elixirc_paths: elixirc_paths(), 20 | deps: deps(), 21 | docs: docs(), 22 | package: package(), 23 | aliases: aliases(), 24 | xref: [exclude: [Decimal]], 25 | dialyzer: [ 26 | plt_add_apps: [:decimal], 27 | flags: [ 28 | :error_handling, 29 | :extra_return, 30 | :missing_return, 31 | :unmatched_returns, 32 | :underspecs 33 | ] 34 | ], 35 | test_coverage: [tool: ExCoveralls], 36 | preferred_cli_env: [ 37 | coveralls: :test, 38 | "coveralls.detail": :test, 39 | "coveralls.html": :test, 40 | "coveralls.post": :test, 41 | "coveralls.github": :test 42 | ] 43 | ] 44 | end 45 | 46 | # Run "mix help compile.app" to learn about applications. 47 | def application do 48 | # Specify extra applications you'll use from Erlang/Elixir 49 | if Mix.env() == :bench do 50 | [extra_applications: [:eex]] 51 | else 52 | [] 53 | end 54 | end 55 | 56 | defp elixirc_paths do 57 | if Mix.env() == :profile do 58 | ~w(lib profile) 59 | else 60 | ["lib"] 61 | end 62 | end 63 | 64 | # Run "mix help deps" to learn about dependencies. 65 | defp deps do 66 | [ 67 | {:benchee_html, "~> 1.0", only: :bench, runtime: false}, 68 | {:benchee_markdown, "~> 0.3", only: :bench, runtime: false}, 69 | {:benchee, "~> 1.3", only: :bench, runtime: false}, 70 | {:castore, "~> 1.0", only: :test, runtime: false}, 71 | {:credo, "~> 1.7.7-rc", only: [:dev, :test], runtime: false}, 72 | {:decimal, "~> 2.1", optional: true}, 73 | {:dialyxir, "~> 1.4", only: [:dev, :test], runtime: false}, 74 | {:ex_doc, "~> 0.34", only: [:dev, :test], runtime: false}, 75 | {:excoveralls, "~> 0.18", only: :test, runtime: false}, 76 | {:exjsx, "~> 4.0", only: [:bench, :profile], runtime: false}, 77 | {:jason, "~> 1.5.0-alpha", only: [:dev, :test, :bench, :profile], runtime: false}, 78 | {:jiffy, "~> 1.1", only: [:bench, :profile], runtime: false}, 79 | {:jsone, "~> 1.8", only: [:bench, :profile], runtime: false}, 80 | {:junit_formatter, "~> 3.4", only: :test, runtime: false}, 81 | {:mix_audit, "~> 2.1", only: [:dev, :test], runtime: false}, 82 | {:stream_data, "~> 1.1", only: [:dev, :test], runtime: false}, 83 | {:thoas, "~> 1.2", only: [:bench, :profile], runtime: false}, 84 | {:tiny, "~> 1.0", only: [:bench, :profile], runtime: false} 85 | ] 86 | end 87 | 88 | defp docs do 89 | [ 90 | main: "Poison", 91 | canonical: "https://hexdocs.pm/poison", 92 | extras: [ 93 | "README.md", 94 | "CHANGELOG.md": [title: "Changelog"], 95 | LICENSE: [title: "License"] 96 | ], 97 | source_ref: "master" 98 | ] 99 | end 100 | 101 | defp package do 102 | [ 103 | files: ~w(lib mix.exs README.md LICENSE VERSION), 104 | maintainers: ["Devin Alexander Torres "], 105 | licenses: ["0BSD"], 106 | links: %{"GitHub" => "https://github.com/devinus/poison"} 107 | ] 108 | end 109 | 110 | defp aliases do 111 | [ 112 | "deps.get": [ 113 | fn _args -> 114 | System.cmd("git", ["submodule", "update", "--init"], 115 | cd: __DIR__, 116 | env: [], 117 | parallelism: true 118 | ) 119 | end, 120 | "deps.get" 121 | ] 122 | ] 123 | end 124 | end 125 | -------------------------------------------------------------------------------- /mix.lock: -------------------------------------------------------------------------------- 1 | %{ 2 | "benchee": {:hex, :benchee, "1.3.1", "c786e6a76321121a44229dde3988fc772bca73ea75170a73fd5f4ddf1af95ccf", [:mix], [{:deep_merge, "~> 1.0", [hex: :deep_merge, repo: "hexpm", optional: false]}, {:statistex, "~> 1.0", [hex: :statistex, repo: "hexpm", optional: false]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "76224c58ea1d0391c8309a8ecbfe27d71062878f59bd41a390266bf4ac1cc56d"}, 3 | "benchee_html": {:hex, :benchee_html, "1.0.1", "1e247c0886c3fdb0d3f4b184b653a8d6fb96e4ad0d0389267fe4f36968772e24", [:mix], [{:benchee, ">= 0.99.0 and < 2.0.0", [hex: :benchee, repo: "hexpm", optional: false]}, {:benchee_json, "~> 1.0", [hex: :benchee_json, repo: "hexpm", optional: false]}], "hexpm", "b00a181af7152431901e08f3fc9f7197ed43ff50421a8347b0c80bf45d5b3fef"}, 4 | "benchee_json": {:hex, :benchee_json, "1.0.0", "cc661f4454d5995c08fe10dd1f2f72f229c8f0fb1c96f6b327a8c8fc96a91fe5", [:mix], [{:benchee, ">= 0.99.0 and < 2.0.0", [hex: :benchee, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "da05d813f9123505f870344d68fb7c86a4f0f9074df7d7b7e2bb011a63ec231c"}, 5 | "benchee_markdown": {:hex, :benchee_markdown, "0.3.3", "d48a1d9782693fae6c294fdb12f653bb90088172d467996bedb9887ff41cf4ef", [:mix], [{:benchee, ">= 1.1.0 and < 2.0.0", [hex: :benchee, repo: "hexpm", optional: false]}], "hexpm", "106dab9ae0b448747da89b9af7285b71841f5d8131f37c6612b7370a157860a4"}, 6 | "bunt": {:hex, :bunt, "1.0.0", "081c2c665f086849e6d57900292b3a161727ab40431219529f13c4ddcf3e7a44", [:mix], [], "hexpm", "dc5f86aa08a5f6fa6b8096f0735c4e76d54ae5c9fa2c143e5a1fc7c1cd9bb6b5"}, 7 | "castore": {:hex, :castore, "1.0.7", "b651241514e5f6956028147fe6637f7ac13802537e895a724f90bf3e36ddd1dd", [:mix], [], "hexpm", "da7785a4b0d2a021cd1292a60875a784b6caef71e76bf4917bdee1f390455cf5"}, 8 | "credo": {:hex, :credo, "1.7.7-rc.0", "a7fb63b6e30e523355fb545cb089c5cba550b6d92e1a70db131e01a5dbb77368", [:mix], [{:bunt, "~> 0.2.1 or ~> 1.0", [hex: :bunt, repo: "hexpm", optional: false]}, {:file_system, "~> 0.2 or ~> 1.0", [hex: :file_system, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "c977f50ada33d6b053122945a0e4e74280585f7f8ac617aca46c913e88cc4de0"}, 9 | "decimal": {:hex, :decimal, "2.1.1", "5611dca5d4b2c3dd497dec8f68751f1f1a54755e8ed2a966c2633cf885973ad6", [:mix], [], "hexpm", "53cfe5f497ed0e7771ae1a475575603d77425099ba5faef9394932b35020ffcc"}, 10 | "deep_merge": {:hex, :deep_merge, "1.0.0", "b4aa1a0d1acac393bdf38b2291af38cb1d4a52806cf7a4906f718e1feb5ee961", [:mix], [], "hexpm", "ce708e5f094b9cd4e8f2be4f00d2f4250c4095be93f8cd6d018c753894885430"}, 11 | "dialyxir": {:hex, :dialyxir, "1.4.3", "edd0124f358f0b9e95bfe53a9fcf806d615d8f838e2202a9f430d59566b6b53b", [:mix], [{:erlex, ">= 0.2.6", [hex: :erlex, repo: "hexpm", optional: false]}], "hexpm", "bf2cfb75cd5c5006bec30141b131663299c661a864ec7fbbc72dfa557487a986"}, 12 | "earmark_parser": {:hex, :earmark_parser, "1.4.39", "424642f8335b05bb9eb611aa1564c148a8ee35c9c8a8bba6e129d51a3e3c6769", [:mix], [], "hexpm", "06553a88d1f1846da9ef066b87b57c6f605552cfbe40d20bd8d59cc6bde41944"}, 13 | "erlex": {:hex, :erlex, "0.2.6", "c7987d15e899c7a2f34f5420d2a2ea0d659682c06ac607572df55a43753aa12e", [:mix], [], "hexpm", "2ed2e25711feb44d52b17d2780eabf998452f6efda104877a3881c2f8c0c0c75"}, 14 | "ex_doc": {:hex, :ex_doc, "0.34.0", "ab95e0775db3df71d30cf8d78728dd9261c355c81382bcd4cefdc74610bef13e", [:mix], [{:earmark_parser, "~> 1.4.39", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_c, ">= 0.1.0", [hex: :makeup_c, repo: "hexpm", optional: true]}, {:makeup_elixir, "~> 0.14 or ~> 1.0", [hex: :makeup_elixir, repo: "hexpm", optional: false]}, {:makeup_erlang, "~> 0.1 or ~> 1.0", [hex: :makeup_erlang, repo: "hexpm", optional: false]}, {:makeup_html, ">= 0.1.0", [hex: :makeup_html, repo: "hexpm", optional: true]}], "hexpm", "60734fb4c1353f270c3286df4a0d51e65a2c1d9fba66af3940847cc65a8066d7"}, 15 | "excoveralls": {:hex, :excoveralls, "0.18.1", "a6f547570c6b24ec13f122a5634833a063aec49218f6fff27de9df693a15588c", [:mix], [{:castore, "~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "d65f79db146bb20399f23046015974de0079668b9abb2f5aac074d078da60b8d"}, 16 | "exjsx": {:hex, :exjsx, "4.0.0", "60548841e0212df401e38e63c0078ec57b33e7ea49b032c796ccad8cde794b5c", [:mix], [{:jsx, "~> 2.8.0", [hex: :jsx, repo: "hexpm", optional: false]}], "hexpm", "32e95820a97cffea67830e91514a2ad53b888850442d6d395f53a1ac60c82e07"}, 17 | "file_system": {:hex, :file_system, "1.0.0", "b689cc7dcee665f774de94b5a832e578bd7963c8e637ef940cd44327db7de2cd", [:mix], [], "hexpm", "6752092d66aec5a10e662aefeed8ddb9531d79db0bc145bb8c40325ca1d8536d"}, 18 | "jason": {:hex, :jason, "1.5.0-alpha.2", "42bc9c545bcdf2d5096044449ddcf85235cdbfbaa19792f4a8be581c8f72608e", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}, {:jason_native, ">= 0.0.0", [hex: :jason_native, repo: "hexpm", optional: true]}], "hexpm", "6dcaa0d9fdc22afe9b4d362f17f20844a85f121c50b6e9b9466ac04fe39f3665"}, 19 | "jiffy": {:hex, :jiffy, "1.1.1", "aca10f47aa91697bf24ab9582c74e00e8e95474c7ef9f76d4f1a338d0f5de21b", [:rebar3], [], "hexpm", "62e1f0581c3c19c33a725c781dfa88410d8bff1bbafc3885a2552286b4785c4c"}, 20 | "jsone": {:hex, :jsone, "1.8.1", "6bc74d3863d55d420077346da97c601711017a057f2fd1df65d6d65dd562fbab", [:rebar3], [], "hexpm", "c78918124148c51a7a84c678e39bbc6281f8cb582f1d88584628a98468e99738"}, 21 | "jsx": {:hex, :jsx, "2.8.3", "a05252d381885240744d955fbe3cf810504eb2567164824e19303ea59eef62cf", [:mix, :rebar3], [], "hexpm", "fc3499fed7a726995aa659143a248534adc754ebd16ccd437cd93b649a95091f"}, 22 | "junit_formatter": {:hex, :junit_formatter, "3.4.0", "d0e8db6c34dab6d3c4154c3b46b21540db1109ae709d6cf99ba7e7a2ce4b1ac2", [:mix], [], "hexpm", "bb36e2ae83f1ced6ab931c4ce51dd3dbef1ef61bb4932412e173b0cfa259dacd"}, 23 | "makeup": {:hex, :makeup, "1.1.2", "9ba8837913bdf757787e71c1581c21f9d2455f4dd04cfca785c70bbfff1a76a3", [:mix], [{:nimble_parsec, "~> 1.2.2 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "cce1566b81fbcbd21eca8ffe808f33b221f9eee2cbc7a1706fc3da9ff18e6cac"}, 24 | "makeup_elixir": {:hex, :makeup_elixir, "0.16.2", "627e84b8e8bf22e60a2579dad15067c755531fea049ae26ef1020cad58fe9578", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.2.3 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "41193978704763f6bbe6cc2758b84909e62984c7752b3784bd3c218bb341706b"}, 25 | "makeup_erlang": {:hex, :makeup_erlang, "1.0.0", "6f0eff9c9c489f26b69b61440bf1b238d95badae49adac77973cbacae87e3c2e", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "ea7a9307de9d1548d2a72d299058d1fd2339e3d398560a0e46c27dab4891e4d2"}, 26 | "mix_audit": {:hex, :mix_audit, "2.1.3", "c70983d5cab5dca923f9a6efe559abfb4ec3f8e87762f02bab00fa4106d17eda", [:make, :mix], [{:jason, "~> 1.1", [hex: :jason, repo: "hexpm", optional: false]}, {:yaml_elixir, "~> 2.9", [hex: :yaml_elixir, repo: "hexpm", optional: false]}], "hexpm", "8c3987100b23099aea2f2df0af4d296701efd031affb08d0746b2be9e35988ec"}, 27 | "nimble_parsec": {:hex, :nimble_parsec, "1.4.0", "51f9b613ea62cfa97b25ccc2c1b4216e81df970acd8e16e8d1bdc58fef21370d", [:mix], [], "hexpm", "9c565862810fb383e9838c1dd2d7d2c437b3d13b267414ba6af33e50d2d1cf28"}, 28 | "statistex": {:hex, :statistex, "1.0.0", "f3dc93f3c0c6c92e5f291704cf62b99b553253d7969e9a5fa713e5481cd858a5", [:mix], [], "hexpm", "ff9d8bee7035028ab4742ff52fc80a2aa35cece833cf5319009b52f1b5a86c27"}, 29 | "stream_data": {:hex, :stream_data, "1.1.0", "ef3a7cac0f200c43caf3e6caf9be63115851b4f1cde3f21afaab220adc40e3d7", [:mix], [], "hexpm", "cccc411d5facf1bab86e7c671382d164f05f8992574c95349d3c8b317e14d953"}, 30 | "thoas": {:hex, :thoas, "1.2.1", "19a25f31177a17e74004d4840f66d791d4298c5738790fa2cc73731eb911f195", [:rebar3], [], "hexpm", "e38697edffd6e91bd12cea41b155115282630075c2a727e7a6b2947f5408b86a"}, 31 | "tiny": {:hex, :tiny, "1.0.1", "535ea7e600cb1c6ba17b53029266d9d7ec54ce29bfb05d906c433907acfa01ca", [:mix], [], "hexpm", "1278a457deb8d99135c378b71f66f21e283d8adea426252a3f8f5e003c536a7b"}, 32 | "yamerl": {:hex, :yamerl, "0.10.0", "4ff81fee2f1f6a46f1700c0d880b24d193ddb74bd14ef42cb0bcf46e81ef2f8e", [:rebar3], [], "hexpm", "346adb2963f1051dc837a2364e4acf6eb7d80097c0f53cbdc3046ec8ec4b4e6e"}, 33 | "yaml_elixir": {:hex, :yaml_elixir, "2.9.0", "9a256da867b37b8d2c1ffd5d9de373a4fda77a32a45b452f1708508ba7bbcb53", [:mix], [{:yamerl, "~> 0.10", [hex: :yamerl, repo: "hexpm", optional: false]}], "hexpm", "0cb0e7d4c56f5e99a6253ed1a670ed0e39c13fc45a6da054033928607ac08dfc"}, 34 | } 35 | -------------------------------------------------------------------------------- /profile/profiler.ex: -------------------------------------------------------------------------------- 1 | defmodule Poison.Profiler do 2 | @moduledoc false 3 | 4 | import Poison.Parser 5 | 6 | data_dir = Path.expand(Path.join(__DIR__, "../bench/data")) 7 | 8 | data = 9 | for path <- Path.wildcard("#{data_dir}/*.json"), into: %{} do 10 | key = 11 | path 12 | |> Path.basename(".json") 13 | |> String.replace(~r/-+/, "_") 14 | # credo:disable-for-next-line Credo.Check.Warning.UnsafeToAtom 15 | |> String.to_atom() 16 | 17 | value = File.read!(path) 18 | {key, value} 19 | end 20 | 21 | keys = Map.keys(data) 22 | 23 | def run do 24 | unquote(keys) 25 | |> Macro.escape() 26 | |> Enum.map(&run/1) 27 | end 28 | 29 | for key <- keys do 30 | def run(unquote(key)) do 31 | parse!(get_data(unquote(key))) 32 | rescue 33 | _exception -> 34 | :error 35 | end 36 | end 37 | 38 | def time do 39 | unquote(keys) 40 | |> Macro.escape() 41 | |> Enum.map(&time/1) 42 | |> Enum.sum() 43 | # credo:disable-for-next-line Credo.Check.Warning.IoInspect 44 | |> IO.inspect() 45 | end 46 | 47 | for key <- keys do 48 | def time(unquote(key)) do 49 | {time, _} = :timer.tc(fn -> run(unquote(key)) end) 50 | time 51 | end 52 | end 53 | 54 | for {key, value} <- data do 55 | defp get_data(unquote(key)) do 56 | unquote(value) 57 | end 58 | end 59 | end 60 | -------------------------------------------------------------------------------- /test/fixtures/unparsable.json: -------------------------------------------------------------------------------- 1 | {"test": 2 | [[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]] 3 | } 4 | -------------------------------------------------------------------------------- /test/poison/decoder_test.exs: -------------------------------------------------------------------------------- 1 | defmodule Poison.DecoderTest do 2 | use ExUnit.Case, async: true 3 | 4 | import Poison.Decode, only: [transform: 2] 5 | 6 | defmodule Person do 7 | defstruct [:name, :address, :contact, age: 42] 8 | end 9 | 10 | defmodule Contact do 11 | defstruct [:email, :telephone] 12 | end 13 | 14 | defmodule Address do 15 | defstruct [:street, :city, :state, :zip] 16 | end 17 | 18 | defmodule Person2 do 19 | defstruct name: nil, age: 42, contacts: [] 20 | end 21 | 22 | defmodule Contact2 do 23 | defstruct [:email, :telephone, call_count: 0] 24 | end 25 | 26 | defimpl Poison.Decoder, for: Address do 27 | def decode(address, _options) do 28 | "#{address.street}, #{address.city}, #{address.state} #{address.zip}" 29 | end 30 | end 31 | 32 | test "decoding single :as with string keys" do 33 | person = %{"name" => "Devin Torres", "age" => 27} 34 | expected = %Person{name: "Devin Torres", age: 27} 35 | assert transform(person, %{as: %Person{}}) == expected 36 | end 37 | 38 | test "decoding single :as with atom keys" do 39 | person = %{name: "Devin Torres", age: 27} 40 | expected = %Person{name: "Devin Torres", age: 27} 41 | assert transform(person, %{keys: :atoms!, as: %Person{}}) == expected 42 | end 43 | 44 | test "decoding :as list with string keys" do 45 | person = [%{"name" => "Devin Torres", "age" => 27}] 46 | expected = [%Person{name: "Devin Torres", age: 27}] 47 | assert transform(person, %{as: [%Person{}]}) == expected 48 | end 49 | 50 | test "decoding nested :as with string keys" do 51 | person = %{"person" => %{"name" => "Devin Torres", "age" => 27}} 52 | actual = transform(person, %{as: %{"person" => %Person{}}}) 53 | expected = %{"person" => %Person{name: "Devin Torres", age: 27}} 54 | assert actual == expected 55 | end 56 | 57 | test "decoding nested :as with atom keys" do 58 | person = %{person: %{name: "Devin Torres", age: 27}} 59 | actual = transform(person, %{keys: :atoms!, as: %{person: %Person{}}}) 60 | expected = %{person: %Person{name: "Devin Torres", age: 27}} 61 | assert actual == expected 62 | end 63 | 64 | test "decoding nested :as list with string keys" do 65 | people = %{"people" => [%{"name" => "Devin Torres", "age" => 27}]} 66 | actual = transform(people, %{as: %{"people" => [%Person{}]}}) 67 | expected = %{"people" => [%Person{name: "Devin Torres", age: 27}]} 68 | assert actual == expected 69 | end 70 | 71 | test "decoding into structs with key subset" do 72 | person = %{"name" => "Devin Torres", "age" => 27, "dob" => "1987-01-29"} 73 | expected = %Person{name: "Devin Torres", age: 27} 74 | assert transform(person, %{as: %Person{}}) == expected 75 | end 76 | 77 | test "decoding into structs with default values" do 78 | person = %{"name" => "Devin Torres"} 79 | expected = %Person{name: "Devin Torres", age: 50} 80 | assert transform(person, %{as: %Person{age: 50}}) == expected 81 | end 82 | 83 | test "decoding into structs with unspecified default values" do 84 | person = %{"name" => "Devin Torres"} 85 | expected = %Person{name: "Devin Torres", age: 42} 86 | assert transform(person, %{as: %Person{}}) == expected 87 | end 88 | 89 | test "decoding into structs with unspecified default values and atom keys" do 90 | person = %{:name => "Devin Torres"} 91 | expected = %Person{name: "Devin Torres", age: 42} 92 | assert transform(person, %{as: %Person{}, keys: :atoms!}) == expected 93 | end 94 | 95 | test "decoding into structs with nil overriding defaults" do 96 | person = %{"name" => "Devin Torres", "age" => nil} 97 | expected = %Person{name: "Devin Torres", age: nil} 98 | assert transform(person, %{as: %Person{}}) == expected 99 | end 100 | 101 | test "decoding into nested structs" do 102 | person = %{ 103 | "name" => "Devin Torres", 104 | "contact" => %{"email" => "devin@torres.com"} 105 | } 106 | 107 | expected = %Person{ 108 | name: "Devin Torres", 109 | contact: %Contact{email: "devin@torres.com"} 110 | } 111 | 112 | assert transform(person, %{as: %Person{contact: %Contact{}}}) == expected 113 | end 114 | 115 | test "decoding into nested struct, empty nested struct" do 116 | person = %{"name" => "Devin Torres"} 117 | expected = %Person{name: "Devin Torres"} 118 | assert transform(person, %{as: %Person{contact: %Contact{}}}) == expected 119 | end 120 | 121 | test "decoding into nested struct list" do 122 | person = %{ 123 | "name" => "Devin Torres", 124 | "contacts" => [ 125 | %{"email" => "devin@torres.com", "call_count" => 10}, 126 | %{"email" => "test@email.com"} 127 | ] 128 | } 129 | 130 | expected = %Person2{ 131 | name: "Devin Torres", 132 | contacts: [ 133 | %Contact2{email: "devin@torres.com", call_count: 10}, 134 | %Contact2{email: "test@email.com", call_count: 0} 135 | ] 136 | } 137 | 138 | decoded = transform(person, %{as: %Person2{contacts: [%Contact2{}]}}) 139 | assert decoded == expected 140 | end 141 | 142 | test "decoding into nested struct list with keys = :atoms" do 143 | person = %{ 144 | name: "Devin Torres", 145 | contacts: [ 146 | %{email: "devin@torres.com", call_count: 10}, 147 | %{email: "test@email.com"} 148 | ] 149 | } 150 | 151 | expected = %Person2{ 152 | name: "Devin Torres", 153 | contacts: [ 154 | %Contact2{email: "devin@torres.com", call_count: 10}, 155 | %Contact2{email: "test@email.com", call_count: 0} 156 | ] 157 | } 158 | 159 | as = %Person2{contacts: [%Contact2{}]} 160 | decoded = transform(person, %{as: as, keys: :atoms}) 161 | assert decoded == expected 162 | end 163 | 164 | test "decoding into nested structs, empty list" do 165 | person = %{"name" => "Devin Torres"} 166 | 167 | expected = %Person2{ 168 | name: "Devin Torres", 169 | contacts: [] 170 | } 171 | 172 | as = %Person2{contacts: [%Contact{}]} 173 | assert transform(person, %{as: as}) == expected 174 | end 175 | 176 | test "decoding into nested structs list with nil overriding default" do 177 | person = %{"name" => "Devin Torres", "contacts" => nil} 178 | expected = %Person2{name: "Devin Torres", contacts: nil} 179 | as = %Person2{contacts: [%Contact{}]} 180 | assert transform(person, %{as: as}) == expected 181 | end 182 | 183 | test "decoding into nested structs with nil overriding defaults" do 184 | person = %{"name" => "Devin Torres", "contact" => nil} 185 | expected = %Person{name: "Devin Torres", contact: nil} 186 | assert transform(person, %{as: %Person{contact: %Contact{}}}) == expected 187 | end 188 | 189 | test "decoding using a defined decoder" do 190 | address = %{ 191 | "street" => "1 Main St.", 192 | "city" => "Austin", 193 | "state" => "TX", 194 | "zip" => "78701" 195 | } 196 | 197 | assert transform(address, %{as: %Address{}}) == 198 | "1 Main St., Austin, TX 78701" 199 | end 200 | 201 | test "decoding a sigle :as function with string keys" do 202 | person = %{"name" => "Devin Torres"} 203 | as = fn %{"name" => _name} -> %Person{} end 204 | expected = %Person{name: "Devin Torres"} 205 | assert transform(person, %{as: as}) == expected 206 | end 207 | 208 | test "decoding a single :as function with atom keys" do 209 | person = %{name: "Devin Torres"} 210 | as = fn %{name: _name} -> %Person{} end 211 | expected = %Person{name: "Devin Torres"} 212 | assert transform(person, %{as: as, keys: :atoms!}) == expected 213 | end 214 | 215 | test "decoding a :as list function with string keys" do 216 | person = [%{"name" => "Devin Torres"}] 217 | as = fn _value -> [%Person{}] end 218 | expected = [%Person{name: "Devin Torres"}] 219 | assert transform(person, %{as: as}) == expected 220 | end 221 | 222 | test "decoding nested :as function with string keys" do 223 | person = %{"person" => %{"name" => "Devin Torres"}} 224 | as = fn _value -> %{"person" => %Person{}} end 225 | actual = transform(person, %{as: as}) 226 | expected = %{"person" => %Person{name: "Devin Torres"}} 227 | assert actual == expected 228 | end 229 | 230 | test "decoding nested structs in :as function with string keys" do 231 | person = %{"name" => "Devin Torres", "contact" => %{"email" => "test@email.com"}} 232 | as = fn _value -> %Person{contact: %Contact{}} end 233 | expected = %Person{name: "Devin Torres", contact: %Contact{email: "test@email.com"}} 234 | assert transform(person, %{as: as}) == expected 235 | end 236 | end 237 | -------------------------------------------------------------------------------- /test/poison/encoder_test.exs: -------------------------------------------------------------------------------- 1 | defmodule Poison.EncoderTest do 2 | use ExUnit.Case, async: true 3 | use ExUnitProperties 4 | 5 | import Poison.TestGenerators 6 | 7 | import Poison, only: [encode!: 1, encode!: 2] 8 | alias Poison.{EncodeError, Encoder} 9 | 10 | test "Atom" do 11 | assert encode!(nil) == "null" 12 | assert encode!(true) == "true" 13 | assert encode!(false) == "false" 14 | assert encode!(:poison) == ~s("poison") 15 | end 16 | 17 | property "Atom" do 18 | check all( 19 | str <- filter(json_string(max_length: 255), &(&1 not in ~w(nil true false))), 20 | # credo:disable-for-next-line Credo.Check.Warning.UnsafeToAtom 21 | value = String.to_atom(str) 22 | ) do 23 | assert encode!(value) == inspect(str) 24 | end 25 | end 26 | 27 | test "Integer" do 28 | assert encode!(42) == "42" 29 | assert encode!(576_460_752_303_423_488) == "576460752303423488" 30 | end 31 | 32 | property "Integer" do 33 | check all(value <- integer()) do 34 | assert encode!(value) == to_string(value) 35 | end 36 | end 37 | 38 | test "Float" do 39 | assert encode!(99.99) == "99.99" 40 | assert encode!(9.9e100) == "9.9e100" 41 | assert encode!(9.9e-100) == "9.9e-100" 42 | end 43 | 44 | property "Float" do 45 | check all(value <- float()) do 46 | assert encode!(value) == to_string(value) 47 | end 48 | end 49 | 50 | test "BitString" do 51 | assert encode!("") == ~s("") 52 | assert encode!("hello world") == ~s("hello world") 53 | assert encode!("hello\nworld") == ~s("hello\\nworld") 54 | assert encode!("\nhello\nworld\n") == ~s("\\nhello\\nworld\\n") 55 | 56 | assert encode!("\"") == ~s("\\"") 57 | assert encode!("\0") == ~s("\\u0000") 58 | assert encode!(<<31>>) == ~s("\\u001F") 59 | assert encode!("☃", escape: :unicode) == ~s("\\u2603") 60 | assert encode!("𝄞", escape: :unicode) == ~s("\\uD834\\uDD1E") 61 | assert encode!("\u2028\u2029", escape: :javascript) == ~s("\\u2028\\u2029") 62 | assert encode!("", escape: :html_safe) == ~s("\\u003C/script\\u003E") 63 | 64 | assert encode!("\uCCCC\uCCCC", escape: :html_safe) == 65 | ~s("쳌\\u003C/script\\u003E쳌") 66 | 67 | assert encode!(~s(), escape: :html_safe) == 68 | ~s("\\u003Cscript\\u003Evar s = \\\"\\u2028\\u2029\\\";\\u003C/script\\u003E") 69 | 70 | assert encode!("", escape: :html_safe) == ~s("\\u003C!-- comment --\\u003E") 71 | assert encode!("one & two", escape: :html_safe) == ~s("one \\u0026 two") 72 | 73 | assert encode!("áéíóúàèìòùâêîôûãẽĩõũ") == ~s("áéíóúàèìòùâêîôûãẽĩõũ") 74 | end 75 | 76 | property "BitString" do 77 | check all(value <- json_string(min_length: 1)) do 78 | assert encode!(value) == inspect(value) 79 | end 80 | 81 | check all( 82 | str <- string(Enum.concat(0xA0..0xD7FF, 0xE000..0x10000), min_length: 1), 83 | elem <- member_of(String.codepoints(str)), 84 | <> = elem 85 | ) do 86 | seq = codepoint |> Integer.to_string(16) |> String.pad_leading(4, "0") 87 | assert encode!(<>, escape: :unicode) == ~s("\\u#{seq}") 88 | end 89 | 90 | check all( 91 | hi <- integer(0xD800..0xDBFF), 92 | lo <- integer(0xDC00..0xDFFF) 93 | ) do 94 | seq1 = hi |> Integer.to_string(16) |> String.pad_leading(4, "0") 95 | seq2 = lo |> Integer.to_string(16) |> String.pad_leading(4, "0") 96 | <> = <> 97 | value = :unicode.characters_to_binary([codepoint], :utf16, :utf8) 98 | assert encode!(value, escape: :unicode) == ~s("\\u#{seq1}\\u#{seq2}") 99 | end 100 | end 101 | 102 | property "List" do 103 | check all(value <- json_list(min_length: 1)) do 104 | assert String.match?(encode!(value), ~r/^\[(([^,]+,)|[^\]]+){1,#{length(value)}}\]$/) 105 | end 106 | end 107 | 108 | test "Map" do 109 | assert encode!(%{}) == "{}" 110 | assert encode!(%{"foo" => "bar"}) == ~s({"foo":"bar"}) 111 | assert encode!(%{foo: :bar}) == ~s({"foo":"bar"}) 112 | assert encode!(%{42 => :bar}) == ~s({"42":"bar"}) 113 | 114 | assert encode!(%{foo: %{bar: %{baz: "baz"}}}, pretty: true) == 115 | "{\n \"foo\": {\n \"bar\": {\n \"baz\": \"baz\"\n }\n }\n}" 116 | 117 | multi_key_map = %{"foo" => "foo1", :foo => "foo2"} 118 | assert encode!(multi_key_map) == ~s({"foo":"foo1","foo":"foo2"}) 119 | error = %EncodeError{message: "duplicate key found: :foo", value: "foo"} 120 | assert Poison.encode(multi_key_map, strict_keys: true) == {:error, error} 121 | end 122 | 123 | property "Map" do 124 | check all(value <- json_map(min_length: 1)) do 125 | assert String.match?(encode!(value), ~r/^\{([^:]+:[^,]+){1,#{map_size(value)}}\}$/) 126 | end 127 | end 128 | 129 | test "Range" do 130 | assert encode!(1..3) == "[1,2,3]" 131 | assert encode!(1..3, pretty: true) == "[\n 1,\n 2,\n 3\n]" 132 | end 133 | 134 | test "Stream" do 135 | range = 1..10 136 | assert encode!(Stream.take(range, 0)) == "[]" 137 | assert encode!(Stream.take(range, 3)) == "[1,2,3]" 138 | assert encode!(Stream.take(range, 3), pretty: true) == "[\n 1,\n 2,\n 3\n]" 139 | end 140 | 141 | # MapSet have an unspecified order 142 | 143 | test "MapSet" do 144 | set = MapSet.new() 145 | assert encode!(set) == "[]" 146 | 147 | set = set |> MapSet.put(1) |> MapSet.put(2) 148 | assert encode!(set) in ~w([1,2] [2,1]) 149 | assert encode!(set, pretty: true) in ["[\n 1,\n 2\n]", "[\n 2,\n 1\n]"] 150 | end 151 | 152 | test "Time" do 153 | {:ok, time} = Time.new(12, 13, 14) 154 | assert encode!(time) == ~s("12:13:14") 155 | end 156 | 157 | test "Date" do 158 | {:ok, date} = Date.new(2000, 1, 1) 159 | assert encode!(date) == ~s("2000-01-01") 160 | end 161 | 162 | test "NaiveDateTime" do 163 | {:ok, datetime} = NaiveDateTime.new(2000, 1, 1, 12, 13, 14) 164 | assert encode!(datetime) == ~s("2000-01-01T12:13:14") 165 | end 166 | 167 | test "DateTime" do 168 | datetime = %DateTime{ 169 | year: 2000, 170 | month: 1, 171 | day: 1, 172 | hour: 12, 173 | minute: 13, 174 | second: 14, 175 | microsecond: {0, 0}, 176 | zone_abbr: "CET", 177 | time_zone: "Europe/Warsaw", 178 | std_offset: -1800, 179 | utc_offset: 3600 180 | } 181 | 182 | assert encode!(datetime) == ~s("2000-01-01T12:13:14+00:30") 183 | 184 | datetime = %DateTime{ 185 | year: 2000, 186 | month: 1, 187 | day: 1, 188 | hour: 12, 189 | minute: 13, 190 | second: 14, 191 | microsecond: {50_000, 3}, 192 | zone_abbr: "UTC", 193 | time_zone: "Etc/UTC", 194 | std_offset: 0, 195 | utc_offset: 0 196 | } 197 | 198 | assert encode!(datetime) == ~s("2000-01-01T12:13:14.050Z") 199 | end 200 | 201 | test "Date.Range" do 202 | assert encode!(Date.range(~D[1969-08-15], ~D[1969-08-18])) == 203 | ~s(["1969-08-15","1969-08-16","1969-08-17","1969-08-18"]) 204 | end 205 | 206 | test "URI" do 207 | uri = URI.parse("https://devinus.io") 208 | assert encode!(uri) == ~s("https://devinus.io") 209 | end 210 | 211 | test "Decimal" do 212 | decimal = Decimal.new("99.9") 213 | assert encode!(decimal) == "99.9" 214 | end 215 | 216 | property "Decimal" do 217 | check all(value <- map(float(), &Decimal.from_float/1)) do 218 | assert encode!(value) == to_string(value) 219 | end 220 | end 221 | 222 | defmodule Derived do 223 | @derive [Poison.Encoder] 224 | defstruct name: "" 225 | end 226 | 227 | defmodule DerivedUsingOnly do 228 | @derive {Poison.Encoder, only: [:name]} 229 | defstruct name: "", size: 0 230 | end 231 | 232 | defmodule DerivedUsingExcept do 233 | @derive {Poison.Encoder, except: [:name]} 234 | defstruct name: "", size: 0 235 | end 236 | 237 | defmodule NonDerived do 238 | defstruct name: "" 239 | end 240 | 241 | test "@derive" do 242 | derived = %Derived{name: "derived"} 243 | non_derived = %NonDerived{name: "non-derived"} 244 | assert Encoder.impl_for!(derived) == Encoder.Poison.EncoderTest.Derived 245 | assert Encoder.impl_for!(non_derived) == Encoder.Any 246 | 247 | derived_using_only = %DerivedUsingOnly{ 248 | name: "derived using :only", 249 | size: 10 250 | } 251 | 252 | assert Poison.decode!(encode!(derived_using_only)) == %{ 253 | "name" => "derived using :only" 254 | } 255 | 256 | derived_using_except = %DerivedUsingExcept{ 257 | name: "derived using :except", 258 | size: 10 259 | } 260 | 261 | assert Poison.decode!(encode!(derived_using_except)) == %{"size" => 10} 262 | end 263 | 264 | test "EncodeError" do 265 | assert_raise EncodeError, fn -> 266 | encode!(make_ref()) 267 | end 268 | end 269 | 270 | property "complex nested input" do 271 | check all( 272 | value <- json_complex_value(), 273 | options <- 274 | optional_map(%{ 275 | escape: one_of([:unicode, :javascript, :html_safe]), 276 | pretty: boolean(), 277 | indent: positive_integer(), 278 | offset: positive_integer() 279 | }) 280 | ) do 281 | assert encode!(value, options) != "" 282 | end 283 | end 284 | end 285 | -------------------------------------------------------------------------------- /test/poison/parser_test.exs: -------------------------------------------------------------------------------- 1 | defmodule Poison.ParserTest do 2 | use ExUnit.Case, async: true 3 | use ExUnitProperties 4 | 5 | import Poison.{TestGenerators, Parser} 6 | alias Poison.ParseError 7 | 8 | test "numbers" do 9 | assert_raise ParseError, "unexpected end of input at position 1", fn -> 10 | parse!("-") 11 | end 12 | 13 | assert_raise ParseError, "unexpected token at position 1: -", fn -> 14 | parse!("--1") 15 | end 16 | 17 | assert_raise ParseError, "unexpected token at position 1: 1", fn -> 18 | parse!("01") 19 | end 20 | 21 | assert_raise ParseError, "unexpected token at position 0: .", fn -> 22 | parse!(".1") 23 | end 24 | 25 | assert_raise ParseError, "unexpected end of input at position 2", fn -> 26 | parse!("1.") 27 | end 28 | 29 | assert_raise ParseError, "unexpected end of input at position 2", fn -> 30 | parse!("1e") 31 | end 32 | 33 | assert_raise ParseError, "unexpected end of input at position 5", fn -> 34 | parse!("1.0e+") 35 | end 36 | 37 | assert parse!("0") === 0 38 | assert parse!("1") === 1 39 | assert parse!("-0") === 0 40 | assert parse!("-1") === -1 41 | assert parse!("0.1") === 0.1 42 | assert parse!("-0.1") === -0.1 43 | assert parse!("0e0") === 0 44 | assert parse!("0E0") === 0 45 | assert parse!("1e0") === 1 46 | assert parse!("1E0") === 1 47 | assert parse!("1.0e0") === 1.0 48 | assert parse!("1e+0") === 1 49 | assert parse!("1.0e+0") === 1.0 50 | assert parse!("0.1e1") === 1 51 | assert parse!("0.1e-1") === 0.1e-1 52 | assert parse!("99.99e99") === 99.99e99 53 | 54 | # credo:disable-for-next-line Credo.Check.Readability.LargeNumbers 55 | assert parse!("123456789.123456789e123") === 1.234567891234568e131 56 | 57 | assert parse!("0", %{decimal: true}) == Decimal.new("0") 58 | assert parse!("-0", %{decimal: true}) == Decimal.new("-0") 59 | assert parse!("99", %{decimal: true}) == Decimal.new("99") 60 | assert parse!("-99", %{decimal: true}) == Decimal.new("-99") 61 | assert parse!("99.99", %{decimal: true}) == Decimal.new("99.99") 62 | assert parse!("-99.99", %{decimal: true}) == Decimal.new("-99.99") 63 | assert parse!("99.99e99", %{decimal: true}) == Decimal.new("99.99e99") 64 | assert parse!("-99.99e99", %{decimal: true}) == Decimal.new("-99.99e99") 65 | 66 | assert parse!("-9.9999999999e9999999999", %{decimal: true}) == 67 | Decimal.new("-9.9999999999e9999999999") 68 | end 69 | 70 | property "number" do 71 | check all(int <- integer()) do 72 | assert parse!(Integer.to_string(int)) === int 73 | end 74 | 75 | check all(value <- float()) do 76 | assert parse!(Float.to_string(value)) == value 77 | end 78 | 79 | check all(value <- map(float(), &Float.to_string/1)) do 80 | assert Decimal.equal?(parse!(value, %{decimal: true}), value) 81 | end 82 | end 83 | 84 | test "strings" do 85 | assert_raise ParseError, "unexpected end of input at position 1", fn -> 86 | parse!(~s(")) 87 | end 88 | 89 | assert_raise ParseError, "unexpected end of input at position 3", fn -> 90 | parse!(~s("\\")) 91 | end 92 | 93 | assert_raise ParseError, "unexpected token at position 2: k", fn -> 94 | parse!(~s("\\k")) 95 | end 96 | 97 | assert_raise ParseError, "unexpected end of input at position 9", fn -> 98 | parse!(~s("\\u2603\\")) 99 | end 100 | 101 | assert_raise ParseError, "unexpected end of input at position 39", fn -> 102 | parse!(~s("Here's a snowman for you: ☃. Good day!)) 103 | end 104 | 105 | assert_raise ParseError, "unexpected end of input at position 2", fn -> 106 | parse!(~s("𝄞)) 107 | end 108 | 109 | assert_raise ParseError, "unexpected token at position 0: á", fn -> 110 | parse!(~s(á)) 111 | end 112 | 113 | assert_raise ParseError, "unexpected token at position 0: \\x1F", fn -> 114 | parse!(~s(\u001F)) 115 | end 116 | 117 | assert_raise ParseError, 118 | ~s(cannot parse value at position 8: "\\\\udcxx"), 119 | fn -> 120 | parse!(~s("\\ud8aa\\udcxx")) 121 | end 122 | 123 | assert_raise ParseError, 124 | ~s(cannot parse value at position 2: "\\\\uxxxx"), 125 | fn -> 126 | parse!(~s("\\uxxxx")) 127 | end 128 | 129 | assert_raise ParseError, 130 | ~s(cannot parse value at position 2: "\\\\uD800\\\\uDBFF"), 131 | fn -> 132 | parse!(~s("\\uD800\\uDBFF")) 133 | end 134 | 135 | assert_raise ParseError, 136 | ~s(cannot parse value at position 2: "\\\\uD800"), 137 | fn -> 138 | parse!(~s("\\uD800")) 139 | end 140 | 141 | assert_raise ParseError, 142 | ~s(cannot parse value at position 2: "\\\\uDC00"), 143 | fn -> 144 | parse!(~s("\\uDC00")) 145 | end 146 | 147 | assert parse!(~s("\\"\\\\\\/\\b\\f\\n\\r\\t")) == ~s("\\/\b\f\n\r\t) 148 | assert parse!(~s("\\u2603")) == "☃" 149 | assert parse!(~s("\\u2028\\u2029")) == "\u2028\u2029" 150 | assert parse!(~s("\\uD834\\uDD1E")) == "𝄞" 151 | assert parse!(~s("\\uD834\\uDD1E")) == "𝄞" 152 | assert parse!(~s("\\uD799\\uD799")) == "힙힙" 153 | assert parse!(~s("✔︎")) == "✔︎" 154 | assert parse!(~s("\\uD83D\\uDC68\\u200D\\uD83D\\uDC76")) == "👨‍👶" 155 | end 156 | 157 | property "strings" do 158 | check all(value <- json_string()) do 159 | assert parse!(inspect(value)) == value 160 | end 161 | 162 | check all(value <- member_of(Enum.concat(0x0..0xD7FF, 0xE000..0xFFFF))) do 163 | seq = value |> Integer.to_string(16) |> String.pad_leading(4, "0") 164 | assert parse!(~s("\\u#{seq}")) == <> 165 | end 166 | 167 | check all( 168 | hi <- integer(0xD800..0xDBFF), 169 | lo <- integer(0xDC00..0xDFFF) 170 | ) do 171 | seq1 = hi |> Integer.to_string(16) |> String.pad_leading(4, "0") 172 | seq2 = lo |> Integer.to_string(16) |> String.pad_leading(4, "0") 173 | <> = <> 174 | 175 | expected = :unicode.characters_to_binary([codepoint], :utf16, :utf8) 176 | assert parse!(~s("\\u#{seq1}\\u#{seq2}")) == expected 177 | end 178 | end 179 | 180 | test "objects" do 181 | assert_raise ParseError, "unexpected end of input at position 1", fn -> 182 | parse!("{") 183 | end 184 | 185 | assert_raise ParseError, "unexpected token at position 1: ,", fn -> 186 | parse!("{,") 187 | end 188 | 189 | assert_raise ParseError, "unexpected token at position 6: }", fn -> 190 | parse!(~s({"foo"})) 191 | end 192 | 193 | assert_raise ParseError, "unexpected token at position 14: }", fn -> 194 | parse!(~s({"foo": "bar",})) 195 | end 196 | 197 | assert parse!("{}") == %{} 198 | assert parse!(~s({"foo": "bar"})) == %{"foo" => "bar"} 199 | 200 | expected = %{"foo" => "bar", "baz" => "quux"} 201 | assert parse!(~s({"foo": "bar", "baz": "quux"})) == expected 202 | 203 | expected = %{"foo" => %{"bar" => "baz"}} 204 | assert parse!(~s({"foo": {"bar": "baz"}})) == expected 205 | end 206 | 207 | test "arrays" do 208 | assert_raise ParseError, "unexpected end of input at position 1", fn -> 209 | parse!("[") 210 | end 211 | 212 | assert_raise ParseError, "unexpected token at position 1: ,", fn -> 213 | parse!("[,") 214 | end 215 | 216 | assert_raise ParseError, "unexpected token at position 3: ]", fn -> 217 | parse!("[1,]") 218 | end 219 | 220 | assert parse!("[]") == [] 221 | assert parse!("[1, 2, 3]") == [1, 2, 3] 222 | assert parse!(~s(["foo", "bar", "baz"])) == ["foo", "bar", "baz"] 223 | assert parse!(~s([{"foo": "bar"}])) == [%{"foo" => "bar"}] 224 | end 225 | 226 | test "whitespace" do 227 | assert_raise ParseError, "unexpected end of input at position 0", fn -> 228 | parse!("") 229 | end 230 | 231 | assert_raise ParseError, "unexpected end of input at position 4", fn -> 232 | parse!(" ") 233 | end 234 | 235 | assert parse!(" [ ] ") == [] 236 | assert parse!(" { } ") == %{} 237 | 238 | assert parse!(" [ 1 , 2 , 3 ] ") == [1, 2, 3] 239 | 240 | expected = %{"foo" => "bar", "baz" => "quux"} 241 | 242 | assert parse!(~s( { "foo" : "bar" , "baz" : "quux" } )) == 243 | expected 244 | end 245 | 246 | test "atom keys" do 247 | uint = :erlang.unique_integer([:positive]) 248 | 249 | assert_raise ParseError, 250 | ~s(cannot parse value at position 2: "key#{uint}"), 251 | fn -> 252 | parse!(~s({"key#{uint}": null}), %{keys: :atoms!}) 253 | end 254 | 255 | assert parse!(~s({"foo": "bar"}), %{keys: :atoms!}) == %{foo: "bar"} 256 | assert parse!(~s({"foo": "bar"}), %{keys: :atoms}) == %{foo: "bar"} 257 | end 258 | 259 | # See: https://github.com/lovasoa/bad_json_parsers 260 | test "deeply nested data structures" do 261 | path = Path.expand(Path.join(__DIR__, "../fixtures/unparsable.json")) 262 | data = File.read!(path) 263 | assert {:ok, _} = parse(data) 264 | end 265 | 266 | property "complex nested input" do 267 | check all( 268 | value <- json_complex_value(), 269 | options <- 270 | optional_map(%{ 271 | keys: one_of([:atoms!, :atoms]), 272 | decimal: boolean() 273 | }) 274 | ) do 275 | assert {:ok, _} = parse(Poison.encode!(value, options)) 276 | end 277 | end 278 | 279 | describe "JSONTestSuite" do 280 | root = Path.expand(Path.join(__DIR__, "../../vendor/JSONTestSuite/test_parsing")) 281 | 282 | for path <- Path.wildcard("#{root}/y_*.json") do 283 | file = Path.basename(path, ".json") 284 | 285 | test "#{file} passes" do 286 | data = File.read!(unquote(path)) 287 | assert {:ok, _} = parse(data) 288 | end 289 | end 290 | 291 | for path <- Path.wildcard("#{root}/n_*.json") do 292 | file = Path.basename(path, ".json") 293 | 294 | test "#{file} fails" do 295 | data = File.read!(unquote(path)) 296 | assert_raise ParseError, fn -> parse!(data) end 297 | end 298 | end 299 | end 300 | 301 | defp parse(iodata, options \\ %{}) do 302 | {:ok, parse!(iodata, options)} 303 | rescue 304 | exception -> 305 | {:error, exception} 306 | end 307 | end 308 | -------------------------------------------------------------------------------- /test/poison_test.exs: -------------------------------------------------------------------------------- 1 | defmodule PoisonTest do 2 | use ExUnit.Case, async: true 3 | 4 | doctest Poison 5 | end 6 | -------------------------------------------------------------------------------- /test/test_helper.exs: -------------------------------------------------------------------------------- 1 | defmodule Poison.TestGenerators do 2 | use ExUnitProperties 3 | 4 | def json_string(options \\ []) do 5 | string( 6 | ~c(\b\t\n\f\r) ++ [0x20..0x7E, 0xA0..0xD7FF, 0xE000..0xFFFD, 0x10000..0x10FFFF], 7 | options 8 | ) 9 | end 10 | 11 | def json_value do 12 | one_of([ 13 | constant(nil), 14 | boolean(), 15 | integer(), 16 | float(), 17 | json_string() 18 | ]) 19 | end 20 | 21 | def json_list(options \\ []) do 22 | list_of(json_value(), options) 23 | end 24 | 25 | def json_map(options \\ []) do 26 | map_of(json_string(min_length: 1), json_value(), options) 27 | end 28 | 29 | def json_complex_value do 30 | tree( 31 | json_value(), 32 | &one_of([ 33 | list_of(&1), 34 | map_of(json_string(min_length: 1), &1) 35 | ]) 36 | ) 37 | end 38 | end 39 | 40 | ExUnit.configure(formatters: [ExUnit.CLIFormatter, JUnitFormatter]) 41 | ExUnit.start() 42 | --------------------------------------------------------------------------------