├── .gitignore ├── .travis.yml ├── Cargo.lock ├── Cargo.toml ├── LICENSE ├── README.md ├── benches └── json_encoding.rs ├── json_in_type_derive ├── .gitignore ├── Cargo.lock ├── Cargo.toml ├── README.md ├── src │ └── lib.rs └── tests │ └── derive.rs ├── src ├── base_types.rs ├── bin.rs ├── lib.rs ├── list.rs ├── object.rs ├── string.rs └── utils.rs └── tests ├── macros.rs └── simple_example.rs /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | **/*.rs.bk 3 | json-in-type.iml 4 | .idea/ 5 | .vscode/ -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: rust 2 | rust: 3 | - 1.31.0 4 | - stable 5 | - beta 6 | - nightly 7 | 8 | stages: 9 | - test 10 | - deploy 11 | 12 | cache: cargo 13 | 14 | env: 15 | - RUSTFLAGS="-C target-cpu=native" 16 | 17 | jobs: 18 | allow_failures: 19 | - rust: nightly 20 | include: 21 | - stage: deploy 22 | rust: stable 23 | script: cargo bench && mkdir -p docs && mv target/criterion docs 24 | before_install: 25 | - sudo apt-get install -y gnuplot 26 | deploy: 27 | - provider: pages 28 | skip-cleanup: true 29 | github-token: $GITHUB_TOKEN 30 | keep-history: true 31 | on: 32 | branch: master 33 | - provider: cargo 34 | token: 35 | secure: "WTq6WyDlGqp2zcrqmAYucAq8vUT5vx/Xr/leuqOTj4v42Yus7CQ+M2YzZTNOoDZhtZrTi3r4WZJu36MaEEBEjDflo/NlYDJmqHjS2klAMOxwBC2bIVLGG9g2XW+6S00XgicaNGFHVFAjBqUHqgYWMK8C2d2BCpdTQO9pnC8yPXUZmlte5SJGh6WrSHJJUYWZOsBXxZMXEjaR3ipNTVXHki2rK67ZFvnJbwCN4Vk/dGPvyxnQUWLkm2SktXzdMMdu0Qzxa6dftDmhk6QkyztqoK5P19BmaDQYh4JUV3YPWV6T4lR1kBXntiFKeD9MR4iVdM/qNawT5DsD+nStsZa48DpoPq7S/ynGRaqFo3zCW/9iBB46ALndFTDE3GvAkKUyclYQC02Fv1VMM5+3prrbIVtG1ZpbtKmExWHY7bJHKCMP6scPtRV6PZ4H3iX1qhfXGQFcXY1kqgQ7bl0zWGjmnT95y9AUPVhY/YSNZf0ixv5BLliWpvzBt4cICCxzIEA2fWc0K7hui5sFKeK8eGli89A5IJTozdlDFlOqJifwnR1FBAZruA/QcnMY9McdotJsv8x6S6+0JtnokkNbQHdWdE45zJPlmvk+SpW2sknO0SKbhmbvKp36qbzGesjIbCM352+4OF3dudbGQsWvYJC8lK74u6SHbRMxwIc+16Sdydc=" 36 | on: 37 | tags: true 38 | 39 | matrix: 40 | allow_failures: 41 | - rust: nightly 42 | fast_finish: true -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | [[package]] 4 | name = "atty" 5 | version = "0.2.13" 6 | source = "registry+https://github.com/rust-lang/crates.io-index" 7 | dependencies = [ 8 | "libc 0.2.65 (registry+https://github.com/rust-lang/crates.io-index)", 9 | "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", 10 | ] 11 | 12 | [[package]] 13 | name = "autocfg" 14 | version = "0.1.7" 15 | source = "registry+https://github.com/rust-lang/crates.io-index" 16 | 17 | [[package]] 18 | name = "bitflags" 19 | version = "1.2.1" 20 | source = "registry+https://github.com/rust-lang/crates.io-index" 21 | 22 | [[package]] 23 | name = "bstr" 24 | version = "0.2.8" 25 | source = "registry+https://github.com/rust-lang/crates.io-index" 26 | dependencies = [ 27 | "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", 28 | "memchr 2.2.1 (registry+https://github.com/rust-lang/crates.io-index)", 29 | "regex-automata 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", 30 | "serde 1.0.102 (registry+https://github.com/rust-lang/crates.io-index)", 31 | ] 32 | 33 | [[package]] 34 | name = "byteorder" 35 | version = "1.3.2" 36 | source = "registry+https://github.com/rust-lang/crates.io-index" 37 | 38 | [[package]] 39 | name = "cast" 40 | version = "0.2.3" 41 | source = "registry+https://github.com/rust-lang/crates.io-index" 42 | dependencies = [ 43 | "rustc_version 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", 44 | ] 45 | 46 | [[package]] 47 | name = "cfg-if" 48 | version = "0.1.10" 49 | source = "registry+https://github.com/rust-lang/crates.io-index" 50 | 51 | [[package]] 52 | name = "clap" 53 | version = "2.33.0" 54 | source = "registry+https://github.com/rust-lang/crates.io-index" 55 | dependencies = [ 56 | "bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)", 57 | "textwrap 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)", 58 | "unicode-width 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", 59 | ] 60 | 61 | [[package]] 62 | name = "cloudabi" 63 | version = "0.0.3" 64 | source = "registry+https://github.com/rust-lang/crates.io-index" 65 | dependencies = [ 66 | "bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)", 67 | ] 68 | 69 | [[package]] 70 | name = "criterion" 71 | version = "0.2.11" 72 | source = "registry+https://github.com/rust-lang/crates.io-index" 73 | dependencies = [ 74 | "atty 0.2.13 (registry+https://github.com/rust-lang/crates.io-index)", 75 | "cast 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", 76 | "clap 2.33.0 (registry+https://github.com/rust-lang/crates.io-index)", 77 | "criterion-plot 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", 78 | "csv 1.1.1 (registry+https://github.com/rust-lang/crates.io-index)", 79 | "itertools 0.8.2 (registry+https://github.com/rust-lang/crates.io-index)", 80 | "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", 81 | "libc 0.2.65 (registry+https://github.com/rust-lang/crates.io-index)", 82 | "num-traits 0.2.10 (registry+https://github.com/rust-lang/crates.io-index)", 83 | "rand_core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", 84 | "rand_os 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", 85 | "rand_xoshiro 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", 86 | "rayon 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)", 87 | "rayon-core 1.6.1 (registry+https://github.com/rust-lang/crates.io-index)", 88 | "serde 1.0.102 (registry+https://github.com/rust-lang/crates.io-index)", 89 | "serde_derive 1.0.102 (registry+https://github.com/rust-lang/crates.io-index)", 90 | "serde_json 1.0.41 (registry+https://github.com/rust-lang/crates.io-index)", 91 | "tinytemplate 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", 92 | "walkdir 2.2.9 (registry+https://github.com/rust-lang/crates.io-index)", 93 | ] 94 | 95 | [[package]] 96 | name = "criterion-plot" 97 | version = "0.3.1" 98 | source = "registry+https://github.com/rust-lang/crates.io-index" 99 | dependencies = [ 100 | "byteorder 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)", 101 | "cast 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", 102 | "itertools 0.8.2 (registry+https://github.com/rust-lang/crates.io-index)", 103 | ] 104 | 105 | [[package]] 106 | name = "crossbeam-deque" 107 | version = "0.7.2" 108 | source = "registry+https://github.com/rust-lang/crates.io-index" 109 | dependencies = [ 110 | "crossbeam-epoch 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)", 111 | "crossbeam-utils 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", 112 | ] 113 | 114 | [[package]] 115 | name = "crossbeam-epoch" 116 | version = "0.8.0" 117 | source = "registry+https://github.com/rust-lang/crates.io-index" 118 | dependencies = [ 119 | "autocfg 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)", 120 | "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", 121 | "crossbeam-utils 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", 122 | "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", 123 | "memoffset 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)", 124 | "scopeguard 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", 125 | ] 126 | 127 | [[package]] 128 | name = "crossbeam-queue" 129 | version = "0.2.0" 130 | source = "registry+https://github.com/rust-lang/crates.io-index" 131 | dependencies = [ 132 | "crossbeam-utils 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", 133 | ] 134 | 135 | [[package]] 136 | name = "crossbeam-utils" 137 | version = "0.7.0" 138 | source = "registry+https://github.com/rust-lang/crates.io-index" 139 | dependencies = [ 140 | "autocfg 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)", 141 | "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", 142 | "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", 143 | ] 144 | 145 | [[package]] 146 | name = "csv" 147 | version = "1.1.1" 148 | source = "registry+https://github.com/rust-lang/crates.io-index" 149 | dependencies = [ 150 | "bstr 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", 151 | "csv-core 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", 152 | "itoa 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)", 153 | "ryu 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", 154 | "serde 1.0.102 (registry+https://github.com/rust-lang/crates.io-index)", 155 | ] 156 | 157 | [[package]] 158 | name = "csv-core" 159 | version = "0.1.6" 160 | source = "registry+https://github.com/rust-lang/crates.io-index" 161 | dependencies = [ 162 | "memchr 2.2.1 (registry+https://github.com/rust-lang/crates.io-index)", 163 | ] 164 | 165 | [[package]] 166 | name = "either" 167 | version = "1.5.3" 168 | source = "registry+https://github.com/rust-lang/crates.io-index" 169 | 170 | [[package]] 171 | name = "fuchsia-cprng" 172 | version = "0.1.1" 173 | source = "registry+https://github.com/rust-lang/crates.io-index" 174 | 175 | [[package]] 176 | name = "hermit-abi" 177 | version = "0.1.3" 178 | source = "registry+https://github.com/rust-lang/crates.io-index" 179 | dependencies = [ 180 | "libc 0.2.65 (registry+https://github.com/rust-lang/crates.io-index)", 181 | ] 182 | 183 | [[package]] 184 | name = "itertools" 185 | version = "0.8.2" 186 | source = "registry+https://github.com/rust-lang/crates.io-index" 187 | dependencies = [ 188 | "either 1.5.3 (registry+https://github.com/rust-lang/crates.io-index)", 189 | ] 190 | 191 | [[package]] 192 | name = "itoa" 193 | version = "0.4.4" 194 | source = "registry+https://github.com/rust-lang/crates.io-index" 195 | 196 | [[package]] 197 | name = "json_in_type" 198 | version = "1.1.1" 199 | dependencies = [ 200 | "criterion 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", 201 | "itoa 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)", 202 | "json_in_type_derive 0.1.2", 203 | "ryu-ecmascript 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", 204 | "serde 1.0.102 (registry+https://github.com/rust-lang/crates.io-index)", 205 | "serde_derive 1.0.102 (registry+https://github.com/rust-lang/crates.io-index)", 206 | "serde_json 1.0.41 (registry+https://github.com/rust-lang/crates.io-index)", 207 | ] 208 | 209 | [[package]] 210 | name = "json_in_type_derive" 211 | version = "0.1.2" 212 | dependencies = [ 213 | "quote 0.6.13 (registry+https://github.com/rust-lang/crates.io-index)", 214 | "syn 0.14.9 (registry+https://github.com/rust-lang/crates.io-index)", 215 | ] 216 | 217 | [[package]] 218 | name = "lazy_static" 219 | version = "1.4.0" 220 | source = "registry+https://github.com/rust-lang/crates.io-index" 221 | 222 | [[package]] 223 | name = "libc" 224 | version = "0.2.65" 225 | source = "registry+https://github.com/rust-lang/crates.io-index" 226 | 227 | [[package]] 228 | name = "memchr" 229 | version = "2.2.1" 230 | source = "registry+https://github.com/rust-lang/crates.io-index" 231 | dependencies = [ 232 | "libc 0.2.65 (registry+https://github.com/rust-lang/crates.io-index)", 233 | ] 234 | 235 | [[package]] 236 | name = "memoffset" 237 | version = "0.5.3" 238 | source = "registry+https://github.com/rust-lang/crates.io-index" 239 | dependencies = [ 240 | "rustc_version 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", 241 | ] 242 | 243 | [[package]] 244 | name = "num-traits" 245 | version = "0.2.10" 246 | source = "registry+https://github.com/rust-lang/crates.io-index" 247 | dependencies = [ 248 | "autocfg 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)", 249 | ] 250 | 251 | [[package]] 252 | name = "num_cpus" 253 | version = "1.11.1" 254 | source = "registry+https://github.com/rust-lang/crates.io-index" 255 | dependencies = [ 256 | "hermit-abi 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", 257 | "libc 0.2.65 (registry+https://github.com/rust-lang/crates.io-index)", 258 | ] 259 | 260 | [[package]] 261 | name = "proc-macro2" 262 | version = "0.4.30" 263 | source = "registry+https://github.com/rust-lang/crates.io-index" 264 | dependencies = [ 265 | "unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", 266 | ] 267 | 268 | [[package]] 269 | name = "proc-macro2" 270 | version = "1.0.6" 271 | source = "registry+https://github.com/rust-lang/crates.io-index" 272 | dependencies = [ 273 | "unicode-xid 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", 274 | ] 275 | 276 | [[package]] 277 | name = "quote" 278 | version = "0.6.13" 279 | source = "registry+https://github.com/rust-lang/crates.io-index" 280 | dependencies = [ 281 | "proc-macro2 0.4.30 (registry+https://github.com/rust-lang/crates.io-index)", 282 | ] 283 | 284 | [[package]] 285 | name = "quote" 286 | version = "1.0.2" 287 | source = "registry+https://github.com/rust-lang/crates.io-index" 288 | dependencies = [ 289 | "proc-macro2 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)", 290 | ] 291 | 292 | [[package]] 293 | name = "rand_core" 294 | version = "0.3.1" 295 | source = "registry+https://github.com/rust-lang/crates.io-index" 296 | dependencies = [ 297 | "rand_core 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", 298 | ] 299 | 300 | [[package]] 301 | name = "rand_core" 302 | version = "0.4.2" 303 | source = "registry+https://github.com/rust-lang/crates.io-index" 304 | 305 | [[package]] 306 | name = "rand_os" 307 | version = "0.1.3" 308 | source = "registry+https://github.com/rust-lang/crates.io-index" 309 | dependencies = [ 310 | "cloudabi 0.0.3 (registry+https://github.com/rust-lang/crates.io-index)", 311 | "fuchsia-cprng 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", 312 | "libc 0.2.65 (registry+https://github.com/rust-lang/crates.io-index)", 313 | "rand_core 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", 314 | "rdrand 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", 315 | "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", 316 | ] 317 | 318 | [[package]] 319 | name = "rand_xoshiro" 320 | version = "0.1.0" 321 | source = "registry+https://github.com/rust-lang/crates.io-index" 322 | dependencies = [ 323 | "byteorder 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)", 324 | "rand_core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", 325 | ] 326 | 327 | [[package]] 328 | name = "rayon" 329 | version = "1.2.1" 330 | source = "registry+https://github.com/rust-lang/crates.io-index" 331 | dependencies = [ 332 | "crossbeam-deque 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)", 333 | "either 1.5.3 (registry+https://github.com/rust-lang/crates.io-index)", 334 | "rayon-core 1.6.1 (registry+https://github.com/rust-lang/crates.io-index)", 335 | ] 336 | 337 | [[package]] 338 | name = "rayon-core" 339 | version = "1.6.1" 340 | source = "registry+https://github.com/rust-lang/crates.io-index" 341 | dependencies = [ 342 | "crossbeam-deque 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)", 343 | "crossbeam-queue 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", 344 | "crossbeam-utils 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", 345 | "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", 346 | "num_cpus 1.11.1 (registry+https://github.com/rust-lang/crates.io-index)", 347 | ] 348 | 349 | [[package]] 350 | name = "rdrand" 351 | version = "0.4.0" 352 | source = "registry+https://github.com/rust-lang/crates.io-index" 353 | dependencies = [ 354 | "rand_core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", 355 | ] 356 | 357 | [[package]] 358 | name = "regex-automata" 359 | version = "0.1.8" 360 | source = "registry+https://github.com/rust-lang/crates.io-index" 361 | dependencies = [ 362 | "byteorder 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)", 363 | ] 364 | 365 | [[package]] 366 | name = "rustc_version" 367 | version = "0.2.3" 368 | source = "registry+https://github.com/rust-lang/crates.io-index" 369 | dependencies = [ 370 | "semver 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", 371 | ] 372 | 373 | [[package]] 374 | name = "ryu" 375 | version = "1.0.2" 376 | source = "registry+https://github.com/rust-lang/crates.io-index" 377 | 378 | [[package]] 379 | name = "ryu-ecmascript" 380 | version = "0.1.1" 381 | source = "registry+https://github.com/rust-lang/crates.io-index" 382 | 383 | [[package]] 384 | name = "same-file" 385 | version = "1.0.5" 386 | source = "registry+https://github.com/rust-lang/crates.io-index" 387 | dependencies = [ 388 | "winapi-util 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", 389 | ] 390 | 391 | [[package]] 392 | name = "scopeguard" 393 | version = "1.0.0" 394 | source = "registry+https://github.com/rust-lang/crates.io-index" 395 | 396 | [[package]] 397 | name = "semver" 398 | version = "0.9.0" 399 | source = "registry+https://github.com/rust-lang/crates.io-index" 400 | dependencies = [ 401 | "semver-parser 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", 402 | ] 403 | 404 | [[package]] 405 | name = "semver-parser" 406 | version = "0.7.0" 407 | source = "registry+https://github.com/rust-lang/crates.io-index" 408 | 409 | [[package]] 410 | name = "serde" 411 | version = "1.0.102" 412 | source = "registry+https://github.com/rust-lang/crates.io-index" 413 | 414 | [[package]] 415 | name = "serde_derive" 416 | version = "1.0.102" 417 | source = "registry+https://github.com/rust-lang/crates.io-index" 418 | dependencies = [ 419 | "proc-macro2 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)", 420 | "quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", 421 | "syn 1.0.8 (registry+https://github.com/rust-lang/crates.io-index)", 422 | ] 423 | 424 | [[package]] 425 | name = "serde_json" 426 | version = "1.0.41" 427 | source = "registry+https://github.com/rust-lang/crates.io-index" 428 | dependencies = [ 429 | "itoa 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)", 430 | "ryu 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", 431 | "serde 1.0.102 (registry+https://github.com/rust-lang/crates.io-index)", 432 | ] 433 | 434 | [[package]] 435 | name = "syn" 436 | version = "0.14.9" 437 | source = "registry+https://github.com/rust-lang/crates.io-index" 438 | dependencies = [ 439 | "proc-macro2 0.4.30 (registry+https://github.com/rust-lang/crates.io-index)", 440 | "quote 0.6.13 (registry+https://github.com/rust-lang/crates.io-index)", 441 | "unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", 442 | ] 443 | 444 | [[package]] 445 | name = "syn" 446 | version = "1.0.8" 447 | source = "registry+https://github.com/rust-lang/crates.io-index" 448 | dependencies = [ 449 | "proc-macro2 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)", 450 | "quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", 451 | "unicode-xid 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", 452 | ] 453 | 454 | [[package]] 455 | name = "textwrap" 456 | version = "0.11.0" 457 | source = "registry+https://github.com/rust-lang/crates.io-index" 458 | dependencies = [ 459 | "unicode-width 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", 460 | ] 461 | 462 | [[package]] 463 | name = "tinytemplate" 464 | version = "1.0.2" 465 | source = "registry+https://github.com/rust-lang/crates.io-index" 466 | dependencies = [ 467 | "serde 1.0.102 (registry+https://github.com/rust-lang/crates.io-index)", 468 | "serde_json 1.0.41 (registry+https://github.com/rust-lang/crates.io-index)", 469 | ] 470 | 471 | [[package]] 472 | name = "unicode-width" 473 | version = "0.1.6" 474 | source = "registry+https://github.com/rust-lang/crates.io-index" 475 | 476 | [[package]] 477 | name = "unicode-xid" 478 | version = "0.1.0" 479 | source = "registry+https://github.com/rust-lang/crates.io-index" 480 | 481 | [[package]] 482 | name = "unicode-xid" 483 | version = "0.2.0" 484 | source = "registry+https://github.com/rust-lang/crates.io-index" 485 | 486 | [[package]] 487 | name = "walkdir" 488 | version = "2.2.9" 489 | source = "registry+https://github.com/rust-lang/crates.io-index" 490 | dependencies = [ 491 | "same-file 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)", 492 | "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", 493 | "winapi-util 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", 494 | ] 495 | 496 | [[package]] 497 | name = "winapi" 498 | version = "0.3.8" 499 | source = "registry+https://github.com/rust-lang/crates.io-index" 500 | dependencies = [ 501 | "winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", 502 | "winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", 503 | ] 504 | 505 | [[package]] 506 | name = "winapi-i686-pc-windows-gnu" 507 | version = "0.4.0" 508 | source = "registry+https://github.com/rust-lang/crates.io-index" 509 | 510 | [[package]] 511 | name = "winapi-util" 512 | version = "0.1.2" 513 | source = "registry+https://github.com/rust-lang/crates.io-index" 514 | dependencies = [ 515 | "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", 516 | ] 517 | 518 | [[package]] 519 | name = "winapi-x86_64-pc-windows-gnu" 520 | version = "0.4.0" 521 | source = "registry+https://github.com/rust-lang/crates.io-index" 522 | 523 | [metadata] 524 | "checksum atty 0.2.13 (registry+https://github.com/rust-lang/crates.io-index)" = "1803c647a3ec87095e7ae7acfca019e98de5ec9a7d01343f611cf3152ed71a90" 525 | "checksum autocfg 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)" = "1d49d90015b3c36167a20fe2810c5cd875ad504b39cff3d4eae7977e6b7c1cb2" 526 | "checksum bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693" 527 | "checksum bstr 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "8d6c2c5b58ab920a4f5aeaaca34b4488074e8cc7596af94e6f8c6ff247c60245" 528 | "checksum byteorder 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "a7c3dd8985a7111efc5c80b44e23ecdd8c007de8ade3b96595387e812b957cf5" 529 | "checksum cast 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "4b9434b9a5aa1450faa3f9cb14ea0e8c53bb5d2b3c1bfd1ab4fc03e9f33fbfb0" 530 | "checksum cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)" = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" 531 | "checksum clap 2.33.0 (registry+https://github.com/rust-lang/crates.io-index)" = "5067f5bb2d80ef5d68b4c87db81601f0b75bca627bc2ef76b141d7b846a3c6d9" 532 | "checksum cloudabi 0.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "ddfc5b9aa5d4507acaf872de71051dfd0e309860e88966e1051e462a077aac4f" 533 | "checksum criterion 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)" = "0363053954f3e679645fc443321ca128b7b950a6fe288cf5f9335cc22ee58394" 534 | "checksum criterion-plot 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "76f9212ddf2f4a9eb2d401635190600656a1f88a932ef53d06e7fa4c7e02fb8e" 535 | "checksum crossbeam-deque 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)" = "c3aa945d63861bfe624b55d153a39684da1e8c0bc8fba932f7ee3a3c16cea3ca" 536 | "checksum crossbeam-epoch 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "5064ebdbf05ce3cb95e45c8b086f72263f4166b29b97f6baff7ef7fe047b55ac" 537 | "checksum crossbeam-queue 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "dfd6515864a82d2f877b42813d4553292c6659498c9a2aa31bab5a15243c2700" 538 | "checksum crossbeam-utils 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ce446db02cdc3165b94ae73111e570793400d0794e46125cc4056c81cbb039f4" 539 | "checksum csv 1.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "37519ccdfd73a75821cac9319d4fce15a81b9fcf75f951df5b9988aa3a0af87d" 540 | "checksum csv-core 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "9b5cadb6b25c77aeff80ba701712494213f4a8418fcda2ee11b6560c3ad0bf4c" 541 | "checksum either 1.5.3 (registry+https://github.com/rust-lang/crates.io-index)" = "bb1f6b1ce1c140482ea30ddd3335fc0024ac7ee112895426e0a629a6c20adfe3" 542 | "checksum fuchsia-cprng 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "a06f77d526c1a601b7c4cdd98f54b5eaabffc14d5f2f0296febdc7f357c6d3ba" 543 | "checksum hermit-abi 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "307c3c9f937f38e3534b1d6447ecf090cafcc9744e4a6360e8b037b2cf5af120" 544 | "checksum itertools 0.8.2 (registry+https://github.com/rust-lang/crates.io-index)" = "f56a2d0bc861f9165be4eb3442afd3c236d8a98afd426f65d92324ae1091a484" 545 | "checksum itoa 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)" = "501266b7edd0174f8530248f87f99c88fbe60ca4ef3dd486835b8d8d53136f7f" 546 | "checksum lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" 547 | "checksum libc 0.2.65 (registry+https://github.com/rust-lang/crates.io-index)" = "1a31a0627fdf1f6a39ec0dd577e101440b7db22672c0901fe00a9a6fbb5c24e8" 548 | "checksum memchr 2.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "88579771288728879b57485cc7d6b07d648c9f0141eb955f8ab7f9d45394468e" 549 | "checksum memoffset 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)" = "75189eb85871ea5c2e2c15abbdd541185f63b408415e5051f5cac122d8c774b9" 550 | "checksum num-traits 0.2.10 (registry+https://github.com/rust-lang/crates.io-index)" = "d4c81ffc11c212fa327657cb19dd85eb7419e163b5b076bede2bdb5c974c07e4" 551 | "checksum num_cpus 1.11.1 (registry+https://github.com/rust-lang/crates.io-index)" = "76dac5ed2a876980778b8b85f75a71b6cbf0db0b1232ee12f826bccb00d09d72" 552 | "checksum proc-macro2 0.4.30 (registry+https://github.com/rust-lang/crates.io-index)" = "cf3d2011ab5c909338f7887f4fc896d35932e29146c12c8d01da6b22a80ba759" 553 | "checksum proc-macro2 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)" = "9c9e470a8dc4aeae2dee2f335e8f533e2d4b347e1434e5671afc49b054592f27" 554 | "checksum quote 0.6.13 (registry+https://github.com/rust-lang/crates.io-index)" = "6ce23b6b870e8f94f81fb0a363d65d86675884b34a09043c81e5562f11c1f8e1" 555 | "checksum quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "053a8c8bcc71fcce321828dc897a98ab9760bef03a4fc36693c231e5b3216cfe" 556 | "checksum rand_core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "7a6fdeb83b075e8266dcc8762c22776f6877a63111121f5f8c7411e5be7eed4b" 557 | "checksum rand_core 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "9c33a3c44ca05fa6f1807d8e6743f3824e8509beca625669633be0acbdf509dc" 558 | "checksum rand_os 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "7b75f676a1e053fc562eafbb47838d67c84801e38fc1ba459e8f180deabd5071" 559 | "checksum rand_xoshiro 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "03b418169fb9c46533f326efd6eed2576699c44ca92d3052a066214a8d828929" 560 | "checksum rayon 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "43739f8831493b276363637423d3622d4bd6394ab6f0a9c4a552e208aeb7fddd" 561 | "checksum rayon-core 1.6.1 (registry+https://github.com/rust-lang/crates.io-index)" = "f8bf17de6f23b05473c437eb958b9c850bfc8af0961fe17b4cc92d5a627b4791" 562 | "checksum rdrand 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "678054eb77286b51581ba43620cc911abf02758c91f93f479767aed0f90458b2" 563 | "checksum regex-automata 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)" = "92b73c2a1770c255c240eaa4ee600df1704a38dc3feaa6e949e7fcd4f8dc09f9" 564 | "checksum rustc_version 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a" 565 | "checksum ryu 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "bfa8506c1de11c9c4e4c38863ccbe02a305c8188e85a05a784c9e11e1c3910c8" 566 | "checksum ryu-ecmascript 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "79f19ef8ea9a62575f9422da5ca81b1529ccf9be3e2150bf175f36612af0575c" 567 | "checksum same-file 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)" = "585e8ddcedc187886a30fa705c47985c3fa88d06624095856b36ca0b82ff4421" 568 | "checksum scopeguard 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b42e15e59b18a828bbf5c58ea01debb36b9b096346de35d941dcb89009f24a0d" 569 | "checksum semver 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403" 570 | "checksum semver-parser 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" 571 | "checksum serde 1.0.102 (registry+https://github.com/rust-lang/crates.io-index)" = "0c4b39bd9b0b087684013a792c59e3e07a46a01d2322518d8a1104641a0b1be0" 572 | "checksum serde_derive 1.0.102 (registry+https://github.com/rust-lang/crates.io-index)" = "ca13fc1a832f793322228923fbb3aba9f3f44444898f835d31ad1b74fa0a2bf8" 573 | "checksum serde_json 1.0.41 (registry+https://github.com/rust-lang/crates.io-index)" = "2f72eb2a68a7dc3f9a691bfda9305a1c017a6215e5a4545c258500d2099a37c2" 574 | "checksum syn 0.14.9 (registry+https://github.com/rust-lang/crates.io-index)" = "261ae9ecaa397c42b960649561949d69311f08eeaea86a65696e6e46517cf741" 575 | "checksum syn 1.0.8 (registry+https://github.com/rust-lang/crates.io-index)" = "661641ea2aa15845cddeb97dad000d22070bb5c1fb456b96c1cba883ec691e92" 576 | "checksum textwrap 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060" 577 | "checksum tinytemplate 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "4574b75faccaacddb9b284faecdf0b544b80b6b294f3d062d325c5726a209c20" 578 | "checksum unicode-width 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "7007dbd421b92cc6e28410fe7362e2e0a2503394908f417b68ec8d1c364c4e20" 579 | "checksum unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "fc72304796d0818e357ead4e000d19c9c174ab23dc11093ac919054d20a6a7fc" 580 | "checksum unicode-xid 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "826e7639553986605ec5979c7dd957c7895e93eabed50ab2ffa7f6128a75097c" 581 | "checksum walkdir 2.2.9 (registry+https://github.com/rust-lang/crates.io-index)" = "9658c94fa8b940eab2250bd5a457f9c48b748420d71293b165c8cdbe2f55f71e" 582 | "checksum winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)" = "8093091eeb260906a183e6ae1abdba2ef5ef2257a21801128899c3fc699229c6" 583 | "checksum winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 584 | "checksum winapi-util 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7168bab6e1daee33b4557efd0e95d5ca70a03706d39fa5f3fe7a236f584b03c9" 585 | "checksum winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 586 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "json_in_type" 3 | description = "a library for fast json serialization" 4 | version = "1.1.1" 5 | authors = ["ophir "] 6 | license = "BSD-2-Clause" 7 | homepage = "https://lovasoa.github.io/json_in_type/" 8 | repository = "https://github.com/lovasoa/json_in_type" 9 | readme = "README.md" 10 | categories = ["encoding"] 11 | keywords = ["json", "serialization"] 12 | documentation = "https://docs.rs/json_in_type" 13 | edition="2018" 14 | 15 | [lib] 16 | name = "json_in_type" 17 | path = "src/lib.rs" 18 | 19 | [[bin]] 20 | name = "json_in_type" 21 | path = "src/bin.rs" 22 | 23 | [dependencies] 24 | ryu-ecmascript = "0.1" 25 | itoa = {version="0.4", features=["i128"]} 26 | 27 | [dev-dependencies] 28 | criterion = "0.2" 29 | serde = "1.0" 30 | serde_derive = "1.0" 31 | serde_json = "1.0" 32 | json_in_type_derive = {path="json_in_type_derive", version="0.1"} 33 | 34 | [[bench]] 35 | name = "json_encoding" 36 | harness = false 37 | 38 | [badges] 39 | travis-ci = { repository = "lovasoa/json_in_type", branch = "master" } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | BSD 2-Clause License 2 | 3 | Copyright (c) 2018, Ophir LOJKINE 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | * Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 12 | * Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 17 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 18 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 19 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 20 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 21 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 22 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 23 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 24 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 25 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 26 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # json_in_type 2 | 3 | Fast json encoder in rust, that does more at compile time, and less at run time. 4 | One notable feature is the ability to encode the structure of JSON objects in their type. 5 | 6 | This allows for a very compact representation of objects in memory, and up to an order of magnitude better performance 7 | than the traditional approach (used by serde's `json!` marco, for instance) where JSON objects are stored in maps. 8 | 9 | The goal of this library is to be as close as possible to the performance 10 | and memory footprint you would get by writing the json by hand in your source code 11 | and using string formatting to insert your dynamic values. 12 | 13 | ```rust 14 | fn write_obj_bad(value: f32) -> String { 15 | format!("{{\"value\":{}}}", value) 16 | } 17 | 18 | // Safer, but equivalent and not less efficient : 19 | fn write_obj_good(value: f32) -> String { 20 | ( json_object! { value } ).to_json_string() 21 | } 22 | ``` 23 | 24 | ## Example use 25 | 26 | ```rust 27 | use json_in_type::*; 28 | 29 | fn main() { 30 | let void = (); 31 | let list = json_list![42u8, true]; 32 | let dynamic_key = "hello"; 33 | 34 | let json_val = json_object!{ 35 | void, list, 36 | [dynamic_key]: "world" 37 | }; 38 | /* The type of json_val is: 39 | 40 | InlinedJSONObjectEntry< 41 | (), 42 | InlinedJSONObjectEntry< 43 | JSONListElem>>, 46 | JSONObjectEntry< 47 | &str, &str, 48 | JSONObjectEnd>>> 49 | */ 50 | 51 | assert_eq!( 52 | r#"{"void":null,"list":[42,true],"hello":"world"}"#, 53 | json_val.to_json_string() 54 | ); 55 | } 56 | ``` 57 | 58 | ## Memory use 59 | The generated types have a very small memory footprint at runtime. 60 | You don't pay for the json structure, only for what you put in it ! 61 | 62 | In the next example, we store the following json structure on only two bytes: 63 | ```json 64 | { 65 | "result_count" : 1, 66 | "errors" : null, 67 | "results" : [ 68 | {"answer":42, "ok":true} 69 | ] 70 | } 71 | ``` 72 | 73 | ```rust 74 | fn test_memory_size() { 75 | let (result_count, answer) = (1u8, 42u8); 76 | let my_val = json_object! { 77 | result_count, 78 | errors: null, 79 | results: json_list![ 80 | json_object!{answer, ok: true} 81 | ] 82 | }; 83 | // my_val weighs only two bytes, because we stored only 2 u8 in it 84 | assert_eq!(2, ::std::mem::size_of_val(&my_val)); 85 | } 86 | ``` 87 | ## Performance 88 | 89 | This library is generally faster than SERDE. 90 | Here are detailed comparison results on different json serialization tasks realized on an AMD Ryzen 5 1600X. 91 | [See detailed benchmark results.](https://lovasoa.github.io/json_in_type/docs/criterion/report/) 92 | 93 | ### Encoding 8 nested json objects using a rust macro 94 | 95 | We use serde's 96 | [`json!`](https://docs.serde.rs/serde_json/macro.json.html) 97 | and json_in_type's 98 | [`json_object!`](https://docs.rs/json_in_type/latest/json_in_type/macro.json_object.html) 99 | macro to encode a nested json object. 100 | 101 | #### Encoded object 102 | We encode a JSON structure composed of 8 nested objects, each of 103 | which contains a single key, that is known at compile time. 104 | The last nested object contains an integer *n* that is not known at compile time. 105 | ```js 106 | {"nested":{"nested":{"nested":{"nested":{"nested":{"nested":{"nested":{"nested":{"value":n}}}}}}}}} 107 | ``` 108 | 109 | #### Benchmark result 110 | ![nested json objects comparison](https://lovasoa.github.io/json_in_type/docs/criterion/encode%20nested%20objects/report/violin.svg) 111 | 112 | ### Encoding a very simple json object using a rust macro 113 | 114 | #### Encoded object 115 | ```json 116 | { 117 | "void": null, 118 | "list": [1, 2, 3, 3], 119 | "hello": "world" 120 | } 121 | ``` 122 | 123 | #### Benchmark result 124 | ![simple object](https://lovasoa.github.io/json_in_type/docs/criterion/encode%20simple%20object%20with%20macro/report/violin.svg) 125 | 126 | ### Encoding a very simple json object using `#[derive(...)]` 127 | 128 | #### Encoded object 129 | ```json 130 | { 131 | "void": null, 132 | "list": [1, 2, 3, 3], 133 | "hello": "world" 134 | } 135 | ``` 136 | 137 | created from the following rust struct 138 | 139 | ```rust 140 | #[derive(Serialize, JSONValue)] 141 | struct MyObject { 142 | void: (), 143 | list: Vec, 144 | hello: String, 145 | } 146 | ``` 147 | 148 | #### Benchmark result 149 | ![simple object](https://lovasoa.github.io/json_in_type/docs/criterion/encode%20simple%20object%20with%20derive/report/violin.svg) 150 | 151 | ## External links 152 | 153 | * docs.rs hosts this crate's [api documentation](https://docs.rs/json_in_type). 154 | * documentation for [the `json_object!` macro](https://docs.rs/json_in_type/latest/json_in_type/macro.json_object.html) 155 | * documentation for [the `JSONValue` trait](https://docs.rs/json_in_type/latest/json_in_type/trait.JSONValue.html) 156 | * You can automatically derive the `JSONValue` trait for your type using the [json_in_type_derive crate](https://docs.rs/json_in_type_derive) 157 | * You can see [json_in_type on crates.io](https://crates.io/crates/json_in_type). 158 | -------------------------------------------------------------------------------- /benches/json_encoding.rs: -------------------------------------------------------------------------------- 1 | #[macro_use] 2 | extern crate json_in_type_derive; 3 | #[macro_use] 4 | extern crate serde_derive; 5 | 6 | use criterion::{AxisScale, Criterion, Fun, ParameterizedBenchmark, PlotConfiguration, Throughput}; 7 | use json_in_type::*; 8 | 9 | fn simple_json_in_type(n: f64) -> Vec { 10 | let obj = json_object! { 11 | void: (), 12 | list: json_list![1.,2.,3.,n], 13 | hello: "world" 14 | }; 15 | obj.to_json_buffer() 16 | } 17 | 18 | fn simple_serde(n: f64) -> Vec { 19 | let obj = serde_json::json!({ 20 | "void": null, 21 | "list": [1., 2., 3., n], 22 | "hello": "world" 23 | }); 24 | serde_json::to_vec(&obj).unwrap() 25 | } 26 | 27 | #[derive(Serialize, JSONValue)] 28 | struct MyObject { 29 | void: (), 30 | list: Vec, 31 | hello: String, 32 | } 33 | 34 | fn simple_serde_derive(n: f64) -> Vec { 35 | let obj = MyObject { 36 | void: (), 37 | list: vec![1., 2., 3., n], 38 | hello: String::from("world"), 39 | }; 40 | serde_json::to_vec(&obj).unwrap() 41 | } 42 | 43 | fn simple_json_in_type_derive(n: f64) -> Vec { 44 | let obj = MyObject { 45 | void: (), 46 | list: vec![1., 2., 3., n], 47 | hello: String::from("world"), 48 | }; 49 | obj.to_json_buffer() 50 | } 51 | 52 | fn nested_json_in_type(n: u8) -> Vec { 53 | // 8 levels of nested objects 54 | let obj = json_object! { 55 | nested: json_object! { 56 | nested: json_object! { 57 | nested: json_object! { 58 | nested: json_object! { 59 | nested: json_object! { 60 | nested: json_object! { 61 | nested: json_object! { 62 | nested: json_object! { 63 | value: n 64 | } 65 | } 66 | } 67 | } 68 | } 69 | } 70 | } 71 | } 72 | }; 73 | obj.to_json_buffer() 74 | } 75 | 76 | fn nested_serde(n: u8) -> Vec { 77 | let obj = serde_json::json!({ 78 | "nested": { 79 | "nested": { 80 | "nested": { 81 | "nested": { 82 | "nested": { 83 | "nested": { 84 | "nested": { 85 | "nested": { 86 | "value": n 87 | } 88 | } 89 | } 90 | } 91 | } 92 | } 93 | } 94 | } 95 | }); 96 | serde_json::to_vec(&obj).unwrap() 97 | } 98 | 99 | fn criterion_benchmark(c: &mut Criterion) { 100 | c.bench_functions( 101 | "encode simple object with macro", 102 | vec![ 103 | Fun::new("json_in_type", |b, i| b.iter(|| simple_json_in_type(*i))), 104 | Fun::new("serde_json", |b, i| b.iter(|| simple_serde(*i))), 105 | ], 106 | 999_999_999.999f64, 107 | ); 108 | c.bench_functions( 109 | "encode simple object with derive", 110 | vec![ 111 | Fun::new("json_in_type", |b, i| { 112 | b.iter(|| simple_json_in_type_derive(*i)) 113 | }), 114 | Fun::new("serde_json", |b, i| b.iter(|| simple_serde_derive(*i))), 115 | ], 116 | 999_999_999.999f64, 117 | ); 118 | c.bench_functions( 119 | "encode nested objects", 120 | vec![ 121 | Fun::new("json_in_type", |b, i| b.iter(|| nested_json_in_type(*i))), 122 | Fun::new("serde_json", |b, i| b.iter(|| nested_serde(*i))), 123 | ], 124 | 0, 125 | ); 126 | 127 | struct BenchStr(String); 128 | impl BenchStr { 129 | fn new(magnitude: usize) -> BenchStr { 130 | BenchStr( 131 | "Lorem ipsum dolor sit amet, consectetur adipiscing elit. \ 132 | In vel erat rutrum, tincidunt lorem nullam\n" 133 | .repeat(1 << magnitude), 134 | ) 135 | } 136 | fn serde(&self) -> Vec { 137 | serde_json::to_vec(&self.0).unwrap() 138 | } 139 | fn json_in_type(&self) -> Vec { 140 | self.0.to_json_buffer() 141 | } 142 | fn bytes_len(&self) -> usize { 143 | self.0.as_bytes().len() 144 | } 145 | } 146 | impl std::fmt::Debug for BenchStr { 147 | fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { 148 | write!(f, "{}-bytes string", self.bytes_len()) 149 | } 150 | } 151 | 152 | c.bench( 153 | "string encoding throughput", 154 | ParameterizedBenchmark::new( 155 | "json_in_type", 156 | |b, i| b.iter(|| i.json_in_type()), 157 | (0..12).step_by(4).map(BenchStr::new), 158 | ) 159 | .with_function("serde", |b, i| b.iter(|| i.serde())) 160 | .throughput(|i| Throughput::Bytes(i.bytes_len() as u32)) 161 | .plot_config(PlotConfiguration::default().summary_scale(AxisScale::Logarithmic)), 162 | ); 163 | 164 | c.bench_functions( 165 | "string with many special chars", 166 | vec![ 167 | Fun::new("json_in_type", |b, i: &String| { 168 | b.iter(|| i.to_json_buffer()) 169 | }), 170 | Fun::new("serde", |b, i: &String| { 171 | b.iter(|| serde_json::to_vec(i).unwrap()) 172 | }), 173 | ], 174 | "\r\n\"Zero\"\r\n\t\"\0\" is the \"null\" byte.".repeat(1024), 175 | ); 176 | } 177 | 178 | criterion::criterion_group! { 179 | name = benches; 180 | config = Criterion::default().noise_threshold(0.05); 181 | targets = criterion_benchmark 182 | } 183 | criterion::criterion_main!(benches); 184 | -------------------------------------------------------------------------------- /json_in_type_derive/.gitignore: -------------------------------------------------------------------------------- 1 | target/ 2 | -------------------------------------------------------------------------------- /json_in_type_derive/Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | [[package]] 4 | name = "itoa" 5 | version = "0.4.4" 6 | source = "registry+https://github.com/rust-lang/crates.io-index" 7 | 8 | [[package]] 9 | name = "json_in_type" 10 | version = "1.1.1" 11 | source = "registry+https://github.com/rust-lang/crates.io-index" 12 | dependencies = [ 13 | "itoa 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)", 14 | "ryu-ecmascript 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", 15 | ] 16 | 17 | [[package]] 18 | name = "json_in_type_derive" 19 | version = "0.1.2" 20 | dependencies = [ 21 | "json_in_type 1.1.1 (registry+https://github.com/rust-lang/crates.io-index)", 22 | "quote 0.6.13 (registry+https://github.com/rust-lang/crates.io-index)", 23 | "syn 0.14.9 (registry+https://github.com/rust-lang/crates.io-index)", 24 | ] 25 | 26 | [[package]] 27 | name = "proc-macro2" 28 | version = "0.4.30" 29 | source = "registry+https://github.com/rust-lang/crates.io-index" 30 | dependencies = [ 31 | "unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", 32 | ] 33 | 34 | [[package]] 35 | name = "quote" 36 | version = "0.6.13" 37 | source = "registry+https://github.com/rust-lang/crates.io-index" 38 | dependencies = [ 39 | "proc-macro2 0.4.30 (registry+https://github.com/rust-lang/crates.io-index)", 40 | ] 41 | 42 | [[package]] 43 | name = "ryu-ecmascript" 44 | version = "0.1.1" 45 | source = "registry+https://github.com/rust-lang/crates.io-index" 46 | 47 | [[package]] 48 | name = "syn" 49 | version = "0.14.9" 50 | source = "registry+https://github.com/rust-lang/crates.io-index" 51 | dependencies = [ 52 | "proc-macro2 0.4.30 (registry+https://github.com/rust-lang/crates.io-index)", 53 | "quote 0.6.13 (registry+https://github.com/rust-lang/crates.io-index)", 54 | "unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", 55 | ] 56 | 57 | [[package]] 58 | name = "unicode-xid" 59 | version = "0.1.0" 60 | source = "registry+https://github.com/rust-lang/crates.io-index" 61 | 62 | [metadata] 63 | "checksum itoa 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)" = "501266b7edd0174f8530248f87f99c88fbe60ca4ef3dd486835b8d8d53136f7f" 64 | "checksum json_in_type 1.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "cd67fec3efeb78a4764b718f020215cdf93da6a9823353851f8715c00d64e65e" 65 | "checksum proc-macro2 0.4.30 (registry+https://github.com/rust-lang/crates.io-index)" = "cf3d2011ab5c909338f7887f4fc896d35932e29146c12c8d01da6b22a80ba759" 66 | "checksum quote 0.6.13 (registry+https://github.com/rust-lang/crates.io-index)" = "6ce23b6b870e8f94f81fb0a363d65d86675884b34a09043c81e5562f11c1f8e1" 67 | "checksum ryu-ecmascript 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "79f19ef8ea9a62575f9422da5ca81b1529ccf9be3e2150bf175f36612af0575c" 68 | "checksum syn 0.14.9 (registry+https://github.com/rust-lang/crates.io-index)" = "261ae9ecaa397c42b960649561949d69311f08eeaea86a65696e6e46517cf741" 69 | "checksum unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "fc72304796d0818e357ead4e000d19c9c174ab23dc11093ac919054d20a6a7fc" 70 | -------------------------------------------------------------------------------- /json_in_type_derive/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "json_in_type_derive" 3 | version = "0.1.2" 4 | authors = ["ophir "] 5 | description = "procedural macros for json_in_type" 6 | license-file = "../LICENSE" 7 | homepage = "https://lovasoa.github.io/json_in_type/" 8 | repository = "https://github.com/lovasoa/json_in_type" 9 | readme = "README.md" 10 | 11 | [lib] 12 | proc-macro = true 13 | 14 | [dependencies] 15 | syn = "0.14.4" 16 | quote = "0.6.3" 17 | 18 | [dev-dependencies] 19 | json_in_type = "1.1" -------------------------------------------------------------------------------- /json_in_type_derive/README.md: -------------------------------------------------------------------------------- 1 | # Procedural macros for json_in_type 2 | 3 | This crate defines a derive mode macro allowing the 4 | automatic derivation of the 5 | [JSONValue](https://docs.rs/json_in_type/latest/json_in_type/trait.JSONValue.html) 6 | trait for custom data structures. 7 | 8 | # Example 9 | 10 | ```rust 11 | extern crate json_in_type; 12 | #[macro_use] 13 | extern crate json_in_type_derive; 14 | use json_in_type::JSONValue; 15 | 16 | #[derive(JSONValue)] 17 | struct MyObject { // This will be encoded as a JSON object 18 | void: (), 19 | list: Vec, 20 | hello: String, 21 | } 22 | 23 | #[derive(JSONValue)] 24 | struct WrapperStruct(u8, u8); // This will be encoded as a JSON list 25 | ``` -------------------------------------------------------------------------------- /json_in_type_derive/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! This module provides a procedural macro to derive JSONValue for custom structs. 2 | //! 3 | //! # Examples 4 | //! ``` 5 | //! extern crate json_in_type; 6 | //! #[macro_use] extern crate json_in_type_derive; 7 | //! use json_in_type::JSONValue; 8 | //! 9 | //! #[derive(JSONValue)] 10 | //! struct MyObject { 11 | //! void: (), 12 | //! list: Vec, 13 | //! hello: String, 14 | //! } 15 | //! 16 | //! let obj = MyObject { 17 | //! void: (), 18 | //! list: vec![1, 2, 3], 19 | //! hello: String::from("world"), 20 | //! }; 21 | //! assert_eq!( 22 | //! r#"{"void":null,"list":[1,2,3],"hello":"world"}"#, 23 | //! obj.to_json_string() 24 | //! ); 25 | //! ``` 26 | #![recursion_limit = "128"] 27 | extern crate proc_macro; 28 | #[macro_use] 29 | extern crate quote; 30 | extern crate syn; 31 | 32 | use self::proc_macro::TokenStream; 33 | use syn::{ 34 | Data, 35 | DeriveInput, 36 | spanned::Spanned 37 | }; 38 | 39 | /// Derive JSONValue for a structure 40 | #[proc_macro_derive(JSONValue)] 41 | pub fn jsonvalue_macro_derive(input: TokenStream) -> TokenStream { 42 | // Construct a representation of Rust code as a syntax tree 43 | // that we can manipulate 44 | let ast = syn::parse(input).unwrap(); 45 | 46 | // Build the trait implementation 47 | impl_jsonvalue_macro(&ast) 48 | } 49 | 50 | fn impl_jsonvalue_macro(ast: &DeriveInput) -> TokenStream { 51 | let name = &ast.ident; 52 | match &ast.data { 53 | Data::Struct(s) => impl_jsonvalue_macro_struct(&name, s), 54 | Data::Enum(e) => impl_jsonvalue_macro_enum(&name, e), 55 | Data::Union(_) => unimplemented!() 56 | } 57 | } 58 | 59 | 60 | fn ident_to_litbytes(ident: &syn::Ident, first: bool) -> syn::LitByteStr { 61 | let mut obj_key_str = format!("\"{}\":", ident); 62 | obj_key_str.insert(0, if first { '{' } else { ',' }); 63 | syn::LitByteStr::new(obj_key_str.as_bytes(), ident.span()) 64 | } 65 | 66 | fn field_to_litbytes(field: &syn::Field, first: bool) -> Option { 67 | field.clone().ident.map(|ident| ident_to_litbytes(&ident, first)) 68 | } 69 | 70 | fn field_to_ident(field: &syn::Field) -> Option { 71 | field.ident.clone() 72 | } 73 | 74 | fn write_named_fields( 75 | fields_named: &syn::FieldsNamed, 76 | ) -> impl quote::ToTokens { 77 | let fs = fields_named.named.clone(); 78 | let names: Vec = fs.iter() 79 | .enumerate() 80 | .flat_map(|(i, f)| field_to_litbytes(f, i == 0)) 81 | .collect(); 82 | let fields: Vec = fs.iter() 83 | .flat_map(field_to_ident) 84 | .collect(); 85 | let end = syn::LitByteStr::new(if fields.is_empty() { b"{}" } else { b"}" }, fs.span()); 86 | quote! { 87 | #( 88 | w.write_all(#names)?; 89 | self.#fields.write_json(w)?; 90 | )* 91 | w.write_all(#end) 92 | } 93 | } 94 | 95 | fn write_unnamed_fields( 96 | fields_named: &syn::FieldsUnnamed, 97 | ) -> impl quote::ToTokens { 98 | let fs = fields_named.unnamed.clone(); 99 | let nums: Vec = fs.iter().enumerate().map(|(i, _)| i as u32).collect(); 100 | let commas: Vec = nums.iter() 101 | .map(|i| syn::LitByteStr::new(if *i == 0 { b"[" } else { b"," }, fs.span())) 102 | .collect(); 103 | let members: Vec = nums.iter() 104 | .map(|i| syn::Member::Unnamed(syn::Index { index: *i, span: fs.span() })) 105 | .collect(); 106 | let end = syn::LitByteStr::new(if commas.is_empty() { b"[]" } else { b"]" }, fs.span()); 107 | quote! { 108 | #( 109 | w.write_all(#commas)?; 110 | self.#members.write_json(w)?; 111 | )* 112 | w.write_all(#end) 113 | } 114 | } 115 | 116 | fn write_fields( 117 | fields: &syn::Fields, 118 | ) -> Box { 119 | match fields { 120 | syn::Fields::Named(fields_named) => 121 | Box::new(write_named_fields(fields_named)), 122 | syn::Fields::Unnamed(fields_unnamed) => 123 | Box::new(write_unnamed_fields(fields_unnamed)), 124 | syn::Fields::Unit => 125 | Box::new(quote! {w.write_all(b"null")}) 126 | } 127 | } 128 | 129 | fn impl_jsonvalue_macro_struct( 130 | name: &syn::Ident, 131 | struct_data: &syn::DataStruct, 132 | ) -> TokenStream { 133 | let write_fields_ts = write_fields(&struct_data.fields); 134 | (quote! { 135 | impl JSONValue for #name { 136 | fn write_json(&self, w: &mut W) -> std::io::Result<()> { 137 | #write_fields_ts 138 | } 139 | } 140 | }).into() 141 | } 142 | 143 | fn impl_jsonvalue_macro_enum( 144 | name: &syn::Ident, 145 | struct_data: &syn::DataEnum, 146 | ) -> TokenStream { 147 | let vs = struct_data.variants.clone(); 148 | let idents: Vec = vs.iter().map(|v| v.ident.clone()).collect(); 149 | let variants_json: Vec<_> = idents.clone().iter() 150 | .map(|ident| ident_to_litbytes(ident, true)) 151 | .collect(); 152 | let names = std::iter::repeat(name); 153 | for v in vs.iter() { 154 | match v.fields { 155 | syn::Fields::Unnamed(_) => unimplemented!(), 156 | syn::Fields::Named(_) => unimplemented!(), 157 | syn::Fields::Unit => (), 158 | } 159 | } 160 | (quote! { 161 | impl JSONValue for #name { 162 | fn write_json(&self, w: &mut W) -> std::io::Result<()> { 163 | match self { 164 | #( 165 | #names::#idents => { 166 | w.write_all(#variants_json)?; 167 | w.write_all(b"true}") 168 | } 169 | ),* 170 | } 171 | } 172 | } 173 | }).into() 174 | } -------------------------------------------------------------------------------- /json_in_type_derive/tests/derive.rs: -------------------------------------------------------------------------------- 1 | extern crate json_in_type; 2 | #[macro_use] 3 | extern crate json_in_type_derive; 4 | 5 | use json_in_type::JSONValue; 6 | 7 | #[allow(dead_code)] 8 | #[derive(JSONValue)] 9 | struct MyObject { 10 | void: (), 11 | list: Vec, 12 | hello: String, 13 | } 14 | 15 | #[test] 16 | fn test_simple_json() { 17 | let obj = MyObject { 18 | void: (), 19 | list: vec![1, 2, 3], 20 | hello: String::from("world"), 21 | }; 22 | assert_eq!( 23 | r#"{"void":null,"list":[1,2,3],"hello":"world"}"#, 24 | obj.to_json_string() 25 | ); 26 | } 27 | 28 | #[allow(dead_code)] 29 | #[derive(JSONValue)] 30 | struct WrapperStruct(u8, u8); 31 | 32 | #[test] 33 | fn test_wrapperstruct() { 34 | let obj = WrapperStruct(9, 4); 35 | assert_eq!("[9,4]", obj.to_json_string()); 36 | } 37 | 38 | #[allow(dead_code)] 39 | #[derive(JSONValue)] 40 | enum Val { A, B, C } 41 | 42 | #[test] 43 | fn test_enum() { 44 | let obj = Val::C; 45 | assert_eq!(r#"{"C":true}"#, obj.to_json_string()); 46 | } 47 | -------------------------------------------------------------------------------- /src/base_types.rs: -------------------------------------------------------------------------------- 1 | //! Serialization of numbers, booleans, and null 2 | 3 | use super::JSONValue; 4 | use std::io; 5 | 6 | macro_rules! impl_json_for_int { 7 | ( $( $json_type:ty ),* ) => { 8 | $( 9 | impl JSONValue for $json_type { 10 | #[inline(always)] 11 | fn write_json(&self, w: &mut W) -> io::Result<()> { 12 | itoa::write(w, *self).map(|_| ()) 13 | } 14 | } 15 | )* 16 | }; 17 | } 18 | 19 | impl_json_for_int!(i8, i16, i32, i64, i128, isize, u8, u16, u32, u64, u128, usize); 20 | 21 | macro_rules! impl_json_for_float { 22 | ( $( $json_type:ty ),* ) => { 23 | $( 24 | impl JSONValue for $json_type { 25 | #[inline(always)] 26 | fn write_json(&self, w: &mut W) -> io::Result<()> { 27 | if self.is_finite() { 28 | w.write_all(ryu_ecmascript::Buffer::new().format(*self).as_bytes()) 29 | } else { 30 | ().write_json(w) // null 31 | } 32 | } 33 | } 34 | )* 35 | }; 36 | } 37 | 38 | impl_json_for_float!(f32, f64); 39 | 40 | impl JSONValue for () { 41 | fn write_json(&self, w: &mut W) -> io::Result<()> { 42 | w.write_all(b"null") 43 | } 44 | } 45 | 46 | /// A JSON value representing the value `true` 47 | /// This is a Zero-Sized type. It takes zero bytes in memory at runtime. 48 | pub struct JSONtrue; 49 | impl JSONValue for JSONtrue { 50 | #[inline] 51 | fn write_json(&self, w: &mut W) -> io::Result<()> { 52 | w.write_all(b"true") 53 | } 54 | } 55 | 56 | /// A JSON value representing the value `false` 57 | /// This is a Zero-Sized type. It takes zero bytes in memory at runtime. 58 | pub struct JSONfalse; 59 | impl JSONValue for JSONfalse { 60 | #[inline] 61 | fn write_json(&self, w: &mut W) -> io::Result<()> { 62 | w.write_all(b"false") 63 | } 64 | } 65 | 66 | impl JSONValue for bool { 67 | fn write_json(&self, w: &mut W) -> io::Result<()> { 68 | if *self { 69 | JSONtrue.write_json(w) 70 | } else { 71 | JSONfalse.write_json(w) 72 | } 73 | } 74 | } 75 | 76 | impl JSONValue for Option { 77 | fn write_json(&self, w: &mut W) -> io::Result<()> { 78 | if let Some(val) = self { 79 | val.write_json(w) 80 | } else { 81 | ().write_json(w) 82 | } 83 | } 84 | } 85 | 86 | #[cfg(test)] 87 | mod tests { 88 | // Note this useful idiom: importing names from outer (for mod tests) scope. 89 | use super::*; 90 | 91 | #[test] 92 | fn test_int() { 93 | assert_eq!("-1234567890", (-1234567890 as i32).to_json_string()); 94 | assert_eq!( 95 | "1234567890123456789", 96 | 1234567890123456789u64.to_json_string() 97 | ); 98 | } 99 | 100 | #[test] 101 | fn test_float() { 102 | use std::f64; 103 | assert_eq!("-1234567890", (-1234567890 as f64).to_json_string()); 104 | assert_eq!("null", (f64::NAN).to_json_string()); 105 | assert_eq!("null", (f64::NEG_INFINITY).to_json_string()); 106 | } 107 | 108 | #[test] 109 | fn test_bool() { 110 | assert_eq!("true", true.to_json_string()); 111 | assert_eq!("false", false.to_json_string()); 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /src/bin.rs: -------------------------------------------------------------------------------- 1 | use json_in_type::*; 2 | 3 | fn main() { 4 | let is_awesome = true; 5 | let heterogeneous_list = json_list![42u8, true]; 6 | let dynamic_key = "hello"; 7 | 8 | let json_val = json_object! { 9 | is_awesome, heterogeneous_list, 10 | [dynamic_key]: "world" 11 | }; 12 | println!("json: {}", json_val.to_json_string()); 13 | println!("size: {}", ::std::mem::size_of_val(&json_val)); 14 | } 15 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | //! **json_in_type** is a library for *very fast* [JSON](http://json.org/) serialization. 2 | //! It does only serialization, not parsing. 3 | //! 4 | //! # Principles 5 | //! This library is fast at runtime because it tries to do more at compile time, 6 | //! using rust's powerful type system, and macros. 7 | //! 8 | //! The base idea when starting to write this library was that a json object 9 | //! with a number of properties that is known at compile-time can be efficiently 10 | //! stored in an ad-hoc type, that will have a compact representation in memory, 11 | //! and will be serialized much faster than a HashMap. 12 | //! 13 | //! # How to use 14 | //! This crate has two main macros, [`json_object!`](macro.json_object.html), 15 | //! and [`json_list!`](macro.json_list.html). 16 | //! Use them to create [json values](trait.JSONValue.html), that you can then serialize. 17 | //! 18 | //! 19 | 20 | pub mod base_types; 21 | pub mod list; 22 | pub mod object; 23 | pub mod string; 24 | pub mod utils; 25 | 26 | use std::fmt; 27 | use std::fmt::Display; 28 | use std::fmt::Formatter; 29 | use std::io; 30 | 31 | /// A trait implemented by types that can be serialized to JSON 32 | /// 33 | /// This trait can be derived for custom structs using 34 | /// [json_in_type_derive](https://docs.rs/json_in_type_derive/) 35 | pub trait JSONValue { 36 | /// Write the object as json to the given writer 37 | /// 38 | /// # Examples 39 | /// 40 | /// Write a JSON object to a file 41 | /// 42 | /// ``` 43 | /// # let mut my_file = ::std::io::sink(); 44 | /// use json_in_type::JSONValue; 45 | /// 46 | /// vec![1, 2, 3].write_json(&mut my_file); 47 | /// ``` 48 | fn write_json(&self, w: &mut W) -> io::Result<()>; 49 | 50 | /// Returns the object formatted as a json string 51 | /// 52 | /// # Panics 53 | /// If you implement JSONValue on your own types and emit invalid UTF-8 54 | /// in write_json. If you use the implementations of JSONValue provided 55 | /// in this library, this function will never panic. 56 | fn to_json_string(&self) -> String { 57 | // This is safe because the bytes we emit are all valid UTF-8 58 | String::from_utf8(self.to_json_buffer()).unwrap() 59 | } 60 | 61 | /// Returns a buffer containing the bytes of a json representation of the object 62 | fn to_json_buffer(&self) -> Vec { 63 | let mut buffer = Vec::with_capacity(512); 64 | self.write_json(&mut buffer).unwrap(); 65 | buffer 66 | } 67 | } 68 | 69 | impl<'a, S: JSONValue + ?Sized> JSONValue for &'a S { 70 | fn write_json(&self, w: &mut W) -> io::Result<()> { 71 | (**self).write_json(w) 72 | } 73 | } 74 | 75 | impl<'a, S: JSONValue + ?Sized> JSONValue for Box { 76 | fn write_json(&self, w: &mut W) -> io::Result<()> { 77 | (**self).write_json(w) 78 | } 79 | } 80 | 81 | /// Encapsulates a [JSONValue](trait.JSONValue.html) and implements useful traits. 82 | /// 83 | /// # Examples 84 | /// 85 | /// ``` 86 | /// use json_in_type::JSON; 87 | /// 88 | /// let x_json = JSON(vec![(), (), ()]); 89 | /// assert_eq!("[null,null,null]", x_json.to_string()); // JSON format 90 | /// 91 | /// println!("{}", x_json); // just works. Displays [null,null,null] 92 | /// 93 | /// let my_buffer : Vec = x_json.into(); 94 | /// assert_eq!(b"[null,null,null]".to_vec(), my_buffer); 95 | /// ``` 96 | pub struct JSON(pub T); 97 | 98 | impl Display for JSON { 99 | fn fmt(&self, f: &mut Formatter) -> fmt::Result { 100 | let mut writer = utils::FormatterWriter(f); 101 | self.0 102 | .write_json(&mut writer) 103 | .map(|_size| ()) 104 | .map_err(|_err| fmt::Error {}) 105 | } 106 | } 107 | 108 | impl From> for Vec { 109 | fn from(json: JSON) -> Self { 110 | json.0.to_json_buffer() 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /src/list.rs: -------------------------------------------------------------------------------- 1 | //! Serialization to JSON lists like `[0,true,"x"]` 2 | 3 | use super::JSONValue; 4 | use std::cell::RefCell; 5 | use std::io; 6 | 7 | #[inline(always)] 8 | fn write_json_iterator(iter: &mut I, w: &mut W) -> io::Result<()> 9 | where 10 | I: Iterator, 11 | J: JSONValue, 12 | W: io::Write, 13 | { 14 | w.write_all(b"[")?; 15 | if let Some(first) = iter.next() { 16 | first.write_json(w)?; 17 | for x in iter { 18 | w.write_all(b",")?; 19 | x.write_json(w)?; 20 | } 21 | } 22 | w.write_all(b"]") 23 | } 24 | 25 | impl JSONValue for Vec { 26 | #[inline(always)] 27 | fn write_json(&self, w: &mut W) -> io::Result<()> { 28 | write_json_iterator(&mut self.iter(), w) 29 | } 30 | } 31 | 32 | /// Allows to serialize an iterator to JSON in a streaming fashion. 33 | /// The iterator needs to be wrapped in a RefCell because it will be consumed 34 | /// as JSON is written. 35 | /// 36 | /// # Examples 37 | /// ### Serialize an iterator JSON 38 | /// ``` 39 | /// use std::cell::RefCell; 40 | /// use std::iter::repeat; 41 | /// use json_in_type::JSONValue; 42 | /// 43 | /// let my_iter = repeat(42).take(3); 44 | /// let my_iter_cell = RefCell::new(my_iter); 45 | /// 46 | /// // The iterator will be consumed as json is produced 47 | /// assert_eq!("[42,42,42]", my_iter_cell.to_json_string()); 48 | /// 49 | /// // Here, the iterator has already been consumed, so there is nothing left to serialize 50 | /// assert_eq!("[]", my_iter_cell.to_json_string()); 51 | /// ``` 52 | /// 53 | /// ### Write a large JSON to a file 54 | /// 55 | /// In this example, we take a potentially large input file, 56 | /// and serialize it to a JSON file containing an array with all the lines 57 | /// from the input file. 58 | /// 59 | /// The output should look like this: 60 | /// 61 | /// ```json 62 | /// [ 63 | /// {"line": 1, "contents": "a line of text"}, 64 | /// {"line": 2, "contents": "another line of text"} 65 | /// ] 66 | /// ``` 67 | /// 68 | /// ``` 69 | /// use std::cell::RefCell; 70 | /// use std::io::BufRead; 71 | /// use std::io::BufReader; 72 | /// use json_in_type::*; 73 | /// 74 | /// # let mut output_file : Vec = vec![]; 75 | /// # let mut input_file = ::std::io::Cursor::new(&b"a line of text\nanother line of text"[..]); 76 | /// 77 | /// let json_lines = BufReader::new(input_file) 78 | /// .lines() 79 | /// .map(|l| l.unwrap()) 80 | /// .enumerate() 81 | /// .map(|(i, contents)| json_object!{line:i+1, contents:contents}); 82 | /// 83 | /// RefCell::new(json_lines).write_json(&mut output_file); 84 | /// 85 | /// # let expected_bytes = r#"[{"line":1,"contents":"a line of text"},{"line":2,"contents":"another line of text"}]"#; 86 | /// # assert_eq!(expected_bytes, ::std::str::from_utf8(&output_file).unwrap()); 87 | /// ``` 88 | impl> JSONValue for RefCell { 89 | #[inline] 90 | fn write_json(&self, w: &mut W) -> io::Result<()> { 91 | write_json_iterator(&mut *self.borrow_mut(), w) 92 | } 93 | } 94 | 95 | /// A struct used to wrap another type and make it serializable as a json list. 96 | /// 97 | /// The other type has to be able to yield values by implementing IntoIterator. 98 | /// 99 | /// # Examples 100 | /// ### Serialize a slice as JSON 101 | /// ``` 102 | /// use json_in_type::list::ToJSONList; 103 | /// use json_in_type::JSONValue; 104 | /// 105 | /// let slice : [u8; 3] = [42,42,42]; 106 | /// 107 | /// assert_eq!("[42,42,42]", ToJSONList(slice).to_json_string()); 108 | /// ``` 109 | pub struct ToJSONList(pub U) 110 | where 111 | for<'a> &'a U: IntoIterator; 112 | 113 | impl JSONValue for ToJSONList 114 | where 115 | for<'a> &'a U: IntoIterator, 116 | { 117 | fn write_json(&self, w: &mut W) -> io::Result<()> { 118 | write_json_iterator(&mut (&self.0).into_iter(), w) 119 | } 120 | } 121 | 122 | pub trait JSONList: JSONValue { 123 | fn write_json_ending(&self, w: &mut W) -> io::Result<()>; 124 | } 125 | 126 | pub struct JSONListElem { 127 | elem: T, 128 | next: U, 129 | } 130 | 131 | impl JSONListElem { 132 | pub fn new(elem: T, next: U) -> JSONListElem { 133 | JSONListElem { elem, next } 134 | } 135 | } 136 | 137 | impl JSONList for JSONListElem { 138 | #[inline(always)] 139 | fn write_json_ending(&self, w: &mut W) -> io::Result<()> { 140 | w.write_all(b",")?; 141 | self.elem.write_json(w)?; 142 | self.next.write_json_ending(w) 143 | } 144 | } 145 | 146 | impl JSONValue for JSONListElem { 147 | fn write_json(&self, w: &mut W) -> io::Result<()> { 148 | w.write_all(b"[")?; 149 | self.elem.write_json(w)?; 150 | self.next.write_json_ending(w) 151 | } 152 | } 153 | 154 | pub struct JSONListEnd; 155 | 156 | impl JSONList for JSONListEnd { 157 | fn write_json_ending(&self, w: &mut W) -> io::Result<()> { 158 | w.write_all(b"]") 159 | } 160 | } 161 | 162 | impl JSONValue for JSONListEnd { 163 | fn write_json(&self, w: &mut W) -> io::Result<()> { 164 | w.write_all(b"[]") 165 | } 166 | } 167 | 168 | /// Creates a static json list that can be serialized very fast. 169 | /// Returns a struct implementing [`JSONValue`](trait.JSONValue.html). 170 | /// 171 | /// # Examples 172 | /// Create a list containing objects of different types. 173 | /// ``` 174 | /// use json_in_type::*; 175 | /// 176 | /// let my_list = json_list![1,true,"hello"]; 177 | /// 178 | /// assert_eq!("[1,true,\"hello\"]", my_list.to_json_string()); 179 | /// ``` 180 | /// 181 | /// Create a list containing special zero-size json values 182 | /// ``` 183 | /// use json_in_type::*; 184 | /// 185 | /// let my_list = json_list![ true, false, null, null ]; 186 | /// 187 | /// assert_eq!("[true,false,null,null]", my_list.to_json_string()); 188 | /// assert_eq!(0, ::std::mem::size_of_val(&my_list)) 189 | /// ``` 190 | #[macro_export] 191 | macro_rules! json_list { 192 | (null $($tt:tt)* ) => { json_list![() $($tt)*] }; 193 | (true $($tt:tt)* ) => { json_list![$crate::base_types::JSONtrue $($tt)*] }; 194 | (false $($tt:tt)* ) => { json_list![$crate::base_types::JSONfalse $($tt)*] }; 195 | 196 | ($elem:expr , $($rest:tt)* ) => { 197 | $crate::list::JSONListElem::new( 198 | $elem, 199 | json_list!($($rest)*) 200 | ) 201 | }; 202 | ($elem:expr) => { json_list![ $elem, ] }; 203 | () => { $crate::list::JSONListEnd{} }; 204 | } 205 | 206 | #[cfg(test)] 207 | mod tests { 208 | // Note this useful idiom: importing names from outer (for mod tests) scope. 209 | use super::*; 210 | 211 | #[test] 212 | fn empty() { 213 | assert_eq!("[]", json_list![].to_json_string()); 214 | } 215 | 216 | #[test] 217 | fn should_be_zero_sized() { 218 | assert_eq!(0, ::std::mem::size_of_val(&json_list![])); 219 | assert_eq!(0, ::std::mem::size_of_val(&json_list![null, null, null])); 220 | } 221 | 222 | #[test] 223 | fn singe_element() { 224 | assert_eq!("[1]", json_list![1].to_json_string()); 225 | assert_eq!("[1]", json_list![1,].to_json_string()); 226 | } 227 | 228 | #[test] 229 | fn multiple_elements() { 230 | assert_eq!("[1,2]", json_list![1, 2].to_json_string()); 231 | assert_eq!("[1,2]", json_list![1, 2,].to_json_string()); 232 | assert_eq!("[1,2,3,4,5]", json_list![1, 2, 3, 4, 5].to_json_string()); 233 | } 234 | } 235 | -------------------------------------------------------------------------------- /src/object.rs: -------------------------------------------------------------------------------- 1 | //! Serialization to JSON objects like `{"x":1,"y":null}` 2 | 3 | use super::string::JSONString; 4 | use super::JSONValue; 5 | use std::collections::HashMap; 6 | use std::hash::BuildHasher; 7 | use std::hash::Hash; 8 | use std::io; 9 | 10 | /// Write a single key-value pair 11 | fn write_object_entry(w: &mut W, key: &K, value: &V) -> io::Result<()> 12 | where 13 | W: io::Write, 14 | K: JSONString, 15 | V: JSONValue, 16 | { 17 | key.write_json(w)?; 18 | w.write_all(b":")?; 19 | value.write_json(w) 20 | } 21 | 22 | /// Write a list of key-value pairs to a writer as a json object 23 | fn write_object<'a, W, K, V, I>(w: &mut W, iter: &mut I) -> io::Result<()> 24 | where 25 | W: io::Write, 26 | K: JSONString, 27 | V: JSONValue, 28 | V: 'a, 29 | K: 'a, 30 | I: Iterator, 31 | { 32 | w.write_all(b"{")?; 33 | if let Some((key, value)) = iter.next() { 34 | write_object_entry(w, key, value)?; 35 | for (key, value) in iter { 36 | w.write_all(b",")?; 37 | write_object_entry(w, key, value)?; 38 | } 39 | } 40 | w.write_all(b"}") 41 | } 42 | 43 | /// A struct used to wrap another type and make it serializable as a json object. 44 | /// The other type has to be able to yield (key, value) pairs by implementing IntoIterator. 45 | /// 46 | /// # Examples 47 | /// 48 | /// Serialize a vec as a json object 49 | /// 50 | /// ``` 51 | /// use json_in_type::object::ToJSONObject; 52 | /// use json_in_type::JSONValue; 53 | /// 54 | /// let my_obj = ToJSONObject(vec![("x", 1), ("y", 2)]); 55 | /// 56 | /// assert_eq!("{\"x\":1,\"y\":2}", my_obj.to_json_string()); 57 | /// ``` 58 | pub struct ToJSONObject(pub I) 59 | where 60 | K: JSONString, 61 | V: JSONValue, 62 | for<'a> &'a I: IntoIterator; 63 | 64 | impl JSONValue for ToJSONObject 65 | where 66 | K: JSONString, 67 | V: JSONValue, 68 | for<'a> &'a I: IntoIterator, 69 | { 70 | fn write_json(&self, w: &mut W) -> io::Result<()> { 71 | let mut iter = (&self.0).into_iter().map(|(k, v)| (k, v)); // Convert a borrowed tuple to a tuple of borrowed values 72 | write_object(w, &mut iter) 73 | } 74 | } 75 | 76 | /// Serialize a HashMap to a JSON object. The property order is not guaranteed. 77 | impl JSONValue for HashMap { 78 | fn write_json(&self, w: &mut W) -> io::Result<()> { 79 | write_object(w, &mut self.iter()) 80 | } 81 | } 82 | 83 | pub trait JSONObject: JSONValue { 84 | fn write_json_ending(&self, f: &mut W, first: bool) -> io::Result<()>; 85 | #[inline] 86 | fn write_json_full(&self, w: &mut W) -> io::Result<()> { 87 | self.write_json_ending(w, true) 88 | } 89 | } 90 | 91 | /// A JSON object stored as a static linked list. 92 | /// This is a generic structure that specializes at compile-time 93 | /// to a structure whose type stores the exact shape of the object. 94 | pub struct JSONObjectEntry { 95 | pub key: K, 96 | pub value: V, 97 | pub next: U, 98 | } 99 | 100 | impl JSONObject for JSONObjectEntry { 101 | #[inline(always)] 102 | fn write_json_ending(&self, w: &mut W, first: bool) -> io::Result<()> { 103 | w.write_all(if first { b"{" } else { b"," })?; 104 | self.key.write_json(w)?; 105 | w.write_all(b":")?; 106 | self.value.write_json(w)?; 107 | self.next.write_json_ending(w, false) 108 | } 109 | } 110 | 111 | impl JSONValue for JSONObjectEntry { 112 | #[inline(always)] 113 | fn write_json(&self, w: &mut W) -> io::Result<()> { 114 | self.write_json_full(w) 115 | } 116 | } 117 | 118 | /// An empty JSON object. This is a Zero Sized Type. 119 | /// It just serves to mark the end of an object in its type, 120 | /// but takes no space in memory at runtime. 121 | pub struct JSONObjectEnd; 122 | 123 | impl JSONObject for JSONObjectEnd { 124 | #[inline(always)] 125 | fn write_json_ending(&self, w: &mut W, first: bool) -> io::Result<()> { 126 | w.write_all(if first { b"{}" } else { b"}" }) 127 | } 128 | } 129 | 130 | impl JSONValue for JSONObjectEnd { 131 | fn write_json(&self, w: &mut W) -> io::Result<()> { 132 | self.write_json_full(w) 133 | } 134 | } 135 | 136 | #[macro_export] 137 | #[doc(hidden)] 138 | macro_rules! inlined_json_object { 139 | (key : $key:ident, value : $value:expr, next : $next:expr) => {{ 140 | use $crate::object::JSONObject; 141 | use $crate::JSONValue; 142 | 143 | struct InlinedJSONObjectEntry { 144 | value: V, 145 | next: U, 146 | } 147 | 148 | impl JSONObject for InlinedJSONObjectEntry { 149 | #[inline(always)] 150 | fn write_json_ending( 151 | &self, 152 | w: &mut W, 153 | first: bool, 154 | ) -> ::std::io::Result<()> { 155 | w.write_all( 156 | if first { 157 | concat!("{\"", stringify!($key), "\":") 158 | } else { 159 | concat!(",\"", stringify!($key), "\":") 160 | } 161 | .as_bytes(), 162 | )?; 163 | self.value.write_json(w)?; 164 | self.next.write_json_ending(w, false) 165 | } 166 | } 167 | 168 | impl JSONValue for InlinedJSONObjectEntry { 169 | fn write_json(&self, w: &mut W) -> ::std::io::Result<()> { 170 | self.write_json_full(w) 171 | } 172 | } 173 | 174 | InlinedJSONObjectEntry { 175 | value: $value, 176 | next: $next, 177 | } 178 | }}; 179 | } 180 | 181 | /// Creates a static json object that can be serialized very fast. 182 | /// Returns a struct implementing [`JSONValue`](trait.JSONValue.html). 183 | /// 184 | /// The macro takes a comma-separated list of key-value pairs. 185 | /// Keys can be written literally, or surrounded by brackets (`[key]`) 186 | /// to reference external variables. A value can be omitted, in which 187 | /// case the the key name must be the name of a variable currently in scope, 188 | /// from which the value will be taken. 189 | /// 190 | /// Values must be expressions of a type implementing JSONValue. 191 | /// 192 | /// # Examples 193 | /// 194 | /// ### Create a simple json object. 195 | /// ``` 196 | /// use json_in_type::*; 197 | /// 198 | /// let my_obj = json_object!{ 199 | /// hello: "world" 200 | /// }; 201 | /// 202 | /// assert_eq!(r#"{"hello":"world"}"#, my_obj.to_json_string()); 203 | /// ``` 204 | /// 205 | /// ### Shorthand property names 206 | /// It is common to create a json object from a set of variables, 207 | /// using the variable names as keys and the contents of the variables as values. 208 | /// ``` 209 | /// use json_in_type::*; 210 | /// 211 | /// let x = "hello"; 212 | /// let y = 42; 213 | /// let z = true; 214 | /// let my_obj = json_object!{ x, y, z }; 215 | /// 216 | /// assert_eq!(r#"{"x":"hello","y":42,"z":true}"#, my_obj.to_json_string()); 217 | /// ``` 218 | /// 219 | /// ### Reference external variables 220 | /// ``` 221 | /// use json_in_type::*; 222 | /// 223 | /// let x = "hello"; 224 | /// let my_obj = json_object!{ 225 | /// [x]: "world", // The trailing comma is ok 226 | /// }; 227 | /// 228 | /// assert_eq!(r#"{"hello":"world"}"#, my_obj.to_json_string()); 229 | /// ``` 230 | /// 231 | /// ### Compute keys dynamically 232 | /// ``` 233 | /// use json_in_type::*; 234 | /// 235 | /// let x = "hello"; 236 | /// let my_obj = json_object!{ 237 | /// [x]: "world", 238 | /// [[x, "_suffix"].concat()]: 42 239 | /// }; 240 | /// 241 | /// assert_eq!(r#"{"hello":"world","hello_suffix":42}"#, my_obj.to_json_string()); 242 | /// ``` 243 | #[macro_export] 244 | macro_rules! json_object { 245 | () => { $crate::object::JSONObjectEnd{} }; 246 | // A null value 247 | ($key:ident : null, $($rest:tt)*) => { json_object!($key : (), $($rest)*) }; 248 | ($key:ident : true, $($rest:tt)*) => { json_object!($key : $crate::base_types::JSONtrue, $($rest)*) }; 249 | ($key:ident : false, $($rest:tt)*) => { json_object!($key : $crate::base_types::JSONfalse, $($rest)*) }; 250 | // Literal key 251 | ($key:ident : $value:expr, $($rest:tt)*) => { 252 | inlined_json_object!{ 253 | key: $key, 254 | value: $value, 255 | next: json_object!($($rest)*) 256 | } 257 | }; 258 | // The key is an expression in square brackets 259 | ([$key:expr] : $value:expr, $($rest:tt)*) => { 260 | $crate::object::JSONObjectEntry { 261 | key: $key, 262 | value: $value, 263 | next: json_object!($($rest)*) 264 | } 265 | }; 266 | // A key that references a variable of the same name 267 | ($key:ident, $($rest:tt)*) => { json_object!($key : $key, $($rest)*) }; 268 | // Simply adding a trailing colon 269 | ($key:ident : $value:ident) => { json_object!($key:$value,) }; 270 | ($key:ident : $value:expr) => { json_object!($key:$value,) }; 271 | ([$key:expr] : $value:expr) => { json_object!([$key]:$value,) }; 272 | ($key:ident) => { json_object!($key,) }; 273 | } 274 | 275 | #[cfg(test)] 276 | mod tests { 277 | // Note this useful idiom: importing names from outer (for mod tests) scope. 278 | use super::*; 279 | 280 | #[test] 281 | fn test_empty() { 282 | assert_eq!("{}", JSONObjectEnd.to_json_string()); 283 | assert_eq!("{}", json_object!().to_json_string()); 284 | } 285 | 286 | #[test] 287 | fn test_single_pair() { 288 | assert_eq!( 289 | r#"{"x":{}}"#, 290 | json_object!(x: json_object!()).to_json_string() 291 | ); 292 | // With a trailing comma: 293 | assert_eq!( 294 | r#"{"x":{}}"#, 295 | json_object!(x: json_object!(),).to_json_string() 296 | ); 297 | } 298 | 299 | #[test] 300 | fn test_two_pairs() { 301 | assert_eq!( 302 | r#"{"x":{},"y":{}}"#, 303 | json_object! { 304 | x : json_object!(), 305 | y : json_object!() 306 | } 307 | .to_json_string() 308 | ); 309 | } 310 | 311 | #[test] 312 | fn test_nested() { 313 | assert_eq!( 314 | r#"{"x":{"y":{}}}"#, 315 | json_object! { 316 | x : json_object! { 317 | y : json_object!() 318 | } 319 | } 320 | .to_json_string() 321 | ); 322 | } 323 | 324 | #[test] 325 | fn test_dynamic_keys() { 326 | let x = "x"; 327 | let y = String::from("y"); 328 | assert_eq!( 329 | r#"{"x":{"y":{}}}"#, 330 | json_object! { 331 | [x] : json_object! { 332 | [y] : json_object!() 333 | } 334 | } 335 | .to_json_string() 336 | ); 337 | } 338 | 339 | #[test] 340 | fn test_hashmap() { 341 | let mut map = HashMap::new(); 342 | map.insert("x", 1); 343 | map.insert("y", 2); 344 | // The order in which the keys are serialized is not guaranteed 345 | let expected = vec![r#"{"x":1,"y":2}"#, r#"{"y":2,"x":1}"#]; 346 | assert!(expected.contains(&&map.to_json_string()[..])); 347 | } 348 | 349 | #[test] 350 | fn test_zero_size() { 351 | use std::mem::size_of_val; 352 | // We should pay only for what we use. 353 | // If we have no reference to external values, the resulting object should take 0 bytes 354 | // in memory 355 | let json_obj = json_object! { 356 | null: null, 357 | nested: json_object! { 358 | also_null: (), 359 | bool : true, 360 | deeper: json_object! { 361 | other_bool: false 362 | } 363 | } 364 | }; 365 | assert_eq!(0, size_of_val(&json_obj)); 366 | } 367 | } 368 | -------------------------------------------------------------------------------- /src/string.rs: -------------------------------------------------------------------------------- 1 | //! Serialization to JSON strings like `"hello world \n"` 2 | use super::JSONValue; 3 | use std::io; 4 | 5 | static ESCAPE_CHARS: [&[u8]; 0x20] = [ 6 | b"\\u0000", b"\\u0001", b"\\u0002", b"\\u0003", b"\\u0004", b"\\u0005", b"\\u0006", b"\\u0007", 7 | b"\\b", b"\\t", b"\\n", b"\\u000b", b"\\f", b"\\r", b"\\u000e", b"\\u000f", b"\\u0010", 8 | b"\\u0011", b"\\u0012", b"\\u0013", b"\\u0014", b"\\u0015", b"\\u0016", b"\\u0017", b"\\u0018", 9 | b"\\u0019", b"\\u001a", b"\\u001b", b"\\u001c", b"\\u001d", b"\\u001e", b"\\u001f", 10 | ]; 11 | 12 | // This bitset represents which bytes can be copied as-is to a JSON string (0) 13 | // And which one need to be escaped (1) 14 | // The characters that need escaping are 0x00 to 0x1F, 0x22 ("), 0x5C (\), 0x7F (DEL) 15 | // Non-ASCII unicode characters can be safely included in a JSON string 16 | static NEEDS_ESCAPING_BITSET: [u64; 4] = [ 17 | //fedcba9876543210_fedcba9876543210_fedcba9876543210_fedcba9876543210 18 | 0b0000000000000000_0000000000000100_1111111111111111_1111111111111111, // 3_2_1_0 19 | 0b1000000000000000_0000000000000000_0001000000000000_0000000000000000, // 7_6_5_4 20 | 0b0000000000000000_0000000000000000_0000000000000000_0000000000000000, // B_A_9_8 21 | 0b0000000000000000_0000000000000000_0000000000000000_0000000000000000, // F_E_D_C 22 | ]; 23 | 24 | #[inline(always)] 25 | fn json_escaped_char(c: u8) -> Option<&'static [u8]> { 26 | let bitset_value = NEEDS_ESCAPING_BITSET[(c / 64) as usize] & (1 << (c % 64)); 27 | if bitset_value == 0 { 28 | None 29 | } else { 30 | Some(match c { 31 | x if x < 0x20 => ESCAPE_CHARS[c as usize], 32 | b'\\' => &b"\\\\"[..], 33 | b'\"' => &b"\\\""[..], 34 | 0x7F => &b"\\u007f"[..], 35 | _ => unreachable!(), 36 | }) 37 | } 38 | } 39 | 40 | /// Implemented by types that can be serialized to a json string. 41 | /// 42 | /// Implement this trait for your type if you want to be able to use it as a 43 | /// key in a json object. 44 | pub trait JSONString: JSONValue {} 45 | 46 | impl JSONValue for char { 47 | fn write_json(&self, w: &mut W) -> io::Result<()> { 48 | w.write_all(b"\"")?; 49 | if let Some(escaped) = json_escaped_char(*self as u8) { 50 | w.write_all(escaped)?; 51 | } else { 52 | write!(w, "{}", self)?; 53 | } 54 | w.write_all(b"\"") 55 | } 56 | } 57 | 58 | impl JSONString for char {} 59 | 60 | impl<'a> JSONValue for &'a str { 61 | fn write_json(&self, w: &mut W) -> io::Result<()> { 62 | w.write_all(b"\"")?; 63 | write_json_common(self, w)?; 64 | w.write_all(b"\"") 65 | } 66 | } 67 | 68 | fn write_json_common(s: &str, w: &mut W) -> io::Result<()> { 69 | #[cfg(any(target_arch = "x86", target_arch = "x86_64"))] 70 | { 71 | if is_x86_feature_detected!("sse4.2") { 72 | return unsafe { write_json_simd(s, w) }; 73 | } 74 | } 75 | write_json_nosimd_prevalidated(s.as_bytes(), 0, 0, w) 76 | } 77 | 78 | #[cfg(any(target_arch = "x86", target_arch = "x86_64"))] 79 | #[target_feature(enable = "sse4.2")] 80 | #[allow(clippy::cast_ptr_alignment)] 81 | unsafe fn write_json_simd(s: &str, w: &mut W) -> io::Result<()> { 82 | use std::arch::x86_64::*; 83 | use std::mem::size_of; 84 | 85 | const VECTOR_SIZE: usize = size_of::<__m128i>(); 86 | 87 | let bytes = s.as_bytes(); 88 | let control_chars = _mm_setr_epi8(0, 0x1f, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0); 89 | let slash = b'\\' as i8; 90 | let quote = b'"' as i8; 91 | let del = 0x7F as i8; 92 | let special_chars = _mm_setr_epi8(slash, quote, del, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0); 93 | 94 | let mut char_index_to_write = 0; 95 | let mut current_index = 0; 96 | for chunk_bytes in bytes.chunks_exact(VECTOR_SIZE) { 97 | let chunk = _mm_loadu_si128(chunk_bytes.as_ptr() as *const _); 98 | let idx_control_chars = _mm_cmpestri( 99 | control_chars, 100 | 2, 101 | chunk, 102 | VECTOR_SIZE as i32, 103 | _SIDD_CMP_RANGES, 104 | ); 105 | let idx_special_chars = _mm_cmpestri( 106 | special_chars, 107 | 3, 108 | chunk, 109 | VECTOR_SIZE as i32, 110 | _SIDD_CMP_EQUAL_ANY, 111 | ); 112 | let needs_write_at = idx_special_chars.min(idx_control_chars) as usize; 113 | if needs_write_at != VECTOR_SIZE { 114 | let chunk_end = current_index + VECTOR_SIZE; 115 | write_json_nosimd_prevalidated( 116 | &bytes[..chunk_end], 117 | char_index_to_write, 118 | current_index + needs_write_at, 119 | w, 120 | )?; 121 | char_index_to_write = chunk_end; 122 | } 123 | current_index += VECTOR_SIZE; 124 | } 125 | write_json_nosimd_prevalidated(&bytes, char_index_to_write, current_index, w) 126 | } 127 | 128 | fn write_json_nosimd_prevalidated( 129 | bytes: &[u8], 130 | char_index_to_write: usize, 131 | current_index: usize, 132 | w: &mut W, 133 | ) -> io::Result<()> { 134 | let mut char_index_to_write = char_index_to_write; 135 | let mut current_index = current_index; 136 | for &c in bytes[current_index..].iter() { 137 | if let Some(escaped) = json_escaped_char(c) { 138 | w.write_all(&bytes[char_index_to_write..current_index])?; 139 | w.write_all(escaped)?; 140 | char_index_to_write = current_index + 1; 141 | } 142 | current_index += 1; 143 | } 144 | w.write_all(&bytes[char_index_to_write..]) 145 | } 146 | 147 | impl<'a> JSONString for &'a str {} 148 | 149 | impl JSONValue for String { 150 | fn write_json(&self, w: &mut W) -> io::Result<()> { 151 | (self as &str).write_json(w) 152 | } 153 | } 154 | 155 | impl JSONString for String {} 156 | 157 | #[cfg(test)] 158 | mod tests { 159 | // Note this useful idiom: importing names from outer (for mod tests) scope. 160 | use super::*; 161 | 162 | #[test] 163 | fn test_chars() { 164 | assert_eq!("\"x\"", 'x'.to_json_string()); 165 | assert_eq!("\"\\n\"", '\n'.to_json_string()); 166 | assert_eq!("\"\\\\\"", '\\'.to_json_string()); 167 | assert_eq!("\"\\u0000\"", '\0'.to_json_string()); 168 | assert_eq!("\"❤\"", '❤'.to_json_string()); 169 | } 170 | 171 | #[test] 172 | fn test_simple_string() { 173 | assert_eq!(r#""""#, "".to_json_string()); 174 | assert_eq!(r#""Hello, world!""#, "Hello, world!".to_json_string()); 175 | assert_eq!(r#""\t\t\n""#, "\t\t\n".to_json_string()); 176 | } 177 | 178 | #[test] 179 | fn test_string_with_control_chars() { 180 | assert_eq!( 181 | r#""0123456789\u001fabcde""#, 182 | "0123456789\x1Fabcde".to_json_string() 183 | ); 184 | assert_eq!( 185 | r#""0123456789\u001eabcde""#, 186 | "0123456789\x1Eabcde".to_json_string() 187 | ); 188 | assert_eq!( 189 | r#""0123456789\u007fabcde""#, 190 | "0123456789\x7Fabcde".to_json_string() 191 | ); 192 | } 193 | 194 | #[test] 195 | fn test_complex_string() { 196 | assert_eq!( 197 | r#""I ❤️ \"pépé\" \n backslash: \\!!!\n""#, 198 | "I ❤️ \"pépé\" \n backslash: \\!!!\n".to_json_string() 199 | ); 200 | } 201 | 202 | #[test] 203 | fn short_strings_of_increasing_length() { 204 | for i in 0..128 { 205 | let xs = String::from("x").repeat(i); 206 | assert_eq!(format!("\"{}\"", xs), xs.to_json_string()); 207 | 208 | let newlines = String::from("\n").repeat(i); 209 | assert_eq!( 210 | format!("\"{}\"", newlines.replace('\n', "\\n")), 211 | newlines.to_json_string() 212 | ); 213 | } 214 | } 215 | 216 | #[test] 217 | fn long_ascii_string() { 218 | let s = String::from("x").repeat(7919); 219 | assert_eq!(format!("\"{}\"", s), s.to_json_string()); 220 | } 221 | 222 | #[test] 223 | fn long_nonascii_string() { 224 | let s = String::from("\u{2a6a5}").repeat(7919); 225 | assert_eq!(format!("\"{}\"", s), s.to_json_string()); 226 | } 227 | 228 | #[test] 229 | fn long_mixed_string() { 230 | let source = String::from("0123456789abcdef0123456789abcdef\0").repeat(7919); 231 | let target = source.replace('\0', "\\u0000"); 232 | assert_eq!(format!("\"{}\"", target), source.to_json_string()); 233 | } 234 | 235 | #[test] 236 | fn many_backslashes() { 237 | let n = 7919; 238 | let s = String::from("\\").repeat(n); 239 | assert_eq!(format!("\"{}\"", s.repeat(2)), s.to_json_string()); 240 | } 241 | } 242 | -------------------------------------------------------------------------------- /src/utils.rs: -------------------------------------------------------------------------------- 1 | //! Useful tools when working with the types in this crate 2 | 3 | use std::fmt; 4 | use std::io; 5 | use std::str; 6 | 7 | /// Converts a Formatter to a Writer 8 | pub struct FormatterWriter<'a, 'b: 'a>(pub &'a mut fmt::Formatter<'b>); 9 | 10 | impl<'a, 'b: 'a> io::Write for FormatterWriter<'a, 'b> { 11 | fn write(&mut self, buf: &[u8]) -> io::Result { 12 | match str::from_utf8(buf) { 13 | Ok(buf_str) => self 14 | .0 15 | .write_str(buf_str) 16 | .map_err(|err| io::Error::new(io::ErrorKind::Other, err)) 17 | .map(|()| buf.len()), 18 | Err(err) => Err(io::Error::new(io::ErrorKind::InvalidData, err)), 19 | } 20 | } 21 | 22 | fn flush(&mut self) -> io::Result<()> { 23 | Ok(()) 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /tests/macros.rs: -------------------------------------------------------------------------------- 1 | use json_in_type::*; 2 | 3 | #[test] 4 | fn test_nested_macro() { 5 | assert_eq!("[{}]", json_list![json_object! {}].to_json_string()); 6 | assert_eq!("[{}]", json_list![json_object! {},].to_json_string()); 7 | assert_eq!( 8 | r#"[{"ok":true}]"#, 9 | json_list![json_object! {ok:true}].to_json_string() 10 | ); 11 | assert_eq!( 12 | r#"[{"a":0,"b":0}]"#, 13 | json_list![json_object! {a:0,b:0}].to_json_string() 14 | ); 15 | assert_eq!( 16 | r#"[{"a":0,"b":0},{"a":0,"b":0}]"#, 17 | json_list![json_object! {a:0,b:0}, json_object! {a:0,b:0},].to_json_string() 18 | ); 19 | } 20 | 21 | #[test] 22 | fn test_mem_size() { 23 | let value_count: u8 = 3; 24 | let (val1, val2, val3): (i8, i8, i8) = (42, -42, 66); 25 | 26 | let small_object = json_object! { 27 | value_count, 28 | values: json_list![ 29 | json_object! {ok:true, value: val1}, 30 | json_object! {ok:true, value: val2}, 31 | json_object! {ok:true, value: val3}, 32 | ] 33 | }; 34 | assert_eq!(4, ::std::mem::size_of_val(&small_object)); 35 | } 36 | 37 | #[test] 38 | fn mem_size_example() { 39 | let (result_count, answer) = (1u8, 42u8); 40 | let my_val = json_object! { 41 | result_count, 42 | errors: null, 43 | results: json_list![ 44 | json_object!{answer, ok: true} 45 | ] 46 | }; 47 | // my_val weighs only two bytes, because we stored only 2 u8 in it 48 | assert_eq!(2, ::std::mem::size_of_val(&my_val)); 49 | assert_eq!( 50 | r#"{"result_count":1,"errors":null,"results":[{"answer":42,"ok":true}]}"#, 51 | my_val.to_json_string() 52 | ); 53 | } 54 | 55 | #[test] 56 | fn handwritten_equivalent() { 57 | fn write_obj_bad(value: i32) -> String { 58 | format!("{{\"value\":{}}}", value) 59 | } 60 | fn write_obj_good(value: i32) -> String { 61 | (json_object! {value}).to_json_string() 62 | } 63 | assert_eq!(write_obj_bad(42), write_obj_good(42)); 64 | } 65 | -------------------------------------------------------------------------------- /tests/simple_example.rs: -------------------------------------------------------------------------------- 1 | use json_in_type::*; 2 | 3 | #[test] 4 | fn test_simple_json() { 5 | let obj = json_object! { 6 | void: (), 7 | list: json_list![1,2,3], 8 | hello: "world" 9 | }; 10 | let mut buf: Vec = vec![]; 11 | obj.write_json(&mut buf).unwrap(); 12 | assert_eq!( 13 | br#"{"void":null,"list":[1,2,3],"hello":"world"}"#.to_vec(), 14 | buf 15 | ); 16 | } 17 | --------------------------------------------------------------------------------