├── .clj-kondo └── config.edn ├── .github └── workflows │ ├── nvd_scan.yml │ └── test.yml ├── .gitignore ├── ChangeLog.md ├── LICENSE ├── README.md ├── benchmarks └── cheshire │ └── test │ └── benchmark.clj ├── cheshire_puss.png ├── nvd-helper ├── deps.edn ├── nvd-clojure.edn └── suppressions.xml ├── project.clj ├── src ├── cheshire │ ├── core.clj │ ├── custom.clj │ ├── exact.clj │ ├── experimental.clj │ ├── factory.clj │ ├── generate.clj │ ├── generate_seq.clj │ └── parse.clj ├── data_readers.clj └── java │ └── cheshire │ └── prettyprint │ └── CustomPrettyPrinter.java └── test ├── all_month.geojson.gz ├── cheshire └── test │ ├── core.clj │ ├── custom.clj │ └── generative.clj ├── multi.json └── pass1.json /.clj-kondo/config.edn: -------------------------------------------------------------------------------- 1 | {:exclude-files "test/cheshire/test/generative.clj"} 2 | -------------------------------------------------------------------------------- /.github/workflows/nvd_scan.yml: -------------------------------------------------------------------------------- 1 | name: Vulnerability Scan 2 | 3 | on: 4 | schedule: 5 | # daily 6 | - cron: "5 0 * * *" 7 | push: 8 | branches: 9 | - master 10 | 11 | jobs: 12 | nvd-scan: 13 | environment: nvd 14 | runs-on: ubuntu-latest 15 | steps: 16 | - name: Checkout 17 | uses: actions/checkout@v4 18 | 19 | - name: Setup Java 20 | uses: actions/setup-java@v4 21 | with: 22 | distribution: 'temurin' 23 | java-version: 21 24 | 25 | - name: Install Clojure Tools 26 | uses: DeLaGuardo/setup-clojure@13.2 27 | with: 28 | cli: 'latest' 29 | lein: 'latest' 30 | 31 | - name: Generate Cache Key File 32 | run: | 33 | # We are using RELEASE for deps, grab current versions for CI cache key 34 | mkdir -p target 35 | VERSION_NVD_CLOJURE=$(curl -s --fail https://clojars.org/api/artifacts/nvd-clojure/nvd-clojure | jq -r '.recent_versions[0].version') 36 | VERSION_DEPENDENCY_CHECK=$(curl -s --fail 'https://search.maven.org/solrsearch/select?q=g:org.owasp+AND+a:dependency-check-core&core=gav&rows=1&wt=json' | jq '.response.docs[0].v') 37 | echo "nvd-clojure=${VERSION_NVD_CLOJURE}" > target/ci-versions.txt 38 | echo "dependency-check=${VERSION_DEPENDENCY_CHECK}" >> target/ci-versions.txt 39 | cat target/ci-versions.txt 40 | working-directory: nvd-helper 41 | 42 | - name: Restore NVD DB & Clojure Deps Cache 43 | # nvd caches its db under ~/.m2/repository/org/owasp so that it can 44 | # conveniently be cached with deps 45 | uses: actions/cache/restore@v4 46 | with: 47 | path: | 48 | ~/.m2/repository 49 | ~/.deps.clj 50 | ~/.gitlibs 51 | key: | 52 | nvd-${{ hashFiles('nvd-helper/target/ci-versions.txt') }} 53 | restore-keys: | 54 | nvd- 55 | 56 | - name: Tools Versions 57 | run: | 58 | echo "java -version" 59 | java -version 60 | echo "lein --version" 61 | lein --version 62 | echo "clojure --version" 63 | clojure --version 64 | 65 | - name: Run NVD Scanner 66 | env: 67 | NVD_API_TOKEN: ${{ secrets.NVD_API_TOKEN }} 68 | run: clojure -J-Dclojure.main.report=stderr -M -m nvd.task.check "nvd-clojure.edn" "$(cd ..; lein classpath)" 69 | working-directory: nvd-helper 70 | 71 | - name: Save NVD DB & Clojure Deps Cache 72 | if: always() # always cache regardless of outcome of nvd scan 73 | uses: actions/cache/save@v4 74 | with: 75 | path: | 76 | ~/.m2/repository 77 | ~/.deps.clj 78 | ~/.gitlibs 79 | # we tack on github.run_id to uniquely identify the cache 80 | # the next cache restore will find the best (and most current) match 81 | key: | 82 | nvd-${{ hashFiles('nvd-helper/target/ci-versions.txt') }}-${{ github.run_id }} 83 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: Test 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | pull_request: 8 | branches: 9 | - master 10 | 11 | jobs: 12 | lint: 13 | runs-on: ubuntu-latest 14 | 15 | steps: 16 | - name: Checkout 17 | uses: actions/checkout@v4 18 | 19 | - name: Clojure deps cache 20 | uses: actions/cache@v4 21 | with: 22 | path: | 23 | ~/.m2/repository 24 | key: ${{ runner.os }}-cljdeps-${{ hashFiles('project.clj') }} 25 | restore-keys: ${{ runner.os }}-cljdeps- 26 | 27 | - name: Setup Java 28 | uses: actions/setup-java@v4 29 | with: 30 | distribution: 'temurin' 31 | # We also lint with eastwood, using latest jdk release can help find 32 | # JDK deprecations 33 | java-version: 24 34 | 35 | - name: Install Clojure Tools 36 | uses: DeLaGuardo/setup-clojure@13.2 37 | with: 38 | lein: 'latest' 39 | 40 | - name: Download deps 41 | run: lein all deps 42 | 43 | - name: Tools Versions 44 | run: | 45 | echo "java -version" 46 | java -version 47 | echo "lein --version" 48 | lein --version 49 | 50 | - name: Lint with eastwood 51 | run: lein eastwood 52 | 53 | - name: Lint with clj-kondo 54 | run: lein clj-kondo-lint 55 | 56 | test: 57 | runs-on: ${{ matrix.os }}-latest 58 | strategy: 59 | fail-fast: false 60 | matrix: 61 | os: [ ubuntu, windows ] 62 | java-version: [ '8', '11', '17', '21', '24' ] 63 | 64 | defaults: 65 | run: 66 | # Windows lein.ps1 script is problematic, force lein.bat by using cmd shell 67 | shell: ${{ matrix.os == 'windows' && 'cmd' || 'bash' }} 68 | 69 | name: test ${{matrix.os}} jdk${{matrix.java-version}} 70 | 71 | steps: 72 | - name: Checkout 73 | uses: actions/checkout@v4 74 | 75 | - name: Clojure deps cache 76 | uses: actions/cache@v4 77 | with: 78 | path: | 79 | ~/.m2/repository 80 | key: ${{ runner.os }}-cljdeps-${{ hashFiles('project.clj') }} 81 | restore-keys: ${{ runner.os }}-cljdeps- 82 | 83 | - name: Setup Java 84 | uses: actions/setup-java@v4 85 | with: 86 | distribution: 'temurin' 87 | java-version: ${{ matrix.java-version }} 88 | 89 | - name: Install Clojure Tools 90 | uses: DeLaGuardo/setup-clojure@13.2 91 | with: 92 | lein: 'latest' 93 | 94 | - name: Download deps 95 | run: lein all deps 96 | 97 | - name: Tools Versions 98 | run: | 99 | echo "java -version" 100 | java -version 101 | echo "lein --version" 102 | lein --version 103 | 104 | - name: Run Tests 105 | run: lein all test :all 106 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | lib/* 2 | multi-lib/* 3 | coref 4 | classes/* 5 | .lein-failures 6 | .lein-deps-sum 7 | target 8 | pom.xml* 9 | doc* 10 | .lein* 11 | .nrepl* 12 | .idea 13 | *.iml 14 | .DS_Store 15 | .lsp 16 | .eastwood 17 | .cache 18 | .cpcache 19 | -------------------------------------------------------------------------------- /ChangeLog.md: -------------------------------------------------------------------------------- 1 | 13 | 14 | # v6.0.0 - 2025-04-15 15 | 16 | * **BREAKING** changes 17 | * [#217](https://github.com/dakrone/cheshire/issues/217): 18 | Windows only: pretty printing now consistently uses JVM's OS default of `\r\n` for `:line-break` 19 | ([@lread](https://github.com/lread)) 20 | * Bump minimum JDK version from v7 to v8 21 | ([@lread](https://github.com/lread)) 22 | * Bump Jackson to v2.18.3 23 | ([@lread](https://github.com/lread)) 24 | * [#209](https://github.com/dakrone/cheshire/pull/209): 25 | Fix type hints on `generate-stream` 26 | ([@souenzzo](https://github.com/souenzzo)]) 27 | * [#210](https://github.com/dakrone/cheshire/issues/210): 28 | Expose new Jackson processing limits via factory options 29 | ([@lread](https://github.com/lread)) 30 | * [#215](https://github.com/dakrone/cheshire/issues/215): 31 | Add `:escape-non-ascii` to factory options 32 | ([@lread](https://github.com/lread)) 33 | * Internal maintenance: 34 | * [#212](https://github.com/dakrone/cheshire/issues/212): 35 | Migrate away from usages of deprecated Jackson 36 | ([@lread](https://github.com/lread)) 37 | * Quality 38 | * [#114](https://github.com/dakrone/cheshire/issues/114): 39 | Freshen changelog and add missing history 40 | ([@lread](https://github.com/lread)) 41 | * [#214](https://github.com/dakrone/cheshire/issues/214): 42 | Add CI on GitHub Actions and with test coverage on Linux and Windows 43 | ([@lread](https://github.com/lread)) 44 | * [#222](https://github.com/dakrone/cheshire/issues/222): 45 | Add linting 46 | ([@lread](https://github.com/lread)) 47 | * [#226](https://github.com/dakrone/cheshire/issues/226): 48 | Add vulnerability scanning for cheshire dependencies 49 | ([@lread](https://github.com/lread)) 50 | 51 | [commit log](https://github.com/dakrone/cheshire/compare/5.13.0...6.0.0) 52 | 53 | # v5.13.0 - 2024-04-01 54 | 55 | * Bump Jackson to v2.17.0 56 | ([@antonmos](https://github.com/antonmos)) 57 | 58 | [commit log](https://github.com/dakrone/cheshire/compare/5.12.0...5.13.0) 59 | 60 | # v5.12.0 - 2023-09-19 61 | 62 | * Bump Jackson to v2.15.2 63 | ([@jakepearson](https://github.com/jakepearson)) 64 | 65 | [commit log](https://github.com/dakrone/cheshire/compare/5.11.0...5.12.0) 66 | 67 | # v5.11.0 - 2022-05-26 68 | 69 | * Bump Jackson to v2.13.3 70 | ([@dakrone](https://github.com/dakrone)) 71 | 72 | [commit log](https://github.com/dakrone/cheshire/compare/5.10.2...5.11.0) 73 | 74 | # v5.10.2 - 2022-01-24 75 | 76 | * [ab52d71](https://github.com/dakrone/cheshire/commit/ab52d71): 77 | Add data reader `#cheshire/json` tag to generate JSON strings 78 | ([@mrroman](https://github.com/mrroman)) 79 | * [#187](https://github.com/dakrone/cheshire/pull/187): 80 | Exclude jackson-data-bind dependency, cheshire doesn't use and it has triggered many CVEs 81 | ([@technomancy](https://github.com/technomancy)) 82 | 83 | [commit log](https://github.com/dakrone/cheshire/compare/5.10.1...5.10.2) 84 | 85 | # v5.10.1 - 2021-07-13 86 | 87 | * Bump Jackson to v2.12.4 88 | ([@dakrone](https://github.com/dakrone)) 89 | * [#170](https://github.com/dakrone/cheshire/pull/170): 90 | Add `:strict-duplicate-detection` to factory options 91 | ([@sjamaan](https://github.com/sjamaan)) 92 | * [7040a4a](https://github.com/dakrone/cheshire/commit/7040a4a): 93 | Optimization: replace internal usage of `doseq` with `reduce` 94 | ([@nilern](https://github.com/nilern)) 95 | 96 | [commit log](https://github.com/dakrone/cheshire/compare/5.10.0...5.10.1) 97 | 98 | # v5.10.0 - 2020-02-04 99 | 100 | * Bump Jackson to v2.10.2 101 | ([@willcohen](https://github.com/willcohen)) 102 | * [#150](https://github.com/dakrone/cheshire/pull/150): 103 | Bump tigris to v0.1.2 104 | ([@aiba](https://github.com/aiba)) 105 | * [9d69b18](https://github.com/dakrone/cheshire/commit/9d69b18): 106 | Fix misplaced docstring (found with clj-kondo) 107 | ([@borkdude](https://github.com/borkdude)) 108 | 109 | [commit log](https://github.com/dakrone/cheshire/compare/5.9.0...5.10.0) 110 | 111 | # v5.9.0 - 2019-08-05 112 | 113 | * Bump Jackson to v2.9.9 114 | ([@kumarshantanu](https://github.com/kumarshantanu)) 115 | * [b980d98](https://github.com/dakrone/cheshire/commit/b980d98): 116 | Add `parse-stream-strict` to parse streams strictly rather than lazily 117 | ([@nilern](https://github.com/nilern)) 118 | * [#141](https://github.com/dakrone/cheshire/pull/141): 119 | Chunk size for lazy seqs is now configurable 120 | ([@johnswanson](https://github.com/johnswanson) & ([@cayennes](https://github.com/cayennes))) 121 | * [fe453ea](https://github.com/dakrone/cheshire/commit/fe453ea): 122 | Fix misplaced docstring (found with clj-kondo) 123 | ([@borkdude](https://github.com/borkdude)) 124 | 125 | [commit log](https://github.com/dakrone/cheshire/compare/5.8.1...5.9.0) 126 | 127 | # v5.8.1 - 2018-09-21 128 | 129 | * Bump Jackson to v2.9.6 130 | ([@ikitommi](https://github.com/ikitommi)) 131 | * [4ceaf16](https://github.com/dakrone/cheshire/commit/4ceaf16): 132 | Add `:quote-field-names` factory option to control quoting field names in encoding 133 | ([@crazymerlyn](https://github.com/crazymerlyn)) 134 | * [#106](https://github.com/dakrone/cheshire/issues/106): 135 | Respect `:indent-objects?` `false` in pretty printer 136 | ([@crazymerlyn](https://github.com/crazymerlyn)) 137 | * [#128](https://github.com/dakrone/cheshire/issues/128): 138 | Add dependency on tools.namespace for clojure 1.9 compatability 139 | ([@dakrone](https://github.com/dakrone)) 140 | * [#131](https://github.com/dakrone/cheshire/issues/131): 141 | Fix type hint in `parse-string-strict` 142 | ([@spieden](https://github.com/spieden)) 143 | 144 | [commit log](https://github.com/dakrone/cheshire/compare/5.8.0...5.8.1) 145 | 146 | # v5.8.0 - 2017-08-15 147 | 148 | * Bump Jackson to v2.9.0 149 | ([@brabster](https://github.com/brabster)) 150 | * [#111](https://github.com/dakrone/cheshire/issues/112): 151 | Fix type hints for un-imported classes 152 | ([@dakrone](https://github.com/dakrone)) 153 | * Add `cheshire.exact` namespace for exactly-once decoding 154 | ([@lucacervello](https://github.com/lucacervello)) 155 | * Add only valid json option when parsing json 156 | ([@lucacervello](https://github.com/lucacervello)) 157 | 158 | [commit log](https://github.com/dakrone/cheshire/compare/5.7.1...5.8.0) 159 | 160 | # v5.7.1 - 2017-04-20 161 | 162 | * [#112](https://github.com/dakrone/cheshire/issues/112): 163 | Remove `condp` usage in `generate` to avoid memory issues for lazy seqs 164 | ([@senior](https://github.com/senior)) 165 | 166 | [commit log](https://github.com/dakrone/cheshire/compare/5.7.0...5.7.1) 167 | 168 | # v5.7.0 - 2017-01-13 169 | 170 | * Bump Jackson to v2.8.6 171 | ([@dakrone](https://github.com/dakrone)) 172 | * [847077d](https://github.com/dakrone/cheshire/commit/847077d): 173 | Allow non const booleans `decode` for `key-fn` 174 | ([@bfabry](https://github.com/bfabry)) 175 | 176 | [commit log](https://github.com/dakrone/cheshire/compare/5.6.3...5.7.0) 177 | 178 | # v5.6.3 - 2016-06-27 179 | 180 | * [#97](https://github.com/dakrone/cheshire/issues/97): 181 | Fix float coercion when encoding 182 | ([@dakrone](https://github.com/dakrone)) 183 | 184 | [commit log](https://github.com/dakrone/cheshire/compare/5.6.2...5.6.3) 185 | 186 | # v5.6.2 - 2016-06-20 187 | 188 | * Bump Jackson to v2.7.5 189 | ([@dakrone](https://github.com/dakrone)) 190 | * [e949e00](https://github.com/dakrone/cheshire/commit/e949e00) 191 | Fix type hints for newer clojure version 192 | ([@pjstadig](https://github.com/pjstadig)) 193 | 194 | [commit log](https://github.com/dakrone/cheshire/compare/5.6.1...5.6.2) 195 | 196 | # v5.6.1 - 2016-04-12 197 | 198 | * [0d90f44](https://github.com/dakrone/cheshire/commit/0d90f44): 199 | Tell javac to generate Java 1.6 compatible class files 200 | ([@Deraen](https://github.com/Deraen)) 201 | 202 | [commit log](https://github.com/dakrone/cheshire/compare/5.6.0...5.6.1) 203 | 204 | # v5.6.0 - 2016-04-10 205 | 206 | * Bump Jackson to v2.7.3 207 | ([@niwinz](https://github.com/niwinz)) 208 | * [#96](https://github.com/dakrone/cheshire/pull/96) 209 | Fixes for type hinting 210 | ([@ahjones](https://github.com/ahjones)) 211 | * [77f9813](https://github.com/dakrone/cheshire/commit/77f9813) 212 | Support byte-array encoding/decoding 213 | ([@blendmaster](https://github.com/blendmaster)) 214 | * [#99](https://github.com/dakrone/cheshire/issues/99): 215 | Make `:pretty` option configurable to use custom pretty printer 216 | ([@prayerslayer](https://github.com/prayerslayer)) 217 | 218 | [commit log](https://github.com/dakrone/cheshire/compare/5.5.0...5.6.0) 219 | 220 | # v5.5.0 - 2015-05-28 221 | 222 | * Bump Jackson to v2.5.3 223 | ([@laczoka](https://github.com/laczoka)) 224 | * [7124611](https://github.com/dakrone/cheshire/commit/7124611): 225 | Add docstrings for clojure-json aliases 226 | ([@danielcompton](https://github.com/danielcompton)) 227 | * [#72](https://github.com/dakrone/cheshire/issues/72) 228 | Address Jackson deprecations 229 | ([@dakrone](https://github.com/dakrone)) 230 | 231 | [commit log](https://github.com/dakrone/cheshire/compare/5.4.0...5.5.0) 232 | 233 | # v5.4.0 - 2014-12-10 234 | 235 | * Bump Jackson to v2.4.4 236 | ([@dakrone](https://github.com/dakrone)) 237 | * [ffac717](https://github.com/dakrone/cheshire/commit/ffac717): 238 | Add CBOR encoding/decoding 239 | ([@dakrone](https://github.com/dakrone)) 240 | * [#53](https://github.com/dakrone/cheshire/issues/53): 241 | Add default encoder for `java.lang.Character` 242 | ([@dakrone](https://github.com/dakrone)) 243 | * [56662c2](https://github.com/dakrone/cheshire/commit/56662c2): 244 | Add sequential write support 245 | ([@kostafey](https://github.com/kostafey)) 246 | 247 | [commit log](https://github.com/dakrone/cheshire/compare/5.3.1...5.4.0) 248 | 249 | # v5.3.1 - 2014-01-10 250 | 251 | * Bump Jackson to v2.3.1 252 | ([@dakrone](https://github.com/dakrone)) 253 | * [#46](https://github.com/dakrone/cheshire/issues/46) 254 | & [#48](https://github.com/dakrone/cheshire/issues/48): 255 | Fix string parsing for 1 and 2 arity methods 256 | ([@maxnoel](https://github.com/maxnoel)) 257 | 258 | [commit log](https://github.com/dakrone/cheshire/compare/5.3.0...5.3.1) 259 | 260 | # v5.3.0 - 2013-12-18 261 | 262 | * Bump Jackson to v2.3.0 263 | ([@dakrone](https://github.com/dakrone)) 264 | * [#48](https://github.com/dakrone/cheshire/issues/48): 265 | Parse streams strictly by default to avoid scoping issues 266 | ([@dakrone](https://github.com/dakrone)) 267 | 268 | [commit log](https://github.com/dakrone/cheshire/compare/5.2.0...5.3.0) 269 | 270 | # v5.2.0 - 2013-05-22 271 | 272 | * Bump tigris to v0.1.1 to use `PushbackReader` 273 | ([@dakrone](https://github.com/dakrone)) 274 | * Performance tweaks 275 | ([@ztellman](https://github.com/ztellman)) 276 | 277 | [commit log](https://github.com/dakrone/cheshire/compare/5.1.2...5.2.0) 278 | 279 | # v5.1.2 - 2013-05-17 280 | 281 | * Bump Jackson to v2.2.1 282 | ([@dakrone](https://github.com/dakrone)) 283 | * [ce4ff056](https://github.com/dakrone/cheshire/commit/ce4ff056): 284 | Add experimental namespace 285 | ([@dakrone](https://github.com/dakrone)) 286 | 287 | [commit log](https://github.com/dakrone/cheshire/compare/5.1.1...5.1.2) 288 | 289 | # v5.1.1 - 2013-04-12 290 | 291 | * [e4cc3b3](https://github.com/dakrone/cheshire/commit/e4cc3b3): 292 | Remove all reflection 293 | ([@amalloy](https://github.com/amalloy)) 294 | * [6d78a56](https://github.com/dakrone/cheshire/commit/6d78a56): 295 | Fixed custom encoder helpers 296 | ([@lynaghk](https://github.com/lynaghk)) 297 | 298 | [commit log](https://github.com/dakrone/cheshire/compare/5.1.0...5.1.1) 299 | 300 | # v5.1.0 - 2013-04-04 301 | 302 | * Bump Jackson to v2.1.4 303 | ([@dakrone](https://github.com/dakrone)) 304 | * [c5f0be3](https://github.com/dakrone/cheshire/commit/c5f0be3): 305 | Allow custom keyword function for encoding 306 | ([@goodwink](https://github.com/goodwink)) 307 | 308 | [commit log](https://github.com/dakrone/cheshire/compare/5.0.2...5.1.0) 309 | 310 | # v5.0.2 - 2013-02-19 311 | 312 | * Bump Jackson to v2.1.3 313 | ([@dakrone](https://github.com/dakrone)) 314 | * [03aff88](https://github.com/dakrone/cheshire/commit/03aff88): 315 | Add more type hinting 316 | ([@ztellman](https://github.com/ztellman)) 317 | 318 | [commit log](https://github.com/dakrone/cheshire/compare/5.0.1...5.0.2) 319 | 320 | # v5.0.1 - 2012-12-04 321 | 322 | * [#32](https://github.com/dakrone/cheshire/issues/32): 323 | Protocol custom encoders now take precedence over regular map encoders. 324 | ([@dakrone](https://github.com/dakrone)) 325 | * Benchmarking is now a separate lein command, not a test selector. 326 | ([@dakrone](https://github.com/dakrone)) 327 | * Performance tweaks 328 | ([@dakrone](https://github.com/dakrone)) 329 | 330 | [commit log](https://github.com/dakrone/cheshire/compare/5.0.0...5.0.1) 331 | 332 | # v5.0.0 - 2012-09-20 333 | 334 | * [#32](https://github.com/dakrone/cheshire/issues/32): 335 | Custom Encoders Changes 336 | ([@dakrone](https://github.com/dakrone)) 337 | * Custom encoder functions were moved to the `cheshire.generate` namespace: 338 | * `cheshire.custom/add-encoder` is now `cheshire.generate/add-encoder` 339 | * `cheshire.custom/remove-encoder` is now `cheshire.generate/remove-encoder` 340 | * In addition, `cheshire.custom/encode` and `cheshire.custom/decode` are no longer 341 | necessary. Use `cheshire.core/encode` and `cheshire.core/decode` instead and 342 | those functions will pick up custom encoders while still preserving the same 343 | level of efficiency. 344 | * The `cheshire.custom` namespace is now deprecated 345 | * Bump Jackson to v2.1.1 346 | ([@dakrone](https://github.com/dakrone)) 347 | 348 | [commit log](https://github.com/dakrone/cheshire/compare/4.0.4...5.0.0) 349 | 350 | # v4.0.4 - 2012-11-19 351 | 352 | * Bump Jackson to v2.1.0 353 | ([@dakrone](https://github.com/dakrone)) 354 | * [811ea45](https://github.com/dakrone/cheshire/commit/811ea45): 355 | Add missing writer arg for custom `encode-stream` passthrough to `encode-stream*` 356 | ([@dakrone](https://github.com/dakrone)) 357 | 358 | [commit log](https://github.com/dakrone/cheshire/compare/4.0.3...4.0.4) 359 | 360 | # v4.0.3 - 2012-09-22 361 | 362 | * Bump Jackson to v2.0.6 363 | ([@dakrone](https://github.com/dakrone)) 364 | * [e7c5088](https://github.com/dakrone/cheshire/commit/e7c5088): 365 | Make clojure a dev dependency 366 | ([@dakrone](https://github.com/dakrone)) 367 | * [10062af](https://github.com/dakrone/cheshire/commit/10062af): 368 | Encode empty sets to empty arrays for consistency with empty array encoding 369 | ([@dlebrero](https://github.com/dlebrero)) 370 | * [9b32e2e](https://github.com/dakrone/cheshire/commit/9b32e2e): 371 | Fix custom encoding of namespaced keywords 372 | ([@dakrone](https://github.com/dakrone)) 373 | 374 | [commit log](https://github.com/dakrone/cheshire/compare/4.0.2...4.0.3) 375 | 376 | # v4.0.2 - 2012-08-30 377 | 378 | * Bump Jackson to v2.0.5 379 | ([@dakrone](https://github.com/dakrone)) 380 | * [bacb3ac](https://github.com/dakrone/cheshire/commit/bacb3ac): 381 | Add support for escaping non-ASCII chars when decoding 382 | ([@dakrone](https://github.com/dakrone)) 383 | * [52a700b](https://github.com/dakrone/cheshire/commit/52a700b): 384 | Add support for custom munging of keys when decoding 385 | ([@dakrone](https://github.com/dakrone)) 386 | 387 | [commit log](https://github.com/dakrone/cheshire/compare/4.0.1...4.0.2) 388 | 389 | # v4.0.1 - 2012-06-12 390 | 391 | * Bump Jackson to v2.0.4 392 | ([@dakrone](https://github.com/dakrone)) 393 | * [6087d7f](https://github.com/dakrone/cheshire/commit/6087d7f): 394 | Add support for encoding `Byte` and `Short` to int 395 | ([@dakrone](https://github.com/dakrone)) 396 | * [d05acde](https://github.com/dakrone/cheshire/commit/d05acde): 397 | Add support for encoding `clojure.lang.Associative` to json map 398 | ([@warsus](https://github.com/warsus)) 399 | * Performance tweaks 400 | ([@dakrone](https://github.com/dakrone)) 401 | 402 | [commit log](https://github.com/dakrone/cheshire/compare/4.0.0...4.0.1) 403 | 404 | # v4.0.0 - 2012-04-13 405 | 406 | * [20ebaa8](https://github.com/dakrone/cheshire/commit/20ebaa8): 407 | Add optional default pretty printer to json generator 408 | ([@drewr](https://github.com/drewr)) 409 | * Support encoding with custom date format 410 | ([@dakrone](https://github.com/dakrone)) 411 | 412 | [commit log](https://github.com/dakrone/cheshire/compare/3.1.0...4.0.0) 413 | 414 | # v3.1.0 - 2012-03-30 415 | 416 | * Bump Jackson to v2.0.0 417 | ([@dakrone](https://github.com/dakrone)) 418 | * Performance tweaks 419 | ([@dakrone](https://github.com/dakrone)) 420 | 421 | [commit log](https://github.com/dakrone/cheshire/compare/3.0.0...3.1.0) 422 | 423 | # v3.0.0 - 2012-03-09 424 | 425 | * [65ace30](https://github.com/dakrone/cheshire/commit/65ace30): 426 | Add support for encoding `clojure.lang.PersitentQueue` 427 | ([@dakrone](https://github.com/dakrone)) 428 | * [89348a7](https://github.com/dakrone/cheshire/commit/89348a7): 429 | Throw `JsonGenerationExcption` instead of `Exception` when encoding fails 430 | ([@dakrone](https://github.com/dakrone)) 431 | * [714bec2](https://github.com/dakrone/cheshire/commit/714bec2): 432 | Custom encoding now falls back to core encoding by default 433 | ([@dakrone](https://github.com/dakrone)) 434 | * [263ed65](https://github.com/dakrone/cheshire/commit/263ed65): 435 | Encode qualified keywords `{:document/name "My document"} -> {"document/name" : "My document"}` 436 | ([@maxweber](https://github.com/maxweber)) 437 | 438 | [commit log](https://github.com/dakrone/cheshire/compare/2.2.2...3.0.0) 439 | 440 | # v2.2.2 - 2012-03-06 441 | 442 | * [6e4df40](https://github.com/dakrone/cheshire/commit/6e4df40): 443 | Generate symbols as strings, encoding non-resolvable symbol without throwing 444 | ([@dakrone](https://github.com/dakrone)) 445 | * [#19](https://github.com/dakrone/cheshire/issues/19): 446 | Fix custom encoding for Longs 447 | ([@dakrone](https://github.com/dakrone)) 448 | 449 | [commit log](https://github.com/dakrone/cheshire/compare/2.2.1...2.2.2) 450 | 451 | # v2.2.1 - 2012-03-05 452 | 453 | * Bump Jackson to v1.9.5 454 | ([@dakrone](https://github.com/dakrone)) 455 | * [d2a9ad8a](https://github.com/dakrone/cheshire/commit/d2a9ad8a): 456 | Add support for `java.util.{Map,List,Set}` 457 | ([@ndimiduk](https://github.com/ndimiduk)) 458 | * Performance tweaks 459 | ([@dakrone](https://github.com/dakrone)) 460 | 461 | [commit log](https://github.com/dakrone/cheshire/compare/2.2.0...2.2.1) 462 | 463 | # v2.2.0 - 2012-02-07 464 | 465 | * [#16](https://github.com/dakrone/cheshire/issues/16): 466 | Allow parsing floats into BigDecimal to retain precision 467 | ([@dakrone](https://github.com/dakrone)) 468 | * Various performance tweaks 469 | ([@dakrone](https://github.com/dakrone)) 470 | 471 | [commit log](https://github.com/dakrone/cheshire/compare/2.1.0...2.2.0) 472 | 473 | # v2.1.0 - 2012-01-27 474 | 475 | * Bump Jackson to v1.9.4 476 | ([@dakrone](https://github.com/dakrone)) 477 | * Introduce `*json-factory*`, `*smile-factory*`, `*cbor-factory*` as a way to specify options to Jackson 478 | ([@dakrone](https://github.com/dakrone)) 479 | * Address some reflection warnings 480 | ([@dakrone](https://github.com/dakrone)) 481 | 482 | [commit log](https://github.com/dakrone/cheshire/compare/2.0.6...2.1.0) 483 | 484 | # v2.0.6 - 2012-01-12 485 | 486 | * [b96106e](https://github.com/dakrone/cheshire/commit/b96106e): 487 | Encode ns-qualified symbols consistently 488 | ([@dakrone](https://github.com/dakrone)) 489 | 490 | [commit log](https://github.com/dakrone/cheshire/compare/2.0.5...2.0.6) 491 | 492 | # v2.0.5 - 2012-01-11 493 | 494 | * [eaee529](https://github.com/dakrone/cheshire/commit/eaee529): 495 | Encode symbols that are not resolvable as strings 496 | ([@dakrone](https://github.com/dakrone)) 497 | 498 | [commit log](https://github.com/dakrone/cheshire/compare/2.0.4...2.0.5) 499 | 500 | # v2.0.4 - 2011-11-29 501 | 502 | * [434099d](https://github.com/dakrone/cheshire/commit/434099d): 503 | Support custom array coercion when parsing 504 | ([@sbtourist](https://github.com/sbtourist)) 505 | 506 | [commit log](https://github.com/dakrone/cheshire/compare/2.0.3...2.0.4) 507 | 508 | # v2.0.3 - 2011-11-26 509 | 510 | * Bump Jackson to v1.9.2 511 | ([@dakrone](https://github.com/dakrone)) 512 | * Support encoding of `clojure.lang.Ratio` 513 | ([@dakrone](https://github.com/dakrone)) 514 | * [de849ce](https://github.com/dakrone/cheshire/commit/de849ce): 515 | Support encoding of `BigDecimal` 516 | ([@CmdrDats](https://github.com/CmdrDats)) 517 | * Don't attempt to parse `nil` things 518 | ([@dakrone](https://github.com/dakrone) and [@zk](https://github.com/zk)) 519 | * [750ae7c](https://github.com/dakrone/cheshire/commit/750ae7c): 520 | Improve error message when encoding fails 521 | ([@dakrone](https://github.com/dakrone)) 522 | 523 | [commit log](https://github.com/dakrone/cheshire/compare/2.0.2...2.0.3) 524 | 525 | # v2.0.2 - 2011-09-16 526 | 527 | * [db755c4](https://github.com/dakrone/cheshire/commit/db755c4): 528 | Support encoding of `BigInteger` 529 | ([@dakrone](https://github.com/dakrone)) 530 | 531 | [commit log](https://github.com/dakrone/cheshire/compare/2.0.1...2.0.2) 532 | 533 | # v2.0.1 - 2011-08-30 534 | 535 | * Bump Jackson to v1.8.5 536 | ([@dakrone](https://github.com/dakrone)) 537 | * [#6](https://github.com/dakrone/cheshire/issues/6): 538 | Support encoding of `java.sql.Timestamp` 539 | ([@dakrone](https://github.com/dakrone)) 540 | * [8c686c2](https://github.com/dakrone/cheshire/commit/8c686c2): 541 | Write namespace when encoding qualified keywords 542 | ([@dakrone](https://github.com/dakrone)) 543 | * Quality 544 | * Automated testing on Travis CI 545 | ([@dakrone](https://github.com/dakrone)) 546 | 547 | [commit log](https://github.com/dakrone/cheshire/compare/2.0.0...2.0.1) 548 | 549 | # v2.0.0 - 2011-08-02 550 | 551 | * Bump Jackson to v1.8.3 552 | ([@dakrone](https://github.com/dakrone)) 553 | * Introduce `cheshire.factory` namespace 554 | ([@dakrone](https://github.com/dakrone)) 555 | * Continue work on custom encoders 556 | ([@dakrone](https://github.com/dakrone)) 557 | 558 | [commit log](https://github.com/dakrone/cheshire/compare/1.1.4...2.0.0) 559 | 560 | # v1.1.4 - 2011-06-28 561 | 562 | * Bump Jackson to v1.8.2 563 | ([@dakrone](https://github.com/dakrone)) 564 | * [85f195b](https://github.com/dakrone/cheshire/commit/85f195b): 565 | Add experimental custom encoders 566 | ([@dakrone](https://github.com/dakrone)) 567 | * [13db92a](https://github.com/dakrone/cheshire/commit/13db92a): 568 | Add convenience methods for adding/removing custom encoders 569 | ([@zk](https://github.com/zk)) 570 | 571 | [commit log](https://github.com/dakrone/cheshire/compare/1.1.3...1.1.4) 572 | 573 | # v1.1.3 - 2011-05-25 574 | 575 | * Bump Jackson to v1.8.1 576 | ([@dakrone](https://github.com/dakrone)) 577 | 578 | [commit log](https://github.com/dakrone/cheshire/compare/da8c206a22afffa55d4023b38ba0f7e6d4d69bdd...1.1.3) 579 | 580 | # v1.1.2 - 2011-04-29 581 | 582 | * [da8c206](https://github.com/dakrone/cheshire/commit/da8c206): 583 | Support clojure v1.3 584 | ([@dakrone](https://github.com/dakrone)) 585 | 586 | [commit log](https://github.com/dakrone/cheshire/compare/1.1.1...da8c206a22afffa55d4023b38ba0f7e6d4d69bdd) 587 | 588 | # v1.1.1 - 2011-04-18 589 | 590 | * [9cdc44c](https://github.com/dakrone/cheshire/commit/9cdc44c): 591 | Add cheshire puss to README :) 592 | ([@dakrone](https://github.com/dakrone)) 593 | * [344e984](https://github.com/dakrone/cheshire/commit/344e984): 594 | Stop using java for parsing 595 | ([@hiredman](https://github.com/hiredman)) 596 | * Quality 597 | * Add time tests, address reflection warnings, and turf unused code 598 | ([@hiredman](https://github.com/hiredman)) 599 | 600 | [commit log](https://github.com/dakrone/cheshire/compare/1.1.0...1.1.1) 601 | 602 | # v1.1.0 - 2011-03-29 603 | 604 | First world-consumption release of cheshire! 605 | 606 | * [04b2ce4](https://github.com/dakrone/cheshire/commit/04b2ce4): 607 | Add `Date` and `UUID` encoding 608 | ([@dakrone](https://github.com/dakrone)) 609 | * [1e21b7d](https://github.com/dakrone/cheshire/commit/1e21b7d): 610 | Add stream handling (encode/decode) 611 | ([@dakrone](https://github.com/dakrone)) 612 | * [215038d](https://github.com/dakrone/cheshire/commit/215038d): 613 | Add helpers for streams 614 | ([@dakrone](https://github.com/dakrone)) 615 | * Add docs and cleanup docstrings 616 | ([@dakrone](https://github.com/dakrone)) 617 | 618 | [commit log](https://github.com/dakrone/cheshire/compare/fca4fbb871ba43b3a00db90d96dd3bc95aa00174...1.1.0) 619 | 620 | # v1.0.2 - 2011-03-25 621 | 622 | * [fca4fbb](https://github.com/dakrone/cheshire/commit/fca4fbb): 623 | Support encoding `#{}` and clojure symbols 624 | ([@dakrone](https://github.com/dakrone)) 625 | 626 | [commit log](https://github.com/dakrone/cheshire/compare/76186a84a68442919d4c7d79eafae9ab40e5b9a0...fca4fbb871ba43b3a00db90d96dd3bc95aa00174) 627 | 628 | # v1.0.1 - 2011-03-25 629 | 630 | * [76186a8](https://github.com/dakrone/cheshire/commit/76186a8): 631 | Add helpers for people coming from `clojure-json` 632 | ([@dakrone](https://github.com/dakrone)) 633 | 634 | [commit log](https://github.com/dakrone/cheshire/compare/33a0e24d7b63fd091afae20666d03ac68c829411...76186a84a68442919d4c7d79eafae9ab40e5b9a0) 635 | 636 | # v1.0.0 - 2011-03-25 637 | 638 | First release of cheshire! 639 | 640 | [commit log](https://github.com/dakrone/cheshire/compare/74eb30ec1a2f94c044c6e46ea4454ec3ab0e2934...33a0e24d7b63fd091afae20666d03ac68c829411) 641 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2011 Matthew Lee Hinman 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | "Software"), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be 12 | included in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 17 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 18 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 19 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 20 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Cheshire 2 | 3 | 4 | 5 | 6 | 'Cheshire Puss,' she began, rather timidly, as she did not at all know 7 | whether it would like the name: however, it only grinned a little 8 | wider. 'Come, it's pleased so far,' thought Alice, and she went 9 | on. 'Would you tell me, please, which way I ought to go from here?' 10 | 11 | 'That depends a good deal on where you want to get to,' said the Cat. 12 | 13 | 'I don't much care where--' said Alice. 14 | 15 | 'Then it doesn't matter which way you go,' said the Cat. 16 | 17 | '--so long as I get SOMEWHERE,' Alice added as an explanation. 18 | 19 | 'Oh, you're sure to do that,' said the Cat, 'if you only walk long 20 | enough.' 21 | 22 |
23 | 24 | Cheshire is fast JSON encoding, based off of clj-json and 25 | clojure-json, with additional features like Date/UUID/Set/Symbol 26 | encoding and SMILE support. 27 | 28 | - [![Docs](https://cljdoc.org/badge/cheshire/cheshire)](https://cljdoc.org/d/cheshire/cheshire) 29 | - [![Clojars Project](https://img.shields.io/clojars/v/cheshire.svg)](https://clojars.org/cheshire) 30 | - [![Continuous Integration Tests](https://github.com/dakrone/cheshire/actions/workflows/test.yml/badge.svg)](https://github.com/dakrone/cheshire/actions/workflows/test.yml) 31 | - [![Vulnerability Scan](https://github.com/dakrone/cheshire/actions/workflows/nvd_scan.yml/badge.svg)](https://github.com/dakrone/cheshire/actions/workflows/nvd_scan.yml) 32 | - Cheshire is a built-in library in the [Babashka](https://babashka.org/) project 33 | 34 | ## Why? 35 | 36 | clojure-json had really nice features (custom encoders), but was slow; 37 | clj-json had no features, but was fast. Cheshire encodes JSON fast, 38 | with added support for more types and the ability to use custom 39 | encoders. 40 | 41 | ## Usage 42 | 43 | ```clojure 44 | [cheshire "6.0.0"] 45 | 46 | ;; Cheshire v6.0.0 uses Jackson 2.18.3 47 | 48 | ;; In your ns statement: 49 | (ns my.ns 50 | (:require [cheshire.core :refer :all])) 51 | ``` 52 | 53 | ### Encoding 54 | 55 | ```clojure 56 | ;; generate some json 57 | (generate-string {:foo "bar" :baz 5}) 58 | 59 | ;; write some json to a stream 60 | (generate-stream {:foo "bar" :baz 5} (clojure.java.io/writer "/tmp/foo")) 61 | 62 | ;; generate some SMILE 63 | (generate-smile {:foo "bar" :baz 5}) 64 | 65 | ;; generate some JSON with Dates 66 | ;; the Date will be encoded as a string using 67 | ;; the default date format: yyyy-MM-dd'T'HH:mm:ss'Z' 68 | (generate-string {:foo "bar" :baz (java.util.Date. 0)}) 69 | 70 | ;; generate some JSON with Dates with custom Date encoding 71 | (generate-string {:baz (java.util.Date. 0)} {:date-format "yyyy-MM-dd"}) 72 | 73 | ;; generate some JSON with pretty formatting 74 | (generate-string {:foo "bar" :baz {:eggplant [1 2 3]}} {:pretty true}) 75 | ;; { 76 | ;; "foo" : "bar", 77 | ;; "baz" : { 78 | ;; "eggplant" : [ 1, 2, 3 ] 79 | ;; } 80 | ;; } 81 | 82 | ;; generate JSON escaping UTF-8 83 | (generate-string {:foo "It costs £100"} {:escape-non-ascii true}) 84 | ;; => "{\"foo\":\"It costs \\u00A3100\"}" 85 | 86 | ;; generate JSON and munge keys with a custom function 87 | (generate-string {:foo "bar"} {:key-fn (fn [k] (.toUpperCase (name k)))}) 88 | ;; => "{\"FOO\":\"bar\"}" 89 | 90 | ;; generate JSON without escaping the characters (by writing it to a file) 91 | (spit "foo.json" (json/generate-string {:foo "bar"} {:pretty true})) 92 | ``` 93 | 94 | In the event encoding fails, Cheshire will throw a `JsonGenerationException`. 95 | 96 | #### Custom Pretty Printing Options 97 | 98 | If Jackson's default pretty printing library is not what you desire, you can 99 | manually create your own pretty printing class and pass to the `generate-string` 100 | or `encode` methods: 101 | 102 | ```clojure 103 | (let [my-pretty-printer (create-pretty-printer 104 | (assoc default-pretty-print-options 105 | :indent-arrays? true))] 106 | (generate-string {:foo [1 2 3]} {:pretty my-pretty-printer})) 107 | ``` 108 | 109 | See the `default-pretty-print-options` for a list of options that can be 110 | changed. 111 | 112 | ### Decoding 113 | 114 | ```clojure 115 | ;; parse some json 116 | (parse-string "{\"foo\":\"bar\"}") 117 | ;; => {"foo" "bar"} 118 | 119 | ;; parse some json and get keywords back 120 | (parse-string "{\"foo\":\"bar\"}" true) 121 | ;; => {:foo "bar"} 122 | 123 | ;; parse some json and munge keywords with a custom function 124 | (parse-string "{\"foo\":\"bar\"}" (fn [k] (keyword (.toUpperCase k)))) 125 | ;; => {:FOO "bar"} 126 | 127 | ;; top-level strings are valid JSON too 128 | (parse-string "\"foo\"") 129 | ;; => "foo" 130 | 131 | ;; parse some SMILE (keywords option also supported) 132 | (parse-smile ) 133 | 134 | ;; parse a stream (keywords option also supported) 135 | (parse-stream (clojure.java.io/reader "/tmp/foo")) 136 | 137 | ;; parse a stream lazily (keywords option also supported) 138 | (parsed-seq (clojure.java.io/reader "/tmp/foo")) 139 | 140 | ;; parse a SMILE stream lazily (keywords option also supported) 141 | (parsed-smile-seq (clojure.java.io/reader "/tmp/foo")) 142 | ``` 143 | 144 | In 2.0.4 and up, Cheshire allows passing in a 145 | function to specify what kind of types to return, like so: 146 | 147 | ```clojure 148 | ;; In this example a function that checks for a certain key 149 | (decode "{\"myarray\":[2,3,3,2],\"myset\":[1,2,2,1]}" true 150 | (fn [field-name] 151 | (if (= field-name "myset") 152 | #{} 153 | []))) 154 | ;; => {:myarray [2 3 3 2], :myset #{1 2}} 155 | ``` 156 | The type must be "transient-able", so use either #{} or [] 157 | 158 | 159 | ### Custom Encoders 160 | 161 | Custom encoding is supported from 2.0.0 and up, if you encounter a 162 | bug, please open a github issue. From 5.0.0 onwards, custom encoding 163 | has been moved to be part of the core namespace (not requiring a 164 | namespace change) 165 | 166 | ```clojure 167 | ;; Custom encoders allow you to swap out the api for the fast 168 | ;; encoder with one that is slightly slower, but allows custom 169 | ;; things to be encoded: 170 | (ns myns 171 | (:require [cheshire.core :refer :all] 172 | [cheshire.generate :refer [add-encoder encode-str remove-encoder]])) 173 | 174 | ;; First, add a custom encoder for a class: 175 | (add-encoder java.awt.Color 176 | (fn [c jsonGenerator] 177 | (.writeString jsonGenerator (str c)))) 178 | 179 | ;; There are also helpers for common encoding actions: 180 | (add-encoder java.net.URL encode-str) 181 | 182 | ;; List of common encoders that can be used: (see generate.clj) 183 | ;; encode-nil 184 | ;; encode-number 185 | ;; encode-seq 186 | ;; encode-date 187 | ;; encode-bool 188 | ;; encode-named 189 | ;; encode-map 190 | ;; encode-symbol 191 | ;; encode-ratio 192 | 193 | ;; Then you can use encode from the custom namespace as normal 194 | (encode (java.awt.Color. 1 2 3)) 195 | ;; => "java.awt.Color[r=1,g=2,b=3]" 196 | 197 | ;; Custom encoders can also be removed: 198 | (remove-encoder java.awt.Color) 199 | 200 | ;; Decoding remains the same, you are responsible for doing custom decoding. 201 | ``` 202 | 203 |

NOTE: `cheshire.custom` has been deprecated in version 5.0.0

204 | 205 | Custom and Core encoding have been combined in Cheshire 5.0.0, so 206 | there is no longer any need to require a different namespace depending 207 | on what you would like to use. 208 | 209 | ### Aliases 210 | 211 | There are also a few aliases for commonly used functions: 212 | 213 | encode -> generate-string 214 | encode-stream -> generate-stream 215 | encode-smile -> generate-smile 216 | decode -> parse-string 217 | decode-stream -> parse-stream 218 | decode-smile -> parse-smile 219 | 220 | ## Features 221 | Cheshire supports encoding standard clojure datastructures, with a few 222 | additions. 223 | 224 | Cheshire encoding supports: 225 | 226 | ### Clojure data structures 227 | - strings 228 | - lists 229 | - vectors 230 | - sets 231 | - maps 232 | - symbols 233 | - booleans 234 | - keywords (qualified and unqualified) 235 | - numbers (Integer, Long, BigInteger, BigInt, Double, Float, Ratio, 236 | Short, Byte, primitives) 237 | - clojure.lang.PersistentQueue 238 | 239 | ### Java classes 240 | - Date 241 | - UUID 242 | - java.sql.Timestamp 243 | - any java.util.Set 244 | - any java.util.Map 245 | - any java.util.List 246 | 247 | ### Custom class encoding while still being fast 248 | 249 | ### Also supports 250 | - Stream encoding/decoding 251 | - Lazy decoding 252 | - Pretty-printing JSON generation 253 | - Unicode escaping 254 | - Custom keyword coercion 255 | - Arbitrary precision for decoded values: 256 | 257 | Cheshire will automatically use a BigInteger if needed for 258 | non-floating-point numbers, however, for floating-point numbers, 259 | Doubles will be used unless the `*use-bigdecimals?*` symbol is bound 260 | to true: 261 | 262 | ```clojure 263 | (ns foo.bar 264 | (require [cheshire.core :as json] 265 | [cheshire.parse :as parse])) 266 | 267 | (json/decode "111111111111111111111111111111111.111111111111111111111111111111111111") 268 | ;; => 1.1111111111111112E32 (a Double) 269 | 270 | (binding [parse/*use-bigdecimals?* true] 271 | (json/decode "111111111111111111111111111111111.111111111111111111111111111111111111")) 272 | ;; => 111111111111111111111111111111111.111111111111111111111111111111111111M (a BigDecimal) 273 | ``` 274 | 275 | - Replacing default encoders for builtin types 276 | - [SMILE encoding/decoding](http://wiki.fasterxml.com/SmileFormatSpec) 277 | 278 | ## Change Log 279 | 280 | [Change log](/ChangeLog.md) is available on GitHub. 281 | 282 | ## Speed 283 | 284 | Cheshire is about twice as fast as data.json. 285 | 286 | Check out the benchmarks in `cheshire.test.benchmark`; or run `lein 287 | benchmark`. If you have scenarios where Cheshire is not performing as 288 | well as expected (compared to a different library), please let me 289 | know. 290 | 291 | ## Experimental things 292 | 293 | In the `cheshire.experimental` namespace: 294 | 295 | ``` 296 | $ echo "Hi. \"THIS\" is a string.\\yep." > /tmp/foo 297 | 298 | $ lein repl 299 | user> (use 'cheshire.experimental) 300 | nil 301 | user> (use 'clojure.java.io) 302 | nil 303 | user> (println (slurp (encode-large-field-in-map {:id "10" 304 | :things [1 2 3] 305 | :body "I'll be removed"} 306 | :body 307 | (input-stream (file "/tmp/foo"))))) 308 | {"things":[1,2,3],"id":"10","body":"Hi. \"THIS\" is a string.\\yep.\n"} 309 | nil 310 | ``` 311 | 312 | `encode-large-field-in-map` is used for streamy JSON encoding where 313 | you want to JSON encode a map, but don't want the map in memory all at 314 | once (it returns a stream). Check out the docstring for full usage. 315 | 316 | It's experimental, like the name says. Based on [Tigris](http://github.com/dakrone/tigris). 317 | 318 | ## Advanced customization for factories 319 | A custom factory can be used like so: 320 | 321 | ```clojure 322 | (ns myns 323 | (:require [cheshire.core :as core] 324 | [cheshire.factory :as factory])) 325 | 326 | (binding [factory/*json-factory* (factory/make-json-factory 327 | {:allow-non-numeric-numbers true})] 328 | (json/decode "{\"foo\":NaN}" true)) 329 | ``` 330 | 331 | See the `default-factory-options` map in 332 | [factory.clj](/src/cheshire/factory.clj) 333 | for a full list of configurable options. Smile and CBOR factories can also be 334 | created, and factories work exactly the same with custom encoding. 335 | 336 | ## Future Ideas/TODOs 337 | - move away from using Java entirely, use Protocols for the 338 | custom encoder (see custom.clj) 339 | - allow custom encoders (see custom.clj) 340 | - figure out a way to encode namespace-qualified keywords 341 | - look into overriding the default encoding handlers with custom handlers 342 | - better handling when java numbers overflow ECMAScript's numbers 343 | (-2^31 to (2^31 - 1)) 344 | - handle encoding java.sql.Timestamp the same as 345 | java.util.Date 346 | - add benchmarking 347 | - get criterium benchmarking ignored for 1.2.1 profile 348 | - look into faster exception handling by pre-allocating an exception 349 | object instead of creating one on-the-fly (maybe ask Steve?) 350 | - make it as fast as possible (ongoing) 351 | 352 | ## License 353 | Release under the MIT license. See LICENSE for the full license. 354 | 355 | ## Thanks 356 | Thanks go to Mark McGranaghan for clj-json and Jim Duey for the name 357 | suggestion. :) 358 | -------------------------------------------------------------------------------- /benchmarks/cheshire/test/benchmark.clj: -------------------------------------------------------------------------------- 1 | (ns cheshire.test.benchmark 2 | (:use [clojure.test]) 3 | (:require [cheshire.core :as core] 4 | [cheshire.custom :as old] 5 | [cheshire.generate :as custom] 6 | [clojure.data.json :as cj] 7 | [clojure.java.io :refer [file input-stream resource]] 8 | [clj-json.core :as clj-json] 9 | [criterium.core :as bench]) 10 | (:import (java.util.zip GZIPInputStream))) 11 | 12 | ;; These tests just print out results, nothing else, they also 13 | ;; currently don't work with clojure 1.2 (but the regular tests do) 14 | 15 | (def test-obj {"int" 3 16 | "boolean" true 17 | "LongObj" (Long/parseLong "2147483647") 18 | "double" 1.23 19 | "nil" nil 20 | "string" "string" 21 | "vec" [1 2 3] 22 | "map" {"a" "b"} 23 | "list" '("a" "b") 24 | "set" #{"a" "b"} 25 | "keyword" :foo}) 26 | 27 | (def test-pretty-opts 28 | {:indentation 4 29 | :indent-arrays? true 30 | :object-field-value-separator ": "}) 31 | 32 | (def big-test-obj 33 | (-> "test/all_month.geojson.gz" 34 | file 35 | input-stream 36 | (GZIPInputStream. ) 37 | slurp 38 | core/decode)) 39 | 40 | (deftest t-bench-clj-json 41 | (println "-------- clj-json Benchmarks --------") 42 | (bench/with-progress-reporting 43 | (bench/quick-bench (clj-json/parse-string 44 | (clj-json/generate-string test-obj)) :verbose)) 45 | (println "-------------------------------------")) 46 | 47 | (deftest t-bench-clojure-json 48 | (println "-------- Data.json Benchmarks -------") 49 | (bench/with-progress-reporting 50 | (bench/quick-bench (cj/read-str (cj/write-str test-obj)) :verbose)) 51 | (println "-------------------------------------")) 52 | 53 | (deftest t-bench-core 54 | (println "---------- Core Benchmarks ----------") 55 | (bench/with-progress-reporting 56 | (bench/bench (core/decode (core/encode test-obj)) :verbose)) 57 | (println "-------------------------------------")) 58 | 59 | (deftest t-bench-pretty 60 | (let [pretty-printer (core/create-pretty-printer test-pretty-opts)] 61 | (println "------- PrettyPrint Benchmarks ------") 62 | (println "........default pretty printer") 63 | (bench/bench (core/encode test-obj {:pretty true})) 64 | (println "........custom pretty printer") 65 | (bench/bench (core/encode test-obj {:pretty pretty-printer})))) 66 | 67 | (deftest t-bench-custom 68 | (println "--------- Custom Benchmarks ---------") 69 | (custom/add-encoder java.net.URL custom/encode-str) 70 | (is (= "\"http://foo.com\"" (core/encode (java.net.URL. "http://foo.com")))) 71 | (let [custom-obj (assoc test-obj "url" (java.net.URL. "http://foo.com"))] 72 | (println "[+] Custom, all custom fields:") 73 | (bench/with-progress-reporting 74 | (bench/quick-bench (core/decode (core/encode custom-obj)) :verbose))) 75 | (println "-------------------------------------")) 76 | 77 | (deftest t-bench-custom-kw-coercion 78 | (println "---- Custom keyword-fn Benchmarks ---") 79 | (let [t (core/encode test-obj)] 80 | (println "[+] (fn [k] (keyword k)) decode") 81 | (bench/with-progress-reporting 82 | (bench/quick-bench (core/decode t (fn [k] (keyword k))))) 83 | (println "[+] basic 'true' keyword-fn decode") 84 | (bench/with-progress-reporting 85 | (bench/quick-bench (core/decode t true))) 86 | (println "[+] no keyword-fn decode") 87 | (bench/with-progress-reporting 88 | (bench/quick-bench (core/decode t))) 89 | (println "[+] (fn [k] (name k)) encode") 90 | (bench/with-progress-reporting 91 | (bench/quick-bench (core/encode test-obj {:key-fn (fn [k] (name k))}))) 92 | (println "[+] no keyword-fn encode") 93 | (bench/with-progress-reporting 94 | (bench/quick-bench (core/encode test-obj)))) 95 | (println "-------------------------------------")) 96 | 97 | (deftest t-large-array 98 | (println "-------- Large array parsing --------") 99 | (let [test-array-json (core/encode (range 1024))] 100 | (bench/with-progress-reporting 101 | (bench/bench (pr-str (core/decode test-array-json))))) 102 | (println "-------------------------------------")) 103 | 104 | (deftest t-large-geojson-object 105 | (println "------- Large GeoJSON parsing -------") 106 | (println "[+] large geojson custom encode") 107 | (bench/with-progress-reporting 108 | (bench/quick-bench (old/encode big-test-obj))) 109 | (println "[+] large geojson encode") 110 | (bench/with-progress-reporting 111 | (bench/quick-bench (core/encode big-test-obj))) 112 | (println "[+] large geojson decode") 113 | (let [s (core/encode big-test-obj)] 114 | (bench/with-progress-reporting 115 | (bench/quick-bench (core/decode s)))) 116 | (println "-------------------------------------")) 117 | -------------------------------------------------------------------------------- /cheshire_puss.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dakrone/cheshire/840d7584c5aae92d8d2d2dd3d0194f4f11be4eb4/cheshire_puss.png -------------------------------------------------------------------------------- /nvd-helper/deps.edn: -------------------------------------------------------------------------------- 1 | {:deps {org.clojure/clojure {:mvn/version "1.12.0"} 2 | ;; it is generally considered bad practice to use RELEASE, but we always want the latest 3 | ;; security tooling 4 | nvd-clojure/nvd-clojure #_:clj-kondo/ignore {:mvn/version "RELEASE"} 5 | org.owasp/dependency-check-core #_:clj-kondo/ignore {:mvn/version "RELEASE"}}} 6 | -------------------------------------------------------------------------------- /nvd-helper/nvd-clojure.edn: -------------------------------------------------------------------------------- 1 | {:delete-config? false 2 | :nvd {:nvd-api {:max-retry-count 30}} 3 | :suppression-file "./suppressions.xml"} 4 | -------------------------------------------------------------------------------- /nvd-helper/suppressions.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 11 | CVE-2024-22871 12 | 13 | 14 | -------------------------------------------------------------------------------- /project.clj: -------------------------------------------------------------------------------- 1 | (defproject cheshire "6.1.0-SNAPSHOT" 2 | :description "JSON and JSON SMILE encoding, fast." 3 | :url "https://github.com/dakrone/cheshire" 4 | :license {:name "The MIT License" 5 | :url "http://opensource.org/licenses/MIT" 6 | :distribution :repo} 7 | :global-vars {*warn-on-reflection* false} 8 | :dependencies [[com.fasterxml.jackson.core/jackson-core "2.18.3"] 9 | [com.fasterxml.jackson.dataformat/jackson-dataformat-smile "2.18.3" 10 | :exclusions [com.fasterxml.jackson.core/jackson-databind]] 11 | [com.fasterxml.jackson.dataformat/jackson-dataformat-cbor "2.18.3" 12 | :exclusions [com.fasterxml.jackson.core/jackson-databind]] 13 | [tigris "0.1.2"]] 14 | :profiles {:dev {:dependencies [[org.clojure/clojure "1.12.0"] 15 | [org.clojure/test.generative "0.1.4"] 16 | [org.clojure/tools.namespace "0.3.1"]]} 17 | :1.7 {:dependencies [[org.clojure/clojure "1.7.0"]]} 18 | :1.8 {:dependencies [[org.clojure/clojure "1.8.0"]]} 19 | :1.9 {:dependencies [[org.clojure/clojure "1.9.0"]]} 20 | :1.10 {:dependencies [[org.clojure/clojure "1.10.3"]]} 21 | :1.11 {:dependencies [[org.clojure/clojure "1.11.4"]]} 22 | :benchmark {:test-paths ["benchmarks"] 23 | :jvm-opts ^:replace ["-Xms1g" "-Xmx1g" "-server"] 24 | :dependencies [[criterium "0.4.6"] 25 | [org.clojure/data.json "0.2.6"] 26 | [clj-json "0.5.3"]]}} 27 | :aliases {"all" ["with-profile" "dev,1.7:dev,1.8:dev,1.9:dev,1.10:dev"] 28 | "benchmark" ["with-profile" "dev,benchmark" "test"] 29 | "pretty-bench" ["with-profile" "dev,benchmark" "test" ":only" 30 | "cheshire.test.benchmark/t-bench-pretty"] 31 | "core-bench" ["with-profile" "dev,benchmark" "test" ":only" 32 | "cheshire.test.benchmark/t-bench-core"] 33 | "clj-kondo-deps" ["with-profile" "+test" "clj-kondo" "--skip-lint" "--copy-configs" "--dependencies" "--parallel" "--lint" "$classpath"] 34 | "clj-kondo-lint" ["do" ["clj-kondo-deps"] ["with-profile" "+test" "clj-kondo" "--parallel" "--lint" "src" "test" "project.clj"]]} 35 | :test-selectors {:default #(and (not (:benchmark %)) 36 | (not (:generative %))) 37 | :generative :generative 38 | :all (constantly true)} 39 | :plugins [[com.github.liquidz/antq "2.11.1276"] 40 | [jonase/eastwood "1.4.3"] 41 | [com.github.clj-kondo/lein-clj-kondo "2025.04.07"]] 42 | :java-source-paths ["src/java"] 43 | :jvm-opts ["-Xmx1024M" 44 | ;; "-XX:+PrintCompilation" 45 | ;; "-XX:+UnlockDiagnosticVMOptions" 46 | ;; "-XX:+PrintInlining" 47 | ] 48 | :eastwood {:namespaces [:source-paths] 49 | :linters [:deprecations]} 50 | :javac-options 51 | ~(let [version (System/getProperty "java.version") 52 | ;; Parse major version from strings like "1.8.0_292" or "11.0.11" 53 | major (-> (re-find #"^(1\.)?(\d+)" version) 54 | (last) 55 | (Integer/parseInt)) 56 | target-opts (case major 57 | 8 ["-source" "1.8" "-target" "1.8"] 58 | (if (>= major 9) 59 | ["--release" "8"] 60 | (throw (ex-info "javac needs a min of JDK 8" {}))))] 61 | (into target-opts ["-Xlint:-options" "-Xlint:deprecation" "-Werror"]))) 62 | -------------------------------------------------------------------------------- /src/cheshire/core.clj: -------------------------------------------------------------------------------- 1 | (ns cheshire.core 2 | "Main encoding and decoding namespace." 3 | (:require [cheshire.factory :as factory] 4 | [cheshire.generate :as gen] 5 | [cheshire.generate-seq :as gen-seq] 6 | [cheshire.parse :as parse]) 7 | (:import (com.fasterxml.jackson.core JsonParser JsonFactory 8 | JsonGenerator PrettyPrinter) 9 | (com.fasterxml.jackson.core.json JsonWriteFeature) 10 | (com.fasterxml.jackson.dataformat.cbor CBORFactory) 11 | (com.fasterxml.jackson.dataformat.smile SmileFactory) 12 | (cheshire.prettyprint CustomPrettyPrinter) 13 | (java.io StringWriter StringReader BufferedReader 14 | ByteArrayOutputStream OutputStream Reader Writer))) 15 | 16 | (defonce default-pretty-print-options 17 | {:indentation " " 18 | :line-break (System/lineSeparator) 19 | :indent-arrays? false 20 | :indent-objects? true 21 | :before-array-values nil 22 | :after-array-values nil 23 | :object-field-value-separator nil}) 24 | 25 | (defn create-pretty-printer 26 | "Returns an instance of CustomPrettyPrinter based on the configuration 27 | provided as argument" 28 | [options] 29 | (let [effective-opts (merge default-pretty-print-options options) 30 | indentation (:indentation effective-opts) 31 | line-break (:line-break effective-opts) 32 | indent-arrays? (:indent-arrays? effective-opts) 33 | indent-objects? (:indent-objects? effective-opts) 34 | before-array-values (:before-array-values effective-opts) 35 | after-array-values (:after-array-values effective-opts) 36 | object-field-value-separator (:object-field-value-separator effective-opts) 37 | indent-with (condp instance? indentation 38 | String indentation 39 | Long (apply str (repeat indentation " ")) 40 | Integer (apply str (repeat indentation " ")) 41 | " ")] 42 | (-> (new CustomPrettyPrinter) 43 | (.setIndentation indent-with line-break indent-objects? indent-arrays?) 44 | (.setBeforeArrayValues before-array-values) 45 | (.setAfterArrayValues after-array-values) 46 | (.setObjectFieldValueSeparator object-field-value-separator)))) 47 | 48 | ;; Generators 49 | (defn generate-string 50 | "Returns a JSON-encoding String for the given Clojure object. Takes an 51 | optional date format string that Date objects will be encoded with. 52 | 53 | The default date format (in UTC) is: yyyy-MM-dd'T'HH:mm:ss'Z'" 54 | (^String [obj] 55 | (generate-string obj nil)) 56 | (^String [obj opt-map] 57 | (let [sw (StringWriter.) 58 | generator (.createGenerator 59 | ^JsonFactory (or factory/*json-factory* 60 | factory/json-factory) 61 | ^Writer sw) 62 | print-pretty (:pretty opt-map)] 63 | (when print-pretty 64 | (condp instance? print-pretty 65 | Boolean 66 | (.useDefaultPrettyPrinter generator) 67 | clojure.lang.IPersistentMap 68 | (.setPrettyPrinter generator (create-pretty-printer print-pretty)) 69 | PrettyPrinter 70 | (.setPrettyPrinter generator print-pretty) 71 | nil)) 72 | ;; legacy fn opt, consider using fatory opt instead 73 | (when (some? (:escape-non-ascii opt-map)) 74 | (.configure generator (.mappedFeature JsonWriteFeature/ESCAPE_NON_ASCII) 75 | (boolean (:escape-non-ascii opt-map)))) 76 | (gen/generate generator obj 77 | (or (:date-format opt-map) factory/default-date-format) 78 | (:ex opt-map) 79 | (:key-fn opt-map)) 80 | (.flush generator) 81 | (.toString sw)))) 82 | 83 | (defn generate-stream 84 | "Returns a BufferedWriter for the given Clojure object with the JSON-encoded 85 | data written to the writer. Takes an optional date format string that Date 86 | objects will be encoded with. 87 | 88 | The default date format (in UTC) is: yyyy-MM-dd'T'HH:mm:ss'Z'" 89 | (^Writer [obj ^Writer writer] 90 | (generate-stream obj writer nil)) 91 | (^Writer [obj ^Writer writer opt-map] 92 | (let [generator (.createGenerator 93 | ^JsonFactory (or factory/*json-factory* 94 | factory/json-factory) 95 | writer) 96 | print-pretty (:pretty opt-map)] 97 | (when print-pretty 98 | (condp instance? print-pretty 99 | Boolean 100 | (.useDefaultPrettyPrinter generator) 101 | clojure.lang.IPersistentMap 102 | (.setPrettyPrinter generator (create-pretty-printer print-pretty)) 103 | PrettyPrinter 104 | (.setPrettyPrinter generator print-pretty) 105 | nil)) 106 | ;; legacy fn opt, consider using fatory opt instead 107 | (when (some? (:escape-non-ascii opt-map)) 108 | (.configure generator (.mappedFeature JsonWriteFeature/ESCAPE_NON_ASCII) 109 | (boolean (:escape-non-ascii opt-map)))) 110 | (gen/generate generator obj (or (:date-format opt-map) 111 | factory/default-date-format) 112 | (:ex opt-map) 113 | (:key-fn opt-map)) 114 | (.flush generator) 115 | writer))) 116 | 117 | (defn create-generator 118 | "Returns JsonGenerator for given writer." 119 | [writer] 120 | (.createGenerator 121 | ^JsonFactory (or factory/*json-factory* 122 | factory/json-factory) 123 | ^Writer writer)) 124 | 125 | (def ^:dynamic ^JsonGenerator *generator* nil) 126 | (def ^:dynamic *opt-map* nil) 127 | 128 | (defmacro with-writer 129 | "Start writing for series objects using the same json generator. 130 | Takes writer and options map as arguments. 131 | Expects its body as sequence of write calls. 132 | Returns a given writer." 133 | [[writer opt-map] & body] 134 | `(let [c-wr# ~writer] 135 | (binding [*generator* (create-generator c-wr#) 136 | *opt-map* ~opt-map] 137 | ~@body 138 | (.flush *generator*) 139 | c-wr#))) 140 | 141 | (defn write 142 | "Write given Clojure object as a piece of data within with-writer. 143 | List of wholeness acceptable values: 144 | - no value - the same as :all 145 | - :all - write object in a regular way with start and end borders 146 | - :start - write object with start border only 147 | - :start-inner - write object and its inner object with start border only 148 | - :end - write object with end border only." 149 | ([obj] (write obj nil)) 150 | ([obj wholeness] 151 | (gen-seq/generate *generator* obj (or (:date-format *opt-map*) 152 | factory/default-date-format) 153 | (:ex *opt-map*) 154 | (:key-fn *opt-map*) 155 | :wholeness wholeness))) 156 | 157 | (defn generate-smile 158 | "Returns a SMILE-encoded byte-array for the given Clojure object. 159 | Takes an optional date format string that Date objects will be encoded with. 160 | 161 | The default date format (in UTC) is: yyyy-MM-dd'T'HH:mm:ss'Z'" 162 | (^bytes [obj] 163 | (generate-smile obj nil)) 164 | (^bytes [obj opt-map] 165 | (let [baos (ByteArrayOutputStream.) 166 | generator (.createGenerator ^SmileFactory 167 | (or factory/*smile-factory* 168 | factory/smile-factory) 169 | ^OutputStream baos)] 170 | (gen/generate generator obj (or (:date-format opt-map) 171 | factory/default-date-format) 172 | (:ex opt-map) 173 | (:key-fn opt-map)) 174 | (.flush generator) 175 | (.toByteArray baos)))) 176 | 177 | (defn generate-cbor 178 | "Returns a CBOR-encoded byte-array for the given Clojure object. 179 | Takes an optional date format string that Date objects will be encoded with. 180 | 181 | The default date format (in UTC) is: yyyy-MM-dd'T'HH:mm:ss'Z'" 182 | (^bytes [obj] 183 | (generate-cbor obj nil)) 184 | (^bytes [obj opt-map] 185 | (let [baos (ByteArrayOutputStream.) 186 | generator (.createGenerator ^CBORFactory 187 | (or factory/*cbor-factory* 188 | factory/cbor-factory) 189 | ^OutputStream baos)] 190 | (gen/generate generator obj (or (:date-format opt-map) 191 | factory/default-date-format) 192 | (:ex opt-map) 193 | (:key-fn opt-map)) 194 | (.flush generator) 195 | (.toByteArray baos)))) 196 | 197 | ;; Parsers 198 | (defn parse-string 199 | "Returns the Clojure object corresponding to the given JSON-encoded string. 200 | An optional key-fn argument can be either true (to coerce keys to keywords), 201 | false to leave them as strings, or a function to provide custom coercion. 202 | 203 | The array-coerce-fn is an optional function taking the name of an array field, 204 | and returning the collection to be used for array values. 205 | 206 | If the top-level object is an array, it will be parsed lazily (use 207 | `parse-strict' if strict parsing is required for top-level arrays." 208 | ([string] (parse-string string nil nil)) 209 | ([string key-fn] (parse-string string key-fn nil)) 210 | ([^String string key-fn array-coerce-fn] 211 | (when string 212 | (parse/parse 213 | (.createParser ^JsonFactory (or factory/*json-factory* 214 | factory/json-factory) 215 | ^Reader (StringReader. string)) 216 | key-fn nil array-coerce-fn)))) 217 | 218 | ;; Parsing strictly 219 | (defn parse-string-strict 220 | "Returns the Clojure object corresponding to the given JSON-encoded string. 221 | An optional key-fn argument can be either true (to coerce keys to keywords), 222 | false to leave them as strings, or a function to provide custom coercion. 223 | 224 | The array-coerce-fn is an optional function taking the name of an array field, 225 | and returning the collection to be used for array values. 226 | 227 | Does not lazily parse top-level arrays." 228 | ([string] (parse-string-strict string nil nil)) 229 | ([string key-fn] (parse-string-strict string key-fn nil)) 230 | ([^String string key-fn array-coerce-fn] 231 | (when string 232 | (parse/parse-strict 233 | (.createParser ^JsonFactory (or factory/*json-factory* 234 | factory/json-factory) 235 | ^Reader (StringReader. string)) 236 | key-fn nil array-coerce-fn)))) 237 | 238 | (defn parse-stream 239 | "Returns the Clojure object corresponding to the given reader, reader must 240 | implement BufferedReader. An optional key-fn argument can be either true (to 241 | coerce keys to keywords),false to leave them as strings, or a function to 242 | provide custom coercion. 243 | 244 | The array-coerce-fn is an optional function taking the name of an array field, 245 | and returning the collection to be used for array values. 246 | 247 | If the top-level object is an array, it will be parsed lazily (use 248 | `parse-strict' if strict parsing is required for top-level arrays. 249 | 250 | If multiple objects (enclosed in a top-level `{}' need to be parsed lazily, 251 | see parsed-seq." 252 | ([rdr] (parse-stream rdr nil nil)) 253 | ([rdr key-fn] (parse-stream rdr key-fn nil)) 254 | ([^BufferedReader rdr key-fn array-coerce-fn] 255 | (when rdr 256 | (parse/parse 257 | (.createParser ^JsonFactory (or factory/*json-factory* 258 | factory/json-factory) 259 | ^Reader rdr) 260 | key-fn nil array-coerce-fn)))) 261 | 262 | (defn parse-stream-strict 263 | "Returns the Clojure object corresponding to the given reader, reader must 264 | implement BufferedReader. An optional key-fn argument can be either true (to 265 | coerce keys to keywords),false to leave them as strings, or a function to 266 | provide custom coercion. 267 | 268 | The array-coerce-fn is an optional function taking the name of an array field, 269 | and returning the collection to be used for array values. 270 | 271 | Does not lazily parse top-level arrays." 272 | ([rdr] (parse-stream-strict rdr nil nil)) 273 | ([rdr key-fn] (parse-stream-strict rdr key-fn nil)) 274 | ([^BufferedReader rdr key-fn array-coerce-fn] 275 | (when rdr 276 | (parse/parse-strict 277 | (.createParser ^JsonFactory (or factory/*json-factory* 278 | factory/json-factory) 279 | ^Reader rdr) 280 | key-fn nil array-coerce-fn)))) 281 | 282 | (defn parse-smile 283 | "Returns the Clojure object corresponding to the given SMILE-encoded bytes. 284 | An optional key-fn argument can be either true (to coerce keys to keywords), 285 | false to leave them as strings, or a function to provide custom coercion. 286 | 287 | The array-coerce-fn is an optional function taking the name of an array field, 288 | and returning the collection to be used for array values." 289 | ([bytes] (parse-smile bytes nil nil)) 290 | ([bytes key-fn] (parse-smile bytes key-fn nil)) 291 | ([^bytes bytes key-fn array-coerce-fn] 292 | (when bytes 293 | (parse/parse 294 | (.createParser ^SmileFactory (or factory/*smile-factory* 295 | factory/smile-factory) bytes) 296 | key-fn nil array-coerce-fn)))) 297 | 298 | (defn parse-cbor 299 | "Returns the Clojure object corresponding to the given CBOR-encoded bytes. 300 | An optional key-fn argument can be either true (to coerce keys to keywords), 301 | false to leave them as strings, or a function to provide custom coercion. 302 | 303 | The array-coerce-fn is an optional function taking the name of an array field, 304 | and returning the collection to be used for array values." 305 | ([bytes] (parse-cbor bytes nil nil)) 306 | ([bytes key-fn] (parse-cbor bytes key-fn nil)) 307 | ([^bytes bytes key-fn array-coerce-fn] 308 | (when bytes 309 | (parse/parse 310 | (.createParser ^CBORFactory (or factory/*cbor-factory* 311 | factory/cbor-factory) bytes) 312 | key-fn nil array-coerce-fn)))) 313 | 314 | (def ^{:doc "Object used to determine end of lazy parsing attempt."} 315 | eof (Object.)) 316 | 317 | ;; Lazy parsers 318 | (defn- parsed-seq* 319 | "Internal lazy-seq parser" 320 | [^JsonParser parser key-fn array-coerce-fn] 321 | (lazy-seq 322 | (let [elem (parse/parse-strict parser key-fn eof array-coerce-fn)] 323 | (when-not (identical? elem eof) 324 | (cons elem (parsed-seq* parser key-fn array-coerce-fn)))))) 325 | 326 | (defn parsed-seq 327 | "Returns a lazy seq of Clojure objects corresponding to the JSON read from 328 | the given reader. The seq continues until the end of the reader is reached. 329 | 330 | The array-coerce-fn is an optional function taking the name of an array field, 331 | and returning the collection to be used for array values. 332 | If non-laziness is needed, see parse-stream." 333 | ([reader] (parsed-seq reader nil nil)) 334 | ([reader key-fn] (parsed-seq reader key-fn nil)) 335 | ([^BufferedReader reader key-fn array-coerce-fn] 336 | (when reader 337 | (parsed-seq* (.createParser ^JsonFactory 338 | (or factory/*json-factory* 339 | factory/json-factory) 340 | ^Reader reader) 341 | key-fn array-coerce-fn)))) 342 | 343 | (defn parsed-smile-seq 344 | "Returns a lazy seq of Clojure objects corresponding to the SMILE read from 345 | the given reader. The seq continues until the end of the reader is reached. 346 | 347 | The array-coerce-fn is an optional function taking the name of an array field, 348 | and returning the collection to be used for array values." 349 | ([reader] (parsed-smile-seq reader nil nil)) 350 | ([reader key-fn] (parsed-smile-seq reader key-fn nil)) 351 | ([^BufferedReader reader key-fn array-coerce-fn] 352 | (when reader 353 | (parsed-seq* (.createParser ^SmileFactory 354 | (or factory/*smile-factory* 355 | factory/smile-factory) 356 | ^Reader reader) 357 | key-fn array-coerce-fn)))) 358 | 359 | ;; aliases for clojure-json users 360 | (defmacro copy-arglists 361 | [dst src] 362 | `(alter-meta! (var ~dst) merge (select-keys (meta (var ~src)) [:arglists]))) 363 | (def encode "Alias to generate-string for clojure-json users" generate-string) 364 | (copy-arglists encode generate-string) 365 | (def encode-stream "Alias to generate-stream for clojure-json users" generate-stream) 366 | (copy-arglists encode-stream generate-stream) 367 | (def encode-smile "Alias to generate-smile for clojure-json users" generate-smile) 368 | (copy-arglists encode-smile generate-smile) 369 | (def decode "Alias to parse-string for clojure-json users" parse-string) 370 | (copy-arglists decode parse-string) 371 | (def decode-strict "Alias to parse-string-strict for clojure-json users" parse-string-strict) 372 | (copy-arglists decode-strict parse-string-strict) 373 | (def decode-stream "Alias to parse-stream for clojure-json users" parse-stream) 374 | (copy-arglists decode-stream parse-stream) 375 | (def decode-smile "Alias to parse-smile for clojure-json users" parse-smile) 376 | (copy-arglists decode-smile parse-smile) 377 | -------------------------------------------------------------------------------- /src/cheshire/custom.clj: -------------------------------------------------------------------------------- 1 | (ns ^{:deprecated "5.0.0"} cheshire.custom 2 | "DEPRECATED 3 | 4 | Methods used for extending JSON generation to different Java classes. 5 | Has the same public API as core.clj so they can be swapped in and out." 6 | (:require [cheshire.core :as core] 7 | [cheshire.factory :as fact] 8 | [cheshire.generate :as generate]) 9 | (:import (java.io BufferedWriter ByteArrayOutputStream StringWriter) 10 | (java.util Date SimpleTimeZone) 11 | (java.text SimpleDateFormat) 12 | (com.fasterxml.jackson.dataformat.smile SmileFactory) 13 | (com.fasterxml.jackson.core JsonFactory JsonGenerator) 14 | (com.fasterxml.jackson.core.json JsonWriteFeature))) 15 | 16 | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; 17 | ;;;;;; DEPRECATED, DO NOT USE ;;;;;; 18 | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; 19 | 20 | 21 | ;; date format rebound for custom encoding 22 | (def ^{:dynamic true :private true} *date-format* nil) 23 | 24 | (defprotocol JSONable 25 | (to-json [t jg])) 26 | 27 | (defn encode* 28 | (^String [obj] 29 | (encode* obj nil)) 30 | (^String [obj opt-map] 31 | (binding [*date-format* (or (:date-format opt-map) fact/default-date-format)] 32 | (let [sw (StringWriter.) 33 | generator (.createGenerator 34 | ^JsonFactory (or fact/*json-factory* fact/json-factory) sw)] 35 | (when (:pretty opt-map) 36 | (.useDefaultPrettyPrinter generator)) 37 | (when (some? (:escape-non-ascii opt-map)) 38 | (.configure generator (.mappedFeature JsonWriteFeature/ESCAPE_NON_ASCII) 39 | (boolean (:escape-non-ascii opt-map)))) 40 | (if obj 41 | (to-json obj generator) 42 | (.writeNull generator)) 43 | (.flush generator) 44 | (.toString sw))))) 45 | 46 | (def encode encode*) 47 | (core/copy-arglists encode encode*) 48 | 49 | (defn encode-stream* 50 | (^String [obj ^BufferedWriter w] 51 | (encode-stream* obj w nil)) 52 | (^String [obj ^BufferedWriter w opt-map] 53 | (binding [*date-format* (or (:date-format opt-map) fact/default-date-format)] 54 | (let [generator (.createGenerator 55 | ^JsonFactory (or fact/*json-factory* fact/json-factory) w)] 56 | (when (:pretty opt-map) 57 | (.useDefaultPrettyPrinter generator)) 58 | (when (some? (:escape-non-ascii opt-map)) 59 | (.configure generator (.mappedFeature JsonWriteFeature/ESCAPE_NON_ASCII) 60 | (boolean (:escape-non-ascii opt-map)))) 61 | (to-json obj generator) 62 | (.flush generator) 63 | w)))) 64 | 65 | 66 | 67 | (def encode-stream encode-stream*) 68 | (core/copy-arglists encode-stream encode-stream*) 69 | 70 | (defn encode-smile* 71 | (^bytes [obj] 72 | (encode-smile* obj nil)) 73 | (^bytes [obj opt-map] 74 | (binding [*date-format* (or (:date-format opt-map) fact/default-date-format)] 75 | (let [baos (ByteArrayOutputStream.) 76 | generator (.createGenerator ^SmileFactory 77 | (or fact/*smile-factory* fact/smile-factory) 78 | baos)] 79 | (to-json obj generator) 80 | (.flush generator) 81 | (.toByteArray baos))))) 82 | 83 | (def encode-smile encode-smile*) 84 | (core/copy-arglists encode-smile encode-smile*) 85 | 86 | ;; there are no differences in parsing, but these are here to make 87 | ;; this a self-contained namespace if desired 88 | (def parse core/decode) 89 | (core/copy-arglists parse core/decode) 90 | (def parse-string core/decode) 91 | (core/copy-arglists parse-string core/decode) 92 | (def parse-stream core/decode-stream) 93 | (core/copy-arglists parse-stream core/decode-stream) 94 | (def parse-smile core/decode-smile) 95 | (core/copy-arglists parse-smile core/decode-smile) 96 | (def parsed-seq core/parsed-seq) 97 | (core/copy-arglists parsed-seq core/parsed-seq) 98 | (def decode core/parse-string) 99 | (core/copy-arglists decode core/parse-string) 100 | (def decode-stream parse-stream) 101 | (core/copy-arglists decode-stream core/parse-stream) 102 | (def decode-smile parse-smile) 103 | (core/copy-arglists decode-smile core/parse-smile) 104 | 105 | ;; aliases for encoding 106 | (def generate-string encode*) 107 | (core/copy-arglists generate-string encode*) 108 | (def generate-string* encode*) 109 | (core/copy-arglists generate-string* encode*) 110 | (def generate-stream encode-stream*) 111 | (core/copy-arglists generate-stream encode-stream*) 112 | (def generate-stream* encode-stream*) 113 | (core/copy-arglists generate-stream* encode-stream*) 114 | (def generate-smile encode-smile*) 115 | (core/copy-arglists generate-smile encode-smile*) 116 | (def generate-smile* encode-smile*) 117 | (core/copy-arglists generate-smile* encode-smile*) 118 | 119 | ;; Generic encoders, these can be used by someone writing a custom 120 | ;; encoder if so desired, after transforming an arbitrary data 121 | ;; structure into a clojure one, these can just be called. 122 | (defn encode-nil 123 | "Encode null to the json generator." 124 | [_ ^JsonGenerator jg] 125 | (.writeNull jg)) 126 | 127 | (defn encode-str 128 | "Encode a string to the json generator." 129 | [^String s ^JsonGenerator jg] 130 | (.writeString jg (str s))) 131 | 132 | (defn encode-number 133 | "Encode anything implementing java.lang.Number to the json generator." 134 | [^java.lang.Number n ^JsonGenerator jg] 135 | (generate/encode-number n jg)) 136 | 137 | (defn encode-long 138 | "Encode anything implementing java.lang.Number to the json generator." 139 | [^Long n ^JsonGenerator jg] 140 | (.writeNumber jg (long n))) 141 | 142 | (defn encode-int 143 | "Encode anything implementing java.lang.Number to the json generator." 144 | [n ^JsonGenerator jg] 145 | (.writeNumber jg (long n))) 146 | 147 | (defn encode-ratio 148 | "Encode a clojure.lang.Ratio to the json generator." 149 | [^clojure.lang.Ratio n ^JsonGenerator jg] 150 | (.writeNumber jg (double n))) 151 | 152 | (defn encode-seq 153 | "Encode a seq to the json generator." 154 | [s ^JsonGenerator jg] 155 | (.writeStartArray jg) 156 | (reduce (fn [jg i] (to-json i jg) jg) jg s) 157 | (.writeEndArray jg)) 158 | 159 | (defn encode-date 160 | "Encode a date object to the json generator." 161 | [^Date d ^JsonGenerator jg] 162 | (let [sdf (SimpleDateFormat. *date-format*)] 163 | (.setTimeZone sdf (SimpleTimeZone. 0 "UTC")) 164 | (.writeString jg (.format sdf d)))) 165 | 166 | (defn encode-bool 167 | "Encode a Boolean object to the json generator." 168 | [^Boolean b ^JsonGenerator jg] 169 | (.writeBoolean jg b)) 170 | 171 | (defn encode-named 172 | "Encode a keyword to the json generator." 173 | [^clojure.lang.Keyword k ^JsonGenerator jg] 174 | (.writeString jg (if-let [ns (namespace k)] 175 | (str ns "/" (name k)) 176 | (name k)))) 177 | 178 | (defn encode-map 179 | "Encode a clojure map to the json generator." 180 | [^clojure.lang.IPersistentMap m ^JsonGenerator jg] 181 | (.writeStartObject jg) 182 | (reduce (fn [^JsonGenerator jg kv] 183 | (let [k (key kv) 184 | v (val kv)] 185 | (.writeFieldName jg (if (instance? clojure.lang.Keyword k) 186 | (if-let [ns (namespace k)] 187 | (str ns "/" (name k)) 188 | (name k)) 189 | (str k))) 190 | (to-json v jg) 191 | jg)) 192 | jg m) 193 | (.writeEndObject jg)) 194 | 195 | (defn encode-symbol 196 | "Encode a clojure symbol to the json generator." 197 | [^clojure.lang.Symbol s ^JsonGenerator jg] 198 | (.writeString jg (str s))) 199 | 200 | ;; extended implementations for clojure datastructures 201 | (extend nil 202 | JSONable 203 | {:to-json encode-nil}) 204 | 205 | (extend java.lang.String 206 | JSONable 207 | {:to-json encode-str}) 208 | 209 | ;; This is lame, thanks for changing all the BigIntegers to BigInts 210 | ;; in 1.3 clojure/core :-/ 211 | (defmacro handle-bigint [] 212 | (when (not= {:major 1 :minor 2} (select-keys *clojure-version* 213 | [:major :minor])) 214 | `(extend clojure.lang.BigInt 215 | JSONable 216 | {:to-json ~'(fn encode-bigint 217 | [^clojure.lang.BigInt n ^JsonGenerator jg] 218 | (.writeNumber jg (.toBigInteger n)))}))) 219 | (handle-bigint) 220 | 221 | (extend clojure.lang.Ratio 222 | JSONable 223 | {:to-json encode-ratio}) 224 | 225 | (extend Long 226 | JSONable 227 | {:to-json encode-long}) 228 | 229 | (extend Short 230 | JSONable 231 | {:to-json encode-int}) 232 | 233 | (extend Byte 234 | JSONable 235 | {:to-json encode-int}) 236 | 237 | (extend java.lang.Number 238 | JSONable 239 | {:to-json encode-number}) 240 | 241 | (extend clojure.lang.ISeq 242 | JSONable 243 | {:to-json encode-seq}) 244 | 245 | (extend clojure.lang.IPersistentVector 246 | JSONable 247 | {:to-json encode-seq}) 248 | 249 | (extend clojure.lang.IPersistentSet 250 | JSONable 251 | {:to-json encode-seq}) 252 | 253 | (extend clojure.lang.IPersistentList 254 | JSONable 255 | {:to-json encode-seq}) 256 | 257 | (extend java.util.Date 258 | JSONable 259 | {:to-json encode-date}) 260 | 261 | (extend java.sql.Timestamp 262 | JSONable 263 | {:to-json #(encode-date (Date. (.getTime ^java.sql.Timestamp %1)) %2)}) 264 | 265 | (extend java.util.UUID 266 | JSONable 267 | {:to-json encode-str}) 268 | 269 | (extend java.lang.Boolean 270 | JSONable 271 | {:to-json encode-bool}) 272 | 273 | (extend clojure.lang.Keyword 274 | JSONable 275 | {:to-json encode-named}) 276 | 277 | (extend clojure.lang.IPersistentMap 278 | JSONable 279 | {:to-json encode-map}) 280 | 281 | (extend clojure.lang.Symbol 282 | JSONable 283 | {:to-json encode-symbol}) 284 | 285 | (extend clojure.lang.Associative 286 | JSONable 287 | {:to-json encode-map}) 288 | 289 | (extend java.util.Map 290 | JSONable 291 | {:to-json encode-map}) 292 | 293 | (extend java.util.Set 294 | JSONable 295 | {:to-json encode-seq}) 296 | 297 | (extend java.util.List 298 | JSONable 299 | {:to-json encode-seq}) 300 | ;; Utility methods to add and remove encoders 301 | (defn add-encoder 302 | "Provide an encoder for a type not handled by Cheshire. 303 | 304 | ex. (add-encoder java.net.URL encode-string) 305 | 306 | See encode-str, encode-map, etc, in the cheshire.custom 307 | namespace for encoder examples." 308 | [cls encoder] 309 | (extend cls 310 | JSONable 311 | {:to-json encoder})) 312 | 313 | (defn remove-encoder 314 | "Remove encoder for a given type. 315 | 316 | ex. (remove-encoder java.net.URL)" 317 | [cls] 318 | (alter-var-root #'JSONable #(assoc % :impls (dissoc (:impls %) cls))) 319 | (clojure.core/-reset-methods JSONable)) 320 | -------------------------------------------------------------------------------- /src/cheshire/exact.clj: -------------------------------------------------------------------------------- 1 | (ns cheshire.exact 2 | (:require [cheshire.factory :as factory] 3 | [cheshire.parse :as parse] 4 | [cheshire.core :as core]) 5 | (:import (java.io StringReader Reader 6 | Writer) 7 | (com.fasterxml.jackson.core JsonFactory))) 8 | 9 | (defn- exact-parse [jp parsed] 10 | (let [valid-json? (try (nil? (.nextToken jp)) 11 | (catch Exception _ false))] 12 | (if valid-json? 13 | parsed 14 | (throw 15 | (IllegalArgumentException. 16 | "Invalid JSON, expected exactly one parseable object but multiple objects were found"))))) 17 | 18 | (defn parse-string 19 | "Like cheshire.core/parse-string 20 | but with only valid json string" 21 | ([string] (parse-string string nil nil)) 22 | ([string key-fn] (parse-string string key-fn nil)) 23 | ([^String string key-fn array-coerce-fn] 24 | (when string 25 | (let [jp (.createParser ^JsonFactory (or factory/*json-factory* 26 | factory/json-factory) 27 | ^Reader (StringReader. string))] 28 | (exact-parse jp (parse/parse jp key-fn nil array-coerce-fn)))))) 29 | 30 | (defn parse-string-strict 31 | ([string] (parse-string-strict string nil nil)) 32 | ([string key-fn] (parse-string-strict string key-fn nil)) 33 | ([^String string key-fn array-coerce-fn] 34 | (when string 35 | (let [jp (.createParser ^JsonFactory (or factory/*json-factory* 36 | factory/json-factory) 37 | ^Writer (StringReader. string))] 38 | (exact-parse jp (parse/parse-strict jp key-fn nil array-coerce-fn)))))) 39 | 40 | (def decode parse-string) 41 | (core/copy-arglists decode parse-string) 42 | (def decode-strict parse-string-strict) 43 | (core/copy-arglists decode-strict parse-string-strict) 44 | -------------------------------------------------------------------------------- /src/cheshire/experimental.clj: -------------------------------------------------------------------------------- 1 | (ns cheshire.experimental 2 | "Experimental JSON encoding/decoding tools." 3 | (:require [cheshire.core :as json] 4 | [tigris.core :refer [str-escaping-input-stream]]) 5 | (:import (java.io ByteArrayInputStream SequenceInputStream))) 6 | 7 | (defn encode-large-field-in-map 8 | "EXPERIMENTAL - SUBJECT TO CHANGE. 9 | 10 | Given a clojure object, a field name and a stream for a the string value of 11 | the field, return a stream that, when read, JSON encodes in a streamy way. 12 | 13 | An optional opt-map can be specified to pass enocding options to the map 14 | encoding, supports the same options as generate-string. 15 | 16 | Note that the input stream is not closed. You are responsible for closing it 17 | by calling .close() on the stream object returned from this method." 18 | ([obj field stream] 19 | (encode-large-field-in-map obj field stream nil)) 20 | ([obj field stream & [opt-map]] 21 | (let [otherstr (json/encode (dissoc obj field) opt-map) 22 | truncstr (subs otherstr 0 (dec (count otherstr))) 23 | stream (str-escaping-input-stream stream) 24 | pre-stream (ByteArrayInputStream. 25 | (.getBytes (str truncstr ",\"" (name field) "\":\""))) 26 | post-stream (ByteArrayInputStream. (.getBytes "\"}"))] 27 | (reduce #(SequenceInputStream. %1 %2) [pre-stream stream post-stream])))) 28 | -------------------------------------------------------------------------------- /src/cheshire/factory.clj: -------------------------------------------------------------------------------- 1 | (ns cheshire.factory 2 | "Factories used for JSON/SMILE generation, used by both the core and 3 | custom generators." 4 | (:import (com.fasterxml.jackson.dataformat.smile SmileFactory) 5 | (com.fasterxml.jackson.dataformat.cbor CBORFactory) 6 | (com.fasterxml.jackson.core TSFBuilder JsonFactory JsonFactory$Feature 7 | StreamReadFeature 8 | StreamReadConstraints StreamWriteConstraints) 9 | (com.fasterxml.jackson.core.json JsonReadFeature 10 | JsonWriteFeature))) 11 | 12 | ;; default date format used to JSON-encode Date objects 13 | (def default-date-format "yyyy-MM-dd'T'HH:mm:ss'Z'") 14 | 15 | (defonce default-factory-options 16 | {:auto-close-source false 17 | :allow-comments false 18 | :allow-unquoted-field-names false 19 | :allow-single-quotes false 20 | :allow-unquoted-control-chars true 21 | :allow-backslash-escaping false 22 | :allow-numeric-leading-zeros false 23 | :allow-non-numeric-numbers false 24 | :intern-field-names false 25 | :canonicalize-field-names false 26 | :escape-non-ascii false 27 | :quote-field-names true 28 | :strict-duplicate-detection false 29 | ;; default values from Jackson 2.18.3 30 | ;; as of this version seem to be enforced for json only and not cbor, smile 31 | :max-input-document-length nil ;; no limit by default 32 | :max-input-token-count nil ;; no limit by default 33 | :max-input-name-length 50000 34 | :max-input-nesting-depth 1000 35 | :max-input-number-length 1000 36 | :max-input-string-length 20000000 37 | :max-output-nesting-depth 1000}) 38 | 39 | (defn- apply-base-opts ^TSFBuilder [^TSFBuilder builder opts] 40 | (-> builder 41 | (.configure StreamReadFeature/AUTO_CLOSE_SOURCE 42 | (boolean (:auto-close-source opts))) 43 | (.configure StreamReadFeature/STRICT_DUPLICATE_DETECTION 44 | (boolean (:strict-duplicate-detection opts))) 45 | (.configure JsonFactory$Feature/INTERN_FIELD_NAMES 46 | (boolean (:intern-field-names opts))) 47 | (.configure JsonFactory$Feature/CANONICALIZE_FIELD_NAMES 48 | (boolean (:canonicalize-field-names opts))) 49 | (.streamReadConstraints (-> (StreamReadConstraints/builder) 50 | (.maxDocumentLength (or (:max-input-document-length opts) -1)) 51 | (.maxTokenCount (or (:max-input-token-count opts) -1)) 52 | (.maxNameLength (:max-input-name-length opts)) 53 | (.maxNestingDepth (:max-input-nesting-depth opts)) 54 | (.maxNumberLength (:max-input-number-length opts)) 55 | (.maxStringLength (:max-input-string-length opts)) 56 | (.build))) 57 | (.streamWriteConstraints (-> (StreamWriteConstraints/builder) 58 | (.maxNestingDepth (:max-output-nesting-depth opts)) 59 | (.build))))) 60 | 61 | (defn- apply-json-opts ^TSFBuilder [^TSFBuilder builder opts] 62 | (-> builder 63 | (.configure JsonReadFeature/ALLOW_JAVA_COMMENTS 64 | (boolean (:allow-comments opts))) 65 | (.configure JsonReadFeature/ALLOW_UNQUOTED_FIELD_NAMES 66 | (boolean (:allow-unquoted-field-names opts))) 67 | (.configure JsonReadFeature/ALLOW_SINGLE_QUOTES 68 | (boolean (:allow-single-quotes opts))) 69 | (.configure JsonReadFeature/ALLOW_UNESCAPED_CONTROL_CHARS 70 | (boolean (:allow-unquoted-control-chars opts))) 71 | (.configure JsonReadFeature/ALLOW_BACKSLASH_ESCAPING_ANY_CHARACTER 72 | (boolean (:allow-backslash-escaping opts))) 73 | (.configure JsonReadFeature/ALLOW_LEADING_ZEROS_FOR_NUMBERS 74 | (boolean (:allow-numeric-leading-zeros opts))) 75 | (.configure JsonReadFeature/ALLOW_NON_NUMERIC_NUMBERS 76 | (boolean (:allow-non-numeric-numbers opts))) 77 | (.configure JsonWriteFeature/QUOTE_FIELD_NAMES 78 | (boolean (:quote-field-names opts))) 79 | (.configure JsonWriteFeature/ESCAPE_NON_ASCII 80 | (boolean (:escape-non-ascii opts))))) 81 | 82 | ;; Factory objects that are needed to do the encoding and decoding 83 | (defn make-json-factory 84 | ^JsonFactory [opts] 85 | (let [opts (merge default-factory-options opts)] 86 | (-> (JsonFactory/builder) 87 | (apply-base-opts opts) 88 | (apply-json-opts opts) 89 | (.build)))) 90 | 91 | (defn make-smile-factory 92 | ^SmileFactory [opts] 93 | (let [opts (merge default-factory-options opts)] 94 | (-> (SmileFactory/builder) 95 | (apply-base-opts opts) 96 | (.build)))) 97 | 98 | (defn make-cbor-factory 99 | ^CBORFactory [opts] 100 | (let [opts (merge default-factory-options opts)] 101 | (-> (CBORFactory/builder) 102 | (apply-base-opts opts) 103 | (.build)))) 104 | 105 | (defonce ^JsonFactory json-factory (make-json-factory default-factory-options)) 106 | (defonce ^SmileFactory smile-factory 107 | (make-smile-factory default-factory-options)) 108 | (defonce ^CBORFactory cbor-factory (make-cbor-factory default-factory-options)) 109 | 110 | ;; dynamically rebindable json factory, if desired 111 | (def ^{:dynamic true :tag JsonFactory} *json-factory* nil) 112 | ;; dynamically rebindable smile factory, if desired 113 | (def ^{:dynamic true :tag SmileFactory} *smile-factory* nil) 114 | ;; dynamically rebindable cbor factory, if desired 115 | (def ^{:dynamic true :tag CBORFactory} *cbor-factory* nil) 116 | -------------------------------------------------------------------------------- /src/cheshire/generate.clj: -------------------------------------------------------------------------------- 1 | (ns cheshire.generate 2 | "Namespace used to generate JSON from Clojure data structures." 3 | (:import (com.fasterxml.jackson.core JsonGenerator JsonGenerationException) 4 | (java.util Date Map List Set SimpleTimeZone UUID) 5 | (java.sql Timestamp) 6 | (java.text SimpleDateFormat) 7 | (java.math BigInteger) 8 | (clojure.lang Keyword Ratio Symbol))) 9 | 10 | ;; date format rebound for custom encoding 11 | (def ^{:dynamic true :private true} *date-format* nil) 12 | 13 | (defmacro tag 14 | ([obj] 15 | `(vary-meta ~obj assoc :tag `JsonGenerator))) 16 | 17 | (defprotocol JSONable 18 | (to-json [t jg])) 19 | 20 | (definline write-string [^JsonGenerator jg ^String str] 21 | `(.writeString ~(tag jg) ~str)) 22 | 23 | (defmacro fail [obj jg ^Exception e] 24 | `(try 25 | (to-json ~obj ~jg) 26 | (catch IllegalArgumentException _# 27 | (throw (or ~e (JsonGenerationException. 28 | (str "Cannot JSON encode object of class: " 29 | (class ~obj) ": " ~obj) 30 | ~jg)))))) 31 | 32 | (defmacro number-dispatch [jg obj e] 33 | (let [g (tag (gensym 'jg)) 34 | o (gensym 'obj) 35 | common-clauses `[Integer (.writeNumber ~g (int ~o)) 36 | Long (.writeNumber ~g (long ~o)) 37 | Double (.writeNumber ~g (double ~o)) 38 | Float (.writeNumber ~g (float ~o)) 39 | BigInteger (.writeNumber 40 | ~g ~(with-meta o {:tag `BigInteger})) 41 | BigDecimal (.writeNumber 42 | ~g ~(with-meta o {:tag `BigDecimal})) 43 | Ratio (.writeNumber ~g (double ~o)) 44 | Short (.writeNumber ~g (int ~o)) 45 | Byte (.writeNumber ~g (int ~o))]] 46 | `(let [~g ~jg 47 | ~o ~obj] 48 | (condp instance? ~o 49 | ~@(if (< 2 (:minor *clojure-version*)) 50 | `[~@common-clauses 51 | clojure.lang.BigInt (.writeNumber 52 | ~g (.toBigInteger 53 | ~(vary-meta obj assoc :tag 54 | `clojure.lang.BigInt)))] 55 | common-clauses) 56 | (fail ~o ~g ~e))))) 57 | 58 | (declare generate) 59 | 60 | (definline generate-basic-map 61 | [^JsonGenerator jg obj ^String date-format ^Exception e] 62 | (let [jg (tag jg)] 63 | `(do 64 | (.writeStartObject ~jg) 65 | (reduce (fn [^JsonGenerator jg# kv#] 66 | (let [k# (key kv#) 67 | v# (val kv#)] 68 | (.writeFieldName jg# (if (keyword? k#) 69 | (.substring (str k#) 1) 70 | (str k#))) 71 | (generate jg# v# ~date-format ~e nil) 72 | jg#)) 73 | ~jg ~obj) 74 | (.writeEndObject ~jg)))) 75 | 76 | (definline generate-key-fn-map 77 | [^JsonGenerator jg obj ^String date-format ^Exception e key-fn] 78 | (let [k (gensym 'k) 79 | jg (tag jg)] 80 | `(do 81 | (.writeStartObject ~jg) 82 | (reduce (fn [^JsonGenerator jg# kv#] 83 | (let [~k (key kv#) 84 | v# (val kv#) 85 | ^String name# (if (keyword? ~k) 86 | (~key-fn ~k) 87 | (str ~k))] 88 | (.writeFieldName jg# name#) 89 | (generate jg# v# ~date-format ~e ~key-fn) 90 | jg#)) 91 | ~jg ~obj) 92 | (.writeEndObject ~jg)))) 93 | 94 | (definline generate-map 95 | [^JsonGenerator jg obj ^String date-format ^Exception e key-fn] 96 | `(if (nil? ~key-fn) 97 | (generate-basic-map ~jg ~obj ~date-format ~e) 98 | (generate-key-fn-map ~jg ~obj ~date-format ~e ~key-fn))) 99 | 100 | (definline generate-array [^JsonGenerator jg obj ^String date-format 101 | ^Exception e key-fn] 102 | (let [jg (tag jg)] 103 | `(do 104 | (.writeStartArray ~jg) 105 | (reduce (fn [jg# item#] (generate jg# item# ~date-format ~e ~key-fn) jg#) ~jg ~obj) 106 | (.writeEndArray ~jg)))) 107 | 108 | (defmacro i? 109 | "Just to shorten 'instance?' and for debugging." 110 | [k obj] 111 | ;;(println :inst? k obj) 112 | `(instance? ~k ~obj)) 113 | 114 | (defn byte-array? [o] 115 | (let [c (class o)] 116 | (and (.isArray c) 117 | (identical? (.getComponentType c) Byte/TYPE)))) 118 | 119 | (defn generate [^JsonGenerator jg obj ^String date-format ^Exception ex key-fn] 120 | (cond 121 | (nil? obj) (.writeNull ^JsonGenerator jg) 122 | (get (:impls JSONable) (class obj)) (#'to-json obj jg) 123 | 124 | (i? clojure.lang.IPersistentMap obj) 125 | (generate-map jg obj date-format ex key-fn) 126 | (i? clojure.lang.IPersistentVector obj) 127 | (generate-array jg obj date-format ex key-fn) 128 | (i? clojure.lang.IPersistentSet obj) 129 | (generate-array jg obj date-format ex key-fn) 130 | (i? clojure.lang.IPersistentList obj) 131 | (generate-array jg obj date-format ex key-fn) 132 | (i? clojure.lang.ISeq obj) 133 | (generate-array jg obj date-format ex key-fn) 134 | (i? clojure.lang.Associative obj) 135 | (generate-map jg obj date-format ex key-fn) 136 | 137 | (i? Number obj) (number-dispatch ^JsonGenerator jg obj ex) 138 | (i? Boolean obj) (.writeBoolean ^JsonGenerator jg ^Boolean obj) 139 | (i? String obj) (write-string ^JsonGenerator jg ^String obj) 140 | (i? Character obj) (write-string ^JsonGenerator jg ^String (str obj)) 141 | (i? Keyword obj) (write-string ^JsonGenerator jg (.substring (str obj) 1)) 142 | (i? Map obj) (generate-map jg obj date-format ex key-fn) 143 | (i? List obj) (generate-array jg obj date-format ex key-fn) 144 | (i? Set obj) (generate-array jg obj date-format ex key-fn) 145 | (byte-array? obj) (.writeBinary ^JsonGenerator jg ^bytes obj) 146 | (i? UUID obj) (write-string ^JsonGenerator jg (.toString ^UUID obj)) 147 | (i? Symbol obj) (write-string ^JsonGenerator jg (.toString ^Symbol obj)) 148 | (i? Date obj) (let [sdf (doto (SimpleDateFormat. date-format) 149 | (.setTimeZone (SimpleTimeZone. 0 "UTC")))] 150 | (write-string ^JsonGenerator jg (.format sdf obj))) 151 | (i? Timestamp obj) (let [sdf (doto (SimpleDateFormat. date-format) 152 | (.setTimeZone (SimpleTimeZone. 0 "UTC")))] 153 | (write-string ^JsonGenerator jg (.format sdf obj))) 154 | :else (fail obj jg ex))) 155 | 156 | ;; Generic encoders, these can be used by someone writing a custom 157 | ;; encoder if so desired, after transforming an arbitrary data 158 | ;; structure into a clojure one, these can just be called. 159 | (defn encode-nil 160 | "Encode null to the json generator." 161 | [_ ^JsonGenerator jg] 162 | (.writeNull jg)) 163 | 164 | (defn encode-str 165 | "Encode a string to the json generator." 166 | [^String s ^JsonGenerator jg] 167 | (.writeString jg (str s))) 168 | 169 | (defn encode-number 170 | "Encode anything implementing java.lang.Number to the json generator." 171 | [^java.lang.Number n ^JsonGenerator jg] 172 | (number-dispatch jg n nil)) 173 | 174 | (defn encode-long 175 | "Encode anything implementing java.lang.Number to the json generator." 176 | [^Long n ^JsonGenerator jg] 177 | (.writeNumber jg (long n))) 178 | 179 | (defn encode-int 180 | "Encode anything implementing java.lang.Number to the json generator." 181 | [n ^JsonGenerator jg] 182 | (.writeNumber jg (long n))) 183 | 184 | (defn encode-ratio 185 | "Encode a clojure.lang.Ratio to the json generator." 186 | [^clojure.lang.Ratio n ^JsonGenerator jg] 187 | (.writeNumber jg (double n))) 188 | 189 | (defn encode-seq 190 | "Encode a seq to the json generator." 191 | [s ^JsonGenerator jg] 192 | (.writeStartArray jg) 193 | (reduce (fn [jg i] (generate jg i *date-format* nil nil) jg) jg s) 194 | (.writeEndArray jg)) 195 | 196 | (defn encode-date 197 | "Encode a date object to the json generator." 198 | [^Date d ^JsonGenerator jg] 199 | (let [sdf (SimpleDateFormat. *date-format*)] 200 | (.setTimeZone sdf (SimpleTimeZone. 0 "UTC")) 201 | (.writeString jg (.format sdf d)))) 202 | 203 | (defn encode-bool 204 | "Encode a Boolean object to the json generator." 205 | [^Boolean b ^JsonGenerator jg] 206 | (.writeBoolean jg b)) 207 | 208 | (defn encode-named 209 | "Encode a keyword to the json generator." 210 | [^clojure.lang.Keyword k ^JsonGenerator jg] 211 | (.writeString jg (if-let [ns (namespace k)] 212 | (str ns "/" (name k)) 213 | (name k)))) 214 | 215 | (defn encode-map 216 | "Encode a clojure map to the json generator." 217 | [^clojure.lang.IPersistentMap m ^JsonGenerator jg] 218 | (.writeStartObject jg) 219 | (reduce (fn [^JsonGenerator jg kv] 220 | (let [k (key kv) 221 | v (val kv)] 222 | (.writeFieldName jg (if (instance? clojure.lang.Keyword k) 223 | (if-let [ns (namespace k)] 224 | (str ns "/" (name k)) 225 | (name k)) 226 | (str k))) 227 | (generate jg v *date-format* nil nil) 228 | jg)) 229 | jg m) 230 | (.writeEndObject jg)) 231 | 232 | (defn encode-symbol 233 | "Encode a clojure symbol to the json generator." 234 | [^clojure.lang.Symbol s ^JsonGenerator jg] 235 | (.writeString jg (str s))) 236 | 237 | ;; Utility methods to add and remove encoders 238 | (defn add-encoder 239 | "Provide an encoder for a type not handled by Cheshire. 240 | 241 | ex. (add-encoder java.net.URL encode-string) 242 | 243 | See encode-str, encode-map, etc, in the cheshire.custom 244 | namespace for encoder examples." 245 | [cls encoder] 246 | (extend cls 247 | JSONable 248 | {:to-json encoder})) 249 | 250 | (defn remove-encoder 251 | "Remove encoder for a given type. 252 | 253 | ex. (remove-encoder java.net.URL)" 254 | [cls] 255 | (alter-var-root #'JSONable #(assoc % :impls (dissoc (:impls %) cls))) 256 | (clojure.core/-reset-methods JSONable)) 257 | -------------------------------------------------------------------------------- /src/cheshire/generate_seq.clj: -------------------------------------------------------------------------------- 1 | (ns cheshire.generate-seq 2 | "Namespace used to generate JSON from Clojure data structures in a 3 | sequential way." 4 | (:require [cheshire.generate :as g]) 5 | (:import (com.fasterxml.jackson.core JsonGenerator) 6 | (java.util Date Map List Set SimpleTimeZone UUID) 7 | (java.sql Timestamp) 8 | (java.text SimpleDateFormat) 9 | (clojure.lang IPersistentCollection Keyword Symbol))) 10 | 11 | (definline write-start-object [^JsonGenerator jg wholeness] 12 | `(if (contains? #{:all :start :start-inner} ~wholeness) 13 | (.writeStartObject ~jg))) 14 | 15 | (definline write-end-object [^JsonGenerator jg wholeness] 16 | `(if (contains? #{:all :end} ~wholeness) 17 | (.writeEndObject ~jg))) 18 | 19 | (definline write-start-array [^JsonGenerator jg wholeness] 20 | `(if (contains? #{:all :start :start-inner} ~wholeness) 21 | (.writeStartArray ~jg))) 22 | 23 | (definline write-end-array [^JsonGenerator jg wholeness] 24 | `(if (contains? #{:all :end} ~wholeness) 25 | (.writeEndArray ~jg))) 26 | 27 | (declare generate) 28 | 29 | (definline generate-basic-map 30 | [^JsonGenerator jg obj ^String date-format ^Exception e 31 | wholeness] 32 | (let [jg (g/tag jg)] 33 | `(do 34 | (write-start-object ~jg ~wholeness) 35 | (reduce (fn [^JsonGenerator jg# kv#] 36 | (let [k# (key kv#) 37 | v# (val kv#)] 38 | (.writeFieldName jg# (if (keyword? k#) 39 | (.substring (str k#) 1) 40 | (str k#))) 41 | (generate jg# v# ~date-format ~e nil 42 | :wholeness (if (= ~wholeness :start-inner) 43 | :start 44 | :all)) 45 | jg#)) 46 | ~jg ~obj) 47 | (write-end-object ~jg ~wholeness)))) 48 | 49 | (definline generate-key-fn-map 50 | [^JsonGenerator jg obj ^String date-format ^Exception e 51 | key-fn wholeness] 52 | (let [k (gensym 'k) 53 | jg (g/tag jg)] 54 | `(do 55 | (write-start-object ~jg ~wholeness) 56 | (reduce (fn [^JsonGenerator jg# kv#] 57 | (let [~k (key kv#) 58 | v# (val kv#) 59 | ^String name# (if (keyword? ~k) 60 | (~key-fn ~k) 61 | (str ~k))] 62 | (.writeFieldName jg# name#) 63 | (generate jg# v# ~date-format ~e ~key-fn 64 | :wholeness (if (= ~wholeness :start-inner) 65 | :start 66 | :all)) 67 | jg#)) 68 | ~jg ~obj) 69 | (write-end-object ~jg ~wholeness)))) 70 | 71 | (definline generate-map 72 | [^JsonGenerator jg obj ^String date-format ^Exception e 73 | key-fn wholeness] 74 | `(if (nil? ~key-fn) 75 | (generate-basic-map ~jg ~obj ~date-format ~e ~wholeness) 76 | (generate-key-fn-map ~jg ~obj ~date-format ~e ~key-fn ~wholeness))) 77 | 78 | (definline generate-array [^JsonGenerator jg obj ^String date-format 79 | ^Exception e key-fn wholeness] 80 | (let [jg (g/tag jg)] 81 | `(do 82 | (write-start-array ~jg ~wholeness) 83 | (reduce (fn [jg# item#] 84 | (generate jg# item# ~date-format ~e ~key-fn 85 | :wholeness (if (= ~wholeness :start-inner) 86 | :start 87 | :all)) 88 | jg#) 89 | ~jg ~obj) 90 | (write-end-array ~jg ~wholeness)))) 91 | 92 | (defn generate [^JsonGenerator jg obj ^String date-format 93 | ^Exception ex key-fn & {:keys [wholeness]}] 94 | (let [wholeness (or wholeness :all)] 95 | (cond 96 | (nil? obj) (.writeNull ^JsonGenerator jg) 97 | (get (:impls g/JSONable) (class obj)) (#'g/to-json obj jg) 98 | 99 | (g/i? IPersistentCollection obj) 100 | (condp instance? obj 101 | clojure.lang.IPersistentMap 102 | (generate-map jg obj date-format ex key-fn wholeness) 103 | clojure.lang.IPersistentVector 104 | (generate-array jg obj date-format ex key-fn wholeness) 105 | clojure.lang.IPersistentSet 106 | (generate-array jg obj date-format ex key-fn wholeness) 107 | clojure.lang.IPersistentList 108 | (generate-array jg obj date-format ex key-fn wholeness) 109 | clojure.lang.ISeq 110 | (generate-array jg obj date-format ex key-fn wholeness) 111 | clojure.lang.Associative 112 | (generate-map jg obj date-format ex key-fn wholeness)) 113 | 114 | (g/i? Number obj) (g/number-dispatch ^JsonGenerator jg obj ex) 115 | (g/i? Boolean obj) (.writeBoolean ^JsonGenerator jg ^Boolean obj) 116 | (g/i? String obj) (g/write-string ^JsonGenerator jg ^String obj) 117 | (g/i? Character obj) (g/write-string ^JsonGenerator jg ^String (str obj)) 118 | (g/i? Keyword obj) (g/write-string ^JsonGenerator jg (.substring (str obj) 1)) 119 | (g/i? Map obj) (generate-map jg obj date-format ex key-fn wholeness) 120 | (g/i? List obj) (generate-array jg obj date-format ex key-fn wholeness) 121 | (g/i? Set obj) (generate-array jg obj date-format ex key-fn wholeness) 122 | (g/i? UUID obj) (g/write-string ^JsonGenerator jg (.toString ^UUID obj)) 123 | (g/i? Symbol obj) (g/write-string ^JsonGenerator jg (.toString ^Symbol obj)) 124 | (g/i? Date obj) (let [sdf (doto (SimpleDateFormat. date-format) 125 | (.setTimeZone (SimpleTimeZone. 0 "UTC")))] 126 | (g/write-string ^JsonGenerator jg (.format sdf obj))) 127 | (g/i? Timestamp obj) (let [sdf (doto (SimpleDateFormat. date-format) 128 | (.setTimeZone (SimpleTimeZone. 0 "UTC")))] 129 | (g/write-string ^JsonGenerator jg (.format sdf obj))) 130 | :else (g/fail obj jg ex)))) 131 | -------------------------------------------------------------------------------- /src/cheshire/parse.clj: -------------------------------------------------------------------------------- 1 | (ns cheshire.parse 2 | (:import (com.fasterxml.jackson.core JsonParser JsonToken))) 3 | 4 | (declare parse*) 5 | 6 | (def ^:dynamic *chunk-size* 32) 7 | 8 | (def ^{:doc "Flag to determine whether float values should be returned as 9 | BigDecimals to retain precision. Defaults to false." 10 | :dynamic true} 11 | *use-bigdecimals?* false) 12 | 13 | (defmacro ^:private tag 14 | ([obj] 15 | `(vary-meta ~obj assoc :tag `JsonParser))) 16 | 17 | (definline parse-object [^JsonParser jp key-fn bd? array-coerce-fn] 18 | (let [jp (tag jp)] 19 | `(do 20 | (.nextToken ~jp) 21 | (loop [mmap# (transient {})] 22 | (if-not (identical? (.getCurrentToken ~jp) 23 | JsonToken/END_OBJECT) 24 | (let [key-str# (.getText ~jp) 25 | _# (.nextToken ~jp) 26 | key# (~key-fn key-str#) 27 | mmap# (assoc! mmap# key# 28 | (parse* ~jp ~key-fn ~bd? ~array-coerce-fn))] 29 | (.nextToken ~jp) 30 | (recur mmap#)) 31 | (persistent! mmap#)))))) 32 | 33 | (definline parse-array [^JsonParser jp key-fn bd? array-coerce-fn] 34 | (let [jp (tag jp)] 35 | `(let [array-field-name# (.currentName ~jp)] 36 | (.nextToken ~jp) 37 | (loop [coll# (transient (if ~array-coerce-fn 38 | (~array-coerce-fn array-field-name#) 39 | []))] 40 | (if-not (identical? (.getCurrentToken ~jp) 41 | JsonToken/END_ARRAY) 42 | (let [coll# (conj! coll# 43 | (parse* ~jp ~key-fn ~bd? ~array-coerce-fn))] 44 | (.nextToken ~jp) 45 | (recur coll#)) 46 | (persistent! coll#)))))) 47 | 48 | (defn lazily-parse-array [^JsonParser jp key-fn bd? array-coerce-fn] 49 | (lazy-seq 50 | (loop [chunk-idx 0, buf (chunk-buffer *chunk-size*)] 51 | (if (identical? (.getCurrentToken jp) JsonToken/END_ARRAY) 52 | (chunk-cons (chunk buf) nil) 53 | (do 54 | (chunk-append buf (parse* jp key-fn bd? array-coerce-fn)) 55 | (.nextToken jp) 56 | (let [chunk-idx* (unchecked-inc chunk-idx)] 57 | (if (< chunk-idx* *chunk-size*) 58 | (recur chunk-idx* buf) 59 | (chunk-cons 60 | (chunk buf) 61 | (lazily-parse-array jp key-fn bd? array-coerce-fn))))))))) 62 | 63 | (defn parse* [^JsonParser jp key-fn bd? array-coerce-fn] 64 | (condp identical? (.getCurrentToken jp) 65 | JsonToken/START_OBJECT (parse-object jp key-fn bd? array-coerce-fn) 66 | JsonToken/START_ARRAY (parse-array jp key-fn bd? array-coerce-fn) 67 | JsonToken/VALUE_STRING (.getText jp) 68 | JsonToken/VALUE_NUMBER_INT (.getNumberValue jp) 69 | JsonToken/VALUE_NUMBER_FLOAT (if bd? 70 | (.getDecimalValue jp) 71 | (.getNumberValue jp)) 72 | JsonToken/VALUE_EMBEDDED_OBJECT (.getBinaryValue jp) 73 | JsonToken/VALUE_TRUE true 74 | JsonToken/VALUE_FALSE false 75 | JsonToken/VALUE_NULL nil 76 | (throw 77 | (Exception. 78 | (str "Cannot parse " (pr-str (.getCurrentToken jp))))))) 79 | 80 | (defn parse-strict [^JsonParser jp key-fn eof array-coerce-fn] 81 | (let [key-fn (or (if (identical? key-fn true) keyword key-fn) identity)] 82 | (.nextToken jp) 83 | (condp identical? (.getCurrentToken jp) 84 | nil 85 | eof 86 | (parse* jp key-fn *use-bigdecimals?* array-coerce-fn)))) 87 | 88 | (defn parse [^JsonParser jp key-fn eof array-coerce-fn] 89 | (let [key-fn (or (if (and (instance? Boolean key-fn) key-fn) keyword key-fn) identity)] 90 | (.nextToken jp) 91 | (condp identical? (.getCurrentToken jp) 92 | nil 93 | eof 94 | 95 | JsonToken/START_ARRAY 96 | (do 97 | (.nextToken jp) 98 | (lazily-parse-array jp key-fn *use-bigdecimals?* array-coerce-fn)) 99 | 100 | (parse* jp key-fn *use-bigdecimals?* array-coerce-fn)))) 101 | -------------------------------------------------------------------------------- /src/data_readers.clj: -------------------------------------------------------------------------------- 1 | {cheshire/json cheshire.core/generate-string} 2 | -------------------------------------------------------------------------------- /src/java/cheshire/prettyprint/CustomPrettyPrinter.java: -------------------------------------------------------------------------------- 1 | package cheshire.prettyprint; 2 | 3 | import com.fasterxml.jackson.core.JsonGenerator; 4 | import com.fasterxml.jackson.core.util.DefaultIndenter; 5 | import com.fasterxml.jackson.core.util.DefaultPrettyPrinter; 6 | 7 | import java.io.IOException; 8 | 9 | public class CustomPrettyPrinter extends DefaultPrettyPrinter { 10 | private String _beforeArrayValues; 11 | private String _afterArrayValues; 12 | private String _objectFieldValueSeparator; 13 | 14 | public CustomPrettyPrinter() { 15 | super(); 16 | } 17 | 18 | @Override 19 | public void beforeArrayValues(JsonGenerator gen) throws IOException { 20 | if (this._beforeArrayValues != null) { 21 | gen.writeRaw(this._beforeArrayValues); 22 | } else { 23 | super.beforeArrayValues(gen); 24 | } 25 | } 26 | 27 | @Override 28 | public void writeEndArray(JsonGenerator gen, int nrOfValues) throws IOException { 29 | if (this._afterArrayValues != null) { 30 | gen.writeRaw(this._afterArrayValues + "]"); 31 | } else { 32 | super.writeEndArray(gen, nrOfValues); 33 | } 34 | } 35 | 36 | @Override 37 | public void writeObjectFieldValueSeparator(JsonGenerator gen) throws IOException { 38 | if (this._objectFieldValueSeparator != null) { 39 | gen.writeRaw(this._objectFieldValueSeparator); 40 | } else { 41 | super.writeObjectFieldValueSeparator(gen); 42 | } 43 | } 44 | 45 | public CustomPrettyPrinter setIndentation(String indentation, String lineBreak, boolean indentObjects, boolean indentArrays) { 46 | Indenter indenter = new DefaultIndenter(indentation, lineBreak); 47 | if (indentArrays) { 48 | this.indentArraysWith(indenter); 49 | } 50 | if (indentObjects) { 51 | this.indentObjectsWith(indenter); 52 | } else { 53 | this.indentObjectsWith(new DefaultPrettyPrinter.FixedSpaceIndenter()); 54 | } 55 | return this; 56 | } 57 | 58 | public CustomPrettyPrinter setBeforeArrayValues(String beforeArrayValues) { 59 | this._beforeArrayValues = beforeArrayValues; 60 | return this; 61 | } 62 | 63 | public CustomPrettyPrinter setAfterArrayValues(String afterArrayValues) { 64 | this._afterArrayValues = afterArrayValues; 65 | return this; 66 | } 67 | 68 | public CustomPrettyPrinter setObjectFieldValueSeparator(String objectFieldValueSeparator) { 69 | this._objectFieldValueSeparator = objectFieldValueSeparator; 70 | return this; 71 | } 72 | 73 | } 74 | -------------------------------------------------------------------------------- /test/all_month.geojson.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dakrone/cheshire/840d7584c5aae92d8d2d2dd3d0194f4f11be4eb4/test/all_month.geojson.gz -------------------------------------------------------------------------------- /test/cheshire/test/core.clj: -------------------------------------------------------------------------------- 1 | (ns cheshire.test.core 2 | (:require [clojure.test :refer [deftest testing is are]] 3 | [clojure.java.io :as io] 4 | [clojure.string :as str] 5 | [cheshire.core :as json] 6 | [cheshire.exact :as json-exact] 7 | [cheshire.generate :as gen] 8 | [cheshire.factory :as fact] 9 | [cheshire.parse :as parse]) 10 | (:import (com.fasterxml.jackson.core JsonGenerationException 11 | JsonParseException) 12 | (com.fasterxml.jackson.core.exc StreamConstraintsException) 13 | (java.io FileInputStream StringReader StringWriter 14 | BufferedReader BufferedWriter 15 | IOException) 16 | (java.sql Timestamp) 17 | (java.util Date UUID))) 18 | 19 | (defn- str-of-len 20 | ([len] 21 | (str-of-len len "x")) 22 | ([len val] 23 | (apply str (repeat len val)))) 24 | 25 | (defn- nested-map [depth] 26 | (reduce (fn [acc n] {(str n) acc}) 27 | {"0" "foo"} 28 | (range 1 depth))) 29 | 30 | (defn- encode-stream->str [obj opts] 31 | (let [sw (StringWriter.) 32 | bw (BufferedWriter. sw)] 33 | (json/generate-stream obj bw opts) 34 | (.toString sw))) 35 | 36 | (def test-obj {"int" 3 "long" (long -2147483647) "boolean" true 37 | "LongObj" (Long/parseLong "2147483647") "double" 1.23 38 | "nil" nil "string" "string" "vec" [1 2 3] "map" {"a" "b"} 39 | "list" (list "a" "b") "short" (short 21) "byte" (byte 3)}) 40 | 41 | (deftest t-ratio 42 | (let [n 1/2] 43 | (is (= (double n) (:num (json/decode (json/encode {:num n}) true)))))) 44 | 45 | (deftest t-long-wrap-around 46 | (is (= 2147483648 (json/decode (json/encode 2147483648))))) 47 | 48 | (deftest t-bigint 49 | (let [n 9223372036854775808] 50 | (is (= n (:num (json/decode (json/encode {:num n}) true)))))) 51 | 52 | (deftest t-biginteger 53 | (let [n (BigInteger. "42")] 54 | (is (= n (:num (json/decode (json/encode {:num n}) true)))))) 55 | 56 | (deftest t-bigdecimal 57 | (let [n (BigDecimal. "42.5")] 58 | (is (= (.doubleValue n) (:num (json/decode (json/encode {:num n}) true)))) 59 | (binding [parse/*use-bigdecimals?* true] 60 | (is (= n (:num (json/decode (json/encode {:num n}) true))))))) 61 | 62 | (deftest test-string-round-trip 63 | (is (= test-obj (json/decode (json/encode test-obj))))) 64 | 65 | (deftest test-generate-accepts-float 66 | (is (= "3.14" (json/encode 3.14)))) 67 | 68 | (deftest test-keyword-encode 69 | (is (= {"key" "val"} 70 | (json/decode (json/encode {:key "val"}))))) 71 | 72 | (deftest test-generate-set 73 | (is (= {"set" ["a" "b"]} 74 | (json/decode (json/encode {"set" #{"a" "b"}}))))) 75 | 76 | (deftest test-generate-empty-set 77 | (is (= {"set" []} 78 | (json/decode (json/encode {"set" #{}}))))) 79 | 80 | (deftest test-generate-empty-array 81 | (is (= {"array" []} 82 | (json/decode (json/encode {"array" []}))))) 83 | 84 | (deftest test-key-coercion 85 | (is (= {"foo" "bar" "1" "bat" "2" "bang" "3" "biz"} 86 | (json/decode 87 | (json/encode 88 | {:foo "bar" 1 "bat" (long 2) "bang" (bigint 3) "biz"}))))) 89 | 90 | (deftest test-keywords 91 | (is (= {:foo "bar" :bat 1} 92 | (json/decode (json/encode {:foo "bar" :bat 1}) true)))) 93 | 94 | (deftest test-symbols 95 | (is (= {"foo" "clojure.core/map"} 96 | (json/decode (json/encode {"foo" 'clojure.core/map}))))) 97 | 98 | (deftest test-accepts-java-map 99 | (is (= {"foo" 1} 100 | (json/decode 101 | (json/encode (doto (java.util.HashMap.) (.put "foo" 1))))))) 102 | 103 | (deftest test-accepts-java-list 104 | (is (= [1 2 3] 105 | (json/decode (json/encode (doto (java.util.ArrayList. 3) 106 | (.add 1) 107 | (.add 2) 108 | (.add 3))))))) 109 | 110 | (deftest test-accepts-java-set 111 | (is (= {"set" [1 2 3]} 112 | (json/decode (json/encode {"set" (doto (java.util.HashSet. 3) 113 | (.add 1) 114 | (.add 2) 115 | (.add 3))}))))) 116 | 117 | (deftest test-accepts-empty-java-set 118 | (is (= {"set" []} 119 | (json/decode (json/encode {"set" (java.util.HashSet. 3)}))))) 120 | 121 | (deftest test-nil 122 | (is (nil? (json/decode nil true)))) 123 | 124 | (deftest test-parsed-seq 125 | (let [br (BufferedReader. (StringReader. "1\n2\n3\n"))] 126 | (is (= (list 1 2 3) (json/parsed-seq br))))) 127 | 128 | (deftest test-smile-round-trip 129 | (is (= test-obj (json/parse-smile (json/generate-smile test-obj))))) 130 | 131 | (def bin-obj {"byte-array" (byte-array (map byte [1 2 3]))}) 132 | 133 | (deftest test-round-trip-binary 134 | (doseq [[p g] {json/parse-string json/generate-string 135 | json/parse-smile json/generate-smile 136 | json/parse-cbor json/generate-cbor}] 137 | (is (let [roundtripped (p (g bin-obj))] 138 | ;; test value equality 139 | (is (= (->> bin-obj (get "byte-array") seq) 140 | (->> roundtripped (get "byte-array") seq))))))) 141 | 142 | (deftest test-smile-factory 143 | (binding [fact/*smile-factory* (fact/make-smile-factory {})] 144 | (is (= {"a" 1} (-> {:a 1} 145 | json/generate-smile 146 | json/parse-smile))))) 147 | 148 | (deftest test-smile-duplicate-detection 149 | (let [smile-data (byte-array [0x3a 0x29 0x0a 0x01 ;; smile header 150 | 0xFa ;; object start 151 | 0x80 0x61 ;; key a 152 | 0xC2 ;; value 1 153 | 0x80 0x61 ;; key a (again) 154 | 0xC4 ;; value 2 155 | 0xFB ;; object end 156 | ])] 157 | (binding [fact/*smile-factory* (fact/make-smile-factory {:strict-duplicate-detection false})] 158 | (is (= {"a" 2} (json/parse-smile smile-data)))) 159 | (binding [fact/*smile-factory* (fact/make-smile-factory {:strict-duplicate-detection true})] 160 | (is (thrown? JsonParseException (json/parse-smile smile-data)))))) 161 | 162 | (deftest test-cbor-factory 163 | (binding [fact/*cbor-factory* (fact/make-cbor-factory {})] 164 | (is (= {"a" 1} (-> {:a 1} 165 | json/generate-cbor 166 | json/parse-cbor))))) 167 | 168 | (deftest test-cbor-duplicate-detection 169 | (let [cbor-data (byte-array [0xbf ;; object begin 170 | 0x61 0x61 ;; key a 171 | 0x01 ;; value 1 172 | 0x61 0x61 ;; key a (again) 173 | 0x02 ;; value 2 174 | 0xff ;; object end 175 | ])] 176 | (binding [fact/*cbor-factory* (fact/make-cbor-factory {:strict-duplicate-detection false})] 177 | (is (= {"a" 2} (json/parse-cbor cbor-data)))) 178 | (binding [fact/*cbor-factory* (fact/make-cbor-factory {:strict-duplicate-detection true})] 179 | (is (thrown? JsonParseException (json/parse-cbor cbor-data)))))) 180 | 181 | (deftest test-aliases 182 | (is (= {"foo" "bar" "1" "bat" "2" "bang" "3" "biz"} 183 | (json/decode 184 | (json/encode 185 | {:foo "bar" 1 "bat" (long 2) "bang" (bigint 3) "biz"}))))) 186 | 187 | (deftest test-date 188 | (is (= {"foo" "1970-01-01T00:00:00Z"} 189 | (json/decode (json/encode {:foo (Date. (long 0))})))) 190 | (is (= {"foo" "1970-01-01"} 191 | (json/decode (json/encode {:foo (Date. (long 0))} 192 | {:date-format "yyyy-MM-dd"}))) 193 | "encode with given date format")) 194 | 195 | (deftest test-sql-timestamp 196 | (is (= {"foo" "1970-01-01T00:00:00Z"} 197 | (json/decode (json/encode {:foo (Timestamp. (long 0))})))) 198 | (is (= {"foo" "1970-01-01"} 199 | (json/decode (json/encode {:foo (Timestamp. (long 0))} 200 | {:date-format "yyyy-MM-dd"}))) 201 | "encode with given date format")) 202 | 203 | (deftest test-uuid 204 | (let [id (UUID/randomUUID) 205 | id-str (str id)] 206 | (is (= {"foo" id-str} (json/decode (json/encode {:foo id})))))) 207 | 208 | (deftest test-char-literal 209 | (is (= "{\"foo\":\"a\"}" (json/encode {:foo \a})))) 210 | 211 | (deftest test-streams 212 | (testing "parse-stream" 213 | (are [parsed parse parsee] (= parsed 214 | (parse (BufferedReader. (StringReader. parsee)))) 215 | {"foo" "bar"} json/parse-stream "{\"foo\":\"bar\"}\n" 216 | {"foo" "bar"} json/parse-stream-strict "{\"foo\":\"bar\"}\n") 217 | 218 | (are [parsed parse parsee] (= parsed 219 | (with-open [rdr (StringReader. parsee)] 220 | (parse rdr true))) 221 | {(keyword "foo baz") "bar"} json/parse-stream "{\"foo baz\":\"bar\"}\n" 222 | {(keyword "foo baz") "bar"} json/parse-stream-strict "{\"foo baz\":\"bar\"}\n")) 223 | 224 | (testing "generate-stream" 225 | (let [sw (StringWriter.) 226 | bw (BufferedWriter. sw)] 227 | (json/generate-stream {"foo" "bar"} bw) 228 | (is (= "{\"foo\":\"bar\"}" (.toString sw)))))) 229 | 230 | (deftest serial-writing 231 | (is (= "[\"foo\",\"bar\"]" 232 | (.toString 233 | (json/with-writer [(StringWriter.) nil] 234 | (json/write [] :start) 235 | (json/write "foo") 236 | (json/write "bar") 237 | (json/write [] :end))))) 238 | (is (= "[1,[2,3],4]" 239 | (.toString 240 | (json/with-writer [(StringWriter.) nil] 241 | (json/write [1 [2]] :start-inner) 242 | (json/write 3) 243 | (json/write [] :end) 244 | (json/write 4) 245 | (json/write [] :end))))) 246 | (is (= "{\"a\":1,\"b\":2,\"c\":3}" 247 | (.toString 248 | (json/with-writer [(StringWriter.) nil] 249 | (json/write {:a 1} :start) 250 | (json/write {:b 2} :bare) 251 | (json/write {:c 3} :end))))) 252 | (is (= (str "[\"start\",\"continue\",[\"implicitly-nested\"]," 253 | "[\"explicitly-nested\"],\"flatten\",\"end\"]") 254 | (.toString 255 | (json/with-writer [(StringWriter.) nil] 256 | (json/write ["start"] :start) 257 | (json/write "continue") 258 | (json/write ["implicitly-nested"]) 259 | (json/write ["explicitly-nested"] :all) 260 | (json/write ["flatten"] :bare) 261 | (json/write ["end"] :end))))) 262 | (is (= "{\"head\":\"head info\",\"data\":[1,2,3],\"tail\":\"tail info\"}" 263 | (.toString 264 | (json/with-writer [(StringWriter.) nil] 265 | (json/write {:head "head info" :data []} :start-inner) 266 | (json/write 1) 267 | (json/write 2) 268 | (json/write 3) 269 | (json/write [] :end) 270 | (json/write {:tail "tail info"} :end)))))) 271 | 272 | (deftest test-multiple-objs-in-file 273 | (is (= {"one" 1, "foo" "bar"} 274 | (first (json/parsed-seq (io/reader "test/multi.json"))))) 275 | (is (= {"two" 2, "foo" "bar"} 276 | (second (json/parsed-seq (io/reader "test/multi.json"))))) 277 | (with-open [s (FileInputStream. (io/file "test/multi.json"))] 278 | (let [r (io/reader s)] 279 | (is (= [{"one" 1, "foo" "bar"} {"two" 2, "foo" "bar"}] 280 | (json/parsed-seq r)))))) 281 | 282 | (deftest test-jsondotorg-pass1 283 | (let [string (slurp "test/pass1.json") 284 | decoded-json (json/decode string) 285 | encoded-json (json/encode decoded-json) 286 | re-decoded-json (json/decode encoded-json)] 287 | (is (= decoded-json re-decoded-json)))) 288 | 289 | (deftest test-namespaced-keywords 290 | (is (= "{\"foo\":\"user/bar\"}" 291 | (json/encode {:foo :user/bar}))) 292 | (is (= {:foo/bar "baz/eggplant"} 293 | (json/decode (json/encode {:foo/bar :baz/eggplant}) true)))) 294 | 295 | (deftest test-array-coerce-fn 296 | (is (= {"set" #{"a" "b"} "array" ["a" "b"] "map" {"a" 1}} 297 | (json/decode 298 | (json/encode {"set" #{"a" "b"} "array" ["a" "b"] "map" {"a" 1}}) false 299 | (fn [field-name] (if (= "set" field-name) #{} [])))))) 300 | 301 | (deftest t-symbol-encoding-for-non-resolvable-symbols 302 | (is (= "{\"bar\":\"clojure.core/pam\",\"foo\":\"clojure.core/map\"}" 303 | (json/encode (sorted-map :foo 'clojure.core/map :bar 'clojure.core/pam)))) 304 | (is (= "{\"bar\":\"clojure.core/pam\",\"foo\":\"foo.bar/baz\"}" 305 | (json/encode (sorted-map :foo 'foo.bar/baz :bar 'clojure.core/pam))))) 306 | 307 | (deftest t-bindable-factories-auto-close-source 308 | (binding [fact/*json-factory* (fact/make-json-factory 309 | {:auto-close-source false})] 310 | (let [br (BufferedReader. (StringReader. "123"))] 311 | (is (= 123 (json/parse-stream br))) 312 | (is (= -1 (.read br))))) 313 | (binding [fact/*json-factory* (fact/make-json-factory 314 | {:auto-close-source true})] 315 | (let [br (BufferedReader. (StringReader. "123"))] 316 | (is (= 123 (json/parse-stream br))) 317 | (is (thrown? IOException (.read br)))))) 318 | 319 | (deftest t-bindable-factories-allow-comments 320 | (let [s "{\"a\": /* comment */ 1, // comment\n \"b\": 2}"] 321 | (binding [fact/*json-factory* (fact/make-json-factory 322 | {:allow-comments true})] 323 | (is (= {"a" 1 "b" 2} (json/decode s)))) 324 | (binding [fact/*json-factory* (fact/make-json-factory 325 | {:allow-comments false})] 326 | (is (thrown? JsonParseException (json/decode s)))))) 327 | 328 | (deftest t-bindable-factories-allow-unquoted-field-names 329 | (let [s "{a: 1, b: 2}"] 330 | (binding [fact/*json-factory* (fact/make-json-factory 331 | {:allow-unquoted-field-names true})] 332 | (is (= {"a" 1 "b" 2} (json/decode s)))) 333 | (binding [fact/*json-factory* (fact/make-json-factory 334 | {:allow-unquoted-field-names false})] 335 | (is (thrown? JsonParseException (json/decode s)))))) 336 | 337 | (deftest t-bindable-factories-allow-single-quotes 338 | (doseq [s ["{'a': \"one\", 'b': \"two\"}" 339 | "{\"a\": 'one', \"b\": 'two'}" 340 | "{'a': 'one', 'b': 'two'}"]] 341 | (testing s 342 | (binding [fact/*json-factory* (fact/make-json-factory 343 | {:allow-single-quotes true})] 344 | (is (= {"a" "one" "b" "two"} (json/decode s)))) 345 | (binding [fact/*json-factory* (fact/make-json-factory 346 | {:allow-single-quotes false})] 347 | (is (thrown? JsonParseException (json/decode s))))))) 348 | 349 | (deftest t-bindable-factories-allow-unquoted-control-chars 350 | (let [s "{\"a\": \"one\ntwo\"}"] 351 | (binding [fact/*json-factory* (fact/make-json-factory 352 | {:allow-unquoted-control-chars true})] 353 | (is (= {"a" "one\ntwo"} (json/decode s)))) 354 | (binding [fact/*json-factory* (fact/make-json-factory 355 | {:allow-unquoted-control-chars false})] 356 | (is (thrown? JsonParseException (json/decode s)))))) 357 | 358 | (deftest t-bindable-factories-allow-backslash-escaping-any-char 359 | (let [s "{\"a\": 00000000001}"] 360 | (binding [fact/*json-factory* (fact/make-json-factory 361 | {:allow-numeric-leading-zeros true})] 362 | (is (= {"a" 1} (json/decode s)))) 363 | (binding [fact/*json-factory* (fact/make-json-factory 364 | {:allow-numeric-leading-zeros false})] 365 | (is (thrown? JsonParseException (json/decode s)))))) 366 | 367 | (deftest t-bindable-factories-allow-numeric-leading-zeros 368 | (let [s "{\"a\": \"\\o\\n\\e\"}"] 369 | (binding [fact/*json-factory* (fact/make-json-factory 370 | {:allow-backslash-escaping true})] 371 | (is (= {"a" "o\ne"} (json/decode s)))) 372 | (binding [fact/*json-factory* (fact/make-json-factory 373 | {:allow-backslash-escaping false})] 374 | (is (thrown? JsonParseException (json/decode s)))))) 375 | 376 | (deftest t-bindable-factories-non-numeric-numbers 377 | (let [s "{\"foo\":NaN}"] 378 | (binding [fact/*json-factory* (fact/make-json-factory 379 | {:allow-non-numeric-numbers true})] 380 | (is (= (type Double/NaN) 381 | (type (:foo (json/decode s true)))))) 382 | (binding [fact/*json-factory* (fact/make-json-factory 383 | {:allow-non-numeric-numbers false})] 384 | (is (thrown? JsonParseException (json/decode s true)))))) 385 | 386 | (deftest t-bindable-factories-optimization-opts 387 | (let [s "{\"a\": \"foo\"}"] 388 | (doseq [opts [{:intern-field-names true} 389 | {:intern-field-names false} 390 | {:canonicalize-field-names true} 391 | {:canonicalize-field-names false}]] 392 | (binding [fact/*json-factory* (fact/make-json-factory opts)] 393 | (is (= {"a" "foo"} (json/decode s))))))) 394 | 395 | (deftest t-bindable-factories-escape-non-ascii 396 | ;; includes testing legacy fn opt of same name can override factory 397 | (let [edn {:foo "It costs £100"} 398 | expected-esc "{\"foo\":\"It costs \\u00A3100\"}" 399 | expected-no-esc "{\"foo\":\"It costs £100\"}" 400 | opt-esc {:escape-non-ascii true} 401 | opt-no-esc {:escape-non-ascii false}] 402 | (testing "default factory" 403 | (doseq [[fn-opts expected] 404 | [[{} expected-no-esc] 405 | [opt-esc expected-esc] 406 | [opt-no-esc expected-no-esc]]] 407 | (testing fn-opts 408 | (is (= expected (json/encode edn fn-opts) (encode-stream->str edn fn-opts)))))) 409 | (testing (str "factory: " opt-esc) 410 | (binding [fact/*json-factory* (fact/make-json-factory opt-esc)] 411 | (doseq [[fn-opts expected] 412 | [[{} expected-esc] 413 | [opt-esc expected-esc] 414 | [opt-no-esc expected-no-esc]]] 415 | (testing (str "fn: " fn-opts) 416 | (is (= expected (json/encode edn fn-opts) (encode-stream->str edn fn-opts))))))) 417 | (testing (str "factory: " opt-no-esc) 418 | (binding [fact/*json-factory* (fact/make-json-factory opt-no-esc)] 419 | (doseq [[fn-opts expected] 420 | [[{} expected-no-esc] 421 | [opt-esc expected-esc] 422 | [opt-no-esc expected-no-esc]]] 423 | (testing (str "fn: " fn-opts) 424 | (is (= expected (json/encode edn fn-opts) (encode-stream->str edn fn-opts))))))))) 425 | 426 | (deftest t-bindable-factories-quoteless 427 | (binding [fact/*json-factory* (fact/make-json-factory 428 | {:quote-field-names true})] 429 | (is (= "{\"a\":\"foo\"}" (json/encode {:a "foo"})))) 430 | (binding [fact/*json-factory* (fact/make-json-factory 431 | {:quote-field-names false})] 432 | (is (= "{a:\"foo\"}" (json/encode {:a "foo"}))))) 433 | 434 | (deftest t-bindable-factories-strict-duplicate-detection 435 | (binding [fact/*json-factory* (fact/make-json-factory 436 | {:strict-duplicate-detection true})] 437 | (is (thrown? JsonParseException 438 | (json/decode "{\"a\": 1, \"b\": 2, \"a\": 3}")))) 439 | 440 | (binding [fact/*json-factory* (fact/make-json-factory 441 | {:strict-duplicate-detection false})] 442 | (is (= {"a" 3 "b" 2} 443 | (json/decode "{\"a\": 1, \"b\": 2, \"a\": 3}"))))) 444 | 445 | (deftest t-bindable-factories-max-input-document-length 446 | (let [edn {"a" (apply str (repeat 10000 "x"))} 447 | sample-data (json/encode edn)] 448 | (binding [fact/*json-factory* (fact/make-json-factory 449 | {:max-input-document-length (count sample-data)})] 450 | (is (= edn (json/decode sample-data)))) 451 | (binding [fact/*json-factory* (fact/make-json-factory 452 | ;; as per Jackson docs, limit is inexact, so dividing input length by 2 should do the trick 453 | {:max-input-document-length (/ (count sample-data) 2)})] 454 | (is (thrown-with-msg? 455 | StreamConstraintsException #"(?i)document length .* exceeds" 456 | (json/decode sample-data)))))) 457 | 458 | (deftest t-bindable-factories-max-input-token-count 459 | ;; A token is a single unit of input, such as a number, a string, an object start or end, or an array start or end. 460 | (let [edn {"1" 2 "3" 4} 461 | sample-data (json/encode edn)] 462 | (binding [fact/*json-factory* (fact/make-json-factory 463 | {:max-input-token-count 6})] 464 | (is (= edn (json/decode sample-data)))) 465 | (binding [fact/*json-factory* (fact/make-json-factory 466 | {:max-input-token-count 5})] 467 | (is (thrown-with-msg? 468 | StreamConstraintsException #"(?i)token count .* exceeds" 469 | (json/decode sample-data)))))) 470 | 471 | (deftest t-bindable-factories-max-input-name-length 472 | (let [k "somekey" 473 | edn {k 1} 474 | sample-data (json/encode edn)] 475 | (binding [fact/*json-factory* (fact/make-json-factory 476 | {:max-input-name-length (count k)})] 477 | (is (= edn (json/decode sample-data)))) 478 | (binding [fact/*json-factory* (fact/make-json-factory 479 | {:max-input-name-length (dec (count k))})] 480 | (is (thrown-with-msg? 481 | StreamConstraintsException #"(?i)name .* exceeds" 482 | (json/decode sample-data))))) 483 | (let [default-limit (:max-input-name-length fact/default-factory-options)] 484 | (let [k (str-of-len default-limit) 485 | edn {k 1} 486 | sample-data (json/encode edn)] 487 | (is (= edn (json/decode sample-data)))) 488 | (let [k (str-of-len (inc default-limit)) 489 | sample-data (json/encode {k 1})] 490 | (is (thrown-with-msg? 491 | StreamConstraintsException #"(?i)name .* exceeds" 492 | (json/decode sample-data)))))) 493 | 494 | (deftest t-bindable-factories-input-nesting-depth 495 | (let [edn (nested-map 100) 496 | sample-data (json/encode edn)] 497 | (binding [fact/*json-factory* (fact/make-json-factory 498 | {:max-input-nesting-depth 100})] 499 | (is (= edn (json/decode sample-data)))) 500 | (binding [fact/*json-factory* (fact/make-json-factory 501 | {:max-input-nesting-depth 99})] 502 | (is (thrown-with-msg? 503 | StreamConstraintsException #"(?i)nesting depth .* exceeds" 504 | (json/decode sample-data)))))) 505 | 506 | (deftest t-bindable-factories-max-input-number-length 507 | (let [num 123456789 508 | edn {"foo" num} 509 | sample-data (json/encode edn)] 510 | (binding [fact/*json-factory* (fact/make-json-factory 511 | {:max-input-number-length (-> num str count)})] 512 | (is (= edn (json/decode sample-data)))) 513 | (binding [fact/*json-factory* (fact/make-json-factory 514 | {:max-input-number-length (-> num str count dec)})] 515 | (is (thrown-with-msg? 516 | StreamConstraintsException #"(?i)number value length .* exceeds" 517 | (json/decode sample-data))))) 518 | (let [default-limit (:max-input-number-length fact/default-factory-options)] 519 | (let [num (bigint (str-of-len default-limit 2)) 520 | edn {"foo" num} 521 | sample-data (json/encode edn)] 522 | (is (= edn (json/decode sample-data)))) 523 | (let [num (bigint (str-of-len (inc default-limit) 2)) 524 | sample-data (json/encode {"foo" num})] 525 | (is (thrown-with-msg? 526 | StreamConstraintsException #"(?i)number value length .* exceeds" 527 | (json/decode sample-data)))))) 528 | 529 | (deftest t-bindable-factories-max-input-string-length 530 | (let [big-string (str-of-len 40000000) 531 | edn {"big-string" big-string} 532 | sample-data (json/encode edn)] 533 | (binding [fact/*json-factory* (fact/make-json-factory 534 | {:max-input-string-length (count big-string)})] 535 | (is (= edn (json/decode sample-data)))) 536 | (binding [fact/*json-factory* (fact/make-json-factory 537 | {:max-input-string-length (dec (count big-string))})] 538 | (is (thrown-with-msg? 539 | StreamConstraintsException #"(?i)string value length .* exceeds" 540 | (json/decode sample-data))))) 541 | (let [default-limit (:max-input-string-length fact/default-factory-options)] 542 | (let [big-string (str-of-len default-limit) 543 | edn {"big-string" big-string} 544 | sample-data (json/encode edn)] 545 | (is (= edn (json/decode sample-data)))) 546 | (let [big-string (str-of-len (inc default-limit)) 547 | sample-data (json/encode {"big-string" big-string})] 548 | (is (thrown-with-msg? 549 | StreamConstraintsException #"(?i)string value length .* exceeds" 550 | (json/decode sample-data)))))) 551 | 552 | (deftest t-bindable-factories-max-output-nesting-depth 553 | (let [edn (nested-map 100)] 554 | (binding [fact/*json-factory* (fact/make-json-factory 555 | {:max-output-nesting-depth 100})] 556 | (is (.contains (json/encode edn) "\"99\""))) 557 | (binding [fact/*json-factory* (fact/make-json-factory 558 | {:max-output-nesting-depth 99})] 559 | (is (thrown-with-msg? 560 | StreamConstraintsException #"(?i)nesting depth .* exceeds" 561 | (json/encode edn)))))) 562 | 563 | (deftest t-persistent-queue 564 | (let [q (conj clojure.lang.PersistentQueue/EMPTY 1 2 3)] 565 | (is (= q (json/decode (json/encode q)))))) 566 | 567 | (deftest t-pretty-print 568 | (is (= (str/join (System/lineSeparator) 569 | ["{" 570 | " \"bar\" : [ {" 571 | " \"baz\" : 2" 572 | " }, \"quux\", [ 1, 2, 3 ] ]," 573 | " \"foo\" : 1" 574 | "}"]) 575 | (json/encode (sorted-map :foo 1 :bar [{:baz 2} :quux [1 2 3]]) 576 | {:pretty true})))) 577 | 578 | (deftest t-pretty-print-custom-linebreak 579 | (is (= (str/join "foo" 580 | ["{" 581 | " \"bar\" : [ {" 582 | " \"baz\" : 2" 583 | " }, \"quux\", [ 1, 2, 3 ] ]," 584 | " \"foo\" : 1" 585 | "}"]) 586 | (json/encode (sorted-map :foo 1 :bar [{:baz 2} :quux [1 2 3]]) 587 | {:pretty {:line-break "foo"}})))) 588 | 589 | (deftest t-pretty-print-illegal-argument 590 | ; just expecting this not to throw 591 | (json/encode {:foo "bar"} 592 | {:pretty []}) 593 | (json/encode {:foo "bar"} 594 | {:pretty nil})) 595 | 596 | (deftest t-custom-pretty-print-with-defaults 597 | (let [test-obj (sorted-map :foo 1 :bar {:baz [{:ulu "mulu"} {:moot "foo"} 3]} :quux :blub) 598 | pretty-str-default (json/encode test-obj {:pretty true}) 599 | pretty-str-custom (json/encode test-obj {:pretty {}})] 600 | (is (= pretty-str-default pretty-str-custom)) 601 | (when-not (= pretty-str-default pretty-str-custom) 602 | ; print for easy comparison 603 | (println "; default pretty print") 604 | (println pretty-str-default) 605 | (println "; custom pretty print with default options") 606 | (println pretty-str-custom)))) 607 | 608 | (deftest t-custom-pretty-print-with-non-defaults 609 | (let [test-obj (sorted-map :foo 1 :bar {:baz [{:ulu "mulu"} {:moot "foo"} 3]} :quux :blub) 610 | test-opts {:pretty {:indentation 4 611 | :indent-arrays? false 612 | :before-array-values "" 613 | :after-array-values "" 614 | :object-field-value-separator ": "}} 615 | expected (str/join (System/lineSeparator) 616 | ["{" 617 | " \"bar\": {" 618 | " \"baz\": [{" 619 | " \"ulu\": \"mulu\"" 620 | " }, {" 621 | " \"moot\": \"foo\"" 622 | " }, 3]" 623 | " }," 624 | " \"foo\": 1," 625 | " \"quux\": \"blub\"" 626 | "}"]) 627 | pretty-str (json/encode test-obj test-opts)] 628 | 629 | ; just to be easy on the eyes in case of error 630 | (when-not (= expected pretty-str) 631 | (println "; pretty print with options - actual") 632 | (println pretty-str) 633 | (println "; pretty print with options - expected") 634 | (println expected)) 635 | (is (= expected pretty-str)))) 636 | 637 | (deftest t-custom-pretty-print-with-noident-objects 638 | (let [test-obj [{:foo 1 :bar 2} {:foo 3 :bar 4}] 639 | test-opts {:pretty {:indent-objects? false}} 640 | expected (str "[ { \"foo\" : 1, \"bar\" : 2 }, " 641 | "{ \"foo\" : 3, \"bar\" : 4 } ]") 642 | pretty-str (json/encode test-obj test-opts)] 643 | ; just to be easy on the eyes in case of error 644 | (when-not (= expected pretty-str) 645 | (println "; pretty print with options - actual") 646 | (println pretty-str) 647 | (println "; pretty print with options - expected") 648 | (println expected)) 649 | (is (= expected pretty-str)))) 650 | 651 | (deftest t-custom-keyword-fn 652 | (is (= {:FOO "bar"} (json/decode "{\"foo\": \"bar\"}" 653 | (fn [k] (keyword (.toUpperCase k)))))) 654 | (is (= {"foo" "bar"} (json/decode "{\"foo\": \"bar\"}" nil))) 655 | (is (= {"foo" "bar"} (json/decode "{\"foo\": \"bar\"}" false))) 656 | (is (= {:foo "bar"} (json/decode "{\"foo\": \"bar\"}" true)))) 657 | 658 | (deftest t-custom-encode-key-fn 659 | (is (= "{\"FOO\":\"bar\"}" 660 | (json/encode {:foo :bar} 661 | {:key-fn (fn [k] (.toUpperCase (name k)))})))) 662 | 663 | (deftest test-add-remove-encoder 664 | (gen/remove-encoder java.net.URL) 665 | (gen/add-encoder java.net.URL gen/encode-str) 666 | (is (= "\"http://foo.com\"" 667 | (json/encode (java.net.URL. "http://foo.com")))) 668 | (gen/remove-encoder java.net.URL) 669 | (is (thrown? JsonGenerationException 670 | (json/encode (java.net.URL. "http://foo.com"))))) 671 | 672 | (defprotocol TestP 673 | (foo [this] "foo method")) 674 | 675 | (defrecord TestR [state]) 676 | 677 | (extend TestR 678 | TestP 679 | {:foo (constantly "bar")}) 680 | 681 | (deftest t-custom-protocol-encoder 682 | (let [rec (TestR. :quux)] 683 | (is (= {:state "quux"} (json/decode (json/encode rec) true))) 684 | (gen/add-encoder cheshire.test.core.TestR 685 | (fn [obj jg] 686 | (.writeString jg (foo obj)))) 687 | (is (= "bar" (json/decode (json/encode rec)))) 688 | (gen/remove-encoder cheshire.test.core.TestR) 689 | (is (= {:state "quux"} (json/decode (json/encode rec) true))))) 690 | 691 | (defprotocol CTestP 692 | (thing [this] "thing method")) 693 | (defrecord CTestR [state]) 694 | (extend CTestR 695 | CTestP 696 | {:thing (constantly "thing")}) 697 | 698 | (deftest t-custom-helpers 699 | (let [thing (CTestR. :state) 700 | remove #(gen/remove-encoder CTestR)] 701 | (gen/add-encoder CTestR (fn [_obj jg] (gen/encode-nil nil jg))) 702 | (is (= nil (json/decode (json/encode thing) true))) 703 | (remove) 704 | (gen/add-encoder CTestR (fn [_obj jg] (gen/encode-str "foo" jg))) 705 | (is (= "foo" (json/decode (json/encode thing) true))) 706 | (remove) 707 | (gen/add-encoder CTestR (fn [_obj jg] (gen/encode-number 5 jg))) 708 | (is (= 5 (json/decode (json/encode thing) true))) 709 | (remove) 710 | (gen/add-encoder CTestR (fn [_obj jg] (gen/encode-long 4 jg))) 711 | (is (= 4 (json/decode (json/encode thing) true))) 712 | (remove) 713 | (gen/add-encoder CTestR (fn [_obj jg] (gen/encode-int 3 jg))) 714 | (is (= 3 (json/decode (json/encode thing) true))) 715 | (remove) 716 | (gen/add-encoder CTestR (fn [_obj jg] (gen/encode-ratio 1/2 jg))) 717 | (is (= 0.5 (json/decode (json/encode thing) true))) 718 | (remove) 719 | (gen/add-encoder CTestR (fn [_obj jg] (gen/encode-seq [:foo :bar] jg))) 720 | (is (= ["foo" "bar"] (json/decode (json/encode thing) true))) 721 | (remove) 722 | (gen/add-encoder CTestR (fn [_obj jg] (gen/encode-date (Date. (long 0)) jg))) 723 | (binding [gen/*date-format* "yyyy-MM-dd'T'HH:mm:ss'Z'"] 724 | (is (= "1970-01-01T00:00:00Z" (json/decode (json/encode thing) true)))) 725 | (remove) 726 | (gen/add-encoder CTestR (fn [_obj jg] (gen/encode-bool true jg))) 727 | (is (= true (json/decode (json/encode thing) true))) 728 | (remove) 729 | (gen/add-encoder CTestR (fn [_obj jg] (gen/encode-named :foo jg))) 730 | (is (= "foo" (json/decode (json/encode thing) true))) 731 | (remove) 732 | (gen/add-encoder CTestR (fn [_obj jg] (gen/encode-map {:foo "bar"} jg))) 733 | (is (= {:foo "bar"} (json/decode (json/encode thing) true))) 734 | (remove) 735 | (gen/add-encoder CTestR (fn [_obj jg] (gen/encode-symbol 'foo jg))) 736 | (is (= "foo" (json/decode (json/encode thing) true))) 737 | (remove))) 738 | 739 | (deftest t-float-encoding 740 | (is (= "{\"foo\":0.01}" (json/encode {:foo (float 0.01)})))) 741 | 742 | (deftest t-non-const-bools 743 | (is (= {:a 1} (json/decode "{\"a\": 1}" (Boolean. true))))) 744 | 745 | (deftest t-invalid-json 746 | (let [invalid-json-message "Invalid JSON, expected exactly one parseable object but multiple objects were found"] 747 | (are [x y] (= x (try 748 | y 749 | (catch Exception e 750 | (.getMessage e)))) 751 | invalid-json-message (json-exact/decode "{\"foo\": 1}asdf") 752 | invalid-json-message (json-exact/decode "{\"foo\": 123}null") 753 | invalid-json-message (json-exact/decode "\"hello\" : 123}") 754 | {"foo" 1} (json/decode "{\"foo\": 1}") 755 | invalid-json-message (json-exact/decode-strict "{\"foo\": 1}asdf") 756 | invalid-json-message (json-exact/decode-strict "{\"foo\": 123}null") 757 | invalid-json-message (json-exact/decode-strict "\"hello\" : 123}") 758 | {"foo" 1} (json/decode-strict "{\"foo\": 1}")))) 759 | -------------------------------------------------------------------------------- /test/cheshire/test/custom.clj: -------------------------------------------------------------------------------- 1 | (ns cheshire.test.custom 2 | "DEPRECATED, kept here to ensure backward compatibility." 3 | (:require [clojure.test :refer [deftest is]] 4 | [clojure.java.io :as io] 5 | [clojure.string :as str] 6 | #_{:clj-kondo/ignore [:deprecated-namespace]} 7 | [cheshire.custom :as json] :reload 8 | [cheshire.factory :as fact] 9 | [cheshire.parse :as parse]) 10 | (:import (java.io StringReader StringWriter 11 | BufferedReader BufferedWriter) 12 | (java.sql Timestamp) 13 | (java.util Date UUID))) 14 | 15 | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; 16 | ;;;;;; DEPRECATED, DO NOT USE ;;;;;; 17 | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; 18 | 19 | 20 | ;; Generally, all tests in here should use json/encode*, unless you 21 | ;; know what you're doing and what you're trying to test. 22 | 23 | (def test-obj {"int" 3 "long" (long -2147483647) "boolean" true 24 | "LongObj" (Long/parseLong "2147483647") "double" 1.23 25 | "nil" nil "string" "string" "vec" [1 2 3] "map" {"a" "b"} 26 | "list" (list "a" "b") "short" (short 21) "byte" (byte 3)}) 27 | 28 | (deftest t-ratio 29 | (let [n 1/2] 30 | (is (= (double n) (:num (json/decode (json/encode* {:num n}) true)))))) 31 | 32 | (deftest t-long-wrap-around 33 | (is (= 2147483648 (json/decode (json/encode* 2147483648))))) 34 | 35 | (deftest t-bigint 36 | (let [n 9223372036854775808] 37 | (is (= n (:num (json/decode (json/encode* {:num n}) true)))))) 38 | 39 | (deftest t-biginteger 40 | (let [n (BigInteger. "42")] 41 | (is (= n (:num (json/decode (json/encode* {:num n}) true)))))) 42 | 43 | (deftest t-bigdecimal 44 | (let [n (BigDecimal. "42.5")] 45 | (is (= (.doubleValue n) (:num (json/decode (json/encode* {:num n}) true)))) 46 | (binding [parse/*use-bigdecimals?* true] 47 | (is (= n (:num (json/decode (json/encode* {:num n}) true))))))) 48 | 49 | (deftest test-string-round-trip 50 | (is (= test-obj (json/decode (json/encode* test-obj))))) 51 | 52 | (deftest test-generate-accepts-float 53 | (is (= "3.14" (json/encode* 3.14)))) 54 | 55 | (deftest test-keyword-encode 56 | (is (= {"key" "val"} 57 | (json/decode (json/encode* {:key "val"}))))) 58 | 59 | (deftest test-generate-set 60 | (is (= {"set" ["a" "b"]} 61 | (json/decode (json/encode* {"set" #{"a" "b"}}))))) 62 | 63 | (deftest test-generate-empty-set 64 | (is (= {"set" []} 65 | (json/decode (json/encode* {"set" #{}}))))) 66 | 67 | (deftest test-generate-empty-array 68 | (is (= {"array" []} 69 | (json/decode (json/encode* {"array" []}))))) 70 | 71 | (deftest test-key-coercion 72 | (is (= {"foo" "bar" "1" "bat" "2" "bang" "3" "biz"} 73 | (json/decode 74 | (json/encode 75 | {:foo "bar" 1 "bat" (long 2) "bang" (bigint 3) "biz"}))))) 76 | 77 | (deftest test-keywords 78 | (is (= {:foo "bar" :bat 1} 79 | (json/decode (json/encode* {:foo "bar" :bat 1}) true)))) 80 | 81 | (deftest test-symbols 82 | (is (= {"foo" "clojure.core/map"} 83 | (json/decode (json/encode* {"foo" 'clojure.core/map}))))) 84 | 85 | (deftest test-accepts-java-map 86 | (is (= {"foo" 1} 87 | (json/decode 88 | (json/encode (doto (java.util.HashMap.) (.put "foo" 1))))))) 89 | 90 | (deftest test-accepts-java-list 91 | (is (= [1 2 3] 92 | (json/decode (json/encode (doto (java.util.ArrayList. 3) 93 | (.add 1) 94 | (.add 2) 95 | (.add 3))))))) 96 | 97 | (deftest test-accepts-java-set 98 | (is (= {"set" [1 2 3]} 99 | (json/decode (json/encode {"set" (doto (java.util.HashSet. 3) 100 | (.add 1) 101 | (.add 2) 102 | (.add 3))}))))) 103 | 104 | (deftest test-accepts-empty-java-set 105 | (is (= {"set" []} 106 | (json/decode (json/encode {"set" (java.util.HashSet. 3)}))))) 107 | 108 | (deftest test-nil 109 | (is (nil? (json/decode nil true)))) 110 | 111 | (deftest test-parsed-seq 112 | (let [br (BufferedReader. (StringReader. "1\n2\n3\n"))] 113 | (is (= (list 1 2 3) (json/parsed-seq br))))) 114 | 115 | (deftest test-smile-round-trip 116 | (is (= test-obj (json/parse-smile (json/generate-smile test-obj))))) 117 | 118 | (deftest test-aliases 119 | (is (= {"foo" "bar" "1" "bat" "2" "bang" "3" "biz"} 120 | (json/decode 121 | (json/encode 122 | {:foo "bar" 1 "bat" (long 2) "bang" (bigint 3) "biz"}))))) 123 | 124 | (deftest test-date 125 | (is (= {"foo" "1970-01-01T00:00:00Z"} 126 | (json/decode 127 | (json/encode* 128 | {:foo (Date. (long 0))})))) 129 | (is (= {"foo" "1970-01-01"} 130 | (json/decode 131 | (json/encode* 132 | {:foo (Date. (long 0))} {:date-format "yyyy-MM-dd"}))) 133 | "encode with given date format")) 134 | 135 | (deftest test-sql-timestamp 136 | (is (= {"foo" "1970-01-01T00:00:00Z"} 137 | (json/decode (json/encode* {:foo (Timestamp. (long 0))})))) 138 | (is (= {"foo" "1970-01-01"} 139 | (json/decode (json/encode* {:foo (Timestamp. (long 0))} 140 | {:date-format "yyyy-MM-dd"}))) 141 | "encode with given date format")) 142 | 143 | (deftest test-uuid 144 | (let [id (UUID/randomUUID) 145 | id-str (str id)] 146 | (is (= {"foo" id-str} (json/decode (json/encode* {:foo id})))))) 147 | 148 | (deftest test-streams 149 | (is (= {"foo" "bar"} 150 | (json/parse-stream 151 | (BufferedReader. (StringReader. "{\"foo\":\"bar\"}\n"))))) 152 | (let [sw (StringWriter.) 153 | bw (BufferedWriter. sw)] 154 | (json/generate-stream {"foo" "bar"} bw) 155 | (is (= "{\"foo\":\"bar\"}" (.toString sw)))) 156 | (is (= {(keyword "foo baz") "bar"} 157 | (with-open [rdr (StringReader. "{\"foo baz\":\"bar\"}\n")] 158 | (json/parse-stream rdr true))))) 159 | 160 | (deftest test-multiple-objs-in-file 161 | (is (= {"one" 1, "foo" "bar"} 162 | (first (json/parsed-seq (io/reader "test/multi.json"))))) 163 | (is (= {"two" 2, "foo" "bar"} 164 | (second (json/parsed-seq (io/reader "test/multi.json")))))) 165 | 166 | (deftest test-jsondotorg-pass1 167 | (let [string (slurp "test/pass1.json") 168 | decoded-json (json/decode string) 169 | encoded-json (json/encode* decoded-json) 170 | re-decoded-json (json/decode encoded-json)] 171 | (is (= decoded-json re-decoded-json)))) 172 | 173 | (deftest test-namespaced-keywords 174 | (is (= "{\"foo\":\"user/bar\"}" 175 | (json/encode* {:foo :user/bar}))) 176 | (is (= {:foo/bar "baz/eggplant"} 177 | (json/decode (json/encode* {:foo/bar :baz/eggplant}) true)))) 178 | 179 | (deftest test-array-coerce-fn 180 | (is (= {"set" #{"a" "b"} "array" ["a" "b"] "map" {"a" 1}} 181 | (json/decode 182 | (json/encode* {"set" #{"a" "b"} 183 | "array" ["a" "b"] 184 | "map" {"a" 1}}) false 185 | (fn [field-name] (if (= "set" field-name) #{} [])))))) 186 | 187 | (deftest t-symbol-encoding-for-non-resolvable-symbols 188 | (is (= "{\"bar\":\"clojure.core/pam\",\"foo\":\"clojure.core/map\"}" 189 | (json/encode* (sorted-map :foo 'clojure.core/map :bar 'clojure.core/pam)))) 190 | (is (= "{\"bar\":\"clojure.core/pam\",\"foo\":\"foo.bar/baz\"}" 191 | (json/encode* (sorted-map :foo 'foo.bar/baz :bar 'clojure.core/pam))))) 192 | 193 | (deftest t-bindable-factories 194 | (binding [fact/*json-factory* (fact/make-json-factory 195 | {:allow-non-numeric-numbers true})] 196 | (is (= (type Double/NaN) 197 | (type (:foo (json/decode "{\"foo\":NaN}" true))))))) 198 | 199 | (deftest t-persistent-queue 200 | (let [q (conj clojure.lang.PersistentQueue/EMPTY 1 2 3)] 201 | (is (= q (json/decode (json/encode* q)))))) 202 | 203 | (deftest t-pretty-print 204 | (is (= (str/join (System/lineSeparator) 205 | ["{" 206 | " \"bar\" : [ {" 207 | " \"baz\" : 2" 208 | " }, \"quux\", [ 1, 2, 3 ] ]," 209 | " \"foo\" : 1" 210 | "}"]) 211 | (json/encode* (sorted-map :foo 1 :bar [{:baz 2} :quux [1 2 3]]) 212 | {:pretty true})))) 213 | 214 | (deftest t-unicode-escaping 215 | (is (= "{\"foo\":\"It costs \\u00A3100\"}" 216 | (json/encode* {:foo "It costs £100"} {:escape-non-ascii true})))) 217 | 218 | (deftest t-custom-keyword-fn 219 | (is (= {:FOO "bar"} (json/decode "{\"foo\": \"bar\"}" 220 | (fn [k] (keyword (.toUpperCase k)))))) 221 | (is (= {"foo" "bar"} (json/decode "{\"foo\": \"bar\"}" nil))) 222 | (is (= {"foo" "bar"} (json/decode "{\"foo\": \"bar\"}" false))) 223 | (is (= {:foo "bar"} (json/decode "{\"foo\": \"bar\"}" true)))) 224 | 225 | (deftest t-json-reader-tag 226 | (is (= "{\"FOO\":\"bar\"}" #cheshire/json {:FOO "bar"} ))) 227 | 228 | ;; Begin custom-only tests 229 | 230 | (deftest test-add-remove-encoder 231 | (json/remove-encoder java.net.URL) 232 | (json/add-encoder java.net.URL json/encode-str) 233 | (is (= "\"http://foo.com\"" 234 | (json/encode (java.net.URL. "http://foo.com")))) 235 | (json/remove-encoder java.net.URL) 236 | (is (thrown? IllegalArgumentException 237 | (json/encode (java.net.URL. "http://foo.com"))))) 238 | 239 | ;; Test that default encoders can be bypassed if so desired. 240 | (deftest test-shadowing-default-encoder 241 | (json/remove-encoder java.util.Date) 242 | (json/add-encoder java.util.Date 243 | (fn [_d jg] (json/encode-str "foo" jg))) 244 | (is (= "\"foo\"" (json/encode* (java.util.Date.)))) 245 | (is (= "\"foo\"" (json/encode* :foo))) 246 | (json/remove-encoder java.util.Date) 247 | (json/add-encoder java.util.Date json/encode-date) 248 | (is (json/encode (java.util.Date.)) 249 | "shouldn't throw an exception after adding back the default.")) 250 | -------------------------------------------------------------------------------- /test/cheshire/test/generative.clj: -------------------------------------------------------------------------------- 1 | (ns cheshire.test.generative 2 | (:require [cheshire.core :as json] 3 | [clojure.test.generative :refer [defspec] :as g] 4 | [clojure.test :refer [deftest is]])) 5 | 6 | ;; determines whether generative stuff is printed to stdout 7 | (def verbose true) 8 | 9 | (defn encode-equality [x] 10 | [x (json/decode (json/encode x))]) 11 | 12 | (defn encode-equality-keys [x] 13 | [x (json/decode (json/encode x) true)]) 14 | 15 | (defspec number-json-encoding 16 | (fn [a b c] [[a b c] (json/decode (json/encode [a b c]))]) 17 | [^int a ^long b ^double c] 18 | (is (= (first %) (last %)))) 19 | 20 | (defspec bool-json-encoding 21 | encode-equality 22 | [^boolean a] 23 | (is (= (first %) (last %)))) 24 | 25 | (defspec symbol-json-encoding 26 | encode-equality 27 | [^symbol a] 28 | (is (= (str (first %)) (last %)))) 29 | 30 | (defspec keyword-json-encoding 31 | encode-equality 32 | [^keyword a] 33 | (is (= (name (first %)) (last %)))) 34 | 35 | (defspec map-json-encoding 36 | encode-equality 37 | [^{:tag (hash-map string (hash-map string (vec string 10) 10) 10)} a] 38 | (is (= (first %) (last %)))) 39 | 40 | (defspec map-keyword-json-encoding 41 | encode-equality-keys 42 | [^{:tag (hash-map keyword (hash-map keyword (list int 10) 10) 10)} a] 43 | (is (= (first %) (last %)))) 44 | 45 | (deftest ^{:generative true} t-generative 46 | ;; I want the seeds to change every time, set the number higher if 47 | ;; you have more than 16 CPU cores 48 | (let [seeds (take 16 (repeatedly #(rand-int 1024)))] 49 | (when-not verbose 50 | (reset! g/report-fn identity)) 51 | (println "Seeds:" seeds) 52 | (binding [g/*msec* 25000 53 | g/*seeds* seeds 54 | g/*verbose* false] 55 | (doall (map deref (g/test-namespaces 'cheshire.test.generative)))))) 56 | -------------------------------------------------------------------------------- /test/multi.json: -------------------------------------------------------------------------------- 1 | {"one":1,"foo":"bar"} 2 | {"two":2,"foo":"bar"} 3 | -------------------------------------------------------------------------------- /test/pass1.json: -------------------------------------------------------------------------------- 1 | [ 2 | "JSON Test Pattern pass1", 3 | {"object with 1 member":["array with 1 element"]}, 4 | {}, 5 | [], 6 | -42, 7 | true, 8 | false, 9 | null, 10 | { 11 | "integer": 1234567890, 12 | "real": -9876.543210, 13 | "e": 0.123456789e-12, 14 | "E": 1.234567890E+34, 15 | "": 23456789012E66, 16 | "zero": 0, 17 | "one": 1, 18 | "space": " ", 19 | "quote": "\"", 20 | "backslash": "\\", 21 | "controls": "\b\f\n\r\t", 22 | "slash": "/ & \/", 23 | "alpha": "abcdefghijklmnopqrstuvwyz", 24 | "ALPHA": "ABCDEFGHIJKLMNOPQRSTUVWYZ", 25 | "digit": "0123456789", 26 | "0123456789": "digit", 27 | "special": "`1~!@#$%^&*()_+-={':[,]}|;.?", 28 | "hex": "\u0123\u4567\u89AB\uCDEF\uabcd\uef4A", 29 | "true": true, 30 | "false": false, 31 | "null": null, 32 | "array":[ ], 33 | "object":{ }, 34 | "address": "50 St. James Street", 35 | "url": "http://www.JSON.org/", 36 | "comment": "// /* */": " ", 38 | " s p a c e d " :[1,2 , 3 39 | 40 | , 41 | 42 | 4 , 5 , 6 ,7 ],"compact":[1,2,3,4,5,6,7], 43 | "jsontext": "{\"object with 1 member\":[\"array with 1 element\"]}", 44 | "quotes": "" \u0022 %22 0x22 034 "", 45 | "\/\\\"\uCAFE\uBABE\uAB98\uFCDE\ubcda\uef4A\b\f\n\r\t`1~!@#$%^&*()_+-=[]{}|;:',./<>?" 46 | : "A key can be any string" 47 | }, 48 | 0.5 ,98.6 49 | , 50 | 99.44 51 | , 52 | 53 | 1066, 54 | 1e1, 55 | 0.1e1, 56 | 1e-1, 57 | 1e00,2e+00,2e-00 58 | ,"rosebud"] --------------------------------------------------------------------------------