├── .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 | - [](https://cljdoc.org/d/cheshire/cheshire)
29 | - [](https://clojars.org/cheshire)
30 | - [](https://github.com/dakrone/cheshire/actions/workflows/test.yml)
31 | - [](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"]
--------------------------------------------------------------------------------