Compare commits

...

108 Commits

Author SHA1 Message Date
Daniel Lemire ddc7b8c7dd update 2025-09-17 18:59:06 -06:00
Daniel Lemire d8f90bdd14 modifying simdjson::from to avoid exceptions when needed. (#2452)
* modifying simdjson::from to avoid exceptions when needed.

* moved the function

* moving the strings.

* more moving around

* updating cmake version in ci
2025-09-17 18:58:25 -06:00
Daniel Lemire e38a4923e5 adding keys as templates in builder (#2453)
* adding keys as templates in builder

* more guarding

* cmake update in ci

* guarding.

* guarding
2025-09-17 18:58:05 -06:00
Dirk Stolle e6dfa2e0ed remove trailing whitespace + fix typos (#2451) 2025-09-16 22:41:14 -06:00
Daniel Lemire 1f369ef210 minor patch which allows us to pass mutable strings to simdjson::from… (#2448)
* minor patch which allows us to pass mutable strings to simdjson::from and fix
an issue with ambiguous integrals

* compatibility patch.
2025-09-15 22:22:25 -06:00
Daniel Lemire 22dcdc9f1e Update README.md 2025-09-15 18:52:15 -06:00
Dirk Stolle dda2dafa30 fix some typos (#2446) 2025-09-15 18:23:13 -06:00
Daniel Lemire 72e9d44e10 fixing indent 2025-09-15 17:08:12 -06:00
Daniel Lemire 8aae14931d release candidate 4.0.2 (#2441)
* release candidate 4.0.2

* more fixes

* fixing typos

* adding macro check
2025-09-15 09:17:43 -06:00
Daniel Lemire ef3d1ac25f fixing 2440 2025-09-14 08:09:27 -06:00
Daniel Lemire 9292480a9b removing leftover <experimental/...> (#2438) 2025-09-14 08:00:26 -06:00
Daniel Lemire 611ea97dbd Add C++26 reflection example link to README 2025-09-12 21:22:45 -04:00
Daniel Lemire f249e7e128 patch release 2025-09-12 19:26:58 -04:00
Marian Klymov 44fddaa807 Replace POSITION_INDEPENDENT_CODE with INTERFACE_POSITION_INDEPENDENT_CODE for older CMake (#2437) 2025-09-12 19:26:22 -04:00
Daniel Lemire be16e1ae04 removing space 2025-09-11 20:58:31 -04:00
Daniel Lemire 124a33c160 Update JSON example and add section on special cases
Removed code block formatting for JSON example and added a section header for special cases.
2025-09-11 19:32:38 -04:00
Daniel Lemire a0b870e9c2 Add example for deserializing 'Car' type
Updated the documentation to include an example of deserializing a 'Car' type using simdjson.
2025-09-11 19:31:15 -04:00
Daniel Lemire 68699eb73c release 4.0.0 2025-09-11 19:25:10 -04:00
Daniel Lemire 06453f1637 Remove Fuzzing Status badge from README
Removed Fuzzing Status badge from README.
2025-09-09 18:44:21 -04:00
Daniel Lemire eb828d97ba fixing a few typos 2025-09-09 18:41:00 -04:00
Daniel Lemire 00f84fb448 simplify 2025-09-09 14:21:14 -04:00
Daniel Lemire 5bf0954c6e hop 2025-09-08 19:26:34 -04:00
Daniel Lemire 3056d54fa3 adding instructions 2025-09-08 17:47:14 -04:00
Daniel Lemire 241b9f6ea7 adding bad/good player test 2025-09-08 17:04:52 -04:00
Daniel Lemire 015daad6a9 Merge branch 'master' of github.com:simdjson/simdjson 2025-09-05 19:50:05 -04:00
Daniel Lemire fc3b7766c0 typo fix 2025-09-05 19:49:57 -04:00
Daniel Lemire b5c78288e5 can we run the address sanitizer under VS in CI ? (#2435)
* can we run the address sanitizer under VS in CI ?

* fixed typo
2025-09-05 18:32:15 -04:00
Daniel Lemire bd86bb0cfb adding UTF-8 test 2025-09-05 14:56:02 -04:00
Daniel Lemire 4840a0347c extra documentation. 2025-09-05 14:44:53 -04:00
Daniel Lemire 7f68baec1e minor tweak 2025-09-05 08:01:04 -04:00
Daniel Lemire e2ea5fb8de This PR adds -> and * operators to our error types and it (#2433)
improves slightly the documentation.
2025-09-04 22:35:01 -04:00
Daniel Lemire 7dfd165ecd Merge branch 'master' of github.com:simdjson/simdjson 2025-09-04 09:46:07 -04:00
Daniel Lemire 4dfa0a2407 guarding 2025-09-03 16:45:09 -04:00
Daniel Lemire e252a21f65 removing expand workaround (#2431)
* removing expland workaround

* minor fixes
2025-09-03 13:30:35 -04:00
Daniel Lemire 89f2b634df guarding from benchmark + rvv 2025-09-02 13:06:25 -04:00
Daniel Lemire addca203d5 adding a 'car builder' benchmark (#2428)
Co-authored-by: Daniel Lemire <dlemire@lemire.me>
2025-08-26 09:16:18 -04:00
Felipe Monteiro 1825d7e88c Add "node:" prefix on fs import (#2426) 2025-08-25 10:21:13 -04:00
Daniel Lemire f87d6af3e9 fix issue 2424 (#2425) 2025-08-24 16:04:56 -04:00
Daniel Lemire 8fff57578c let us default on developer mode when building with VScode 2025-08-23 20:59:58 -04:00
Daniel Lemire 056d66926a Renames a few macros and extends slightly our basic builder (#2422)
* This PR renames a few macros and extends slightly our basic builder

* minor tuning
2025-08-20 09:01:58 -04:00
Daniel Lemire b434a50681 doing more to discourage value_unsafe(); (#2421) 2025-08-19 16:10:38 -04:00
Daniel Lemire e8ff2fa692 This is a small reorg of the new convert code so that we only expose 'simdjson::from' as experimental (#2418)
* This is a small reorg of the new convert code so that we only expose 'simdjson::from'.
This can be changed in a future release, but we don't want our users to start depending
on code that we might need to change.

* marking simdjson::from as experimental

* updating tests to match recent changes
2025-08-15 15:13:36 -04:00
Daniel Lemire 1ebf115b2b improving the documentation of raw json access (#2416)
* improving the documentation of raw json access

* minor fix

* documentation update

* guarding for exceptions

* fixing exception issue

* fix test

* update.

* saving comments

* more technical fixes

* correcting path in ci test

---------

Co-authored-by: Daniel Lemire <dlemire@lemire.me>
2025-08-15 10:26:20 -04:00
Joshua Gawley 172fbe785d Use std::string_view in parser.load function (#2417) 2025-08-15 10:26:03 -04:00
Daniel Lemire a10f096af2 let us just say that it should not work 2025-08-14 11:49:42 -04:00
Daniel Lemire a85e474360 documentation update 2025-08-14 09:33:11 -04:00
Daniel Lemire 05752d6c6f Update ubuntu22-cxx20.yml 2025-08-13 21:17:41 -04:00
Joseph Olabisi fb9a689dff Adding DOM support for json path with wildcard (#2346)
* wip brute-force wildcard for json_path

* wip - bruteforce surface wildcard with result

* partially handle keys with wildcard

* wip - nested paths/pointers on wildcard results

* wip

* wip - handling child properties of wildcard result

* done - handling child properties of wildcard result

* fix key

* add support for wildcard for arrays

* handle array INCORRECT_TYPE

* rename at_path_new to at_path_with_wildcard

* add benchmark

* fix benchmark

* use memcmp

* minor improvements

* refactor to tail recursion

* nit

* approximately 30% improvement in runtime

* nit

* corrected logic

* nit

* cleanup

* add some initial tests

* cleanup 2

* modified:   CMakeLists.txt

* cleanup

* cleanup

* cleanup

* cleanup

* restore examples/quickstart/CMakeLists.txt

* cleanup

* restore quickstart.cpp

* revert array-inl.h

* cleanup array-inl.h

* revert object-inl.h

* cleanup object-inl.h

* cleanup

* nit

* nit

* add test

* address some feedbacks

* address additional feedbacks (copilot)

* final changes based on feedback - reduce string allocations

* fix bug in object-inl.h

* fix logic for wildcards inside arrays

* add test for wildcard in nested array

* refactor and create util for getting key and json path

* minor error handling

* refactor process_json_path_of_child_element from recursion to loop in order to prevent stack overflow

* fix bug with array, add boundary check to get_next_key_and_json_path function

* add more tests

* fix boundary check and unnecessary string allocation

* minor changes

* fix

* fix jsonpathutil

* remove unneccessary string allocation

* some minor fixes

* documentation

* removing printout

* various fixes

* reorg of the CI test file

---------

Co-authored-by: Daniel Lemire <daniel@lemire.me>
2025-08-13 21:16:32 -04:00
Daniel Lemire 472f818fe5 trimming repeated code 2025-08-13 20:40:04 -04:00
Daniel Lemire 5c9c133632 improve how we pad strings. (#2415) 2025-08-13 18:41:14 -04:00
Daniel Lemire faf921bc7e introducing a thread-local parser and removing ranges (#2412)
* introducing a thread-local parser

* adding functionality to release the memory

* some more documentation.

* fixing build

* adding benchmarks for 'from'

* generalizing the code somewhat.

* adding tests, fixing the benchmark (now with arrays and streams), and a
minor update to document_stream

* adding missing files (I forgot to check them).

* We cannot use [[nodiscard]] without guarding it, it is C++17

* fixing the cmake

* marking it as experimental

* removing ranges support (it is too experimental)

* putting back documentation.

* guarding SIMDJSON_CONSTEVAL more carefully.

---------

Co-authored-by: Daniel Lemire <dlemire@lemire.me>
2025-08-13 18:08:25 -04:00
Daniel Lemire 626eedc3d8 documentation update 2025-08-13 16:24:55 -04:00
Daniel Lemire 294aa64f94 Update bug_report.md 2025-08-13 15:01:53 -04:00
Daniel Lemire 397b40e574 more documentation tuning 2025-08-11 15:39:10 -04:00
Daniel Lemire fdc93528d1 updating documentation. 2025-08-11 13:48:01 -04:00
Francisco Geiman Thiesen 3e00a431a0 Merge pull request #2406 from simdjson/fix-convert-ci-failures
Introducing simplified api with from/to adapters (thanks to @the-moisrex for driving this) and fixing all the CI errors.
2025-08-08 10:42:32 -07:00
Pavel Novikov d3626c6a37 removed now unnecessary <utility> header, (#2411)
disabled copy ctor and assignment for `vector_with_small_buffer`
2025-08-08 08:39:28 -04:00
Daniel Lemire 686a1869c8 updating commit 2025-08-08 08:38:41 -04:00
Daniel Lemire 4c0f86db44 minor fixes 2025-08-07 23:59:30 -04:00
Daniel Lemire 6c4c934457 added a new test. 2025-08-07 18:11:44 -04:00
Daniel Lemire fcae795042 minor fix 2025-08-07 16:05:24 -04:00
Daniel Lemire b7c51156b9 Sped up serialization fix (#2409)
* clang format

* added `chars()` method

* implemented vector with small buffer instead of `std::vector`

* added missing <utility> header

* minor fixes

---------

Co-authored-by: Pavel Novikov <dev-ape@yandex.ru>
Co-authored-by: Daniel Lemire <dlemire@lemire.me>
2025-08-07 16:04:20 -04:00
Pavel Novikov 662e3d96c7 Sped up serialization by 10..70%-ish (#2408)
* clang format

* added `chars()` method

* implemented vector with small buffer instead of `std::vector`

* added missing <utility> header
2025-08-07 16:03:39 -04:00
Daniel Lemire 64009f7063 more code simplification. 2025-08-07 13:25:18 -04:00
Daniel Lemire 74fb3ecac7 removing another pragma and some code simplification. 2025-08-07 13:23:21 -04:00
Daniel Lemire 26abf1d180 removing macros 2025-08-07 11:39:51 -04:00
Francisco Geiman Thiesen e878dc6be3 Addressing @the-moisrex review. 2025-08-06 03:05:32 +00:00
Francisco Geiman Thiesen efa03f5733 Fix to_bad_array test to handle both exception and non-exception error cases
The test was failing in CI with g++-13 because it only handled the
exception case. However, the array() method is marked noexcept and
returns a simdjson_result that may contain an error code instead of
throwing an exception.

This fix checks for both cases:
1. If array_result.error() is not SUCCESS, verify it's INCORRECT_TYPE
2. If no error is returned initially, the exception may be thrown when
   iterating over the result

This ensures the test passes regardless of whether the error is
reported via error code or exception.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-08-05 20:51:17 +00:00
Francisco Geiman Thiesen 8519623257 Fix unused parameter warning in json_iterator::assert_valid_position for SIMDJSON_CLANG_VISUAL_STUDIO
Added (void)position; to suppress unused parameter warning when compiling with SIMDJSON_CLANG_VISUAL_STUDIO defined, where the position parameter isn't used in the SIMDJSON_ASSUME statements.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-08-05 19:27:01 +00:00
Francisco Geiman Thiesen 484a092c31 Adding a few tests for the simdjson::to adapter. 2025-08-05 16:29:59 +00:00
Francisco Geiman Thiesen 8bb520adbf Merge master and regenerate amalgamated files
Resolved conflicts by regenerating the amalgamated single-header
files (simdjson.h, simdjson.cpp, and singleheader.zip) using the
amalgamate.py script after merging latest changes from master.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-08-03 20:16:47 +00:00
Francisco Geiman Thiesen ec8e6a6758 Fix -Werror=effc++ warnings and test issues in convert.h
This commit addresses multiple issues:

1. Fixed -Werror=effc++ warnings by using #pragma to disable the
   warning for constructors that cannot initialize all members in
   the member initialization list due to error handling requirements.

2. Added proper error tracking (m_error member) to handle cases where
   document initialization fails, preventing segfaults when using
   invalid documents.

3. Fixed lifetime issues in tests where temporary auto_parser objects
   were being used, causing dangling references. Tests now properly
   store the parser object before using it.

4. Simplified range adaptor tests that were expecting features not
   yet implemented in simdjson's ondemand API.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-08-03 19:26:58 +00:00
Francisco Geiman Thiesen 769528b6c0 Fix segmentation fault in auto_parser constructor for C++20
The issue was that we were trying to initialize ondemand::document
directly from simdjson_result<ondemand::document> in the member
initializer list. This caused a segfault in C++20 builds.

The fix explicitly handles the simdjson_result in the constructor
body, checking for errors and using value_unsafe() to extract the
document. This avoids potential issues with implicit conversions
and ensures proper error handling.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-08-03 19:01:42 +00:00
Francisco Geiman Thiesen 9fbc577be7 Fix member initialization order warning in auto_parser
The compiler was warning about member initialization order mismatch.
C++ initializes members in the order they are declared in the class,
not the order they appear in the initializer list.

Fixed by reordering member declarations to match the initialization
order needed: m_doc must be initialized before m_parser since we
need to call parser.iterate() before moving the parser.

This fixes the -Werror=reorder compilation error in CI.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-08-03 18:48:07 +00:00
Francisco Geiman Thiesen a7c95e9cc8 Fix initialization order in auto_parser constructor
The issue was that we were calling m_parser.iterate() after moving
the parser, which could leave it in an invalid state. In C++20,
this might behave differently than C++17.

Fixed by reordering the member initializer list to call
parser.iterate() BEFORE moving the parser into m_parser.

This ensures the document is created while the parser is still valid.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-08-03 18:36:14 +00:00
Francisco Geiman Thiesen 248d4eb2aa Try using parentheses instead of braces for document initialization
The issue might be related to how brace initialization vs parentheses
initialization handles implicit conversion from simdjson_result<document>
to document. This could be compiler-specific behavior.

Using parentheses initialization to ensure the conversion operator
is called properly.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-08-03 18:12:50 +00:00
Francisco Geiman Thiesen 559493e2bf Fix auto_parser constructor to handle document initialization correctly
The issue was that the auto_parser constructor was using implicit
conversion from simdjson_result<document> to document, which could
cause issues with certain implementations (particularly fallback).

Changed to use value_unsafe() to explicitly extract the document
after the parser is fully initialized. This ensures the document
is in a valid state for subsequent operations.

This fixes the ondemand_convert_tests failure in CI with clang++-16.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-08-03 15:28:40 +00:00
Francisco Geiman Thiesen 75acae5c46 Fix C++20 compatibility issues in convert.h
- Remove constexpr from functions that call non-constexpr methods
- The no_errors and to<T> adaptors were marked constexpr but call
  simdjson_result methods that are not constexpr in C++20
- This was causing compilation failures in CI for C++20 builds
- Tests now compile and pass with both C++17 and C++20

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-08-03 06:01:50 +00:00
Francisco Geiman Thiesen bd761ef573 Fix ranges support for C++20 compatibility
Instead of disabling the feature, provide C++20-compatible implementation
of the pipe operators for ranges support. The range_adaptor_closure is
C++23-only, so we implement our own pipe operators for C++20.

This preserves the core functionality of the PR while ensuring
compatibility across different compiler versions.
2025-08-03 05:39:10 +00:00
Francisco Geiman Thiesen 76ed73f07f Disable ranges-dependent tests to fix compilation
The test_no_errors() and to_clean_array() tests depend on the C++23
ranges features that we disabled. This commit conditionally compiles
these tests out when ranges support is disabled.
2025-08-03 05:34:34 +00:00
Francisco Geiman Thiesen 1b59b38de8 Disable C++23 ranges features to fix CI compatibility
The ranges features were causing compatibility issues across different
compilers and platforms. Disabling them for now until C++23 support
is more widespread.

This should fix the remaining Ubuntu and Windows CI failures.
2025-08-03 05:11:31 +00:00
Francisco Geiman Thiesen 4794b5d936 Update amalgamated files with convert.h fixes
Regenerate singleheader/simdjson.cpp and singleheader/simdjson.h
to include all the fixes for CI compatibility issues.
2025-08-02 17:06:45 +00:00
Francisco Geiman Thiesen 114f14924c Simplify ranges feature detection for C++23
Only enable range_adaptor_closure features when compiling with C++23
or later, as this feature is not available in C++20 implementations.
2025-08-02 16:04:58 +00:00
Francisco Geiman Thiesen e94825c9a8 Fix preprocessor check for __cpp_lib_ranges_zip
Add defined() check before comparing the value to avoid preprocessor
errors in compilers where this macro doesn't exist (like g++-13
with certain configurations).
2025-08-02 15:43:31 +00:00
Francisco Geiman Thiesen 333fd72f98 Fix CI failures in convert.h implementation
- Fix deprecated reflect_value warning by using reflect_constant
- Fix std::const_iterator C++23 requirement by using auto_iterator
- Fix C++23 std::ranges::range_adaptor_closure availability check
- Add convert.h to main simdjson.h includes

These changes ensure compatibility across different C++ standards
and compiler versions, fixing the Ubuntu CI failures.

Co-Authored-By: Claude <noreply@anthropic.com>
2025-08-02 15:04:25 +00:00
Daniel Lemire dd4d02617f removing unnecessary hack 2025-07-31 18:40:54 -04:00
Daniel Lemire e28d63334e fix 2025-07-31 13:07:29 -04:00
Daniel Lemire 3d1ab87ecb Merge branch 'master' into release_candidate_4_0_0 2025-07-31 10:24:54 -04:00
Borislav Stanimirov d365cfb4a1 remove cmake_policy (#2404)
* properly gitignore Visual Studio artifacts

* remove cmake_policy
2025-07-31 10:18:28 -04:00
Daniel Lemire b9e308a727 marking them as 'really inline' 2025-07-28 11:47:09 -04:00
Brad Bramble aa6817d5aa Fix linker errors for downstream users caused by non-inline symbols in header (#2403) 2025-07-28 11:44:32 -04:00
Francisco Geiman Thiesen 90e4a66c93 Bringing a bit more use-cases for reflection based serializations (optional). Also adding string-based enum handling as requested on X. (#2395)
* Adding type validation, enhancing optional type support and adding test a few more tests.

* Adding support for string-based enum serlalization and deserialization.

* Removing unintentional endline.

* Removing trailing whitespace.

* Adding simpler api as suggested by moisrex.

* Removing explicit optiona<int> and optional<std::string> references and using concepts instead! Credit goes to Lemire for pointing this out and suggesting a concepts based approach here.

* Removing tests that are not relevant for this branch.

* Removing api related changes. That will be done by moisrex.

* Removing unnecessary new endlines.

* Removing tests related to api changes and cleaning-up irrelevant tests.

* removing broken reference

* Removing trailing whitespace
2025-07-23 09:24:05 +02:00
evbse e6240f18c4 Add version to amalgamated files (#2400) 2025-07-23 09:20:06 +02:00
M. Bahoosh d43fb6ff84 test for no_errors 2025-07-21 05:24:12 -10:00
M. Bahoosh 5eec29a6db Moving iterator's storage to auto_parser 2025-07-21 05:18:13 -10:00
M. Bahoosh 228501f786 From/To adaptors 2025-07-21 03:22:20 -10:00
M. Bahoosh 9d9f2427c5 Make auto_parser and auto_iterator comply with ranges. 2025-07-20 03:22:07 -10:00
M. Bahoosh 8d53840253 Removing unneeded code 2025-07-19 08:37:04 -10:00
M. Bahoosh 0a82fb110f Auto Iterator 2025-07-19 08:35:49 -10:00
M. Bahoosh 11f273c580 Moving ondemand::document into auto_parser 2025-07-19 04:37:13 -10:00
M. Bahoosh 31662531ac Basic Auto Parser 2025-07-19 03:58:21 -10:00
Daniel Lemire 97eb557388 Merge branch 'release_candidate_4_0_0' of github.com:simdjson/simdjson into release_candidate_4_0_0 2025-07-16 12:08:03 -04:00
Daniel Lemire 41ced28821 adding a space 2025-07-16 12:07:49 -04:00
Daniel Lemire 1078eb4034 minor update to the release candidate (#2394) 2025-07-16 12:05:09 -04:00
evbse 6029af75b3 Improve fallback implementation (#2393) 2025-07-15 14:39:09 -04:00
Daniel Lemire 0cd774097f [skip ci] doc fixes 2025-07-14 23:46:30 -04:00
Daniel Lemire b5e27af4da release candidate 4.0.0 2025-07-14 15:55:00 -04:00
Daniel Lemire c806e955c4 C++26 static reflection (#2282)
* Initial work on JSON builder

* moving the files back to ondemand for now.

* tweak

* more later

* update

* minor edits

* dropping vs arm (missing support)

* adding tests. we still specialized write_string_escaped

* tweaking

* fix typo

* tweaking the approach

* minor fix

* missing store

* another missing store

* Attempt at fixing failing serialization tests. (#2292)

* Fixing appeand_float typo (#2294)

* applying a couple of fixes

* updating single header

* fix for pre C++17 if constexpr

* Fixing unused argument problem and updating the singleheader file

* various pedantic fixes

* Sketch of builder

* reordering.

* simplify

* Adding draft of static reflection based deserialization

* Updating simdjson singleheader

* patching the automated deserialization.

* automated

* Adding support for smart pointers of user defined types.

* Adding specialization for smart pointers for basic types. I think it is highly likely that this can be done in a more generic way.

* Referncing a later version of rapidjson that fixed the issue related with assignment attempt of a const variable for GenericStringRef class.

* guarding the tests

* adding documentation for string_builder

* saving

* rename to 'append'

* saving

* non-functional benchmarks (#2342)

* non-functional benchmarks

* Fix typo

* various fixes

* tweaking

---------

Co-authored-by: Daniel Lemire <dlemire@lemire.me>
Co-authored-by: Francisco Geiman Thiesen <franciscogthiesen@gmail.com>

* tuning

* various minor fixes

* minor tweak

* minor simplification

* updating amal

* adding a cast

* update

* fancy casting

* removing dead code

* Pushing latest changes. CITM benchmark is still not working.

* Still not working, but now I am getting only 10 errors.

* add static reflection benchmark to 'large random' benchmark and allows (#2349)

deserialization (with static reflection) from objects and arrays.

Co-authored-by: Daniel Lemire <dlemire@lemire.me>

* Removing std::map from CitmCatalog definition, since that is not currently supported.

* Added free to rust bench, segfault is still happening..

* The syntax changed: ^E became ^^E. (#2350)

* The syntax changed: ^E became ^^E.

* guarding

---------

Co-authored-by: Daniel Lemire <dlemire@lemire.me>

* Adding support for string_view_keyed_map types.

* Adding concepts as a conditional include.

* updating single-header

* Adding concepts to ondemand deps

* rust benchmark is finally working

* Fixing small typo in docs.

* adding docker config and instructions so that our users can test the static reflection (#2358)

* adding docker config and instructions so that our users can test the
static reflection

* completing the instructions

* pruning white spaces

---------

Co-authored-by: Daniel Lemire <dlemire@lemire.me>

* minor optimizations on the JSON builder branch

* avoiding undef behaviour

* saving

* somewhat nicer builder

* make it possible to run just one benchmark

* adding linux perf

* fixing minor issue

* updating swar

* Adding real world compilation benchmark (#2379)

* Adding compilation benchmark for json parsing with and without reflection

* Moving it to the benchmark folder, also reducing a bit the number of iterations.

* Removing script from root folder.

* Reducing number of iterations

* Update benchmark/benchmark_reflection_usage_compilation.sh

Co-authored-by: Daniel Lemire <daniel@lemire.me>

* Update benchmark/benchmark_reflection_usage_compilation.sh

Co-authored-by: Daniel Lemire <daniel@lemire.me>

* Update benchmark/benchmark_reflection_usage_compilation.sh

Co-authored-by: Daniel Lemire <daniel@lemire.me>

* Making the script more customizable and also test whether the compiler being used supports reflection before actually running the benchmark

---------

Co-authored-by: Daniel Lemire <daniel@lemire.me>

* Using define_static_string from  https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2025/p3491r2.html (#2389)

* Applying changes needed after latest reflection paper updates.

* Working, but no template for yet.

* Updating single-header to incldue the use of define_static_string.

* copying over master

---------

Co-authored-by: Daniel Lemire <dlemire@lemire.me>
Co-authored-by: Francisco Geiman Thiesen <franciscogthiesen@gmail.com>
2025-07-14 15:43:52 -04:00
162 changed files with 79869 additions and 1450 deletions
+4 -1
View File
@@ -9,7 +9,8 @@ assignees: ''
Before submitting an issue, please ensure that you have read the documentation:
* Basics is an overview of how to use simdjson and its APIs: https://github.com/simdjson/simdjson/blob/master/doc/basics.md
* Basics is an overview of how to use simdjson and its APIs to parse JSON: https://github.com/simdjson/simdjson/blob/master/doc/basics.md
* Builder is an overview of how to use simdjson to generate JSON: https://github.com/simdjson/simdjson/blob/master/doc/builder.md
* Performance shows some more advanced scenarios and how to tune for them: https://github.com/simdjson/simdjson/blob/master/doc/performance.md
* Contributing: https://github.com/simdjson/simdjson/blob/master/CONTRIBUTING.md
* We follow the [JSON specification as described by RFC 8259](https://www.rfc-editor.org/rfc/rfc8259.txt) (T. Bray, 2017). If you wish to support features that are not part of RFC 8259, then you should not refer to your issue as a bug.
@@ -55,6 +56,8 @@ We support up-to-date 64-bit ARM and x64 FreeBSD, macOS, Windows and Linux syste
pre-release version of a compiler, do not report it as a bug to simdjson. However, we always
invite contributions either in the form an analysis or of a code contribution.
Under Windows, we support Visual Studio (both with LLVM and without). We do not support MinGW and other alternate compiler systems. Windows users should be aware that there [is a long-running bug with GCC under Windows](https://gcc.gnu.org/bugzilla/show_bug.cgi?id=54412).
**Indicate whether you are willing or able to provide a bug fix as a pull request**
If you plan to contribute to simdjson, please read our guide:
+2 -1
View File
@@ -9,7 +9,8 @@ assignees: ''
Before submitting an issue, please ensure that you have read the documentation:
* Basics is an overview of how to use simdjson and its APIs: https://github.com/simdjson/simdjson/blob/master/doc/basics.md
* Basics is an overview of how to use simdjson and its APIs to parse JSON: https://github.com/simdjson/simdjson/blob/master/doc/basics.md
* Builder is an overview of how to use simdjson to generate JSON: https://github.com/simdjson/simdjson/blob/master/doc/builder.md
* Performance shows some more advanced scenarios and how to tune for them: https://github.com/simdjson/simdjson/blob/master/doc/performance.md
* Contributing: https://github.com/simdjson/simdjson/blob/master/CONTRIBUTING.md
* We follow the [JSON specification as described by RFC 8259](https://www.rfc-editor.org/rfc/rfc8259.txt) (T. Bray, 2017).
@@ -9,7 +9,8 @@ assignees: ''
Before submitting an issue, please ensure that you have read the documentation:
* Basics is an overview of how to use simdjson and its APIs: https://github.com/simdjson/simdjson/blob/master/doc/basics.md
* Basics is an overview of how to use simdjson and its APIs to parse JSON: https://github.com/simdjson/simdjson/blob/master/doc/basics.md
* Builder is an overview of how to use simdjson to generate JSON: https://github.com/simdjson/simdjson/blob/master/doc/builder.md
* Performance shows some more advanced scenarios and how to tune for them: https://github.com/simdjson/simdjson/blob/master/doc/performance.md
* Contributing: https://github.com/simdjson/simdjson/blob/master/CONTRIBUTING.md
* We follow the [JSON specification as described by RFC 8259](https://www.rfc-editor.org/rfc/rfc8259.txt) (T. Bray, 2017).
+49 -4
View File
@@ -1,8 +1,53 @@
Short title (summary):
Description
- What did you change and why? (1-3 sentences)
- Issue reproduced / related issue: link the issue if relevant (e.g. #123)
Type of change
- [ ] Bug fix
- [ ] New feature
- [ ] Refactor / cleanup
- [ ] Documentation / tests
- [ ] Other (please describe):
How to verify / test
- Add additional tests to verify bugs or new features.
- If you claim performance gains, you should provide benchmark numbers using high quality benchmarking code.
Our tests check whether you have introduced trailing white space. If such a test fails, please check the "artifacts button" above, which if you click it gives a link to a downloadable file to help you identify the issue. You can also run scripts/remove_trailing_whitespace.sh locally if you have a bash shell and the sed command available on your system.
Please read before contributing:
- CONTRIBUTING: https://github.com/simdjson/simdjson/blob/master/CONTRIBUTING.md
- HACKING: https://github.com/simdjson/simdjson/blob/master/HACKING.md
If you plan to contribute to simdjson, please read our
CONTRIBUTING guide: https://github.com/simdjson/simdjson/blob/master/CONTRIBUTING.md and our
HACKING guide: https://github.com/simdjson/simdjson/blob/master/HACKING.md
If you can, we recommend running our tests with the sanitizers turned on.
For non-Visual Studio users, it is as easy as doing:
```bash
cmake -B build -D SIMDJSON_SANITIZE=ON -D SIMDJSON_DEVELOPER_MODE=ON
cmake --build build
ctest --test-dir build
```
Our CI checks, among other things, for trailing whitespace. If a test fails for that reason,
use the "artifacts" button to download the artifact and inspect the problematic lines,
or run `scripts/remove_trailing_whitespace.sh` locally if you have a bash shell and `sed`.
Checklist before submitting
- [ ] I added/updated tests covering my change (if applicable)
- [ ] Code builds locally and passes my check
- [ ] Documentation / README updated if needed
- [ ] Commits are atomic and messages are clear
- [ ] I linked the related issue (if applicable)
Final notes
- For large PRs, prefer smaller incremental PRs or request staged review.
Thanks for the contribution!
+29
View File
@@ -0,0 +1,29 @@
name: Ubuntu rvv VLEN=1024 (clang 18)
on:
push:
branches:
- master
pull_request:
branches:
- master
jobs:
build:
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@v4
- name: Install packages
run: |
sudo apt-get update -q -y
sudo apt-get install -y cmake make g++-riscv64-linux-gnu qemu-user-static clang-18
- name: Build
run: |
CXX=clang++-18 CXXFLAGS="--target=riscv64-linux-gnu -march=rv64gcv_zvbb" \
cmake --toolchain=cmake/toolchains-ci/riscv64-linux-gnu.cmake -DCMAKE_BUILD_TYPE=Release -B build
cmake --build build/ -j$(nproc)
- name: Test VLEN=1024
run: |
export QEMU_LD_PREFIX="/usr/riscv64-linux-gnu"
export QEMU_CPU="rv64,v=on,zvbb=on,vlen=1024,rvv_ta_all_1s=on,rvv_ma_all_1s=on"
ctest --timeout 1800 --output-on-failure --test-dir build -j $(nproc)
+29
View File
@@ -0,0 +1,29 @@
name: Ubuntu rvv VLEN=128 (clang 17)
on:
push:
branches:
- master
pull_request:
branches:
- master
jobs:
build:
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@v4
- name: Install packages
run: |
sudo apt-get update -q -y
sudo apt-get install -y cmake make g++-riscv64-linux-gnu qemu-user-static clang-17
- name: Build
run: |
CXX=clang++-17 CXXFLAGS="--target=riscv64-linux-gnu -march=rv64gcv" \
cmake --toolchain=cmake/toolchains-ci/riscv64-linux-gnu.cmake -DCMAKE_BUILD_TYPE=Release -B build
cmake --build build/ -j$(nproc)
- name: Test VLEN=128
run: |
export QEMU_LD_PREFIX="/usr/riscv64-linux-gnu"
export QEMU_CPU="rv64,v=on,vlen=128,rvv_ta_all_1s=on,rvv_ma_all_1s=on"
ctest --timeout 1800 --output-on-failure --test-dir build -j $(nproc)
+29
View File
@@ -0,0 +1,29 @@
name: Ubuntu rvv VLEN=256 (gcc 14)
on:
push:
branches:
- master
pull_request:
branches:
- master
jobs:
build:
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@v4
- name: Install packages
run: |
sudo apt-get update -q -y
sudo apt-get install -y cmake make g++-14-riscv64-linux-gnu qemu-user-static
- name: Build
run: |
CXX=riscv64-linux-gnu-g++-14 CXXFLAGS=-march=rv64gcv \
cmake --toolchain=cmake/toolchains-ci/riscv64-linux-gnu.cmake -DCMAKE_BUILD_TYPE=Release -B build
cmake --build build/ -j$(nproc)
- name: Test VLEN=256
run: |
export QEMU_LD_PREFIX="/usr/riscv64-linux-gnu"
export QEMU_CPU="rv64,v=on,zvbb=on,vlen=256,rvv_ta_all_1s=on,rvv_ma_all_1s=on"
ctest --timeout 1800 --output-on-failure --test-dir build -j $(nproc)
+48 -16
View File
@@ -14,20 +14,52 @@ jobs:
with:
path: dependencies/.cache
key: ${{ hashFiles('dependencies/CMakeLists.txt') }}
- name: Use cmake
- name: Configure Debug Build
run: |
mkdir builddebug &&
cd builddebug &&
CXX=g++-12 cmake -DSIMDJSON_CXX_STANDARD=20 -DCMAKE_BUILD_TYPE=Debug -DSIMDJSON_GOOGLE_BENCHMARKS=OFF -DSIMDJSON_DEVELOPER_MODE=ON -DBUILD_SHARED_LIBS=OFF .. &&
cmake --build . &&
ctest --output-on-failure -LE explicitonly -j &&
cd .. &&
mkdir build &&
cd build &&
CXX=g++-12 cmake -DSIMDJSON_CXX_STANDARD=20 -DSIMDJSON_GOOGLE_BENCHMARKS=ON -DSIMDJSON_DEVELOPER_MODE=ON -DBUILD_SHARED_LIBS=OFF -DCMAKE_INSTALL_PREFIX:PATH=destination .. &&
cmake --build . &&
ctest --output-on-failure -LE explicitonly -j &&
cmake --install . &&
echo -e '#include <simdjson.h>\nint main(int argc,char**argv) {simdjson::dom::parser parser;simdjson::dom::element tweets = parser.load(argv[1]); }' > tmp.cpp && c++ -Idestination/include -Ldestination/lib -std=c++17 -Wl,-rpath,destination/lib -o linkandrun tmp.cpp -lsimdjson && ./linkandrun jsonexamples/twitter.json &&
cd ../tests/installation_tests/find &&
mkdir build && cd build && cmake -DCMAKE_INSTALL_PREFIX:PATH=../../../build/destination .. && cmake --build .
CXX=g++-12 cmake -DSIMDJSON_CXX_STANDARD=20 -DCMAKE_BUILD_TYPE=Debug -DSIMDJSON_GOOGLE_BENCHMARKS=OFF -DSIMDJSON_DEVELOPER_MODE=ON -DBUILD_SHARED_LIBS=OFF -B builddebug
- name: Compile Debug Build
run: |
cmake --build builddebug
- name: Test Debug Build
run: |
ctest --output-on-failure -LE explicitonly -j --test-dir builddebug
- name: Configure Release Build
run: |
CXX=g++-12 cmake -DSIMDJSON_CXX_STANDARD=20 -DSIMDJSON_GOOGLE_BENCHMARKS=ON -DSIMDJSON_DEVELOPER_MODE=ON -DBUILD_SHARED_LIBS=OFF -DCMAKE_INSTALL_PREFIX:PATH=destination -B build
- name: Compile Release Build
run: |
cmake --build build
- name: Test Release Build
run: |
ctest --output-on-failure -LE explicitonly -j --test-dir build
- name: Install Release Build
run: |
cmake --install build
- name: Generate Example Code
run: |
echo -e '#include <simdjson.h>\nint main(int argc,char**argv) {simdjson::dom::parser parser;simdjson::dom::element tweets = parser.load(argv[1]); }' > tmp.cpp
- name: Compile Example Code
run: |
c++ -Idestination/include -Ldestination/lib -std=c++17 -Wl,-rpath,destination/lib -o linkandrun tmp.cpp -lsimdjson
- name: Run Example Code
run: |
./linkandrun jsonexamples/twitter.json
- name: Configure Find Tests
run: |
cd tests/installation_tests/find && \
cmake -DCMAKE_INSTALL_PREFIX:PATH=../../../destination -B build
- name: Compile Find Tests
run: |
cd tests/installation_tests/find && cmake --build build
@@ -0,0 +1,22 @@
name: Ubuntu 24.04 CI (CXX 20, noexcept)
on: [push, pull_request]
jobs:
ubuntu-build:
if: >-
! contains(toJSON(github.event.commits.*.message), '[skip ci]') &&
! contains(toJSON(github.event.commits.*.message), '[skip github]')
runs-on: ubuntu-24.04
strategy:
matrix:
cxx: [g++-13, clang++-16]
steps:
- uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6
- name: Prepare
run: cmake -DSIMDJSON_CXX_STANDARD=20 -DSIMDJSON_EXCEPTIONS=OFF -DSIMDJSON_DEVELOPER_MODE=ON -B build
env:
CXX: ${{matrix.cxx}}
- name: Build
run: cmake --build build -j=2
- name: Test
run: ctest --output-on-failure --test-dir build
+1 -1
View File
@@ -31,4 +31,4 @@ jobs:
echo -e '#include <simdjson.h>\nint main(int argc,char**argv) {simdjson::dom::parser parser;simdjson::dom::element tweets = parser.load(argv[1]); }' > tmp.cpp && c++ -Idestination/include -Ldestination/lib -std=c++17 -Wl,-rpath,destination/lib -o linkandrun tmp.cpp -lsimdjson && ./linkandrun jsonexamples/twitter.json &&
mkdir testfindpackage &&
cd testfindpackage &&
echo -e 'cmake_minimum_required(VERSION 3.1)\nproject(simdjsontester)\nset(CMAKE_CXX_STANDARD 17)\nfind_package(simdjson REQUIRED)'> CMakeLists.txt && mkdir build && cd build && cmake -DCMAKE_INSTALL_PREFIX:PATH=../destination .. && cmake --build .
echo -e 'cmake_minimum_required(VERSION 3.14)\nproject(simdjsontester)\nset(CMAKE_CXX_STANDARD 17)\nfind_package(simdjson REQUIRED)'> CMakeLists.txt && mkdir build && cd build && cmake -DCMAKE_INSTALL_PREFIX:PATH=../destination .. && cmake --build .
+1 -1
View File
@@ -31,4 +31,4 @@ jobs:
echo -e '#include <simdjson.h>\nint main(int argc,char**argv) {simdjson::dom::parser parser;simdjson::dom::element tweets = parser.load(argv[1]); }' > tmp.cpp && c++ -Idestination/include -Ldestination/lib -std=c++17 -Wl,-rpath,destination/lib -o linkandrun tmp.cpp -lsimdjson && ./linkandrun jsonexamples/twitter.json &&
mkdir testfindpackage &&
cd testfindpackage &&
echo -e 'cmake_minimum_required(VERSION 3.1)\nproject(simdjsontester)\nset(CMAKE_CXX_STANDARD 17)\nfind_package(simdjson REQUIRED)'> CMakeLists.txt && mkdir build && cd build && cmake -DCMAKE_INSTALL_PREFIX:PATH=../destination .. && cmake --build .
echo -e 'cmake_minimum_required(VERSION 3.14)\nproject(simdjsontester)\nset(CMAKE_CXX_STANDARD 17)\nfind_package(simdjson REQUIRED)'> CMakeLists.txt && mkdir build && cd build && cmake -DCMAKE_INSTALL_PREFIX:PATH=../destination .. && cmake --build .
+30
View File
@@ -0,0 +1,30 @@
name: VS17-CI-SANITIZE
on: [push, pull_request]
jobs:
ci:
if: >-
! contains(toJSON(github.event.commits.*.message), '[skip ci]') &&
! contains(toJSON(github.event.commits.*.message), '[skip github]')
name: windows-vs17
runs-on: windows-latest
strategy:
fail-fast: false
matrix:
include:
- {gen: Visual Studio 17 2022, arch: x64, shared: OFF, build_type: Debug}
- {gen: Visual Studio 17 2022, arch: x64, shared: OFF, build_type: Release}
- {gen: Visual Studio 17 2022, arch: x64, shared: OFF, build_type: RelWithDebInfo}
steps:
- name: checkout
uses: actions/checkout@v4
- name: Configure
run: |
cmake -G "${{matrix.gen}}" -A ${{matrix.arch}} -DSANITIZE=ON -DSIMDJSON_DEVELOPER_MODE=ON -DSIMDJSON_COMPETITION=OFF -DBUILD_SHARED_LIBS=${{matrix.shared}} -B build
- name: Build
run: cmake --build build --config ${{matrix.build_type}} --verbose
- name: Run tests
run: |
cd build
ctest -C ${{matrix.build_type}} -LE explicitonly --output-on-failure
+2 -2
View File
@@ -38,7 +38,7 @@ cmake-build-release/
.history/
# Visual Studio artifacts
/VS/
/.vs/
# C/C++ build outputs
.build/
@@ -106,4 +106,4 @@ objs
!.vscode/extensions.json
# clangd
.cache
.cache
+3
View File
@@ -3,6 +3,9 @@
{"column": 95 },
{"column": 120 }
],
"cmake.configureArgs": [
"-DSIMDJSON_DEVELOPER_MODE=ON"
],
"files.trimTrailingWhitespace": true,
"files.associations": {
".clangd": "yaml",
+27 -6
View File
@@ -1,11 +1,9 @@
cmake_minimum_required(VERSION 3.14)
cmake_policy(VERSION 3.5) # For doctest
project(
simdjson
# The version number is modified by tools/release.py
VERSION 3.13.0
VERSION 4.0.4
DESCRIPTION "Parsing gigabytes of JSON per second"
HOMEPAGE_URL "https://simdjson.org/"
LANGUAGES CXX C
@@ -22,8 +20,8 @@ string(
# ---- Options, variables ----
# These version numbers are modified by tools/release.py
set(SIMDJSON_LIB_VERSION "26.0.0" CACHE STRING "simdjson library version")
set(SIMDJSON_LIB_SOVERSION "26" CACHE STRING "simdjson library soversion")
set(SIMDJSON_LIB_VERSION "27.0.0" CACHE STRING "simdjson library version")
set(SIMDJSON_LIB_SOVERSION "27" CACHE STRING "simdjson library soversion")
option(SIMDJSON_BUILD_STATIC_LIB "Build simdjson_static library along with simdjson (only makes sense if BUILD_SHARED_LIBS=ON)" OFF)
if(SIMDJSON_BUILD_STATIC_LIB AND NOT BUILD_SHARED_LIBS)
@@ -45,6 +43,20 @@ if(SIMDJSON_DISABLE_DEPRECATED_API)
)
endif()
if(${CMAKE_VERSION} VERSION_GREATER_EQUAL "3.25.0")
option(SIMDJSON_STATIC_REFLECTION "Enables static reflection (experimental), requires C++26" OFF)
else()
set(SIMDJSON_STATIC_REFLECTION OFF CACHE BOOL "Enables static reflection (experimental)" FORCE)
message(WARNING "SIMDJSON_STATIC_REFLECTION is disabled because your CMake version is below 3.25")
endif()
if(SIMDJSON_STATIC_REFLECTION)
simdjson_add_props(
target_compile_definitions PUBLIC
SIMDJSON_STATIC_REFLECTION=1
)
endif()
option(SIMDJSON_DEVELOPMENT_CHECKS "Enable development-time aids, such as \
checks for incorrect API usage. Enabled by default in DEBUG." OFF)
if(SIMDJSON_DEVELOPMENT_CHECKS)
@@ -100,7 +112,16 @@ simdjson_add_props(
PRIVATE "$<BUILD_INTERFACE:${PROJECT_SOURCE_DIR}/src>"
)
simdjson_add_props(target_compile_features PUBLIC cxx_std_11)
if(SIMDJSON_STATIC_REFLECTION)
# We would like to require C++26, but no compiler supports that!
# This is a hack:
simdjson_add_props(
target_compile_options PUBLIC
-freflection -fexpansion-statements -stdlib=libc++ -std=c++26
)
else()
simdjson_add_props(target_compile_features PUBLIC cxx_std_11)
endif()
# workaround for GNU GCC poor AVX load/store code generation
if(
+1 -1
View File
@@ -38,7 +38,7 @@ PROJECT_NAME = simdjson
# could be handy for archiving the generated documentation or if some version
# control system is used.
PROJECT_NUMBER = "3.13.0"
PROJECT_NUMBER = "4.0.4"
# Using the PROJECT_BRIEF tag one can provide an optional one line description
# for a project that appears at the top of each page and should give viewer a
+46 -8
View File
@@ -20,13 +20,54 @@ If you plan to contribute to simdjson, please read our [CONTRIBUTING](https://gi
Build Quickstart
------------------------------
For non-Windows system,
```bash
mkdir build
cd build
cmake -D SIMDJSON_DEVELOPER_MODE=ON ..
cmake --build .
cmake -B -D SIMDJSON_DEVELOPER_MODE=ON ..
cmake --build build
ctest --test-dir build
```
It is similar for Visual Studio users, please see the CMake or Visual Studio documentation.
By default the library is built in Release mode.
Assertions and development checks
------------------------------
We do not use conventional `assert` in simdjson. Instead we use the macro
`SIMDJSON_ASSUME`:
```cpp
SIMDJSON_ASSUME(something_that_is_true());
```
Sometimes, you need to do a bit more work that a simple check.
The `SIMDJSON_DEVELOPMENT_CHECKS` macro is true only in Debug mode unless manually set.
It is acceptable to add checks that you would not do in Release mode as long as
they are guarded:
```cpp
#if SIMDJSON_DEVELOPMENT_CHECKS
// do sanity checks here
```
Working with sanitizers
------------------------------
The simdjson library must be memory-safe. We cannot allow buffer overruns.
During development, if you system supports it, we recommend configuring
the project with `-D SIMDJSON_SANITIZE=ON`.
```bash
cmake -B -D SIMDJSON_SANITIZE=ON -D SIMDJSON_DEVELOPER_MODE=ON ..
cmake --build build
ctest --test-dir build
```
Design notes
------------------------------
@@ -103,12 +144,9 @@ simdjson's source structure, from the top level, looks like this:
* generic/stage2/*.h: `simdjson::<implementation>::stage2` namespace. Generic implementation of the tape creator, which consumes the index from stage 1 and actually parses numbers and string and such. Used for the DOM interface.
Other important files and directories:
* **.drone.yml:** Definitions for Drone CI.
* **.appveyor.yml:** Definitions for Appveyor CI (Windows).
* **.circleci:** Definitions for Circle CI.
* **.github/workflows:** Definitions for GitHub Actions (CI).
* **singleheader:** Contains generated `simdjson.h` and `simdjson.cpp` that we release. The files `singleheader/simdjson.h` and `singleheader/simdjson.cpp` should never be edited by hand.
* **singleheader/amalgamate.py:** Generates `singleheader/simdjson.h` and `singleheader/simdjson.cpp` for release (python script).
* **singleheader/amalgamate.py:** Generates `singleheader/simdjson.h` and `singleheader/simdjson.cpp` for release (python script). If you add a new implementation (e.g., rvv), you need to edit this file (IMPLEMENTATIONS).
* **benchmark:** This is where we do benchmarking. Benchmarking is core to every change we make; the
cardinal rule is don't regress performance without knowing exactly why, and what you're trading
for it. Many of our benchmarks are microbenchmarks. We are effectively doing controlled scientific experiments for the purpose of understanding what affects our performance. So we simplify as much as possible. We try to avoid irrelevant factors such as page faults, interrupts, unnecessary system calls. We recommend checking the performance as follows:
+2 -2
View File
@@ -1,5 +1,3 @@
[![Fuzzing Status](https://oss-fuzz-build-logs.storage.googleapis.com/badges/simdjson.svg)](https://bugs.chromium.org/p/oss-fuzz/issues/list?sort=-opened&can=1&q=proj:simdjson)
[![][license img]][license] [![][licensemit img]][licensemit]
@@ -109,6 +107,7 @@ Documentation
Usage documentation is available:
* [Basics](doc/basics.md) is an overview of how to use simdjson and its APIs.
* [Builder](doc/builder.md) is an overview of how to efficiently write JSON strings using simdjson.
* [Performance](doc/performance.md) shows some more advanced scenarios and how to tune for them.
* [Implementation Selection](doc/implementation-selection.md) describes runtime CPU detection and
how you can work with it.
@@ -118,6 +117,7 @@ Godbolt
-------------
Some users may want to browse code along with the compiled assembly. You want to check out the following lists of examples:
* [C++26 reflection example](https://godbolt.org/z/K3Px64TqK)
* [simdjson examples with errors handled through exceptions](https://godbolt.org/z/7G5qE4sr9)
* [simdjson examples with errors without exceptions](https://godbolt.org/z/e9dWb9E4v)
+16
View File
@@ -4,6 +4,9 @@ add_subdirectory(dom)
include_directories( . linux )
link_libraries(simdjson-windows-headers test-data)
link_libraries(simdjson)
if(SIMDJSON_STATIC_REFLECTION)
add_compile_definitions(SIMDJSON_STATIC_REFLECTION=1)
endif(SIMDJSON_STATIC_REFLECTION)
add_executable(benchfeatures benchfeatures.cpp)
add_executable(get_corpus_benchmark get_corpus_benchmark.cpp)
@@ -32,3 +35,16 @@ if (TARGET benchmark::benchmark)
endif()
endif()
include(CheckCXXCompilerFlag)
check_cxx_compiler_flag("-std=c++20" SIMDJSON_COMPILER_SUPPORTS_CXX20)
if(SIMDJSON_STATIC_REFLECTION)
add_subdirectory(static_reflect)
else()
if(SIMDJSON_EXCEPTIONS AND SIMDJSON_COMPILER_SUPPORTS_CXX20)
add_subdirectory(from)
add_subdirectory(car_builder)
endif()
endif(SIMDJSON_STATIC_REFLECTION)
+618
View File
@@ -0,0 +1,618 @@
#!/bin/bash
# JSON Parsing Compilation Benchmark: Reflection Usage vs Manual Parsing
# Compares compilation times when ACTUALLY USING reflection for parsing vs manual parsing
# This measures the compile-time cost of reflection-based automatic deserialization
set -e
echo "=== simdjson Reflection Usage Compilation Benchmark ==="
echo "Measuring compilation impact of ACTUALLY USING reflection for parsing"
echo "Starting at: $(date)"
echo
echo "╔════════════════════════════════════════════════════════════════════════════╗"
echo "║ METHODOLOGY ║"
echo "╠════════════════════════════════════════════════════════════════════════════╣"
echo "║ ║"
echo "║ FAIR COMPARISON STRATEGY: ║"
echo "║ This benchmark compares two DIFFERENT approaches to parsing the same JSON: ║"
echo "║ ║"
echo "║ • MANUAL PARSING: Traditional simdjson with explicit .get() calls ║"
echo "║ - Uses doc[\"field\"].get(variable) for each field ║"
echo "║ - No reflection involved ║"
echo "║ ║"
echo "║ • REFLECTION PARSING: Automatic deserialization with reflection ║"
echo "║ - Uses doc.get<MyStruct>() for automatic field mapping ║"
echo "║ - Relies on compile-time reflection to generate parsing code ║"
echo "║ ║"
echo "║ WHAT WE'RE MEASURING: ║"
echo "║ • Compile-time cost of reflection-based automatic deserialization ║"
echo "║ • Template instantiation overhead for reflection parsing ║"
echo "║ • Code generation complexity from using reflection features ║"
echo "║ ║"
echo "║ TEST SCENARIOS: ║"
echo "║ ║"
echo "║ 1. SIMPLE STRUCT: Basic fields (string, int, bool) ║"
echo "║ - Measures baseline reflection overhead ║"
echo "║ ║"
echo "║ 2. NESTED STRUCT: Multiple levels of nested objects ║"
echo "║ - Measures reflection complexity scaling ║"
echo "║ ║"
echo "║ 3. COMPLEX STRUCT: Arrays, optional fields, mixed types ║"
echo "║ - Measures real-world reflection usage impact ║"
echo "║ ║"
echo "║ WHY THIS IS MEANINGFUL: ║"
echo "║ • Shows actual cost of using reflection features ║"
echo "║ • Measures compile-time code generation overhead ║"
echo "║ • Helps developers understand reflection's compilation impact ║"
echo "║ • Compares equivalent functionality implemented two different ways ║"
echo "║ ║"
echo "╚════════════════════════════════════════════════════════════════════════════╝"
echo
# Configuration
ITERATIONS=10
JOBS=4
# ───────────────────────────── BOX-PRINT HELPER ────────────────────────────
BOX_WIDTH=74 # characters between the pipes
print_box_line() { # usage: print_box_line "text"
printf "║ %-*s ║\n" "${BOX_WIDTH}" "$1"
}
# Function to test if a compiler supports reflection with debug output
test_reflection_support() {
local compiler="$1"
echo " → Testing compiler: $compiler"
if [ ! -x "$compiler" ] && ! command -v "$compiler" >/dev/null 2>&1; then
echo " → Compiler not found or not executable"
return 1
fi
# Check compiler version first
echo " → Compiler version: $("$compiler" --version 2>/dev/null | head -n1 || echo "version check failed")"
# Simple test: check if compiler accepts reflection flags
local test_file=$(mktemp /tmp/reflection_test_XXXXXX.cpp)
cat > "$test_file" << 'EOF'
int main() {
return 0;
}
EOF
echo " → Testing basic reflection flags..."
local test_exe=$(mktemp /tmp/reflection_test_XXXXXX)
local basic_result=$("$compiler" -freflection -fexpansion-statements -std=c++26 "$test_file" -o "$test_exe" 2>&1)
local basic_exit_code=$?
if [ $basic_exit_code -ne 0 ]; then
echo " → Basic flags FAILED with exit code $basic_exit_code"
echo " → Error output: $basic_result"
rm -f "$test_file" "$test_exe"
return 1
fi
echo " → Basic flags: OK"
rm -f "$test_exe"
# Test reflection syntax
echo " → Testing reflection syntax..."
cat > "$test_file" << 'EOF'
struct Test {
int x;
};
int main() {
auto refl = ^^Test;
return 0;
}
EOF
local syntax_result=$("$compiler" -freflection -fexpansion-statements -std=c++26 "$test_file" -o "$test_exe" 2>&1)
local syntax_exit_code=$?
if [ $syntax_exit_code -eq 0 ]; then
echo " → Reflection syntax: OK"
echo " → ✓ REFLECTION SUPPORT CONFIRMED"
rm -f "$test_file" "$test_exe"
return 0
else
echo " → Reflection syntax FAILED with exit code $syntax_exit_code"
echo " → Error output: $syntax_result"
rm -f "$test_file" "$test_exe"
return 1
fi
}
# Find a compiler with reflection support
echo "Searching for clang++ with reflection support..."
REFLECTION_CXX=""
REFLECTION_CC=""
# List of potential clang++ locations to check
POTENTIAL_COMPILERS=(
"/usr/local/bin/clang++"
"/opt/clang/bin/clang++"
"/usr/bin/clang++"
"clang++"
)
# If CXX is already set, test it first
if [ -n "$CXX" ]; then
echo "Testing user-specified compiler: $CXX"
if test_reflection_support "$CXX"; then
REFLECTION_CXX="$CXX"
echo "✓ User-specified compiler supports reflection: $CXX"
else
echo "✗ User-specified compiler does not support reflection: $CXX"
echo "Will search for alternative..."
fi
fi
# If we don't have a working compiler yet, search for one
if [ -z "$REFLECTION_CXX" ]; then
for compiler in "${POTENTIAL_COMPILERS[@]}"; do
echo "Testing: $compiler"
if test_reflection_support "$compiler"; then
REFLECTION_CXX="$compiler"
echo "✓ Found reflection-enabled compiler: $compiler"
break
else
echo "✗ No reflection support: $compiler"
fi
done
fi
# Check if we found a working compiler
if [ -z "$REFLECTION_CXX" ]; then
echo
echo "╔════════════════════════════════════════════════════════════════════════════╗"
echo "║ ERROR ║"
echo "╠════════════════════════════════════════════════════════════════════════════╣"
echo "║ ║"
echo "║ No clang++ compiler with reflection support found! ║"
echo "║ ║"
echo "║ This benchmark requires a compiler that supports C++26 reflection. ║"
echo "║ ║"
echo "║ Options: ║"
echo "║ 1. Use the Docker container: ./p2996/run_docker.sh ║"
echo "║ 2. Build clang with reflection from: https://github.com/bloomberg/clang-p2996 ║"
echo "║ 3. Set CXX environment variable to point to reflection-enabled clang++ ║"
echo "║ ║"
echo "║ Example: CXX=/path/to/reflection-clang++ ./benchmark_script.sh ║"
echo "║ ║"
echo "╚════════════════════════════════════════════════════════════════════════════╝"
exit 1
fi
# Set the compilers
export CXX="$REFLECTION_CXX"
# Find corresponding C compiler
if [ -n "$CC" ]; then
REFLECTION_CC="$CC"
elif [ "$REFLECTION_CXX" = "/usr/local/bin/clang++" ]; then
REFLECTION_CC="/usr/local/bin/clang"
elif [ "$REFLECTION_CXX" = "/opt/clang/bin/clang++" ]; then
REFLECTION_CC="/opt/clang/bin/clang"
elif [ "$REFLECTION_CXX" = "/usr/bin/clang++" ]; then
REFLECTION_CC="/usr/bin/clang"
else
REFLECTION_CC="clang"
fi
export CC="$REFLECTION_CC"
echo
echo "Using reflection-enabled compiler: $($CXX --version | head -n1)"
echo "Using C compiler: $($CC --version | head -n1)"
echo
# Function to create manual parsing test
create_manual_parsing_test() {
local test_name="$1"
local struct_complexity="$2"
cat > "${test_name}_manual.cpp" << 'EOF'
#include <simdjson.h>
#include <iostream>
#include <string>
#include <vector>
#include <optional>
// Test structures
struct Person {
std::string name;
int age;
bool active;
};
struct Address {
std::string street;
std::string city;
int zipcode;
};
struct Employee {
Person person;
Address address;
std::vector<std::string> skills;
std::optional<std::string> department;
double salary;
};
// Manual parsing functions
bool parse_person_manual(simdjson::ondemand::value& val, Person& person) {
auto obj = val.get_object();
if (obj.error()) return false;
for (auto field : obj) {
std::string_view key = field.unescaped_key();
if (key == "name") {
std::string_view name_val;
if (field.value().get(name_val)) return false;
person.name = name_val;
} else if (key == "age") {
if (field.value().get(person.age)) return false;
} else if (key == "active") {
if (field.value().get(person.active)) return false;
}
}
return true;
}
bool parse_address_manual(simdjson::ondemand::value& val, Address& address) {
auto obj = val.get_object();
if (obj.error()) return false;
for (auto field : obj) {
std::string_view key = field.unescaped_key();
if (key == "street") {
std::string_view street_val;
if (field.value().get(street_val)) return false;
address.street = street_val;
} else if (key == "city") {
std::string_view city_val;
if (field.value().get(city_val)) return false;
address.city = city_val;
} else if (key == "zipcode") {
if (field.value().get(address.zipcode)) return false;
}
}
return true;
}
bool parse_employee_manual(simdjson::ondemand::document& doc, Employee& employee) {
auto obj = doc.get_object();
if (obj.error()) return false;
for (auto field : obj) {
std::string_view key = field.unescaped_key();
if (key == "person") {
auto person_val = field.value();
if (!parse_person_manual(person_val, employee.person)) return false;
} else if (key == "address") {
auto addr_val = field.value();
if (!parse_address_manual(addr_val, employee.address)) return false;
} else if (key == "skills") {
auto skills_array = field.value().get_array();
if (skills_array.error()) return false;
for (auto skill : skills_array) {
std::string_view skill_val;
if (skill.get(skill_val)) return false;
employee.skills.emplace_back(skill_val);
}
} else if (key == "department") {
std::string_view dept_val;
if (!field.value().get(dept_val)) {
employee.department = dept_val;
}
} else if (key == "salary") {
if (field.value().get(employee.salary)) return false;
}
}
return true;
}
int main() {
simdjson::ondemand::parser parser;
std::string json_str = R"({
"person": {
"name": "John Doe",
"age": 30,
"active": true
},
"address": {
"street": "123 Main St",
"city": "Anytown",
"zipcode": 12345
},
"skills": ["C++", "JSON", "Programming"],
"department": "Engineering",
"salary": 85000.50
})";
simdjson::ondemand::document doc;
auto error = parser.iterate(simdjson::pad(json_str)).get(doc);
if (error) {
std::cerr << "Parse error" << std::endl;
return 1;
}
Employee employee;
if (!parse_employee_manual(doc, employee)) {
std::cerr << "Manual parsing failed" << std::endl;
return 1;
}
std::cout << "Manual parsing successful: " << employee.person.name
<< ", age " << employee.person.age << std::endl;
return 0;
}
EOF
}
# Function to create reflection parsing test
create_reflection_parsing_test() {
local test_name="$1"
local struct_complexity="$2"
cat > "${test_name}_reflection.cpp" << 'EOF'
#include <simdjson.h>
#include <iostream>
#include <string>
#include <vector>
#include <optional>
// Test structures (same as manual version)
struct Person {
std::string name;
int age;
bool active;
};
struct Address {
std::string street;
std::string city;
int zipcode;
};
struct Employee {
Person person;
Address address;
std::vector<std::string> skills;
std::optional<std::string> department;
double salary;
};
int main() {
simdjson::ondemand::parser parser;
std::string json_str = R"({
"person": {
"name": "John Doe",
"age": 30,
"active": true
},
"address": {
"street": "123 Main St",
"city": "Anytown",
"zipcode": 12345
},
"skills": ["C++", "JSON", "Programming"],
"department": "Engineering",
"salary": 85000.50
})";
simdjson::ondemand::document doc;
auto error = parser.iterate(simdjson::pad(json_str)).get(doc);
if (error) {
std::cerr << "Parse error" << std::endl;
return 1;
}
// Use reflection-based automatic deserialization
Employee employee;
auto result = doc.get<Employee>();
if (result.error()) {
std::cerr << "Reflection parsing failed" << std::endl;
return 1;
}
employee = result.value();
std::cout << "Reflection parsing successful: " << employee.person.name
<< ", age " << employee.person.age << std::endl;
return 0;
}
EOF
}
# Function to time compilation of parsing approach
time_parsing_compilation() {
local description="$1"
local test_file="$2"
local use_reflection="$3"
local iteration="$4"
echo "[$iteration] $description"
# Clean build
rm -rf build_parsing_test
mkdir build_parsing_test
cd build_parsing_test
# Copy test file
cp "../$test_file" .
echo " Configuring..."
if [ "$use_reflection" = "true" ]; then
cmake -DCMAKE_CXX_COMPILER="$CXX" \
-DSIMDJSON_DEVELOPER_MODE=ON \
-DSIMDJSON_STATIC_REFLECTION=ON \
-DBUILD_SHARED_LIBS=OFF \
../.. >/dev/null 2>&1
else
cmake -DCMAKE_CXX_COMPILER="$CXX" \
-DSIMDJSON_DEVELOPER_MODE=ON \
-DSIMDJSON_STATIC_REFLECTION=OFF \
-DBUILD_SHARED_LIBS=OFF \
../.. >/dev/null 2>&1
fi
echo " Building simdjson..."
cmake --build . --target simdjson >/dev/null 2>&1
echo " Compiling parsing test..."
# Time just the test compilation
start_time=$(date +%s.%N)
"$CXX" -std=c++17 -I../../include "$test_file" -L. -lsimdjson -o parsing_test >/dev/null 2>&1
end_time=$(date +%s.%N)
# Calculate time duration
time_taken=$(echo "$end_time $start_time" | awk '{printf "%.3f", $1 - $2}')
echo " Completed in: ${time_taken}s"
cd ..
rm -rf build_parsing_test
echo "$time_taken"
}
# Create test files
echo "Creating test files..."
create_manual_parsing_test "complex" "complex"
create_reflection_parsing_test "complex" "complex"
# Arrays to store times
times_manual=""
times_reflection=""
echo
echo "=== MANUAL PARSING COMPILATION ==="
echo "Testing traditional simdjson parsing with explicit .get() calls"
echo
for i in $(seq 1 $ITERATIONS); do
time_result=$(time_parsing_compilation "Compiling manual parsing test" "complex_manual.cpp" "false" "$i" | tail -n1)
times_manual="$times_manual $time_result"
done
echo
echo "=== REFLECTION PARSING COMPILATION ==="
echo "Testing automatic deserialization with doc.get<Struct>()"
echo
for i in $(seq 1 $ITERATIONS); do
time_result=$(time_parsing_compilation "Compiling reflection parsing test" "complex_reflection.cpp" "true" "$i" | tail -n1)
times_reflection="$times_reflection $time_result"
done
echo
echo "╔════════════════════════════════════════════════════════════════════════════╗"
echo "║ REFLECTION USAGE COMPILATION RESULTS ║"
echo "╠════════════════════════════════════════════════════════════════════════════╣"
echo "║ ║"
echo "║ MANUAL PARSING (explicit .get() calls): ║"
count=1
for t in $times_manual; do
if [[ "$t" =~ ^[0-9]+\.?[0-9]*$ ]]; then
line=$(printf "Run %2d: %7.3f seconds" "$count" "$t")
print_box_line "$line"
count=$((count + 1))
fi
done
echo "║ ║"
echo "║ REFLECTION PARSING (automatic doc.get<Struct>()): ║"
count=1
for t in $times_reflection; do
if [[ "$t" =~ ^[0-9]+\.?[0-9]*$ ]]; then
line=$(printf "Run %2d: %7.3f seconds" "$count" "$t")
print_box_line "$line"
count=$((count + 1))
fi
done
echo "╚════════════════════════════════════════════════════════════════════════════╝"
echo
echo "╔════════════════════════════════════════════════════════════════════════════╗"
echo "║ ANALYSIS SUMMARY ║"
echo "╠════════════════════════════════════════════════════════════════════════════╣"
echo "║ ║"
# Calculate averages and percentages - filter to only numeric values first
manual_numbers=""
reflection_numbers=""
for t in $times_manual; do
if [[ "$t" =~ ^[0-9]+\.?[0-9]*$ ]]; then
manual_numbers="$manual_numbers $t"
fi
done
for t in $times_reflection; do
if [[ "$t" =~ ^[0-9]+\.?[0-9]*$ ]]; then
reflection_numbers="$reflection_numbers $t"
fi
done
if [ -n "$manual_numbers" ] && [ -n "$reflection_numbers" ]; then
manual_avg=$(echo "$manual_numbers" | awk '{sum=0; for(i=1;i<=NF;i++) sum+=$i; print sum/NF}')
reflection_avg=$(echo "$reflection_numbers" | awk '{sum=0; for(i=1;i<=NF;i++) sum+=$i; print sum/NF}')
overhead=$(echo "$reflection_avg $manual_avg" | awk '{printf "%.3f", $1 - $2}')
if [ $(echo "$manual_avg > 0" | awk '{print ($1 > 0)}') -eq 1 ]; then
percent=$(echo "$reflection_avg $manual_avg" | awk '{printf "%.1f", ($1 - $2) / $2 * 100}')
else
percent="0"
fi
print_box_line "MANUAL PARSING RESULTS:"
print_box_line "$(printf "Average compilation time: %.3fs" "$manual_avg")"
print_box_line ""
print_box_line "REFLECTION PARSING RESULTS:"
print_box_line "$(printf "Average compilation time: %.3fs" "$reflection_avg")"
print_box_line ""
print_box_line "REFLECTION OVERHEAD:"
print_box_line "$(printf "Additional time: %.3fs (%+.1f%%)" "$overhead" "$percent")"
print_box_line ""
else
echo "║ ERROR: Could not extract valid timing data ║"
echo "║ Manual times: $times_manual"
echo "║ Reflection times: $times_reflection"
echo "║ ║"
fi
echo "╠════════════════════════════════════════════════════════════════════════════╣"
echo "║ INTERPRETATION ║"
echo "╠════════════════════════════════════════════════════════════════════════════╣"
echo "║ ║"
echo "║ WHAT THESE RESULTS SHOW: ║"
echo "║ ║"
echo "║ • COMPILE-TIME COST: How much longer reflection parsing takes to compile ║"
echo "║ - Higher % = more expensive template instantiation and codegen ║"
echo "║ ║"
echo "║ • CODE GENERATION OVERHEAD: Reflection creates parsing code at compile ║"
echo "║ time, which requires more template processing than manual parsing ║"
echo "║ ║"
echo "║ • DEVELOPER TRADE-OFF: Reflection provides automatic deserialization ║"
echo "║ but at the cost of increased compilation time ║"
echo "║ ║"
echo "║ EVALUATION: ║"
echo "║ • Low overhead (0-20%): Reflection is compile-time efficient ║"
echo "║ • Medium overhead (20-50%): Noticeable but potentially acceptable ║"
echo "║ • High overhead (50%+): Significant compilation cost for reflection ║"
echo "║ ║"
echo "║ REAL-WORLD IMPACT: ║"
echo "║ • Small projects: Absolute time matters more than percentage ║"
echo "║ • Large projects: Percentage overhead compounds across many files ║"
echo "║ • CI/CD pipelines: Longer builds affect development velocity ║"
echo "║ ║"
echo "╚════════════════════════════════════════════════════════════════════════════╝"
# Clean up test files
rm -f complex_manual.cpp complex_reflection.cpp
echo
echo "Completed at: $(date)"
+14
View File
@@ -0,0 +1,14 @@
# Executable
add_executable(benchmark_car_builder benchmark_car_builder.cpp)
# Compile for C++20.
target_compile_features(benchmark_car_builder PRIVATE cxx_std_20)
# Check if -march=native is supported
include(CheckCXXCompilerFlag)
check_cxx_compiler_flag("-march=native" SIMDJSON_SUPPORTS_MARCH_NATIVE)
if(SIMDJSON_SUPPORTS_MARCH_NATIVE)
target_compile_options(benchmark_car_builder PRIVATE -march=native)
endif()
target_include_directories(benchmark_car_builder PRIVATE ${CMAKE_CURRENT_LIST_DIR}/..)
@@ -0,0 +1,127 @@
#include "event_counter.h"
#include <random>
#include <vector>
#include <simdjson.h>
event_collector collector;
struct Car {
std::string make;
std::string model;
int64_t year; // We deliberately do not include the tire pressure.
};
std::vector<Car> generate_random_cars(size_t count) {
static const std::vector<std::string> makes = {"Toyota", "Honda", "Ford",
"BMW", "Mazda"};
static const std::vector<std::string> models = {"Camry", "Civic", "Focus",
"320i", "3"};
static thread_local std::mt19937 rng{std::random_device{}()};
std::uniform_int_distribution<int> make_dist(0, makes.size() - 1);
std::uniform_int_distribution<int> model_dist(0, models.size() - 1);
std::uniform_int_distribution<int64_t> year_dist(2000, 2025);
std::uniform_real_distribution<double> pressure_dist(30.0, 45.0);
std::vector<Car> cars;
cars.reserve(count);
for (size_t i = 0; i < count; ++i) {
Car car;
car.make = makes[make_dist(rng)];
car.model = models[model_dist(rng)];
car.year = year_dist(rng);
cars.push_back(std::move(car));
}
return cars;
}
std::string_view serialize(simdjson::builder::string_builder &sb,
const std::vector<Car> &cars) {
sb.clear();
sb.start_array();
for (const auto &car : cars) {
sb.start_object();
sb.append_key_value("make", car.make);
sb.append_comma();
sb.append_key_value("model", car.model);
sb.append_comma();
sb.append_key_value("year", car.year);
sb.end_object();
}
sb.end_array();
std::string_view result;
if (sb.view().get(result)) {
return ""; // unexpected (error)
}
return result;
}
double pretty_print(const std::string &name, size_t num_chars,
std::pair<event_aggregate, size_t> result) {
const auto &agg = result.first;
size_t N = result.second;
num_chars *= N;
printf("%-40s : %8.2f ns %8.2f GB/s", name.c_str(),
agg.elapsed_ns() / num_chars, num_chars / agg.elapsed_ns());
if (collector.has_events()) {
printf(" %8.2f GHz %8.2f cycles/char %8.2f ins./char %8.2f i/c",
agg.cycles() / agg.elapsed_ns(), agg.cycles() / num_chars,
agg.instructions() / num_chars, agg.instructions() / agg.cycles());
}
printf("\n");
return num_chars / agg.elapsed_ns();
}
template <class function_type>
std::pair<event_aggregate, size_t>
bench(const function_type &&function, size_t min_repeat = 100,
size_t min_time_ns = 40'000'000, size_t max_repeat = 10000000) {
size_t N = min_repeat;
if (N == 0) {
N = 1;
}
event_aggregate warm_aggregate{};
for (size_t i = 0; i < N; i++) {
std::atomic_thread_fence(std::memory_order_acquire);
collector.start();
function();
std::atomic_thread_fence(std::memory_order_release);
event_count allocate_count = collector.end();
warm_aggregate << allocate_count;
if ((i + 1 == N) && (warm_aggregate.total_elapsed_ns() < min_time_ns) &&
(N < max_repeat)) {
N *= 10;
}
}
event_aggregate aggregate{};
for (size_t i = 0; i < 10; i++) {
std::atomic_thread_fence(std::memory_order_acquire);
collector.start();
for (size_t i = 0; i < N; i++) {
function();
}
std::atomic_thread_fence(std::memory_order_release);
event_count allocate_count = collector.end();
aggregate << allocate_count;
}
return {aggregate, N};
}
void run_benchmarks() {
std::vector<Car> source = generate_random_cars(100000);
simdjson::builder::string_builder sb;
size_t volume = serialize(sb, source).size();
pretty_print("string_builder", volume, bench([&source, &sb]() -> size_t {
return serialize(sb, source).size();
}));
}
int main() {
for (size_t trial = 0; trial < 3; trial++) {
printf("Trial %zu:\n", trial + 1);
run_benchmarks();
printf("\n");
}
return EXIT_SUCCESS;
}
+2 -3
View File
@@ -25,12 +25,11 @@ int main(int argc, char *argv[]) {
exit(1);
}
const char *filename = argv[1];
auto v = simdjson::padded_string::load(filename);
if (v.error()) {
simdjson::padded_string p;
if (simdjson::padded_string::load(filename).get(p)) {
std::cerr << "Could not load the file " << filename << std::endl;
return EXIT_FAILURE;
}
const simdjson::padded_string& p = v.value_unsafe();
if (test_baseline) {
std::wclog << "Baseline: Getline + normal parse... " << std::endl;
std::cout << "Gigabytes/second\t"
+1
View File
@@ -122,6 +122,7 @@ struct event_aggregate {
}
double elapsed_sec() const { return total.elapsed_sec() / iterations; }
double total_elapsed_ns() const { return total.elapsed_ns(); }
double elapsed_ns() const { return total.elapsed_ns() / iterations; }
double cycles() const { return total.cycles() / iterations; }
double instructions() const { return total.instructions() / iterations; }
+15
View File
@@ -0,0 +1,15 @@
# Executable
add_executable(from_benchmark from_benchmark.cpp)
# Compile for C++20.
if(CMAKE_CXX_STANDARD LESS 20)
target_compile_features(from_benchmark PRIVATE cxx_std_20)
endif()
# Check if -march=native is supported
include(CheckCXXCompilerFlag)
check_cxx_compiler_flag("-march=native" SIMDJSON_SUPPORTS_MARCH_NATIVE)
if(SIMDJSON_SUPPORTS_MARCH_NATIVE)
target_compile_options(from_benchmark PRIVATE -march=native)
endif()
target_include_directories(from_benchmark PRIVATE ${CMAKE_CURRENT_LIST_DIR}/..)
+77
View File
@@ -0,0 +1,77 @@
#ifndef BENCHMARK_HELPERS_H
#define BENCHMARK_HELPERS_H
#include "event_counter.h"
#include <atomic>
event_collector collector;
template <class function_type>
std::pair<event_aggregate, size_t>
bench(const function_type &&function, size_t min_repeat = 10,
size_t min_time_ns = 40'000'000, size_t max_repeat = 10000000) {
size_t N = min_repeat;
if (N == 0) {
N = 1;
}
event_aggregate warm_aggregate{};
for (size_t i = 0; i < N; i++) {
std::atomic_thread_fence(std::memory_order_acquire);
collector.start();
function();
std::atomic_thread_fence(std::memory_order_release);
event_count allocate_count = collector.end();
warm_aggregate << allocate_count;
if ((i + 1 == N) && (warm_aggregate.total_elapsed_ns() < min_time_ns) &&
(N < max_repeat)) {
N *= 10;
}
}
event_aggregate aggregate{};
for (size_t i = 0; i < 10; i++) {
std::atomic_thread_fence(std::memory_order_acquire);
collector.start();
for (size_t i = 0; i < N; i++) {
function();
}
std::atomic_thread_fence(std::memory_order_release);
event_count allocate_count = collector.end();
aggregate << allocate_count;
}
return {aggregate, N};
}
double pretty_print(const std::string &name, size_t num_chars,
std::pair<event_aggregate, size_t> result) {
const auto &agg = result.first;
size_t N = result.second;
num_chars *= N;
printf("%-40s : %8.2f ns %8.2f GB/s", name.c_str(),
agg.elapsed_ns() / num_chars, num_chars / agg.elapsed_ns());
if (collector.has_events()) {
printf(" %8.2f GHz %8.2f cycles/char %8.2f ins./char %8.2f i/c",
agg.cycles() / agg.elapsed_ns(), agg.cycles() / num_chars,
agg.instructions() / num_chars, agg.instructions() / agg.cycles());
}
printf("\n");
return num_chars / agg.elapsed_ns();
}
double pretty_print_array(const std::string &name, size_t num_chars, size_t num_elements,
std::pair<event_aggregate, size_t> result) {
const auto &agg = result.first;
size_t N = result.second;
num_chars *= N;
printf("%-40s : %8.2f ns/char %8.2f ns/value %8.2f GB/s", name.c_str(),
agg.elapsed_ns() / num_chars, agg.elapsed_ns() / num_elements, num_chars / agg.elapsed_ns());
if (collector.has_events()) {
printf(" %8.2f GHz %8.2f cycles/char %8.2f ins./char %8.2f ins./value %8.2f i/c",
agg.cycles() / agg.elapsed_ns(), agg.cycles() / num_chars,
agg.instructions() / num_chars, agg.instructions() / num_elements, agg.instructions() / agg.cycles());
}
printf("\n");
return num_chars / agg.elapsed_ns();
}
#endif
+146
View File
@@ -0,0 +1,146 @@
#ifndef CAR_HELPERS_H
#define CAR_HELPERS_H
#include <iomanip>
#include <random>
#include <sstream>
#include <vector>
#include <simdjson.h>
struct Car {
std::string make;
std::string model;
int64_t year; // We deliberately do not include the tire pressure.
};
using namespace simdjson;
std::string random_car_json() {
static const std::vector<std::string> makes = {"Toyota", "Honda", "Ford",
"BMW", "Mazda"};
static const std::vector<std::string> models = {"Camry", "Civic", "Focus",
"320i", "3"};
static thread_local std::mt19937 rng{std::random_device{}()};
std::uniform_int_distribution<int> make_dist(0, makes.size() - 1);
std::uniform_int_distribution<int> model_dist(0, models.size() - 1);
std::uniform_int_distribution<int> year_dist(2000, 2025);
std::uniform_real_distribution<double> pressure_dist(30.0, 45.0);
std::ostringstream oss;
oss << R"({ "make": ")" << makes[make_dist(rng)] << R"(", "model": ")"
<< models[model_dist(rng)] << R"(", "year": )" << year_dist(rng)
<< R"(, "tire_pressure": [ )" << std::fixed << std::setprecision(1)
<< pressure_dist(rng) << ", " << pressure_dist(rng) << R"( ] })";
return oss.str();
}
std::string generate_car_json_stream(size_t N) {
std::ostringstream oss;
for (size_t i = 0; i < N; ++i) {
oss << random_car_json();
oss << "\n";
}
return oss.str();
}
std::string generate_car_json_array(size_t N) {
std::ostringstream oss;
oss << "[\n";
for (size_t i = 0; i < N; ++i) {
oss << random_car_json();
if (i < N - 1) {
oss << ",";
}
oss << "\n";
}
oss << "]";
return oss.str();
}
// We want to maximize C++ portability, but this is not needed with static
// reflection (C++26).
template <>
simdjson_inline simdjson_result<Car>
simdjson::ondemand::document::get() & noexcept {
ondemand::object obj;
auto error = get_object().get(obj);
if (error) {
return error;
}
Car car;
if ((error = obj["make"].get_string(car.make))) {
return error;
}
if ((error = obj["model"].get_string(car.model))) {
return error;
}
if ((error = obj["year"].get_int64().get(car.year))) {
return error;
}
return car;
}
template <>
simdjson_inline simdjson_result<Car> simdjson::ondemand::value::get() noexcept {
ondemand::object obj;
auto error = get_object().get(obj);
if (error) {
return error;
}
Car car;
if ((error = obj["make"].get_string(car.make))) {
return error;
}
if ((error = obj["model"].get_string(car.model))) {
return error;
}
if ((error = obj["year"].get_int64().get(car.year))) {
return error;
}
return car;
}
template <>
simdjson_inline simdjson_result<Car>
simdjson::ondemand::document_reference::get() & noexcept {
ondemand::object obj;
auto error = get_object().get(obj);
if (error) {
return error;
}
Car car;
if ((error = obj["make"].get_string(car.make))) {
return error;
}
if ((error = obj["model"].get_string(car.model))) {
return error;
}
if ((error = obj["year"].get_int64().get(car.year))) {
return error;
}
return car;
}
template <>
simdjson_inline simdjson_result<Car>
simdjson::ondemand::document_reference::get() && noexcept {
ondemand::object obj;
auto error = get_object().get(obj);
if (error) {
return error;
}
Car car;
if ((error = obj["make"].get_string(car.make))) {
return error;
}
if ((error = obj["model"].get_string(car.model))) {
return error;
}
if ((error = obj["year"].get_int64().get(car.year))) {
return error;
}
return car;
}
#endif
+132
View File
@@ -0,0 +1,132 @@
// We are going to assume C++20.
#include <cstdint>
#include <cstdio>
#include <cstdlib>
#include <simdjson.h>
#include <string>
#include "benchmark_helpers.h"
#include "car_helpers.h"
using namespace simdjson;
void run_benchmarks() {
printf("Running benchmarks on tiny input...\n");
simdjson::padded_string json =
R"({ "make": "Toyota", "model": "Camry", "year": 2018, "tire_pressure": [ 40.1, 39.9 ] })"_padded;
volatile size_t dummy = 0;
ondemand::parser parser;
// Classic On-Demand
pretty_print("simdjson classic", json.size(),
bench([&json, &dummy, &parser]() {
ondemand::document doc = parser.iterate(json);
Car car = doc.get<Car>();
dummy = dummy + car.year;
}));
// Benchmark simdjson::from() without parser
pretty_print("simdjson::from<Car>() (no parser)", json.size(),
bench([&json, &dummy]() {
Car car = simdjson::from(json);
dummy = dummy + car.year;
}));
// Benchmark simdjson::from() with parser
pretty_print("simdjson::from<Car>() (with parser)", json.size(),
bench([&json, &dummy, &parser]() {
Car car = simdjson::from(parser, json);
dummy = dummy + car.year;
}));
}
void run_array_benchmarks() {
printf("Running benchmarks on large array...\n");
size_t N = 1'000'000;
simdjson::padded_string json = generate_car_json_array(N);
volatile size_t dummy = 0;
ondemand::parser parser;
// Classic On-Demand
pretty_print_array("simdjson classic", json.size(), N,
bench([&json, &dummy, &parser]() {
dummy = 0;
ondemand::document doc = parser.iterate(json);
ondemand::array array = doc.get_array();
for (auto value : array) {
Car car = value.get<Car>();
dummy = dummy + car.year;
}
}));
// from with array
pretty_print_array("simdjson::from(json).array()", json.size(), N,
bench([&json, &dummy, &parser]() {
dummy = 0;
for (auto value : simdjson::from(json).array()) {
Car car = value.get<Car>();
dummy = dummy + car.year;
}
}));
#if SIMDJSON_SUPPORTS_RANGES_FROM
// Benchmark simdjson::from() without parser (EXPERIMENTAL)
pretty_print_array("simdjson::from<Car>() (experimental, no parser)",
json.size(), N, bench([&json, &dummy]() {
dummy = 0;
for (Car car :
simdjson::from(json) | simdjson::as<Car>()) {
dummy = dummy + car.year;
}
}));
// Benchmark simdjson::from() with parser (EXPERIMENTAL)
pretty_print_array("simdjson::from<Car>() (experimental, with parser)",
json.size(), N, bench([&json, &dummy, &parser]() {
dummy = 0;
for (Car car : simdjson::from(parser, json) |
simdjson::as<Car>()) {
dummy = dummy + car.year;
}
}));
#endif // SIMDJSON_SUPPORTS_RANGES_FROM
}
void run_stream_benchmarks() {
printf("Running stream benchmarks...\n");
simdjson::padded_string json = generate_car_json_stream(1'000'000);
volatile size_t dummy = 0;
ondemand::parser parser;
// Classic On-Demand
pretty_print("simdjson classic", json.size(),
bench([&json, &dummy, &parser]() {
ondemand::document_stream stream = parser.iterate_many(json);
for (auto doc : stream) {
Car car = doc.get<Car>();
dummy = dummy + car.year;
}
}));
// from_many
pretty_print("simdjson with thread local", json.size(),
bench([&json, &dummy, &parser]() {
ondemand::document_stream stream =
ondemand::parser::get_parser().iterate_many(json);
for (auto doc : stream) {
Car car = doc.get<Car>();
dummy = dummy + car.year;
}
}));
}
int main() {
for (size_t trial = 0; trial < 3; trial++) {
printf("Trial %zu:\n", trial + 1);
run_array_benchmarks();
run_stream_benchmarks();
run_benchmarks();
printf("\n");
}
return EXIT_SUCCESS;
}
+5 -1
View File
@@ -9,7 +9,11 @@ simdjson_never_inline
double bench(std::string filename, simdjson::padded_string& p) {
std::chrono::time_point<std::chrono::steady_clock> start_clock =
std::chrono::steady_clock::now();
simdjson::padded_string::load(filename).value_unsafe().swap(p);
auto error = simdjson::padded_string::load(filename).get(p);
if(error) {
std::cerr << simdjson::error_message(error) << std::endl;
std::abort();
}
std::chrono::time_point<std::chrono::steady_clock> end_clock =
std::chrono::steady_clock::now();
std::chrono::duration<double> elapsed = end_clock - start_clock;
@@ -23,7 +23,29 @@ struct simdjson_ondemand {
};
BENCHMARK_TEMPLATE(large_random, simdjson_ondemand)->UseManualTime();
#if SIMDJSON_STATIC_REFLECTION
struct simdjson_ondemand_static_reflect {
static constexpr diff_flags DiffFlags = diff_flags::NONE;
ondemand::parser parser{};
bool run(simdjson::padded_string &json, std::vector<point> &result) {
auto doc = parser.iterate(json);
if(auto e = doc.get_array().get<std::vector<point>>(result); e) { return false; }
// We can also do it like so:
//for (ondemand::object coord : doc) {
// result.emplace_back(coord.get<point>());
//}
// It seems that doing the reflection is slower than doing the manual lookup.
// E.g., it is faster if we do result.emplace_back(coord["x"], coord["y"], coord["z"]);
return true;
}
};
BENCHMARK_TEMPLATE(large_random, simdjson_ondemand_static_reflect)->UseManualTime();
#endif
} // namespace large_random
#endif // SIMDJSON_EXCEPTIONS
+48
View File
@@ -0,0 +1,48 @@
# Include reflect-cpp
CPMAddPackage(
NAME reflect-cpp
GITHUB_REPOSITORY getml/reflect-cpp
GIT_TAG v0.17.0
EXCLUDE_FROM_ALL YES
)
option(SIMDJSON_USE_RUST "Build the static_reflect benchmark" OFF)
if(SIMDJSON_USER_RUST)
if(NOT WIN32)
# We want the check whether Rust is available before trying to build a crate.
CPMAddPackage(
NAME corrosion
GITHUB_REPOSITORY corrosion-rs/corrosion
VERSION 0.4.4
DOWNLOAD_ONLY ON
OPTIONS "Rust_FIND_QUIETLY OFF"
)
include("${corrosion_SOURCE_DIR}/cmake/FindRust.cmake")
endif()
if(RUST_FOUND)
message(STATUS "Rust found: " ${Rust_VERSION} )
add_subdirectory("${corrosion_SOURCE_DIR}" "${PROJECT_BINARY_DIR}/_deps/corrosion" EXCLUDE_FROM_ALL)
# Important: we want to build in release mode!
corrosion_import_crate(MANIFEST_PATH "serde-benchmark/Cargo.toml" NO_LINKER_OVERRIDE PROFILE release)
else()
message(STATUS "Rust/Cargo is unavailable." )
message(STATUS "We will not benchmark serde-benchmark." )
if (${CMAKE_SYSTEM_NAME} MATCHES "Darwin")
message(STATUS "Under macOS, you may be able to install rust with")
message(STATUS "curl https://sh.rustup.rs -sSf | sh")
elseif(CMAKE_SYSTEM_NAME STREQUAL "Linux")
message(STATUS "Under Linux, you may be able to install rust with a command such as")
message(STATUS "apt-get install cargo" )
message(STATUS "or" )
message(STATUS "curl https://sh.rustup.rs -sSf | sh")
endif()
endif()
else(SIMDJSON_USER_RUST)
message(STATUS "We will not benchmark serde-benchmark." )
endif(SIMDJSON_USER_RUST)
# Add the benchmark executable targets
add_subdirectory(twitter_benchmark)
add_subdirectory(citm_catalog_benchmark)
@@ -0,0 +1,52 @@
#ifndef BENCHMARK_HELPER_HPP
#define BENCHMARK_HELPER_HPP
#include "event_counter.h"
#include <atomic>
inline event_collector &get_collector() {
static event_collector collector;
return collector;
}
template <class function_type>
event_aggregate bench(const function_type &function, size_t min_repeat = 10,
size_t min_time_ns = 1000000000,
size_t max_repeat = 100000) {
event_collector &collector = get_collector();
event_aggregate aggregate{};
size_t N = min_repeat;
if (N == 0) {
N = 1;
}
for (size_t i = 0; i < N; i++) {
std::atomic_thread_fence(std::memory_order_acquire);
collector.start();
function();
std::atomic_thread_fence(std::memory_order_release);
event_count allocate_count = collector.end();
aggregate << allocate_count;
if ((i + 1 == N) && (aggregate.total_elapsed_ns() < min_time_ns) &&
(N < max_repeat)) {
N *= 10;
}
}
return aggregate;
}
// Source of the 2 functions below:
// https://github.com/simdutf/simdutf/blob/master/benchmarks/base64/benchmark_base64.cpp
inline void pretty_print(size_t strings, size_t bytes, std::string name,
event_aggregate agg) {
event_collector &collector = get_collector();
printf("%-60s : ", name.c_str());
printf(" %5.2f MB/s ", bytes * 1000 / agg.elapsed_ns());
printf(" %5.2f Ms/s ", strings * 1000 / agg.elapsed_ns());
if (collector.has_events()) {
printf(" %5.2f GHz ", agg.cycles() / agg.elapsed_ns());
printf(" %5.2f c/b ", agg.cycles() / bytes);
printf(" %5.2f i/b ", agg.instructions() / bytes);
printf(" %5.2f i/c ", agg.instructions() / agg.cycles());
}
printf("\n");
}
#endif
@@ -0,0 +1,14 @@
add_executable(benchmark_serialization_citm_catalog benchmark_serialization_citm_catalog.cpp)
# Link with Rust benchmarking code if available
if(TARGET serde-benchmark)
message(STATUS "serde-benchmark target was created. Linking CITM catalog benchmark with serde-benchmark.")
target_link_libraries(benchmark_serialization_citm_catalog PRIVATE serde-benchmark)
target_compile_definitions(benchmark_serialization_citm_catalog PRIVATE SIMDJSON_RUST_VERSION="${Rust_VERSION}")
endif()
target_link_libraries(benchmark_serialization_citm_catalog PRIVATE simdjson::simdjson nlohmann_json)
target_link_libraries(benchmark_serialization_citm_catalog PRIVATE reflectcpp)
target_compile_definitions(benchmark_serialization_citm_catalog PRIVATE SIMDJSON_BENCH_CPP_REFLECT=1)
target_compile_definitions(benchmark_serialization_citm_catalog PRIVATE JSON_FILE="${BENCH_CITM_JSON}")
@@ -0,0 +1,177 @@
#include <cassert>
#include <cstdlib>
#include <ctime>
#include <format>
#include <fstream>
#include <iostream>
#include <nlohmann/json.hpp>
#include <simdjson.h>
#include <string>
#include "citm_catalog_data.h"
#include "nlohmann_citm_catalog_data.h"
#include "../benchmark_utils/benchmark_helper.h"
#if SIMDJSON_BENCH_CPP_REFLECT
#include <rfl.hpp>
#include <rfl/json.hpp>
void bench_reflect_cpp(CitmCatalog &data) {
std::string output = rfl::json::write(data);
size_t output_volume = output.size();
printf("# output volume: %zu bytes\n", output_volume);
volatile size_t measured_volume = 0;
pretty_print(1, output_volume, "bench_reflect_cpp",
bench([&data, &measured_volume, &output_volume]() {
std::string output = rfl::json::write(data);
measured_volume = output.size();
if (measured_volume != output_volume) {
printf("mismatch\n");
}
}));
}
#endif // SIMDJSON_BENCH_CPP_REFLECT
#ifdef SIMDJSON_RUST_VERSION
#include "../serde-benchmark/serde_benchmark.h"
void bench_rust(serde_benchmark::CitmCatalog *data) {
const char * output = serde_benchmark::str_from_citm(data);
size_t output_volume = strlen(output);
printf("# output volume: %zu bytes\n", output_volume);
volatile size_t measured_volume = 0;
pretty_print(1, output_volume, "bench_rust",
bench([&data, &measured_volume, &output_volume]() {
const char * output = serde_benchmark::str_from_citm(data);
measured_volume = strlen(output);
if (measured_volume != output_volume) {
printf("mismatch\n");
}
serde_benchmark::free_str(const_cast<char*>(output));
}));
serde_benchmark::free_str(const_cast<char*>(output));
}
#endif // SIMDJSON_RUST_VERSION
void bench_nlohmann(CitmCatalog &data) {
std::string output = nlohmann_serialize(data);
size_t output_volume = output.size();
printf("# output volume: %zu bytes\n", output_volume);
volatile size_t measured_volume = 0;
pretty_print(1, output_volume, "bench_nlohmann",
bench([&data, &measured_volume, &output_volume]() {
std::string output = nlohmann_serialize(data);
measured_volume = output.size();
if (measured_volume != output_volume) {
printf("mismatch\n");
}
}));
}
void bench_simdjson_static_reflection(CitmCatalog &data) {
simdjson::builder::string_builder sb;
simdjson::builder::append(sb, data);
std::string_view p;
if(sb.view().get(p)) {
std::cerr << "Error!" << std::endl;
}
size_t output_volume = p.size();
sb.clear();
printf("# output volume: %zu bytes\n", output_volume);
volatile size_t measured_volume = 0;
pretty_print(sizeof(data), output_volume, "bench_simdjson_static_reflection",
bench([&data, &measured_volume, &output_volume, &sb]() {
sb.clear();
simdjson::builder::append(sb, data);
std::string_view p;
if(sb.view().get(p)) {
std::cerr << "Error!" << std::endl;
}
measured_volume = sb.size();
if (measured_volume != output_volume) {
printf("mismatch\n");
}
}));
}
std::string read_file(const std::string &file_path, size_t read_size = 65536) {
std::ifstream stream(file_path, std::ios::binary);
if(!stream) {
std::cerr << "Could not open file '" << file_path << "'" << std::endl;
exit(EXIT_FAILURE);
}
stream.exceptions(std::ios_base::badbit);
std::string out;
std::string buf(read_size, '\0');
while (stream.read(&buf[0], read_size)) {
out.append(buf, 0, size_t(stream.gcount()));
}
out.append(buf, 0, size_t(stream.gcount()));
return out;
}
// Function to check if benchmark name contains filter substring
bool matches_filter(const std::string& benchmark_name, const std::string& filter) {
return filter.empty() || benchmark_name.find(filter) != std::string::npos;
}
int main(int argc, char* argv[]) {
std::string filter;
// Parse command-line arguments
for (int i = 1; i < argc; ++i) {
if (strcmp(argv[i], "-f") == 0 || strcmp(argv[i], "--filter") == 0) {
if (i + 1 < argc) {
filter = argv[++i];
} else {
std::cerr << "Error: -f/--filter requires an argument" << std::endl;
return EXIT_FAILURE;
}
}
}
// Testing correctness of round-trip (serialization + deserialization)
std::string json_str = read_file(JSON_FILE);
// Loading up the data into a structure.
simdjson::ondemand::parser parser;
simdjson::ondemand::document doc;
if(parser.iterate(simdjson::pad(json_str)).get(doc)) {
std::cerr << "Error loading the document!" << std::endl;
return EXIT_FAILURE;
}
CitmCatalog my_struct;
if(doc.get<CitmCatalog>().get(my_struct)) {
std::cerr << "Error loading CitmCatalog!" << std::endl;
return EXIT_FAILURE;
}
// Benchmarking the serialization
if (matches_filter("nlohmann", filter)) {
bench_nlohmann(my_struct);
}
if (matches_filter("simdjson_static_reflection", filter)) {
bench_simdjson_static_reflection(my_struct);
}
#ifdef SIMDJSON_RUST_VERSION
if (matches_filter("rust", filter)) {
printf("# WARNING: The Rust benchmark may not be directly comparable since it does not use an equivalent data structure.\n");
// Create a Rust-compatible CitmCatalog structure from the JSON string
serde_benchmark::CitmCatalog* rust_data =
serde_benchmark::citm_from_str(json_str.c_str(), json_str.size());
if (rust_data == nullptr) {
printf("# Failed to initialize Rust data structure\n");
} else {
bench_rust(rust_data);
serde_benchmark::free_citm(rust_data);
}
}
#endif
#if SIMDJSON_BENCH_CPP_REFLECT
if (matches_filter("reflect_cpp", filter)) {
bench_reflect_cpp(my_struct);
}
#endif
return EXIT_SUCCESS;
}
@@ -0,0 +1,82 @@
#ifndef CITM_CATALOG_DATA_H
#define CITM_CATALOG_DATA_H
#include <string>
#include <vector>
#include <map>
struct Area {
int64_t id;
std::string name;
int64_t parent;
std::vector<int64_t> childAreas;
bool operator==(const Area &other) const = default;
};
struct AudienceSubCategory {
int64_t id;
std::string name;
int64_t parent;
bool operator==(const AudienceSubCategory &other) const = default;
};
struct Event {
int64_t id;
std::string name;
std::string description;
int64_t subTopic;
int64_t topic;
std::vector<int64_t> audience;
bool operator==(const Event &other) const = default;
};
struct Performance {
int64_t id;
std::string name;
int64_t event;
std::string start;
int64_t venueCode;
bool operator==(const Performance &other) const = default;
};
struct SeatCategory {
int64_t id;
std::string name;
std::vector<int64_t> areas;
bool operator==(const SeatCategory &other) const = default;
};
struct SubTopic {
int64_t id;
std::string name;
int64_t parent;
bool operator==(const SubTopic &other) const = default;
};
struct Topic {
int64_t id;
std::string name;
bool operator==(const Topic &other) const = default;
};
struct Venue {
int64_t id;
std::string name;
int64_t address;
bool operator==(const Venue &other) const = default;
};
struct CitmCatalog {
std::map<std::string, Area> areas;
std::map<std::string, AudienceSubCategory> audienceSubCategory;
std::map<std::string, Event> events;
std::map<std::string, Performance> performances;
std::map<std::string, SeatCategory> seatCategory;
std::map<std::string, SubTopic> subTopic;
std::map<std::string, Topic> topic;
std::map<std::string, Venue> venue;
bool operator==(const CitmCatalog &other) const = default;
};
#endif
@@ -0,0 +1,171 @@
// nlohmann_citm_catalog_data.h
#ifndef NLOHMANN_CITM_CATALOG_DATA_H
#define NLOHMANN_CITM_CATALOG_DATA_H
#include "citm_catalog_data.h"
#include <nlohmann/json.hpp>
#include <string>
using json = nlohmann::json;
// ---- Area ----
inline void to_json(json &j, const Area &a) {
j = json{
{"id", a.id},
{"name", a.name},
{"parent", a.parent},
{"childAreas", a.childAreas}
};
}
inline void from_json(const json &j, Area &a) {
j.at("id").get_to(a.id);
j.at("name").get_to(a.name);
j.at("parent").get_to(a.parent);
j.at("childAreas").get_to(a.childAreas);
}
// ---- AudienceSubCategory ----
inline void to_json(json &j, const AudienceSubCategory &asc) {
j = json{
{"id", asc.id},
{"name", asc.name},
{"parent", asc.parent}
};
}
inline void from_json(const json &j, AudienceSubCategory &asc) {
j.at("id").get_to(asc.id);
j.at("name").get_to(asc.name);
j.at("parent").get_to(asc.parent);
}
// ---- Event ----
inline void to_json(json &j, const Event &e) {
j = json{
{"id", e.id},
{"name", e.name},
{"description", e.description},
{"subTopic", e.subTopic},
{"topic", e.topic},
{"audience", e.audience}
};
}
inline void from_json(const json &j, Event &e) {
j.at("id").get_to(e.id);
j.at("name").get_to(e.name);
j.at("description").get_to(e.description);
j.at("subTopic").get_to(e.subTopic);
j.at("topic").get_to(e.topic);
j.at("audience").get_to(e.audience);
}
// ---- Performance ----
inline void to_json(json &j, const Performance &p) {
j = json{
{"id", p.id},
{"name", p.name},
{"event", p.event},
{"start", p.start},
{"venueCode", p.venueCode}
};
}
inline void from_json(const json &j, Performance &p) {
j.at("id").get_to(p.id);
j.at("name").get_to(p.name);
j.at("event").get_to(p.event);
j.at("start").get_to(p.start);
j.at("venueCode").get_to(p.venueCode);
}
// ---- SeatCategory ----
inline void to_json(json &j, const SeatCategory &sc) {
j = json{
{"id", sc.id},
{"name", sc.name},
{"areas", sc.areas}
};
}
inline void from_json(const json &j, SeatCategory &sc) {
j.at("id").get_to(sc.id);
j.at("name").get_to(sc.name);
j.at("areas").get_to(sc.areas);
}
// ---- SubTopic ----
inline void to_json(json &j, const SubTopic &st) {
j = json{
{"id", st.id},
{"name", st.name},
{"parent", st.parent}
};
}
inline void from_json(const json &j, SubTopic &st) {
j.at("id").get_to(st.id);
j.at("name").get_to(st.name);
j.at("parent").get_to(st.parent);
}
// ---- Topic ----
inline void to_json(json &j, const Topic &t) {
j = json{
{"id", t.id},
{"name", t.name}
};
}
inline void from_json(const json &j, Topic &t) {
j.at("id").get_to(t.id);
j.at("name").get_to(t.name);
}
// ---- Venue ----
inline void to_json(json &j, const Venue &v) {
j = json{
{"id", v.id},
{"name", v.name},
{"address", v.address}
};
}
inline void from_json(const json &j, Venue &v) {
j.at("id").get_to(v.id);
j.at("name").get_to(v.name);
j.at("address").get_to(v.address);
}
// ---- CitmCatalog ----
inline void to_json(json &j, const CitmCatalog &c) {
j = json{
{"areas", c.areas},
{"audienceSubCategory", c.audienceSubCategory},
{"events", c.events},
{"performances", c.performances},
{"seatCategory", c.seatCategory},
{"subTopic", c.subTopic},
{"topic", c.topic},
{"venue", c.venue}
};
}
inline void from_json(const json &j, CitmCatalog &c) {
j.at("areas").get_to(c.areas);
j.at("audienceSubCategory").get_to(c.audienceSubCategory);
j.at("events").get_to(c.events);
j.at("performances").get_to(c.performances);
j.at("seatCategory").get_to(c.seatCategory);
j.at("subTopic").get_to(c.subTopic);
j.at("topic").get_to(c.topic);
j.at("venue").get_to(c.venue);
}
// Optional convenience functions for benchmarking
inline std::string nlohmann_serialize(const CitmCatalog &catalog) {
json j = catalog;
return j.dump();
}
inline bool nlohmann_deserialize(const std::string &json_in, CitmCatalog &catalog) {
try {
catalog = json::parse(json_in);
return false; // success
} catch(...) {
return true; // failure
}
}
#endif // NLOHMANN_CITM_CATALOG_DATA_H
+103
View File
@@ -0,0 +1,103 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 3
[[package]]
name = "itoa"
version = "1.0.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b"
[[package]]
name = "libc"
version = "0.2.158"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d8adc4bb1803a324070e64a98ae98f38934d91957a99cfb3a43dcbc01bc56439"
[[package]]
name = "memchr"
version = "2.7.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3"
[[package]]
name = "proc-macro2"
version = "1.0.86"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77"
dependencies = [
"unicode-ident",
]
[[package]]
name = "quote"
version = "1.0.37"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af"
dependencies = [
"proc-macro2",
]
[[package]]
name = "ryu"
version = "1.0.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f"
[[package]]
name = "serde"
version = "1.0.209"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "99fce0ffe7310761ca6bf9faf5115afbc19688edd00171d81b1bb1b116c63e09"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde-benchmark"
version = "0.1.0"
dependencies = [
"libc",
"serde",
"serde_json",
]
[[package]]
name = "serde_derive"
version = "1.0.209"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a5831b979fd7b5439637af1752d535ff49f4860c0f341d1baeb6faf0f4242170"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "serde_json"
version = "1.0.127"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8043c06d9f82bd7271361ed64f415fe5e12a77fdb52e573e7f06a516dea329ad"
dependencies = [
"itoa",
"memchr",
"ryu",
"serde",
]
[[package]]
name = "syn"
version = "2.0.76"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "578e081a14e0cefc3279b0472138c513f37b41a08d5a3cca9b6e4e8ceb6cd525"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "unicode-ident"
version = "1.0.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b"
@@ -0,0 +1,17 @@
[package]
name = "serde-benchmark"
version = "0.1.0"
[lib]
path = "lib.rs"
crate-type = ["cdylib"]
[dependencies]
serde = { version = "1.0", features = ["derive"] }
libc = "0.2"
serde_json = "1.0"
[profile.release]
opt-level = 3
debug = false
lto = true
@@ -0,0 +1,18 @@
## Rust Serde FFI
This folder includes FFI bindings for rust/serde.
### Links
- https://github.com/eqrion/cbindgen/blob/master/docs.md
- https://gist.github.com/zbraniecki/b251714d77ffebbc73c03447f2b2c69f
- https://michael-f-bryan.github.io/rust-ffi-guide/setting_up.html
### Building
- Generating cbindgen output
- Install dependencies with `brew install cbindgen` or `apt-get install cbindgen` or `cargo install cbindgen` or the equivalent: we used `cargo install --version 0.23.0 cbindgen`.
- Go to the directory where this README.md file is located
- Generate with `cbindgen --config cbindgen.toml --crate serde-benchmark --output serde_benchmark.h`
- Building
- Run with `cargo build --release`
@@ -0,0 +1,12 @@
autogen_warning = "/* Warning, this file is autogenerated by cbindgen. Don't modify this manually. */"
include_version = true
braces = "SameLine"
line_length = 100
tab_width = 2
language = "C++"
namespaces = ["serde_benchmark"]
include_guard = "serde_benchmark_ffi_h"
[parse]
parse_deps = true
include = ["serde_json", "serde"]
@@ -0,0 +1,482 @@
extern crate serde;
extern crate serde_json;
extern crate libc;
use libc::{c_char, size_t};
use serde::{Serialize, Deserialize};
use std::{collections::HashMap, ffi::CString, ptr, slice};
use serde::de::{self, Deserializer};
/******************************************************/
/******************************************************/
/**
* Warning: the C++ code may not generate the same JSON.
*/
/******************************************************/
/******************************************************/
// This has no equivalent in C++:
#[derive(Serialize, Deserialize)]
pub struct Metadata {
result_type: String,
iso_language_code: String,
}
#[derive(Serialize, Deserialize)]
pub struct User {
id: i64,
id_str: String,
name: String,
screen_name: String,
location: String,
description: String,
// C++ does not have those:
// url: Option<String>,
//protected: bool,
//listed_count: i64,
//created_at: String,
//favourites_count: i64,
//utc_offset: Option<i64>,
//time_zone: Option<String>,
//geo_enabled: bool,
verified: bool,
followers_count: i64,
friends_count: i64,
statuses_count: i64,
// C++ does not have those:
//lang: String,
//profile_background_color: String,
//profile_background_image_url: String,
//profile_background_image_url_https: String,
//profile_background_tile: bool,
//profile_image_url: String,
//profile_image_url_https: String,
//profile_banner_url: Option<String>,
//profile_link_color: String,
//profile_sidebar_border_color: String,
//profile_sidebar_fill_color: String,
//profile_text_color: String,
//profile_use_background_image: bool,
//default_profile: bool,
//default_profile_image: bool,
//following: bool,
//follow_request_sent: bool,
//notifications: bool,
}
#[derive(Serialize, Deserialize)]
pub struct Hashtag {
text: String,
// C++ has those but D. Lemire does not know what they are, they don't appear in the JSON:
// int64_t indices_start;
// int64_t indices_end;
}
#[derive(Serialize, Deserialize)]
pub struct Url {
url: String,
expanded_url: String,
display_url: String,
// C++ has those but D. Lemire does not know what they are, they don't appear in the JSON:
// int64_t indices_start;
// int64_t indices_end;
}
#[derive(Serialize, Deserialize)]
pub struct UserMention {
id: i64,
name: String,
screen_name: String,
// Not in the C++ equivalent:
//id_str: String,
//indices: Vec<i64>,
// C++ has those but D. Lemire does not know what they are, they don't appear in the JSON:
// int64_t indices_start;
// int64_t indices_end;
}
#[derive(Serialize, Deserialize)]
pub struct Entities {
hashtags: Vec<Hashtag>,
urls: Vec<Url>,
user_mentions: Vec<UserMention>,
}
#[derive(Serialize, Deserialize)]
pub struct Status {
created_at: String,
id: i64,
text: String,
user: User,
entities: Entities,
retweet_count: i64,
favorite_count: i64,
favorited: bool,
retweeted: bool,
// None of these are in the C++ equivalent:
/*
metadata: Metadata,
id_str: String,
source: String,
truncated: bool,
in_reply_to_status_id: Option<i64>,
in_reply_to_status_id_str: Option<String>,
in_reply_to_user_id: Option<i64>,
in_reply_to_user_id_str: Option<String>,
in_reply_to_screen_name: Option<String>,
geo: Option<String>,
coordinates: Option<String>,
place: Option<String>,
contributors: Option<String>,
lang: String,
*/
}
#[derive(Serialize, Deserialize)]
pub struct TwitterData {
statuses: Vec<Status>,
}
#[no_mangle]
pub unsafe extern "C" fn twitter_from_str(raw_input: *const c_char, raw_input_length: size_t) -> *mut TwitterData {
let input = std::str::from_utf8_unchecked(slice::from_raw_parts(raw_input as *const u8, raw_input_length));
match serde_json::from_str(&input) {
Ok(result) => Box::into_raw(Box::new(result)),
Err(_) => std::ptr::null_mut(),
}
}
#[no_mangle]
pub unsafe extern "C" fn str_from_twitter(raw: *mut TwitterData) -> *const c_char {
let twitter_thing = { &*raw };
let serialized = serde_json::to_string(&twitter_thing).unwrap();
return std::ffi::CString::new(serialized.as_str()).unwrap().into_raw()
}
#[no_mangle]
pub unsafe extern "C" fn free_twitter(raw: *mut TwitterData) {
if raw.is_null() {
return;
}
drop(Box::from_raw(raw))
}
#[no_mangle]
pub unsafe extern fn free_string(ptr: *const c_char) {
let _ = std::ffi::CString::from_raw(ptr as *mut _);
}
// Functions associated with the CitmCatalog benchmark
#[derive(Serialize, Deserialize)]
pub struct Area {
pub id: i64,
pub name: Option<String>, // Changed to Option
pub parent: i64,
#[serde(rename = "childAreas")]
pub child_areas: Vec<i64>,
}
#[derive(Serialize, Deserialize)]
pub struct AudienceSubCategory {
pub id: i64,
pub name: Option<String>, // Changed to Option
pub parent: i64,
}
#[derive(Serialize, Deserialize, Debug)]
pub struct Event {
#[serde(default)]
pub description: Option<String>,
pub id: i64,
#[serde(default)]
pub logo: Option<String>,
#[serde(default)]
pub name: Option<String>,
#[serde(default)]
pub subTopicIds: Vec<i64>,
#[serde(default)]
pub subjectCode: Option<String>,
#[serde(default)]
pub subtitle: Option<String>,
#[serde(default)]
pub topicIds: Vec<i64>,
// Add a catch-all for any other fields
#[serde(flatten)]
pub extra: HashMap<String, serde_json::Value>,
}
#[derive(Serialize, Deserialize, Debug)]
pub struct Performance {
#[serde(default)]
pub id: i64,
#[serde(default)]
pub name: Option<String>,
#[serde(default)]
pub event: i64,
// This is the key fix - accept any JSON value type for timestamps
// This allows both string dates and integer timestamps (line 3511)
#[serde(default)]
pub start: serde_json::Value,
#[serde(rename = "venueCode")]
pub venue_code: String,
// Add a catch-all for any other fields
#[serde(flatten)]
pub extra: HashMap<String, serde_json::Value>,
}
#[derive(Serialize, Deserialize)]
pub struct SeatCategory {
pub id: i64,
pub name: Option<String>, // Changed to Option
pub areas: Vec<i64>,
}
#[derive(Serialize, Deserialize)]
pub struct SubTopic {
pub id: i64,
pub name: Option<String>, // Changed to Option
pub parent: i64,
}
#[derive(Serialize, Deserialize)]
pub struct Topic {
pub id: i64,
pub name: Option<String>, // Changed to Option
}
#[derive(Serialize, Deserialize)]
pub struct Venue {
pub id: i64,
pub name: Option<String>, // Changed to Option
pub address: i64,
}
// Custom deserializers
fn deserialize_string_to_area<'de, D>(deserializer: D) -> Result<HashMap<String, Area>, D::Error>
where D: Deserializer<'de> {
let string_map: HashMap<String, String> = HashMap::deserialize(deserializer)?;
let mut result = HashMap::new();
for (id, name) in string_map {
let id_num = id.parse::<i64>().unwrap_or(0);
result.insert(id.clone(), Area {
id: id_num,
name: Some(name),
parent: 0,
child_areas: Vec::new(),
});
}
Ok(result)
}
fn deserialize_string_to_audience_subcategory<'de, D>(deserializer: D) -> Result<HashMap<String, AudienceSubCategory>, D::Error>
where D: Deserializer<'de> {
let string_map: HashMap<String, String> = HashMap::deserialize(deserializer)?;
let mut result = HashMap::new();
for (id, name) in string_map {
let id_num = id.parse::<i64>().unwrap_or(0);
result.insert(id.clone(), AudienceSubCategory {
id: id_num,
name: Some(name),
parent: 0,
});
}
Ok(result)
}
fn deserialize_string_to_seat_category<'de, D>(deserializer: D) -> Result<HashMap<String, SeatCategory>, D::Error>
where D: Deserializer<'de> {
let string_map: HashMap<String, String> = HashMap::deserialize(deserializer)?;
let mut result = HashMap::new();
for (id, name) in string_map {
let id_num = id.parse::<i64>().unwrap_or(0);
result.insert(id.clone(), SeatCategory {
id: id_num,
name: Some(name),
areas: Vec::new(),
});
}
Ok(result)
}
fn deserialize_string_to_subtopic<'de, D>(deserializer: D) -> Result<HashMap<String, SubTopic>, D::Error>
where D: Deserializer<'de> {
let string_map: HashMap<String, String> = HashMap::deserialize(deserializer)?;
let mut result = HashMap::new();
for (id, name) in string_map {
let id_num = id.parse::<i64>().unwrap_or(0);
result.insert(id.clone(), SubTopic {
id: id_num,
name: Some(name),
parent: 0,
});
}
Ok(result)
}
fn deserialize_string_to_topic<'de, D>(deserializer: D) -> Result<HashMap<String, Topic>, D::Error>
where D: Deserializer<'de> {
let string_map: HashMap<String, String> = HashMap::deserialize(deserializer)?;
let mut result = HashMap::new();
for (id, name) in string_map {
let id_num = id.parse::<i64>().unwrap_or(0);
result.insert(id.clone(), Topic {
id: id_num,
name: Some(name),
});
}
Ok(result)
}
fn deserialize_string_to_venue<'de, D>(deserializer: D) -> Result<HashMap<String, Venue>, D::Error>
where D: Deserializer<'de> {
let string_map: HashMap<String, String> = HashMap::deserialize(deserializer)?;
let mut result = HashMap::new();
for (id, name) in string_map {
result.insert(id.clone(), Venue {
id: 0,
name: Some(name),
address: 0,
});
}
Ok(result)
}
#[derive(Serialize, Deserialize, Debug)]
pub struct CitmCatalog {
#[serde(rename = "areaNames")]
pub area_names: HashMap<String, String>,
#[serde(rename = "audienceSubCategoryNames")]
pub audience_subcategory_names: HashMap<String, String>,
#[serde(default)]
#[serde(rename = "blockNames")]
pub block_names: HashMap<String, String>,
pub events: HashMap<String, Event>,
#[serde(default)]
pub performances: Vec<Performance>,
#[serde(rename = "seatCategoryNames")]
pub seat_category_names: HashMap<String, String>,
#[serde(rename = "subTopicNames")]
pub subtopic_names: HashMap<String, String>,
#[serde(default)]
#[serde(rename = "subjectNames")]
pub subject_names: HashMap<String, String>,
#[serde(rename = "topicNames")]
pub topic_names: HashMap<String, String>,
#[serde(rename = "topicSubTopics")]
pub topic_subtopics: HashMap<String, Vec<i64>>,
#[serde(rename = "venueNames")]
pub venue_names: HashMap<String, String>,
// Catch-all for other fields
#[serde(flatten)]
pub extra: HashMap<String, serde_json::Value>,
}
/// Creates a CitmCatalog from a JSON string (UTF-8 encoded).
#[no_mangle]
pub unsafe extern "C" fn citm_from_str(
raw_input: *const c_char,
raw_input_length: usize
) -> *mut CitmCatalog {
if raw_input.is_null() {
eprintln!("Error: Input pointer is null");
return ptr::null_mut();
}
// Convert the raw pointer + length into a Rust slice
let bytes = slice::from_raw_parts(raw_input as *const u8, raw_input_length);
let input_str = match std::str::from_utf8(bytes) {
Ok(s) => s,
Err(e) => {
eprintln!("Error: Invalid UTF-8 string: {}", e);
return ptr::null_mut();
}
};
// Try deserializing the input string into CitmCatalog
match serde_json::from_str::<CitmCatalog>(input_str) {
Ok(catalog) => Box::into_raw(Box::new(catalog)),
Err(e) => {
eprintln!("Error deserializing JSON: {}", e);
eprintln!("JSON snippet (first 200 chars): {:.200}...", input_str);
ptr::null_mut()
}
}
}
/// Serializes a CitmCatalog into a JSON string (UTF-8).
#[no_mangle]
pub unsafe extern "C" fn str_from_citm(raw_catalog: *mut CitmCatalog) -> *mut c_char {
if raw_catalog.is_null() {
eprintln!("Error: Catalog pointer is null");
return ptr::null_mut();
}
// Fix: Actually serialize the catalog
let catalog = &*raw_catalog;
match serde_json::to_string(catalog) {
Ok(serialized) => {
match CString::new(serialized) {
Ok(cstr) => cstr.into_raw(),
Err(e) => {
eprintln!("Error creating CString: {}", e);
ptr::null_mut()
}
}
},
Err(e) => {
eprintln!("Error serializing catalog to JSON: {}", e);
ptr::null_mut()
}
}
}
/// Frees the CitmCatalog pointer.
#[no_mangle]
pub unsafe extern "C" fn free_citm(raw_catalog: *mut CitmCatalog) {
if !raw_catalog.is_null() {
drop(Box::from_raw(raw_catalog));
}
}
#[no_mangle]
pub extern "C" fn free_str(ptr: *mut c_char) {
if !ptr.is_null() {
unsafe {
// Convert back into a CString, which automatically frees the memory
let _ = CString::from_raw(ptr);
}
}
}
@@ -0,0 +1,45 @@
#ifndef serde_benchmark_ffi_h
#define serde_benchmark_ffi_h
/* Generated with cbindgen:0.28.0 */
/* Warning, this file is autogenerated by cbindgen. Don't modify this manually. */
#include <cstdarg>
#include <cstdint>
#include <cstdlib>
#include <ostream>
#include <new>
namespace serde_benchmark {
struct CitmCatalog;
struct TwitterData;
extern "C" {
TwitterData *twitter_from_str(const char *raw_input, size_t raw_input_length);
const char *str_from_twitter(TwitterData *raw);
void free_twitter(TwitterData *raw);
void free_string(const char *ptr);
/// Creates a CitmCatalog from a JSON string (UTF-8 encoded).
CitmCatalog *citm_from_str(const char *raw_input, uintptr_t raw_input_length);
/// Serializes a CitmCatalog into a JSON string (UTF-8).
char *str_from_citm(CitmCatalog *raw_catalog);
/// Frees the CitmCatalog pointer.
void free_citm(CitmCatalog *raw_catalog);
void free_str(char *ptr);
} // extern "C"
} // namespace serde_benchmark
#endif // serde_benchmark_ffi_h
@@ -0,0 +1,14 @@
# Add executable targets
add_executable(benchmark_serialization_twitter benchmark_serialization_twitter.cpp)
if(TARGET serde-benchmark)
message(STATUS "serde-benchmark target was created. Linking benchmarks and serde-benchmark.")
target_link_libraries(benchmark_serialization_twitter PRIVATE serde-benchmark)
target_compile_definitions(benchmark_serialization_twitter PRIVATE SIMDJSON_RUST_VERSION="${Rust_VERSION}")
endif()
target_link_libraries(benchmark_serialization_twitter PRIVATE simdjson::simdjson nlohmann_json)
target_link_libraries(benchmark_serialization_twitter PRIVATE reflectcpp)
target_compile_definitions(benchmark_serialization_twitter PRIVATE SIMDJSON_BENCH_CPP_REFLECT=1)
target_compile_definitions(benchmark_serialization_twitter PRIVATE JSON_FILE="${EXAMPLE_JSON}")
@@ -0,0 +1,168 @@
#include <cassert>
#include <cstdlib>
#include <ctime>
#include <format>
#include <fstream>
#include <iostream>
#include <nlohmann/json.hpp>
#include <simdjson.h>
#include <string>
#include "twitter_data.h"
#include "nlohmann_twitter_data.h"
#include "../benchmark_utils/benchmark_helper.h"
#if SIMDJSON_BENCH_CPP_REFLECT
#include <rfl.hpp>
#include <rfl/json.hpp>
void bench_reflect_cpp(TwitterData &data) {
std::string output = rfl::json::write(data);
size_t output_volume = output.size();
printf("# output volume: %zu bytes\n", output_volume);
volatile size_t measured_volume = 0;
pretty_print(1, output_volume, "bench_reflect_cpp",
bench([&data, &measured_volume, &output_volume]() {
std::string output = rfl::json::write(data);
measured_volume = output.size();
if (measured_volume != output_volume) {
printf("mismatch\n");
}
}));
}
#endif // SIMDJSON_BENCH_CPP_REFLECT
#ifdef SIMDJSON_RUST_VERSION
#include "../serde-benchmark/serde_benchmark.h"
void bench_rust(serde_benchmark::TwitterData *data) {
const char * output = serde_benchmark::str_from_twitter(data);
size_t output_volume = strlen(output);
printf("# output volume: %zu bytes\n", output_volume);
volatile size_t measured_volume = 0;
pretty_print(1, output_volume, "bench_rust",
bench([&data, &measured_volume, &output_volume]() {
const char * output = serde_benchmark::str_from_twitter(data);
serde_benchmark::free_string(output);
}));
}
#endif
template <class T> void bench_simdjson_static_reflection(T &data) {
simdjson::builder::string_builder sb;
simdjson::builder::append(sb, data);
std::string_view p;
if(sb.view().get(p)) {
std::cerr << "Error!" << std::endl;
}
size_t output_volume = p.size();
sb.clear();
printf("# output volume: %zu bytes\n", output_volume);
volatile size_t measured_volume = 0;
pretty_print(sizeof(data), output_volume, "bench_simdjson_static_reflection",
bench([&data, &measured_volume, &output_volume, &sb]() {
sb.clear();
simdjson::builder::append(sb, data);
std::string_view p;
if(sb.view().get(p)) {
std::cerr << "Error!" << std::endl;
}
measured_volume = sb.size();
if (measured_volume != output_volume) {
printf("mismatch\n");
}
}));
}
void bench_nlohmann(TwitterData &data) {
std::string output = nlohmann_serialize(data);
size_t output_volume = output.size();
printf("# output volume: %zu bytes\n", output_volume);
volatile size_t measured_volume = 0;
pretty_print(1, output_volume, "bench_nlohmann",
bench([&data, &measured_volume, &output_volume]() {
std::string output = nlohmann_serialize(data);
measured_volume = output.size();
if (measured_volume != output_volume) {
printf("mismatch\n");
}
}));
}
size_t WriteCallback(void *contents, size_t size, size_t nmemb, void *userp) {
((std::string *)userp)->append((char *)contents, size * nmemb);
return size * nmemb;
}
std::string read_file(std::string filename) {
printf("# Reading file %s\n", filename.c_str());
constexpr size_t read_size = 4096;
auto stream = std::ifstream(filename.c_str());
stream.exceptions(std::ios_base::badbit);
std::string out;
std::string buf(read_size, '\0');
while (stream.read(&buf[0], read_size)) {
out.append(buf, 0, size_t(stream.gcount()));
}
out.append(buf, 0, size_t(stream.gcount()));
return out;
}
// Function to check if benchmark name contains filter substring
bool matches_filter(const std::string& benchmark_name, const std::string& filter) {
return filter.empty() || benchmark_name.find(filter) != std::string::npos;
}
int main(int argc, char* argv[]) {
std::string filter;
// Parse command-line arguments
for (int i = 1; i < argc; ++i) {
if (strcmp(argv[i], "-f") == 0 || strcmp(argv[i], "--filter") == 0) {
if (i + 1 < argc) {
filter = argv[++i];
} else {
std::cerr << "Error: -f/--filter requires an argument" << std::endl;
return EXIT_FAILURE;
}
}
}
// Testing correctness of round-trip (serialization + deserialization)
std::string json_str = read_file(JSON_FILE);
// Loading up the data into a structure.
simdjson::ondemand::parser parser;
simdjson::ondemand::document doc;
if(parser.iterate(simdjson::pad(json_str)).get(doc)) {
std::cerr << "Error loading the document!" << std::endl;
return EXIT_FAILURE;
}
TwitterData my_struct;
if(doc.get<TwitterData>().get(my_struct)) {
std::cerr << "Error loading TwitterData!" << std::endl;
return EXIT_FAILURE;
}
// Benchmarking the serialization
if (matches_filter("nlohmann", filter)) {
bench_nlohmann(my_struct);
}
if (matches_filter("simdjson_static_reflection", filter)) {
bench_simdjson_static_reflection(my_struct);
}
#ifdef SIMDJSON_RUST_VERSION
if (matches_filter("rust", filter)) {
printf("# WARNING: The Rust benchmark may not be directly comparable since it does not use an equivalent data structure.\n");
serde_benchmark::TwitterData * td = serde_benchmark::twitter_from_str(json_str.c_str(), json_str.size());
bench_rust(td);
serde_benchmark::free_twitter(td);
}
#endif
#if SIMDJSON_BENCH_CPP_REFLECT
if (matches_filter("reflect_cpp", filter)) {
bench_reflect_cpp(my_struct);
}
#endif
return EXIT_SUCCESS;
}
@@ -0,0 +1,116 @@
#ifndef NLOHMANN_TWITTER_DATA_H
#define NLOHMANN_TWITTER_DATA_H
#include "twitter_data.h"
#include <nlohmann/json.hpp>
void to_json(nlohmann::json &j, const User &u) {
j = nlohmann::json{{"id", u.id},
{"name", u.name},
{"screen_name", u.screen_name},
{"location", u.location},
{"description", u.description},
{"verified", u.verified},
{"followers_count", u.followers_count},
{"friends_count", u.friends_count},
{"statuses_count", u.statuses_count}};
}
void to_json(nlohmann::json &j, const Hashtag &h) {
j = nlohmann::json{{"text", h.text},
{"indices_start", h.indices_start},
{"indices_end", h.indices_end}};
}
void to_json(nlohmann::json &j, const Url &u) {
j = nlohmann::json{{"url", u.url},
{"expanded_url", u.expanded_url},
{"display_url", u.display_url},
{"indices_start", u.indices_start},
{"indices_end", u.indices_end}};
}
void to_json(nlohmann::json &j, const UserMention &um) {
j = nlohmann::json{{"id", um.id},
{"name", um.name},
{"screen_name", um.screen_name},
{"indices_start", um.indices_start},
{"indices_end", um.indices_end}};
}
void to_json(nlohmann::json &j, const Entities &e) {
j = nlohmann::json{{"hashtags", e.hashtags},
{"urls", e.urls},
{"user_mentions", e.user_mentions}};
}
void to_json(nlohmann::json &j, const Status &s) {
j = nlohmann::json{{"created_at", s.created_at},
{"id", s.id},
{"text", s.text},
{"user", s.user},
{"entities", s.entities},
{"retweet_count", s.retweet_count},
{"favorite_count", s.favorite_count},
{"favorited", s.favorited},
{"retweeted", s.retweeted}};
}
std::string nlohmann_serialize(const std::vector<Hashtag>& v) {
nlohmann::json a = nlohmann::json::array();
for(const Hashtag & h : v) {
a.push_back(nlohmann::json{{"text", h.text},
{"indices_start", h.indices_start},
{"indices_end", h.indices_end}});
}
return a.dump();
}
std::string nlohmann_serialize(const std::vector<Url>& v) {
nlohmann::json a = nlohmann::json::array();
for(const Url & u : v) {
a.push_back(nlohmann::json{{"url", u.url},
{"expanded_url", u.expanded_url},
{"display_url", u.display_url},
{"indices_start", u.indices_start},
{"indices_end", u.indices_end}});
}
return a.dump();
}
std::string nlohmann_serialize(const std::vector<UserMention>& v) {
nlohmann::json a = nlohmann::json::array();
for(const UserMention & um : v) {
a.push_back(nlohmann::json{{"id", um.id},
{"name", um.name},
{"screen_name", um.screen_name},
{"indices_start", um.indices_start},
{"indices_end", um.indices_end}});
}
return a.dump();
}
std::string nlohmann_serialize(const std::vector<Status>& v) {
nlohmann::json a = nlohmann::json::array();
for(const Status & s : v) {
a.push_back(nlohmann::json{{"created_at", s.created_at},
{"id", s.id},
{"text", s.text},
{"user", s.user},
{"entities", s.entities},
{"retweet_count", s.retweet_count},
{"favorite_count", s.favorite_count},
{"favorited", s.favorited},
{"retweeted", s.retweeted}});
}
return a.dump();
}
void to_json(nlohmann::json &j, const TwitterData &t) {
j = nlohmann::json{{"statuses", t.statuses}};
}
std::string nlohmann_serialize(const TwitterData &data) {
return nlohmann_serialize(data.statuses);
}
#endif // NLOHMANN_TWITTER_DATA_H
@@ -0,0 +1,71 @@
#ifndef TWITTER_DATA_H
#define TWITTER_DATA_H
#include <string>
#include <vector>
struct User {
int64_t id;
std::string id_str;
std::string name;
std::string screen_name;
std::string location;
std::string description;
bool verified;
int64_t followers_count;
int64_t friends_count;
int64_t statuses_count;
bool operator<=>(const User &other) const = default;
};
struct Hashtag {
std::string text;
int64_t indices_start;
int64_t indices_end;
bool operator<=>(const Hashtag &other) const = default;
};
struct Url {
std::string url;
std::string expanded_url;
std::string display_url;
int64_t indices_start;
int64_t indices_end;
bool operator<=>(const Url &other) const = default;
};
struct UserMention {
int64_t id;
std::string name;
std::string screen_name;
int64_t indices_start;
int64_t indices_end;
bool operator<=>(const UserMention &other) const = default;
};
struct Entities {
std::vector<Hashtag> hashtags;
std::vector<Url> urls;
std::vector<UserMention> user_mentions;
bool operator==(const Entities &other) const = default;
};
struct Status {
std::string created_at;
int64_t id;
std::string text;
User user;
Entities entities;
int64_t retweet_count;
int64_t favorite_count;
bool favorited;
bool retweeted;
bool operator==(const Status &other) const = default;
};
struct TwitterData {
std::vector<Status> statuses;
bool operator==(const TwitterData &other) const = default;
};
#endif
+12 -3
View File
@@ -4,7 +4,7 @@
add_library(simdjson-internal-flags INTERFACE)
if(NOT DEFINED CMAKE_POSITION_INDEPENDENT_CODE)
# We default to ON for all targets, so that we can use the library in shared libraries.
set_target_properties(simdjson-internal-flags PROPERTIES POSITION_INDEPENDENT_CODE ON)
set_target_properties(simdjson-internal-flags PROPERTIES INTERFACE_POSITION_INDEPENDENT_CODE ON)
endif(NOT DEFINED CMAKE_POSITION_INDEPENDENT_CODE)
option(SIMDJSON_CHECK_EOF "Check for the end of the input buffer. The setting is unnecessary since we require padding of the inputs. You should expect tests to fail with this option turned on." OFF)
@@ -32,6 +32,9 @@ undefined behavior.")
link_libraries(
-fsanitize=address -fno-omit-frame-pointer -fno-sanitize-recover=all
)
elseif (CMAKE_CXX_COMPILER_ID STREQUAL "MSVC")
add_compile_options(-fsanitize=address)
link_libraries(-fsanitize=address)
else()
message(
STATUS
@@ -112,8 +115,14 @@ endif()
# We compile tools, tests, etc. with C++ 17. Override yourself if you need on a
# target.
set(SIMDJSON_CXX_STANDARD 17 CACHE STRING "the C++ standard to use for simdjson")
set(CMAKE_CXX_STANDARD ${SIMDJSON_CXX_STANDARD})
if(SIMDJSON_STATIC_REFLECTION)
# This is temporary.
set(SIMDJSON_CXX_STANDARD 26 CACHE STRING "the C++ standard to use for simdjson")
#set(CMAKE_CXX_STANDARD ${SIMDJSON_CXX_STANDARD})
else()
set(SIMDJSON_CXX_STANDARD 17 CACHE STRING "the C++ standard to use for simdjson")
set(CMAKE_CXX_STANDARD ${SIMDJSON_CXX_STANDARD})
endif()
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS OFF)
set(CMAKE_THREAD_PREFER_PTHREAD ON)
@@ -0,0 +1,4 @@
set(CMAKE_SYSTEM_NAME Linux)
set(CMAKE_SYSTEM_PROCESSOR riscv64)
set(CMAKE_CROSSCOMPILING_EMULATOR "qemu-riscv64-static")
+1 -1
View File
@@ -106,7 +106,7 @@ int main() {}
CPMAddPackage(
NAME rapidjson
URL https://github.com/Tencent/rapidjson/archive/f54b0e47a08782a6131cc3d60f94d038fa6e0a51.zip
URL https://github.com/Tencent/rapidjson/archive/805d7ed5dfe97a39b8b0816fd5eeed8731dc4936.zip
DOWNLOAD_ONLY YES
)
add_library(rapidjson INTERFACE)
+293 -71
View File
@@ -1,48 +1,51 @@
The Basics
==========
An overview of what you need to know to use simdjson, with examples.
- [The Basics](#the-basics)
- [Requirements](#requirements)
- [Including simdjson](#including-simdjson)
- [Using simdjson with package managers](#using-simdjson-with-package-managers)
- [Using simdjson as a CMake dependency](#using-simdjson-as-a-cmake-dependency)
- [Versions](#versions)
- [The basics: loading and parsing JSON documents](#the-basics-loading-and-parsing-json-documents)
- [Documents are iterators](#documents-are-iterators)
- [Parser, document and JSON scope](#parser-document-and-json-scope)
- [string_view](#string_view)
- [Avoiding pitfalls: enable development checks](#avoiding-pitfalls-enable-development-checks)
- [Using the parsed JSON](#using-the-parsed-json)
- [Using the parsed JSON: additional examples](#using-the-parsed-json-additional-examples)
- [Adding support for custom types](#adding-support-for-custom-types)
- [1. Specialize `simdjson::ondemand::value::get` to get custom types (pre-C++20)](#1-specialize-simdjsonondemandvalueget-to-get-custom-types-pre-c20)
- [2. Use `tag_invoke` for custom types (C++20)](#2-use-tag_invoke-for-custom-types-c20)
- [Minifying JSON strings without parsing](#minifying-json-strings-without-parsing)
- [UTF-8 validation (alone)](#utf-8-validation-alone)
- [JSON Pointer](#json-pointer)
- [JSONPath](#jsonpath)
- [Error handling](#error-handling)
- [Error handling examples without exceptions](#error-handling-examples-without-exceptions)
- [Disabling exceptions](#disabling-exceptions)
- [Exceptions](#exceptions)
- [Current location in document](#current-location-in-document)
- [Checking for trailing content](#checking-for-trailing-content)
- [Rewinding](#rewinding)
- [Newline-Delimited JSON (ndjson) and JSON lines](#newline-delimited-json-ndjson-and-json-lines)
- [Parsing numbers inside strings](#parsing-numbers-inside-strings)
- [Dynamic Number Types](#dynamic-number-types)
- [Raw strings from keys](#raw-strings-from-keys)
- [General direct access to the raw JSON string](#general-direct-access-to-the-raw-json-string)
- [Storing directly into an existing string instance](#storing-directly-into-an-existing-string-instance)
- [Thread safety](#thread-safety)
- [Standard compliance](#standard-compliance)
- [Backwards compatibility](#backwards-compatibility)
- [Examples](#examples)
- [Performance tips](#performance-tips)
- [Further reading](#further-reading)
An overview of what you need to know to use simdjson to parse JSON documents, with examples.
[Our documentation regarding the generation (serialization) of JSON documents is in a
separate document](https://github.com/simdjson/simdjson/blob/master/doc/builder.md).
- [Requirements](#requirements)
- [Including simdjson](#including-simdjson)
- [Using simdjson with package managers](#using-simdjson-with-package-managers)
- [Using simdjson as a CMake dependency](#using-simdjson-as-a-cmake-dependency)
- [Versions](#versions)
- [The basics: loading and parsing JSON documents](#the-basics-loading-and-parsing-json-documents)
- [Documents are iterators](#documents-are-iterators)
* [Parser, document and JSON scope](#parser-document-and-json-scope)
- [string_view](#string_view)
- [Avoiding pitfalls: enable development checks](#avoiding-pitfalls-enable-development-checks)
- [Using the parsed JSON](#using-the-parsed-json)
* [Using the parsed JSON: additional examples](#using-the-parsed-json-additional-examples)
- [Adding support for custom types](#adding-support-for-custom-types)
* [1. Specialize `simdjson::ondemand::value::get` to get custom types (pre-C++20)](#1-specialize-simdjsonondemandvalueget-to-get-custom-types-pre-c20)
* [2. Use `tag_invoke` for custom types (C++20)](#2-use-tag_invoke-for-custom-types-c20)
* [3. Using static reflection (C++26)](#3-using-static-reflection-c26)
* [The simdjson::from shortcut (experimental, C++20)](#the-simdjsonfrom-shortcut-experimental-c20)
- [Minifying JSON strings without parsing](#minifying-json-strings-without-parsing)
- [UTF-8 validation (alone)](#utf-8-validation-alone)
- [JSON Pointer](#json-pointer)
- [JSONPath](#jsonpath)
- [Error handling](#error-handling)
* [Error handling examples without exceptions](#error-handling-examples-without-exceptions)
* [Disabling exceptions](#disabling-exceptions)
* [Exceptions](#exceptions)
* [Current location in document](#current-location-in-document)
* [Checking for trailing content](#checking-for-trailing-content)
- [Rewinding](#rewinding)
- [Newline-Delimited JSON (ndjson) and JSON lines](#newline-delimited-json-ndjson-and-json-lines)
- [Parsing numbers inside strings](#parsing-numbers-inside-strings)
- [Dynamic Number Types](#dynamic-number-types)
- [Raw strings from keys](#raw-strings-from-keys)
- [General direct access to the raw JSON string](#general-direct-access-to-the-raw-json-string)
* [Raw JSON string for objects and arrays](#raw-json-string-for-objects-and-arrays)
- [Storing directly into an existing string instance](#storing-directly-into-an-existing-string-instance)
- [Thread safety](#thread-safety)
- [Standard compliance](#standard-compliance)
- [Backwards compatibility](#backwards-compatibility)
- [Examples](#examples)
- [Performance tips](#performance-tips)
- [Further reading](#further-reading)
Requirements
------------------
@@ -51,7 +54,7 @@ The simdjson library is widely deployed in popular systems such as the Node.js r
environment.
- A recent compiler (LLVM clang 6 or better, GNU GCC 7.4 or better, Xcode 11 or better) on POSIX systems such as macOS, FreeBSD or Linux. We require that the compiler supports the C++11 standard or better. We test the library on a big-endian system (IBM s390x with Linux).
- Visual Studio 2017 or better. We support the LLVM clang compiler under Visual Studio (clang-cl) as well as as the regular Visual Studio compiler. For better release performance (both compile time and execution time), we recommend Visual Studio users adopt LLVM (clang-cl). We also support MinGW 64-bit under Windows.
- Visual Studio 2017 or better. We support the LLVM clang compiler under Visual Studio (clang-cl) as well as the regular Visual Studio compiler. For better release performance (both compile time and execution time), we recommend Visual Studio users adopt LLVM (clang-cl). We discourage using GCC under Windows: there [is a long-running bug with GCC under Windows](https://gcc.gnu.org/bugzilla/show_bug.cgi?id=54412).
Support for AVX-512 require a processor with AVX512-VBMI2 support (Ice Lake or better, AMD Zen 4 or better) under a 64-bit system and a recent compiler (LLVM clang 6 or better, GCC 8 or better, Visual Studio 2019 or better). You need a correspondingly recent assembler such as gas (2.30+) or nasm (2.14+): recent compilers usually come with recent assemblers. If you mix a recent compiler with an incompatible/old assembler (e.g., when using a recent compiler with an old Linux distribution), you may get errors at build time because the compiler produces instructions that the assembler does not recognize: you should update your assembler to match your compiler (e.g., upgrade binutils to version 2.30 or better under Linux) or use an older compiler matching the capabilities of your assembler.
@@ -75,14 +78,14 @@ c++ myproject.cpp simdjson.cpp
```
Note:
- We recommend that you use simdjson by copying the single-header `simdjson.h` file along with the source file `simdjson.cpp` directly in your project, as they are part of [every release](https://github.com/simdjson/simdjson/releases) as assets. In this manner, you only have to compile `simdjson.cpp` as any other source file: it works well in every development environment. However, you may also use simdjson as a git submodule ([example](https://github.com/simdjson/cmakedemo)), using FetchContent ([example](https://github.com/simdjson/cmake_demo_single_file)), with ExternalProject_Add ([example](https://github.com/simdjson/cmakedemo_externalproject)) or with CPM ([example](https://github.com/cpm-cmake/CPM.cmake/tree/master/examples/simdjson)).
- We recommend that you use simdjson by copying the single-header `simdjson.h` file along with the source file `simdjson.cpp` directly into your project, as they are part of [every release](https://github.com/simdjson/simdjson/releases) as assets. In this manner, you only have to compile `simdjson.cpp` as any other source file: it works well in every development environment. However, you may also use simdjson as a git submodule ([example](https://github.com/simdjson/cmakedemo)), using FetchContent ([example](https://github.com/simdjson/cmake_demo_single_file)), with ExternalProject_Add ([example](https://github.com/simdjson/cmakedemo_externalproject)) or with CPM ([example](https://github.com/cpm-cmake/CPM.cmake/tree/master/examples/simdjson)).
- Users on macOS and other platforms where default compilers do not provide C++11 compliant by default should request it with the appropriate flag (e.g., `c++ -std=c++11 myproject.cpp simdjson.cpp`).
- The library relies on [runtime CPU detection](implementation-selection.md): avoid specifying an architecture at compile time (e.g., `-march-native`) if you want your binaries to run everywhere.
Using simdjson with package managers
------------------
You can install the simdjson library on your system or in your project using multiple package managers such as MSYS2, the conan package manager, vcpkg, brew, the apt package manager (debian-based Linux systems), the FreeBSD package manager (FreeBSD), and so on. E.g., [we provide an complete example with vcpkg](https://github.com/simdjson/simdjson-vcpkg) that works under Windows. [Visit our wiki for more details](https://github.com/simdjson/simdjson/wiki/Installing-simdjson-with-a-package-manager).
You can install the simdjson library on your system or in your project using multiple package managers such as MSYS2, the conan package manager, vcpkg, brew, the apt package manager (debian-based Linux systems), the FreeBSD package manager (FreeBSD), and so on. E.g., [we provide a complete example with vcpkg](https://github.com/simdjson/simdjson-vcpkg) that works under Windows. [Visit our wiki for more details](https://github.com/simdjson/simdjson/wiki/Installing-simdjson-with-a-package-manager).
@@ -177,6 +180,18 @@ auto json = padded_string::load("twitter.json"); // load JSON file 'twitter.json
ondemand::document doc = parser.iterate(json); // position a pointer at the beginning of the JSON data
```
If you prefer not to create your own `ondemand::parser` instance, you can access
a thread-local version by calling `ondemand::parser.get_parser()`.
```c++
ondemand::document doc = ondemand::parser.get_parser().iterate(json);
```
However, you should be careful because a parser instance can only be used for one
document at a time, thus it is only applicable when you are only parsing one
document per thread at any one time.
You can also create a padded string---and call `iterate()`:
```c++
@@ -236,7 +251,6 @@ Consider reusing the same buffers and limiting memory allocations.
By default, the simdjson library throws exceptions (`simdjson_error`) on errors. We omit `try`-`catch` clauses from our illustrating examples: if you omit `try`-`catch` in your code, an uncaught exception will halt your program. It is also possible to use simdjson without generating exceptions, and you may even build the library without exception support at all. See [Error handling](#error-handling) for details.
Some users may want to browse code along with the compiled assembly. You want to check out the following lists of examples:
* [simdjson examples with errors handled through exceptions](https://godbolt.org/z/98Kx9Kqjn)
@@ -306,7 +320,7 @@ At the cost of some memory allocation, you may convert your `std::string_view` i
For convenience, we also allow [storing an escaped string directly into an existing string instance](#storing-directly-into-an-existing-string-instance).
The `std::string_view` class has become standard as part of C++17 but it is not always available
on compilers which only supports C++11. When we detect that `string_view` is natively
on compilers that only supports C++11. When we detect that `string_view` is natively
available, we define the macro `SIMDJSON_HAS_STRING_VIEW`.
When we detect that it is unavailable,
@@ -329,7 +343,7 @@ We recommend that you first compile and run your code in debug mode:
The simdjson code will set `SIMDJSON_DEVELOPMENT_CHECKS=1` in debug mode. Because
the C++ standard does not provide a direct way of checking for a debug build, and
because you may want the checks while building with otimizations, you can set
because you may want the checks while building with optimizations, you can set
the macro `SIMDJSON_DEVELOPMENT_CHECKS` to 1 prior to including
the `simdjson.h` header to enable these additional checks: just make sure you remove the
definition once your code has been tested. When `SIMDJSON_DEVELOPMENT_CHECKS` is set to 1, the
@@ -357,9 +371,8 @@ and arrays (`simdjson::ondemand::array`).
We also have a generic ephemeral type (`simdjson::ondemand::value`) which represents a potential
array or object, or scalar type (`double`, `uint64_t`, `int64_t`, `bool`, `null`, string) inside
an array or an object. Both generic types (`simdjson::ondemand::document` and
`simdjson::ondemand::value`) have a `type()` method returning a `json_type` value describing the
value (`json_type::array`, `json_type::object`, `json_type::number`, `json_type::string`,
`json_type::boolean`, `json_type::null`). A generic value (`simdjson::ondemand::value`)
`simdjson::ondemand::value`) have a `type()` method returning a `json_type` value describing indicating the type (`json_type::array`, `json_type::object`, `json_type::number`, `json_type::string`,
`json_type::boolean`, `json_type::null`, and `json_type::unknown` for unrecognized types). The `type()` method does not consume nor validate the value: e.g., you must still call `is_null()` to check that the value is a `null` even if `json_type::null` is returned. Starting with simdjson 4.0, we return `json_type::unknown` for bad tokens such as the `NaN` token in `{"key":NaN}`. A `json_type::unknown` type value indicates an error in the JSON document but you might still be able to proceed, see [General direct access to the raw JSON string](#general-direct-access-to-the-raw-json-string). A generic value (`simdjson::ondemand::value`)
is only valid temporarily, as soon as you access other values, other keys in objects, etc.
it becomes invalid: you should therefore consume the value immediately by converting it to a
scalar type, an array or an object.
@@ -392,7 +405,7 @@ support for users who avoid exceptions. See [the simdjson error handling documen
ondemand::object and ondemand::array. We also have explicit methods such as `get_string()`, `get_double()`,
`get_uint64()`, `get_int64()`, `get_bool()`, `get_object()` and `get_array()`. After a cast or an explicit method,
the number, string or boolean will be parsed, or the initial `{` or `[` will be verified for `ondemand::object` and `ondemand::array`. An exception may be thrown if
the cast is not possible: there error code is `simdjson::INCORRECT_TYPE` (see [Error handling](#error-handling)). Importantly, when getting an ondemand::object or ondemand::array instance, its content is
the cast is not possible: the error code is `simdjson::INCORRECT_TYPE` (see [Error handling](#error-handling)). Importantly, when getting an ondemand::object or ondemand::array instance, its content is
not validated: you are only guaranteed that the corresponding initial character (`{` or `[`) is present. Thus,
for example, you could have an ondemand::object instance pointing at the invalid JSON `{ "this is not a valid object" }`: the validation occurs as you access the content.
The `get_string()` returns a valid UTF-8 string, after
@@ -439,7 +452,7 @@ support for users who avoid exceptions. See [the simdjson error handling documen
* **Field Access:** To get the value of the "foo" field in an object, use `object["foo"]`. This will
scan through the object looking for the field with the matching string, doing a character-by-character
comparison. It may generate the error `simdjson::NO_SUCH_FIELD` if there is no such key in the object, it may throw an exception (see [Error handling](#error-handling)). For efficiency reason, you should avoid looking up the same field repeatedly: e.g., do
not do `object["foo"]` followed by `object["foo"]` with the same `object` instance. For best performance, you should try to query the keys in the same order they appear in the document. If you need several keys and you cannot predict the order they will appear in, it is recommended to iterate through all keys `for(auto field : object) {...}`. Generally, you should not mix and match iterating through an object (`for(auto field : object) {...}`) and key accesses (`object["foo"]`): if you need to iterate through an object after a key access, you need to call `reset()` on the object. Whenever you call `reset()`, you need to keep in mind that though you can iterate over the array repeatedly, values should be consumedonly once (e.g., repeatedly calling `unescaped_key()` on the same key is forbidden). Keep in mind that On-Demand does not buffer or save the result of the parsing: if you repeatedly access `object["foo"]`, then it must repeatedly seek the key and parse the content. The library does not provide a distinct function to check if a key is present, instead we recommend you attempt to access the key: e.g., by doing `ondemand::value val{}; if (!object["foo"].get(val)) {...}`, you have that `val` contains the requested value inside the if clause. It is your responsibility as a user to temporarily keep a reference to the value (`auto v = object["foo"]`), or to consume the content and store it in your own data structures. If you consume an
not do `object["foo"]` followed by `object["foo"]` with the same `object` instance. Generally, you should not mix and match iterating through an object (`for(auto field : object) {...}`) and key accesses (`object["foo"]`): if you need to iterate through an object after a key access, you need to call `reset()` on the object. Whenever you call `reset()`, you need to keep in mind that though you can iterate over the array repeatedly, values should be consumedonly once (e.g., repeatedly calling `unescaped_key()` on the same key is forbidden). Keep in mind that On-Demand does not buffer or save the result of the parsing: if you repeatedly access `object["foo"]`, then it must repeatedly seek the key and parse the content. The library does not provide a distinct function to check if a key is present, instead we recommend you attempt to access the key: e.g., by doing `ondemand::value val{}; if (!object["foo"].get(val)) {...}`, you have that `val` contains the requested value inside the if clause. It is your responsibility as a user to temporarily keep a reference to the value (`auto v = object["foo"]`), or to consume the content and store it in your own data structures. If you consume an
object twice: `std::string_view(object["foo"]` followed by `std::string_view(object["foo"]` then your code
is in error. Furthermore, you can only consume one field at a time, on the same object. The
value instance you get from `content["bids"]` becomes invalid when you call `content["asks"]`.
@@ -502,7 +515,7 @@ support for users who avoid exceptions. See [the simdjson error handling documen
> double y = doc["y"]; // The cursor is now after the 2 (at })
> double x = doc["x"]; // Success: [] loops back around to find "x"
> ```
* **Output to strings:** Given a document, a value, an array or an object in a JSON document, you can output a JSON string version suitable to be parsed again as JSON content: `simdjson::to_json_string(element)`. A call to `to_json_string` consumes fully the element: if you apply it on a document, the JSON pointer is advanced to the end of the document. The `simdjson::to_json_string` does not allocate memory. The `to_json_string` function should not be confused with retrieving the value of a string instance which are escaped and represented using a lightweight `std::string_view` instance pointing at an internal string buffer inside the parser instance. To illustrate, the first of the following two code segments will print the unescaped string `"test"` complete with the quote whereas the second one will print the escaped content of the string (without the quotes).
* **Output to strings:** Given a document, a value, an array or an object in a JSON document, you can output a JSON string version suitable to be parsed again as JSON content: `simdjson::to_json_string(element)`. A call to `to_json_string` consumes fully the element: if you apply it on a document, the internal pointer is advanced to the end of the document. The `simdjson::to_json_string` does not allocate memory. The `to_json_string` function should not be confused with retrieving the value of a string instance which are escaped and represented using a lightweight `std::string_view` instance pointing at an internal string buffer inside the parser instance. To illustrate, the first of the following two code segments will print the unescaped string `"test"` complete with the quote whereas the second one will print the escaped content of the string (without the quotes).
> ```C++
> // serialize a JSON to an escaped std::string instance so that it can be parsed again as JSON
> auto silly_json = R"( { "test": "result" } )"_padded;
@@ -685,6 +698,9 @@ support for users who avoid exceptions. See [the simdjson error handling documen
cout << "null";
}
break;
case ondemand::json_type::unknown:
cout << "unknown"; // indicates an error
break;
}
}
void basics_treewalk() {
@@ -820,15 +836,17 @@ for (ondemand::object points : parser.iterate(points_json)) {
Adding support for custom types
----------------------
There are 2 main ways provided by simdjson to deserialize a value into a custom type:
There are 3 main ways provided by simdjson to deserialize a value into a custom type:
1. Provide a [**template specialization** for member functions](https://en.cppreference.com/w/cpp/language/template_specialization#Members_of_specializations)
1. Specialize `simdjson::ondemand::document::get` for the whole document
2. Specialize `simdjson::ondemand::value::get` for each value
2. Using `tag_invoke` *(the recommended way if your system supports C++20 or better)*
3. Using static reflection (requires C++26 or better)
We describe both of them in the following sections. Most users who have systems compatible with
We describe all of them in the following sections. Most users who have systems compatible with
C++20 or better should skip ahead to [using `tag_invoke` for custom types (C++20)](#2-use-tag_invoke-for-custom-types-c20) as it is more powerful and simpler.
The C++26 approach is even simpler.
### 1. Specialize `simdjson::ondemand::value::get` to get custom types (pre-C++20)
@@ -866,13 +884,13 @@ type:
We may do so by providing additional template definitions to the `ondemand::value` type.
We may start by providing a definition for `std::vector<double>` as follows. Observe
how we guard the code with `#if !SIMDJSON_SUPPORTS_DESERIALIZATION`: that is because the necessary code
how we guard the code with `#if !SIMDJSON_SUPPORTS_CONCEPTS`: that is because the necessary code
is automatically provided by simdjson if C++20 (and concepts) are available.
See [Use `tag_invoke` for custom types](#2-use-tag_invoke-for-custom-types-c20) if you have
C++20 support.
```c++
#if !SIMDJSON_SUPPORTS_DESERIALIZATION
#if !SIMDJSON_SUPPORTS_CONCEPTS
// The code is unnecessary with C++20:
template <>
simdjson_inline simdjson_result<std::vector<double>>
@@ -928,7 +946,7 @@ struct Car {
std::vector<double> tire_pressure;
};
#if !SIMDJSON_SUPPORTS_DESERIALIZATION
#if !SIMDJSON_SUPPORTS_CONCEPTS
// This code is not necessary if you have a C++20 compliant system:
template <>
simdjson_inline simdjson_result<std::vector<double>>
@@ -1029,7 +1047,7 @@ struct Car {
std::vector<double> tire_pressure;
};
#if !SIMDJSON_SUPPORTS_DESERIALIZATION
#if !SIMDJSON_SUPPORTS_CONCEPTS
// This code is not necessary if you have a C++20 compliant system:
template <>
simdjson_inline simdjson_result<std::vector<double>>
@@ -1124,7 +1142,7 @@ The first argument is usually a tag type (often an empty struct) that uniquely i
If your system supports C++20, we recommend that you adopt the `tag_invoke` approach
instead to deserialize custom types. It may prove to be considerably simpler. When
simdjson detects the necessary support, it sets the `SIMDJSON_SUPPORTS_DESERIALIZATION` macro
simdjson detects the necessary support, it sets the `SIMDJSON_SUPPORTS_CONCEPTS` macro
to 1, otherwise it is set to 0.
Consider a custom class `Car`:
@@ -1138,7 +1156,7 @@ struct Car {
};
```
Observe how we defined the class to use types that simdjson does not directly support (`float`, `int`).
Observe how we define the class to use types that simdjson does not directly support (`float`, `int`).
With C++20 support, the library grabs from the JSON the generic type (`double`, `int`) and then it
casts it automatically.
@@ -1301,8 +1319,8 @@ from `std::string_view` instances:
})"_padded;
ondemand::parser parser;
ondemand::document doc = parser.iterate(json);
std:map<std::string,Car> cars;
error = doc.get<std:map<std::string,Car>>().get(cars);
std::map<std::string,Car> cars;
error = doc.get<std::map<std::string,Car>>().get(cars);
// car has value car1->Car{"Toyota", "Camry", 2018, {40.1f, 39.9f}}
// error is simdjson::SUCCESS
```
@@ -1342,6 +1360,148 @@ With this code, deserializing an `std::list<Car>` instance would capture only th
that are not made by Toyota.
### 3. Using static reflection (C++26)
If you have a C++26 compatible compiler, you can compile
your code with the `SIMDJSON_STATIC_REFLECTION` macro set:
```cpp
#define SIMDJSON_STATIC_REFLECTION 1
//...
#include "simdjson.h"
```
Then you can deserialize a type such as `Car` automatically:
```C++
struct Car {
std::string make;
std::string model;
int year;
std::vector<float> tire_pressure;
};
std::string json = R"( { "make": "Toyota", "model": "Camry", "year": 2018,
"tire_pressure": [ 40.1, 39.9 ] } )";
simdjson::ondemand::parser parser;
simdjson::ondemand::document doc = parser.iterate(simdjson::pad(json));
Car c = doc.get<Car>();
```
We try to automate the parsing of any given structure or class
by looking at its non-static public members. At compile-time,
the library looks at a simple structure like `Car` and
maps it to parsing code. We call the default constructor,
and then assign values to the public members.
#### Special cases
However, there are instances where the construction cannot
be easily automated. Let us consider a class without any
public member.
```cpp
class MyDate {
public:
void assign(std::string_view str) {
date_str = str;
}
const std::string& to_string() const {
return date_str;
}
private:
std::string date_str;
};
```
This class has a default constructor, but it must be initialized
with the `assign` method. We need to help the library with
a `tag_invoke` function (just as in the C++20 case).
```cpp
namespace simdjson {
template <typename simdjson_value>
auto tag_invoke(deserialize_tag, simdjson_value &val, MyDate& date) {
std::string_view str;
auto error = val.get_string().get(str);
if(error) { return error; }
date.assign(str);
return simdjson::SUCCESS;
}
} // namespace simdjson
```
Once this is done, we can now automatically parse a custom type
like `complicated_weather_data` containing `MyDate` values.
```cpp
struct complicated_weather_data {
std::vector<MyDate> time;
std::vector<float> temperature;
};
```
The code might be as simple as the following.
```cpp
auto padded = R"({"time":["2023-03-15T12:00:00Z"],"temperature":[42]})"_padded;
simdjson::ondemand::parser parser;
simdjson::ondemand::document doc = parser.iterate(padded);
complicated_weather_data p = doc.get<>(complicated_weather_data);
```
Thus you can combine C++26 static reflection with custom deserialization
functions.
You can also automatically serialize the `Car` instance to a JSON string, see
our [Builder documentation](builder.md).
### The simdjson::from shortcut (experimental, C++20)
For even more convenience, you can parse a JSON document directly to a supported
type without a document instance like so:
```cpp
Car car = simdjson::from(json);
```
You can also use the `simdjson::from` syntax without exceptions, like so:
```cpp
Car car;
simdjson::error_code err = simdjson::from(json_car).get(car);
```
You can also use the `simdjson::from` syntax to iterate over an array.
```cpp
for(auto val : simdjson::from(json).array()) {
Car c = val.get<Car>(); // ...
}
```
Standard STL types are supported:
```cpp
std::map<std::string, std::string> obj =
simdjson::from(R"({"key": "value"})"_padded);
```
The `simdjson::from` construction is EXPERIMENTAL and subject to changes.
Minifying JSON strings without parsing
----------------------
@@ -1383,9 +1543,9 @@ If you find yourself needing only fast Unicode functions, consider using the sim
JSON Pointer
------------
The simdjson library also supports [JSON pointer](https://tools.ietf.org/html/rfc6901) through the `at_pointer()` method, letting you reach further down into the document in a single call. JSON pointer is supported by both the [DOM approach](https://github.com/simdjson/simdjson/blob/master/doc/dom.md#json-pointer) as well as the On-Demand approach.
The simdjson library also supports [JSON pointer](https://tools.ietf.org/html/rfc6901) through the `at_pointer()` method, letting you reach further down into the document in a single call. JSON Pointer is supported by both the [DOM approach](https://github.com/simdjson/simdjson/blob/master/doc/dom.md#json-pointer) as well as the On-Demand approach.
**Note:** The On-Demand implementation of JSON pointer relies on `find_field` which implies that it does not unescape keys when matching.
**Note:** The On-Demand implementation of JSON Pointer relies on `find_field` which implies that it does not unescape keys when matching.
Consider the following example:
@@ -1405,7 +1565,7 @@ index allows you to select the indexed node. Within objects, the string value of
select the value. If your keys contain the characters '/' or '~', they must be escaped as '~1' and
'~0' respectively. An empty JSON Pointer Path refers to the whole document.
For multiple JSON pointer queries on a document, one can call `at_pointer` multiple times.
For multiple JSON Pointer queries on a document, one can call `at_pointer` multiple times.
```c++
auto cars_json = R"( [
@@ -1560,7 +1720,7 @@ x = obj.at_path("$.d.foo2.a.2"); // 30
Error handling
--------------
Error handing with exception and a single try/catch clause makes the code simple, but it gives you little control over errors. For easier debugging or more robust error handling, you may want to consider our exception-free approach.
Error handling with exception and a single try/catch clause makes the code simple, but it gives you little control over errors. For easier debugging or more robust error handling, you may want to consider our exception-free approach.
The entire simdjson API is usable with and without exceptions. All simdjson APIs that can fail return `simdjson_result<T>`, which is a &lt;value, error_code&gt;
pair. You can retrieve the value with .get() without generating an exception, like so:
@@ -2154,7 +2314,8 @@ The simdjson library supports parsing valid numbers inside strings which makes i
three methods: `get_double_in_string`, `get_int64_in_string` and `get_uint64_in_string`. However, it is important to note that these methods are not substitute to the regular
`get_double`, `get_int64` and `get_uint64`. The usage of the `get_*_in_string` methods is solely to parse valid JSON numbers inside strings, and so we expect users to call these
methods appropriately. In particular, a valid JSON number has no leading and no trailing whitespaces, and the strings `"nan"`, `"1e"` and `"infinity"` will not be accepted as valid
numbers. As an example, suppose we have the following JSON text:
numbers (although you have access to the raw string with the `raw_json_token()` method, see [General direct access to the raw JSON string](#general-direct-access-to-the-raw-json-string)
). As an example, suppose we have the following JSON text:
```c++
auto json =
@@ -2473,10 +2634,36 @@ The `raw_json_token()` should be fast and free of allocation.
Given a quote-deliminated string, you find the string sequence inside the quote with a
single line of code:
```C++
```cpp
std::string_view noquote(std::string_view v) { return {v.data()+1, v.find_last_of('"')-1}; }
```
The `raw_json_token()` method can enable you to provide fallbacks when parsing fails.
Consider the following example.
```cpp
padded_string json = "{\"key\": NaN}"_padded;
simdjson::ondemand::parser parser;
simdjson::ondemand::document doc = parser.iterate(json);
simdjson::ondemand::object object = doc.get_object();
simdjson::ondemand::value val = object["key"];
simdjson::ondemand::json_type type = val.type();
// type == simdjson::ondemand::json_type::unknown
try {
double num = val.get_double();
} catch (const simdjson::simdjson_error& e) {
// e == simdjson::error_code::INCORRECT_TYPE
std::string_view str = val.raw_json_token();
// str == "NaN"
}
```
The NaN is not supported in JSON. However, in the On-Demand API, you can check
the string corresponding to the JSON token and determine how to handle it.
### Raw JSON string for objects and arrays
If your value is an array or an object, `raw_json_token()` returns effectively a single
character (`[`) or (`}`) which is not very useful. For arrays and objects, we have another
method called `raw_json()` which consumes (traverses) the array or the object.
@@ -2995,10 +3182,45 @@ Performance tips
- The On-Demand front-end works best when doing a single pass over the input: avoid calling `count_elements`, `rewind`, `reset` and similar methods.
- If you are familiar with assembly language, you may use the online tool godbolt to explore the compiled code. The following example may work: [https://godbolt.org/z/xE4GWs573](https://godbolt.org/z/xE4GWs573).
- Given a field `field` in an object, calling `field.key()` is often faster than `field.unescaped_key()` so if you do not need an unescaped `std::string_view` instance, prefer `field.key()`. Similarly, we expect `field.escaped_key()` to be faster than `field.unescaped_key()` even though both return a `std::string_view` instance.
- For release builds, we recommend setting `NDEBUG` pre-processor directive when compiling the `simdjson` library. Importantly, using the optimization flags `-O2` or `-O3` under GCC and LLVM clang does not set the `NDEBUG` directive, you must set it manually (e.g., `-DNDEBUG`).
- For release builds, we recommend setting the `NDEBUG` pre-processor directive when compiling the `simdjson` library. Importantly, using the optimization flags `-O2` or `-O3` under GCC and LLVM clang does not set the `NDEBUG` directive, you must set it manually (e.g., `-DNDEBUG`).
- For long streams of JSON documents, consider [`iterate_many`](iterate_many.md) and [`parse_many`](parse_many.md) for better performance.
- Never seek to access a field twice (e.g., o["data"] and later again o["data"]). Instead capture once an ondemand::value and reuse it.
- If you must access several different keys in an object, it might be preferable to iterate through all the fields in the object instead, and branch on the field keys.
- If you must access several different keys in an object, it might be preferable to iterate through all the fields in the object instead, and branch on the field keys. Consider this example.
```cpp
auto json = R"({"price": 123.456789, "volume": 9999,
"timestamp": "2025-09-04T09:45:00Z",
"symbol": "XYZ", "currency": "USD", "change": 1.23,
"isActive": true})"_padded;
simdjson::ondemand::parser parser;
simdjson::ondemand::document doc = parser.iterate(json);
for(auto keyvalue : doc.get_object()) {
simdjson::ondemand::raw_json_string key = keyvalue.key();
switch(key[0]) {
case 'p': // price
if (key == "price") {
std::string_view price_str = keyvalue.value().raw_json();
std::cout << "Price: " << price_str << std::endl;
}
break;
case 'v': // volume
if (key == "volume") {
std::string_view volume_str = keyvalue.value().raw_json();
std::cout << "Volume: " << volume_str << std::endl;
}
break;
case 't': // timestamp
if (key == "timestamp") {
std::string_view timestamp = keyvalue.value();
std::cout << "Timestamp: " << timestamp << std::endl;
}
break;
default: break;
}
}
```
- If possible, refer to each object and array in your code once. For example, the following code repeatedly refers to the `"data"` key to create an object...
```C++
std::string_view make = o["data"]["make"];
+257
View File
@@ -0,0 +1,257 @@
Builder
==========
Sometimes you want to generate JSON string outputs efficiently.
The simdjson library provides high-performance low-level facilities.
When using these low-level functionalities, you are responsible to
define the structure of your JSON document. Our more advanced interface
automates the process using C++26 static reflection: you get both high
speed and high convenience.
- [Builder](#builder)
* [Overview: string_builder](#overview--string-builder)
* [Example: string_builder](#example--string-builder)
* [C++26 static reflection](#c--26-static-reflection)
+ [Without `string_buffer` instance](#without--string-buffer--instance)
+ [Without `string_buffer` instance but with explicit error handling](#without--string-buffer--instance-but-with-explicit-error-handling)
Overview: string_builder
---------------------------
The string_builder class is a low-level utility for constructing JSON strings representing documents. It is optimized for performance, potentially leveraging kernel-specific features like SIMD instructions for tasks such as string escaping. This class supports atomic types (e.g., booleans, numbers, strings) but does not handle composed types directly (like arrays or objects).
Note that JSON strings are always encoded as UTF-8.
An `string_builder` is created with an initial buffer capacity (e.g., 1kB). The memory
is reallocated when needed.
The efficiency of `string_builder` stems from its internal use of a resizable array or buffer. When you append data, it adds the characters to this buffer, resizing it only when necessary, typically in a way that minimizes reallocations. This approach contrasts with regular string concatenation, where each operation creates a new string, copying all previous content, leading to quadratic time complexity for repeated concatenations.
It has the following methods to add content to the string:
- `append(number_type v)`: Appends a number (including booleans) to the JSON buffer. Booleans are converted to the strings "false" or "true". Numbers are formatted according to the JSON standard, with floating-point numbers using the shortest representation that accurately reflects the value.
- `append(char c)`: Appends a single character to the JSON buffer.
- `append_null()`: Appends the string "null" to the JSON buffer.
- `clear()`: Clears the contents of the JSON buffer, resetting the position to 0 while retaining the allocated capacity.
- `escape_and_append(std::string_view input)`: Appends a string view to the JSON buffer after escaping special characters (e.g., quotes, backslashes) as required by JSON.
- `escape_and_append_with_quotes(std::string_view input)` Appends a string view surrounded by double quotes (e.g., "input") to the JSON buffer after escaping special characters. For constant strings, you may also do `escape_and_append_with_quotes<"mystring">()`.
- `escape_and_append_with_quotes(char input)`: Appends a single character surrounded by double quotes (e.g., "c") to the JSON buffer after escaping it if necessary.
- `append_raw(const char *c)`: Appends a null-terminated C string directly to the JSON buffer without escaping.
- `append_raw(std::string_view input)`: Appends a string view directly to the JSON buffer without escaping.
- `append_raw(const char *str, size_t len)`: Appends a specified number of characters from a C string directly to the JSON
- `append_key_value(key,value)`: Appends a key and a value (`"json":somevalue`)
- `append_key_value<"mykey">(value)`: Appends a key and a value (`"json":somevalue`), useful when the key is a compile-time constant (C++20).
After writing the content, if you have reasons to believe that the content might violate UTF-8 conventions, you can check it as follows:
- `validate_unicode()`: Checks if the content in the JSON buffer is valid UTF-8. Returns: true if the content is valid UTF-8, false otherwise.
You might need to do unicode validation if you have strings in your data structures containing
malformed UTF-8. Note that we do not automatically call `validate_unicode()`.
Once you are satisfied, you can recover the string as follows:
- `operator std::string()`: Converts the JSON buffer to an std::string. (Might throw if an error occurred.)
- `operator std::string_view()`: Converts the JSON buffer to an std::string_view. (Might throw if an error occurred.)
- `view()`: Returns a view of the written JSON buffer as a `simdjson_result<std::string_view>` (C++20).
The later method (`view()`) is recommended. For performance reasons, we expect you to explicitly call `validate_unicode()` as needed (e.g., prior to calling `view()`).
Example: string_builder
---------------------------
```C++
struct Car {
std::string make;
std::string model;
int64_t year;
std::vector<double> tire_pressure;
};
void serialize_car(const Car& car, simdjson::builder::string_builder& builder) {
// start of JSON
builder.start_object();
// "make"
builder.append_key_value("make", car.make);
builder.append_comma();
// "model"
builder.append_key_value("model", car.model);
builder.append_comma();
// "year"
builder.append_key_value("year", car.year);
builder.append_comma();
// "tire_pressure"
builder.escape_and_append_with_quotes("tire_pressure");
builder.append_colon();
builder.start_array();
// vector tire_pressure
for (size_t i = 0; i < car.tire_pressure.size(); ++i) {
builder.append(car.tire_pressure[i]);
if (i < car.tire_pressure.size() - 1) {
builder.append_comma();
}
}
builder.end_array();
builder.end_object();
}
bool car_test() {
simdjson::builder::string_builder sb;
Car c = {"Toyota", "Corolla", 2017, {30.0,30.2,30.513,30.79}};
serialize_car(c, sb);
std::string_view p{sb};
// p holds the JSON:
// "{\"make\":\"Toyota\",\"model\":\"Corolla\",\"year\":2017,\"tire_pressure\":[30.0,30.2,30.513,30.79]}"
return true;
}
```
The `string_builder` constructor takes an optional parameter which specifies the initial
memory allocation in byte. If you know approximately the size of your JSON output, you can
pass this value as a parameter (e.g., `simdjson::builder::string_builder sb{1233213}`).
The `string_builder` might throw an exception in case of error when you cast it result to `std::string_view`. If you wish to avoid exceptions, you can use the following programming pattern:
```cpp
std::string_view p;
if(sb.view().get(p)) {
return false; // there was an error
}
```
In all cases, the `std::string_view` instance depends the corresponding `string_builder` instance.
### C++20
If you have C++20, you can simplify the code, as the `std::vector<double>` is automatically supported. Further, we can pass the keys (which are compile-time
constant) as template parameter (for improved performance).
```cpp
Car c = {"Toyota", "Corolla", 2017, {30.0,30.2,30.513,30.79}};
simdjson::builder::string_builder sb;
sb.start_object();
sb.append_key_value<"make">(c.make);
sb.append_comma();
sb.append_key_value<"model">(c.model);
sb.append_comma();
sb.append_key_value<"year">(c.year);
sb.append_comma();
sb.append_key_value<"tire_pressure">(c.tire_pressure);
sb.end_object();
std::string_view p = sb.view();
```
With C++20, you can similarly handle standard containers transparently.
For example, you can serialize `std::map<std::string,T>` types.
```cpp
std::map<std::string,double> c = {{"key1", 1}, {"key2", 1}};
simdjson::builder::string_builder sb;
sb.append(c);
std::string_view p = sb.view();
```
You can also serialize `std::vector<T>` types.
```cpp
std::vector<std::vector<double>> c = {{1.0, 2.0}, {3.0, 4.0}};
simdjson::builder::string_builder sb;
sb.append(c);
std::string_view p = sb.view();
```
You can also skip the creation for the `string_builder` instance in such simple cases.
```cpp
std::vector<std::vector<double>> c = {{1.0, 2.0}, {3.0, 4.0}};
std::string json = simdjson::to_json(c);
```
We do recommend that you create and reuse the `string_builder` instance for performance
reasons.
C++26 static reflection
------------------------
Static reflection (or compile-time reflection) in C++26 introduces a powerful compile-time mechanism that allows a program to inspect and manipulate its own structure, such as types, variables, functions, and other program elements, during compilation. Unlike runtime reflection in languages like Java or Python, C++26s static reflection operates entirely at compile time, aligning with C++s emphasis on zero-overhead abstractions and high performance. It means
that you can delegate much of the work to the library.
If you have a compiler with support C++26 static reflection, you can compile
your code with the `SIMDJSON_STATIC_REFLECTION` macro set:
```cpp
#define SIMDJSON_STATIC_REFLECTION 1
//...
#include "simdjson.h"
```
And then you can append your data structures to a `string_builder` instance
automatically. In most cases, it should work automatically:
```cpp
struct Car {
std::string make;
std::string model;
int64_t year;
std::vector<double> tire_pressure;
};
bool car_test() {
simdjson::builder::string_builder sb;
Car c = {"Toyota", "Corolla", 2017, {30.0,30.2,30.513,30.79}};
sb << c;
std::string_view p{sb};
// p holds the JSON:
// "{\"make\":\"Toyota\",\"model\":\"Corolla\",\"year\":2017,\"tire_pressure\":[30.0,30.2,30.513,30.79]}"
return true;
}
```
### Without `string_buffer` instance
In some instances, you might want to create a string directly from your own data type.
You can create a string directly, without an explicit `string_builder` instance
with the `simdjson::to_json` template function.
(Under the hood a `string_builder` instance may still be created.)
```cpp
struct Car {
std::string make;
std::string model;
int64_t year;
std::vector<double> tire_pressure;
};
void f() {
Car c = {"Toyota", "Corolla", 2017, {30.0,30.2,30.513,30.79}};
std::string json = simdjson::to_json(c);
}
```
If you know the output size, in bytes, of your JSON string, you may
pass it as a second parameter (e.g., `simdjson::to_json(c, 31123)`).
### Without `string_buffer` instance but with explicit error handling
If prefer a version without exceptions and explicit error handling, you can use the following
pattern:
```cpp
std::string json;
if(simdjson::to(c).get(json)) {
// there was an error
} else {
// json contain the serialized JSON
}
```
+77 -2
View File
@@ -1,7 +1,10 @@
The Document-Object-Model (DOM) front-end
==========
An overview of what you need to know to use simdjson, with examples.
An overview of what you need to know to use simdjson to parse JSON documents with
our DOM API, with examples. [Our documentation regarding the generation (serialization) of JSON documents is in a
separate document](https://github.com/simdjson/simdjson/blob/master/doc/builder.md).
* [DOM vs On-Demand](#dom-vs-on-demand)
* [The Basics: Loading and Parsing JSON Documents](#the-basics-loading-and-parsing-json-documents-using-the-dom-front-end)
@@ -28,6 +31,10 @@ a conventional Document-Object-Model (DOM) front-end. In such a scenario, the JS
entirely parsed, validated and materialized in memory as the first step. The programmer may
then access the parsed data using this in-memory model.
On-Demand is a different model where you parse just what you need, directly into your own
data structure. The On-Demand approach, when well tuned, can provide superior performance.
[We refer you to the On-Demand documentation for further details](https://github.com/simdjson/simdjson/blob/master/doc/basics.md).
The Basics: Loading and Parsing JSON Documents using the DOM front-end
----------------------------------------------
@@ -122,7 +129,7 @@ Once you have an element, you can navigate it with idiomatic C++ iterators, oper
The strings contain unescaped valid UTF-8 strings: no unmatched surrogate is allowed.
Internally, numbers are stored as either 64-bit integers or 64-bit floating-point numbers.
Thus it is possible to get the full 64-bit integer range (either signed or unsigned).
By default, the string `-0` is parsed as the integer 0 as in Pytho or C++. If you set the macro
By default, the string `-0` is parsed as the integer 0 as in Python or C++. If you set the macro
`SIMDJSON_MINUS_ZERO_AS_FLOAT` to `1` when building simdjson, you can get that `-0` is mapped to `-0.0`
as in JavaScript. You can get the desired effect by building simdjson with cmake setting the
`SIMDJSON_MINUS_ZERO_AS_FLOAT` to on: `cmake -B build -D SIMDJSON_MINUS_ZERO_AS_FLOAT=ON`.
@@ -347,6 +354,74 @@ if(error) { /*won't happen*/ }
```
## Using `at_path_with_wildcard` for JSONPath Queries
The `at_path_with_wildcard` function in simdjson extends the JSONPath querying capabilities by supporting wildcard expressions (`*`) in JSON paths. This allows users to retrieve multiple elements from a JSON document in a single query. For example, you can use `$.address.*` to fetch all fields within the `address` object or `$.phoneNumbers[*].numbers[*]` to retrieve all phone numbers across multiple objects in an array.
The `*` wildcard matches all elements at a specific level. For instance, `$.address.*` retrieves all key-value pairs in the `address` object, while `$.*.streetAddress` fetches all `streetAddress` fields across objects at the root level. You can combine wildcards with array indexing. For example, `$.phoneNumbers[*].numbers[1]` retrieves the second number from each `numbers` array in the `phoneNumbers` array. If no elements match the wildcard query, the function returns an empty result. For instance, querying `$.empty_object.*` or `$.empty_array.*` will yield an empty set.
### Example Usage
Here is an example demonstrating the use of `at_path_with_wildcard`:
```cpp
simdjson::padded_string json_string = R"(
{
"firstName": "John",
"lastName": "doe",
"age": 26,
"address": {
"streetAddress": "naist street",
"city": "Nara",
"postalCode": "630-0192"
},
"phoneNumbers": [
{
"type": "iPhone",
"numbers": ["0123-4567-8888", "0123-4567-8788"]
},
{
"type": "home",
"numbers": ["0123-4567-8910"]
}
]
})"_padded;
dom::parser parser;
dom::element parsed_json = parser.parse(json_string);
std::vector<dom::element> values;
// Fetch all fields in the address object
auto error = parsed_json.at_path_with_wildcard("$.address.*").get(values);
if(error) {
// do something
}
for (auto &value : values) {
std::string_view field;
error = value.get(field);
if(error) {
// do something
}
std::cout << field << std::endl;
}
// Fetch all phone numbers
error = parsed_json.at_path_with_wildcard("$.phoneNumbers[*].numbers[*]").get(values);
if(error) {
// do something
}
for (auto &value : values) {
std::string_view number;
error = value.get(number);
if(error) {
// do something
}
std::cout << number << std::endl;
}
```
This function is particularly useful for extracting data from complex JSON structures with nested arrays and objects. By leveraging wildcards, you can simplify your queries and reduce the need for multiple iterations.
Error Handling
--------------
+1 -1
View File
@@ -309,7 +309,7 @@ The first argument is usually a tag type (often an empty struct) that uniquely i
You can deserialize you own data structures conveniently if your system supports C++20.
When it is the case, the macro `SIMDJSON_SUPPORTS_DESERIALIZATION` will be set to 1 by
When it is the case, the macro `SIMDJSON_SUPPORTS_CONCEPTS` will be set to 1 by
the simdjson library.
Consider a custom class `Car`:
-2
View File
@@ -173,8 +173,6 @@ Recent versions of Microsoft Visual Studio on Windows provides support for the L
We recommend Visual Studio users prefer LLVM (clang-cl). It compiles to faster release binaries. Furthermore, it compilers faster in release mode.
Under Windows, we also support the GNU GCC compiler via MSYS2. The performance of 64-bit MSYS2 under Windows is excellent (on par with Linux).
Power Usage and Downclocking
--------------
+9 -3
View File
@@ -20,10 +20,14 @@ IF(${CMAKE_SYSTEM_NAME} MATCHES "Linux")
endif()
add_quickstart_test(quickstart_noexceptions quickstart_noexceptions.cpp NO_EXCEPTIONS LABELS acceptance)
add_quickstart_test(quickstart_noexceptions11 quickstart_noexceptions.cpp NO_EXCEPTIONS CXX_STANDARD c++11)
if(NOT SIMDJSON_STATIC_REFLECTION)
add_quickstart_test(quickstart_noexceptions11 quickstart_noexceptions.cpp NO_EXCEPTIONS CXX_STANDARD c++11)
endif(NOT SIMDJSON_STATIC_REFLECTION)
add_quickstart_test(quickstart2_noexceptions quickstart2_noexceptions.cpp NO_EXCEPTIONS LABELS acceptance)
add_quickstart_test(quickstart2_noexceptions11 quickstart2_noexceptions.cpp NO_EXCEPTIONS CXX_STANDARD c++11)
if(NOT SIMDJSON_STATIC_REFLECTION)
add_quickstart_test(quickstart2_noexceptions11 quickstart2_noexceptions.cpp NO_EXCEPTIONS CXX_STANDARD c++11)
endif(NOT SIMDJSON_STATIC_REFLECTION)
# On-Demand Quick Start
if (SIMDJSON_EXCEPTIONS)
@@ -33,6 +37,8 @@ IF(${CMAKE_SYSTEM_NAME} MATCHES "Linux")
endif()
add_quickstart_test(quickstart_ondemand_noexceptions quickstart_ondemand_noexceptions.cpp NO_EXCEPTIONS LABELS quickstart_ondemand acceptance)
add_quickstart_test(quickstart_ondemand_noexceptions11 quickstart_ondemand_noexceptions.cpp NO_EXCEPTIONS CXX_STANDARD c++11 LABELS quickstart_ondemand)
if(NOT SIMDJSON_STATIC_REFLECTION)
add_quickstart_test(quickstart_ondemand_noexceptions11 quickstart_ondemand_noexceptions.cpp NO_EXCEPTIONS CXX_STANDARD c++11 LABELS quickstart_ondemand)
endif(NOT SIMDJSON_STATIC_REFLECTION)
endif()
+1 -1
View File
@@ -18,7 +18,7 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t *Data, size_t Size) {
auto error = parser.parse(str).get(elem);
if (error) { return 0; }
std::string minified=simdjson::minify(elem);
std::string minified = simdjson::minify(elem);
(void)minified;
return 0;
}
+2
View File
@@ -53,4 +53,6 @@
#include "simdjson/dom.h"
#include "simdjson/ondemand.h"
#include "simdjson/convert.h"
#include "simdjson/convert-inl.h"
#endif // SIMDJSON_H
+13 -2
View File
@@ -134,6 +134,12 @@ namespace {
tmp = vpaddq_u8(tmp, tmp);
return vgetq_lane_u16(vreinterpretq_u16_u8(tmp), 0);
}
// Returns 4-bit out of each byte, alternating between the high 4 bits and low
// bits result it is 64 bit.
simdjson_inline uint64_t to_bitmask64() const {
return vget_lane_u64(
vreinterpret_u64_u8(vshrn_n_u16(vreinterpretq_u16_u8(*this), 4)), 0);
}
simdjson_inline bool any() const { return vmaxvq_u32(vreinterpretq_u32_u8(*this)) != 0; }
};
@@ -210,7 +216,7 @@ namespace {
// Bit-specific operations
simdjson_inline simd8<bool> any_bits_set(simd8<uint8_t> bits) const { return vtstq_u8(*this, bits); }
simdjson_inline bool any_bits_set_anywhere() const { return this->max_val() != 0; }
simdjson_inline bool any_bits_set_anywhere() const { return vmaxvq_u32(vreinterpretq_u32_u8(*this)) != 0; }
simdjson_inline bool any_bits_set_anywhere(simd8<uint8_t> bits) const { return (*this & bits).any_bits_set_anywhere(); }
template<int N>
simdjson_inline simd8<uint8_t> shr() const { return vshrq_n_u8(*this, N); }
@@ -223,7 +229,12 @@ namespace {
return lookup_table.apply_lookup_16_to(*this);
}
// Returns 4-bit out of each byte, alternating between the high 4 bits and low
// bits result it is 64 bit.
simdjson_inline uint64_t to_bitmask64() const {
return vget_lane_u64(
vreinterpret_u64_u8(vshrn_n_u16(vreinterpretq_u16_u8(*this), 4)), 0);
}
// Copies to 'output" all bytes corresponding to a 0 in the mask (interpreted as a bitset).
// Passing a 0 value for mask would be equivalent to writing out every byte to output.
// Only the first 16 - count_ones(mask) bytes of the result are significant but 16 bytes
@@ -46,6 +46,32 @@ simdjson_inline backslash_and_quote backslash_and_quote::copy_and_find(const uin
};
}
struct escaping {
static constexpr uint32_t BYTES_PROCESSED = 16;
simdjson_inline static escaping copy_and_find(const uint8_t *src, uint8_t *dst);
simdjson_inline bool has_escape() { return escape_bits != 0; }
simdjson_inline int escape_index() { return trailing_zeroes(escape_bits) / 4; }
uint64_t escape_bits;
}; // struct escaping
simdjson_inline escaping escaping::copy_and_find(const uint8_t *src, uint8_t *dst) {
static_assert(SIMDJSON_PADDING >= (BYTES_PROCESSED - 1), "escaping finder must process fewer than SIMDJSON_PADDING bytes");
simd8<uint8_t> v(src);
v.store(dst);
simd8<bool> is_quote = (v == '"');
simd8<bool> is_backslash = (v == '\\');
simd8<bool> is_control = (v < 32);
return {
(is_backslash | is_quote | is_control).to_bitmask64()
};
}
} // unnamed namespace
} // namespace arm64
} // namespace simdjson
+1
View File
@@ -10,6 +10,7 @@
#include "simdjson/error.h"
#include "simdjson/portability.h"
#include "simdjson/concepts.h"
#include "simdjson/constevalutil.h"
/**
* @brief The top level simdjson namespace, containing everything the library provides.
+12 -11
View File
@@ -199,17 +199,6 @@ double from_chars(const char *first, const char* end) noexcept;
// We assume by default static linkage
#define SIMDJSON_DLLIMPORTEXPORT
#endif
/**
* Workaround for the vcpkg package manager. Only vcpkg should
* ever touch the next line. The SIMDJSON_USING_LIBRARY macro is otherwise unused.
*/
#if SIMDJSON_USING_LIBRARY
#define SIMDJSON_DLLIMPORTEXPORT __declspec(dllimport)
#endif
/**
* End of workaround for the vcpkg package manager.
*/
#else
#define SIMDJSON_DLLIMPORTEXPORT
#endif
@@ -356,4 +345,16 @@ namespace std {
#define SIMDJSON_AVX512_ALLOWED 1
#endif
#ifndef __has_cpp_attribute
#define simdjson_lifetime_bound
#elif __has_cpp_attribute(msvc::lifetimebound)
#define simdjson_lifetime_bound [[msvc::lifetimebound]]
#elif __has_cpp_attribute(clang::lifetimebound)
#define simdjson_lifetime_bound [[clang::lifetimebound]]
#elif __has_cpp_attribute(lifetimebound)
#define simdjson_lifetime_bound [[lifetimebound]]
#else
#define simdjson_lifetime_bound
#endif
#endif // SIMDJSON_COMMON_DEFS_H
+44 -3
View File
@@ -50,28 +50,69 @@
#endif
#endif
#ifndef SIMDJSON_CONSTEXPR_LAMBDA
#if SIMDJSON_CPLUSPLUS17
#define SIMDJSON_CONSTEXPR_LAMBDA constexpr
#else
#define SIMDJSON_CONSTEXPR_LAMBDA
#endif
#endif
#ifdef __has_include
#if __has_include(<version>)
#include <version>
#endif
#endif
// The current specification is unclear on how we detect
// static reflection, both __cpp_lib_reflection and
// __cpp_impl_reflection are proposed in the draft specification.
// For now, we disable static reflect by default. It must be
// specified at compiler time.
#ifndef SIMDJSON_STATIC_REFLECTION
#define SIMDJSON_STATIC_REFLECTION 0 // disabled by default.
#endif
#if defined(__apple_build_version__)
#if __apple_build_version__ < 14000000
#define SIMDJSON_CONCEPT_DISABLED 1 // apple-clang/13 doesn't support std::convertible_to
#endif
#endif
#if defined(__cpp_lib_ranges) && __cpp_lib_ranges >= 201911L
#include <ranges>
#define SIMDJSON_SUPPORTS_RANGES 1
#else
#define SIMDJSON_SUPPORTS_RANGES 0
#endif
#if defined(__cpp_concepts) && !defined(SIMDJSON_CONCEPT_DISABLED)
#if __cpp_concepts >= 201907L
#include <utility>
#define SIMDJSON_SUPPORTS_CONCEPTS 1
#else
#define SIMDJSON_SUPPORTS_CONCEPTS 0
#endif
#else // defined(__cpp_concepts) && !defined(SIMDJSON_CONCEPT_DISABLED)
#define SIMDJSON_SUPPORTS_CONCEPTS 0
#endif // defined(__cpp_concepts) && !defined(SIMDJSON_CONCEPT_DISABLED)
// copy SIMDJSON_SUPPORTS_CONCEPTS to SIMDJSON_SUPPORTS_DESERIALIZATION.
#if SIMDJSON_SUPPORTS_CONCEPTS
#define SIMDJSON_SUPPORTS_DESERIALIZATION 1
#else
#define SIMDJSON_SUPPORTS_DESERIALIZATION 0
#endif
#else // defined(__cpp_concepts) && !defined(SIMDJSON_CONCEPT_DISABLED)
#define SIMDJSON_SUPPORTS_DESERIALIZATION 0
#endif // defined(__cpp_concepts) && !defined(SIMDJSON_CONCEPT_DISABLED)
#if !defined(SIMDJSON_CONSTEVAL)
#if defined(__cpp_consteval) && __cpp_consteval >= 201811L && defined(__cpp_lib_constexpr_string) && __cpp_lib_constexpr_string >= 201907L
#define SIMDJSON_CONSTEVAL 1
#else
#define SIMDJSON_CONSTEVAL 0
#endif // defined(__cpp_consteval) && __cpp_consteval >= 201811L && defined(__cpp_lib_constexpr_string) && __cpp_lib_constexpr_string >= 201907L
#endif // !defined(SIMDJSON_CONSTEVAL)
#endif // SIMDJSON_COMPILER_CHECK_H
+5 -4
View File
@@ -1,6 +1,6 @@
#ifndef SIMDJSON_CONCEPTS_H
#define SIMDJSON_CONCEPTS_H
#if SIMDJSON_SUPPORTS_DESERIALIZATION
#if SIMDJSON_SUPPORTS_CONCEPTS
#include <concepts>
#include <type_traits>
@@ -32,7 +32,9 @@ SIMDJSON_IMPL_CONCEPT(op_append, operator+=)
#undef SIMDJSON_IMPL_CONCEPT
} // namespace details
template <typename T>
concept is_pair = requires { typename T::first_type; typename T::second_type; } &&
std::same_as<T, std::pair<typename T::first_type, typename T::second_type>>;
template <typename T>
concept string_view_like = std::is_convertible_v<T, std::string_view> &&
!std::is_convertible_v<T, const char*>;
@@ -115,7 +117,6 @@ concept optional_type = requires(std::remove_cvref_t<T> obj) {
{ obj.value() } -> std::same_as<typename std::remove_cvref_t<T>::value_type&>;
requires requires(typename std::remove_cvref_t<T>::value_type &&val) {
obj.emplace(std::move(val));
obj = std::move(val);
{
obj.value_or(val)
} -> std::convertible_to<typename std::remove_cvref_t<T>::value_type>;
@@ -127,5 +128,5 @@ concept optional_type = requires(std::remove_cvref_t<T> obj) {
} // namespace concepts
} // namespace simdjson
#endif // SIMDJSON_SUPPORTS_DESERIALIZATION
#endif // SIMDJSON_SUPPORTS_CONCEPTS
#endif // SIMDJSON_CONCEPTS_H
+53
View File
@@ -0,0 +1,53 @@
#ifndef SIMDJSON_CONSTEVALUTIL_H
#define SIMDJSON_CONSTEVALUTIL_H
#include <string>
#include <string_view>
#include <array>
#if SIMDJSON_CONSTEVAL
namespace simdjson {
namespace constevalutil {
constexpr static std::array<uint8_t, 256> json_quotable_character = {
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
constexpr static std::array<std::string_view, 32> control_chars = {
"\\u0000", "\\u0001", "\\u0002", "\\u0003", "\\u0004", "\\u0005", "\\u0006",
"\\u0007", "\\b", "\\t", "\\n", "\\u000b", "\\f", "\\r",
"\\u000e", "\\u000f", "\\u0010", "\\u0011", "\\u0012", "\\u0013", "\\u0014",
"\\u0015", "\\u0016", "\\u0017", "\\u0018", "\\u0019", "\\u001a", "\\u001b",
"\\u001c", "\\u001d", "\\u001e", "\\u001f"};
// unoptimized, meant for compile-time execution
consteval std::string consteval_to_quoted_escaped(std::string_view input) {
std::string out = "\"";
for (char c : input) {
if (json_quotable_character[uint8_t(c)]) {
if (c == '"') {
out.append("\\\"");
} else if (c == '\\') {
out.append("\\\\");
} else {
std::string_view v = control_chars[uint8_t(c)];
out.append(v);
}
} else {
out.push_back(c);
}
}
out.push_back('"');
return out;
}
} // namespace constevalutil
} // namespace simdjson
#endif // SIMDJSON_CONSTEVAL
#endif // SIMDJSON_CONSTEVALUTIL_H
+133
View File
@@ -0,0 +1,133 @@
#ifndef SIMDJSON_CONVERT_INL_H
#define SIMDJSON_CONVERT_INL_H
#include "simdjson/convert.h"
#if SIMDJSON_SUPPORTS_CONCEPTS
namespace simdjson {
namespace convert {
namespace internal {
// auto_parser method definitions
template <typename parser_type>
inline auto_parser<parser_type>::auto_parser(parser_type &&parser, ondemand::document &&doc) noexcept requires(!std::is_pointer_v<parser_type>)
: m_parser{std::move(parser)}, m_doc{std::move(doc)} {}
template <typename parser_type>
inline auto_parser<parser_type>::auto_parser(parser_type &&parser, padded_string_view const str) noexcept requires(!std::is_pointer_v<parser_type>)
: m_parser{std::move(parser)}, m_doc{}, m_error{SUCCESS} {
m_error = m_parser.iterate(str).get(m_doc);
}
template <typename parser_type>
inline auto_parser<parser_type>::auto_parser(std::remove_pointer_t<parser_type> &parser, ondemand::document &&doc) noexcept requires(std::is_pointer_v<parser_type>)
: m_parser{&parser}, m_doc{std::move(doc)} {}
template <typename parser_type>
inline auto_parser<parser_type>::auto_parser(std::remove_pointer_t<parser_type> &parser, padded_string_view const str) noexcept requires(std::is_pointer_v<parser_type>)
: m_parser{&parser}, m_doc{}, m_error{SUCCESS} {
m_error = m_parser->iterate(str).get(m_doc);
}
template <typename parser_type>
inline auto_parser<parser_type>::auto_parser(padded_string_view const str) noexcept requires(std::is_pointer_v<parser_type>)
: auto_parser{ondemand::parser::get_parser(), str} {}
template <typename parser_type>
inline auto_parser<parser_type>::auto_parser(parser_type parser, ondemand::document &&doc) noexcept requires(std::is_pointer_v<parser_type>)
: auto_parser{*parser, std::move(doc)} {}
template <typename parser_type>
inline std::remove_pointer_t<parser_type> &auto_parser<parser_type>::parser() noexcept {
if constexpr (std::is_pointer_v<parser_type>) {
return *m_parser;
} else {
return m_parser;
}
}
template <typename parser_type>
template <typename T>
inline simdjson_result<T> auto_parser<parser_type>::result() noexcept(is_nothrow_gettable<T>) {
if (m_error != SUCCESS) {
return m_error;
}
return m_doc.get<T>();
}
template <typename parser_type>
template <typename T>
simdjson_warn_unused simdjson_inline error_code auto_parser<parser_type>::get(T &value) && noexcept(is_nothrow_gettable<T>) {
return result<T>().get(value);
}
template <typename parser_type>
inline simdjson_result<ondemand::array> auto_parser<parser_type>::array() noexcept {
return result<ondemand::array>();
}
template <typename parser_type>
inline simdjson_result<ondemand::object> auto_parser<parser_type>::object() noexcept {
return result<ondemand::object>();
}
template <typename parser_type>
inline simdjson_result<ondemand::number> auto_parser<parser_type>::number() noexcept {
return result<ondemand::number>();
}
template <typename parser_type>
template <typename T>
inline auto_parser<parser_type>::operator T() noexcept(false) {
if (m_error != SUCCESS) {
throw simdjson_error(m_error);
}
return m_doc.get<T>();
}
template <typename parser_type>
template <typename T>
inline std::optional<T> auto_parser<parser_type>::optional() noexcept(is_nothrow_gettable<T>) {
if (m_error != SUCCESS) {
return std::nullopt;
}
T value;
if (m_doc.get<T>().get(value)) [[unlikely]] {
return std::nullopt;
}
return {std::move(value)};
}
// to_adaptor method definitions
template <typename T>
inline T to_adaptor<T>::operator()(simdjson_result<ondemand::value> &val) const noexcept {
return val.get<T>();
}
template <typename T>
inline auto to_adaptor<T>::operator()(padded_string_view const str) const noexcept {
return auto_parser{str};
}
template <typename T>
inline auto to_adaptor<T>::operator()(ondemand::parser &parser, padded_string_view const str) const noexcept {
return auto_parser<ondemand::parser *>{parser, str};
}
template <typename T>
inline auto to_adaptor<T>::operator()(std::string str) const noexcept {
return auto_parser{pad_with_reserve(str)};
}
template <typename T>
inline auto to_adaptor<T>::operator()(ondemand::parser &parser, std::string str) const noexcept {
return auto_parser<ondemand::parser *>{parser, pad_with_reserve(str)};
}
} // namespace internal
} // namespace convert
} // namespace simdjson
#endif // SIMDJSON_SUPPORTS_CONCEPTS
#endif // SIMDJSON_CONVERT_INL_H
+118
View File
@@ -0,0 +1,118 @@
#ifndef SIMDJSON_CONVERT_H
#define SIMDJSON_CONVERT_H
#include "simdjson/ondemand.h"
#include <optional>
#if SIMDJSON_SUPPORTS_CONCEPTS
namespace simdjson {
namespace convert {
namespace internal {
/**
* A utility class for automatically parsing JSON documents.
* This template is NOT part of our public API.
* It is subject to changes.
* @private
*/
template <typename parser_type = ondemand::parser*>
struct auto_parser {
private:
parser_type m_parser;
ondemand::document m_doc;
error_code m_error{SUCCESS};
template <typename T>
static constexpr bool is_nothrow_gettable = requires(ondemand::document doc) {
{ doc.get<T>() } noexcept;
};
public:
explicit auto_parser(parser_type &&parser, ondemand::document &&doc) noexcept requires(!std::is_pointer_v<parser_type>);
explicit auto_parser(parser_type &&parser, padded_string_view const str) noexcept requires(!std::is_pointer_v<parser_type>);
explicit auto_parser(std::remove_pointer_t<parser_type> &parser, ondemand::document &&doc) noexcept requires(std::is_pointer_v<parser_type>);
explicit auto_parser(std::remove_pointer_t<parser_type> &parser, padded_string_view const str) noexcept requires(std::is_pointer_v<parser_type>);
explicit auto_parser(padded_string_view const str) noexcept requires(std::is_pointer_v<parser_type>);
explicit auto_parser(parser_type parser, ondemand::document &&doc) noexcept requires(std::is_pointer_v<parser_type>);
auto_parser(auto_parser const &) = delete;
auto_parser &operator=(auto_parser const &) = delete;
auto_parser(auto_parser &&) noexcept = default;
auto_parser &operator=(auto_parser &&) noexcept = default;
~auto_parser() = default;
simdjson_warn_unused std::remove_pointer_t<parser_type> &parser() noexcept;
template <typename T>
simdjson_warn_unused simdjson_inline simdjson_result<T> result() noexcept(is_nothrow_gettable<T>);
template <typename T>
simdjson_warn_unused simdjson_inline error_code get(T &value) && noexcept(is_nothrow_gettable<T>);
simdjson_warn_unused simdjson_inline simdjson_result<ondemand::array> array() noexcept;
simdjson_warn_unused simdjson_inline simdjson_result<ondemand::object> object() noexcept;
simdjson_warn_unused simdjson_inline simdjson_result<ondemand::number> number() noexcept;
//template <typename T>
//simdjson_warn_unused simdjson_inline explicit(false) operator simdjson_result<T>() noexcept(is_nothrow_gettable<T>);
template <typename T>
simdjson_warn_unused simdjson_inline explicit(false) operator T() noexcept(false);
template <typename T>
simdjson_warn_unused simdjson_inline std::optional<T> optional() noexcept(is_nothrow_gettable<T>);
};
/**
* A utility class for adapting values for the `auto_parser`.
* This template is not part of our public API. It is subject to changes.
* @private
*/
template <typename T = void>
struct to_adaptor {
T operator()(simdjson_result<ondemand::value> &val) const noexcept;
auto operator()(padded_string_view const str) const noexcept;
auto operator()(ondemand::parser &parser, padded_string_view const str) const noexcept;
// The std::string is padded with reserve to ensure there is enough space for padding.
// Some sanitizers may not like this, so you can use simdjson::pad instead.
// simdjson::from(simdjson::pad(str))
auto operator()(std::string str) const noexcept;
auto operator()(ondemand::parser &parser, std::string str) const noexcept;
};
} // namespace internal
} // namespace convert
/**
* The simdjson::from instance is EXPERIMENTAL AND SUBJECT TO CHANGES.
*
* The `from` instance is a utility adaptor for parsing JSON strings into objects.
* It provides a convenient way to convert JSON data into C++ objects using the `auto_parser`.
*
* Example usage:
*
* ```cpp
* std::map<std::string, std::string> obj =
* simdjson::from(R"({"key": "value"})"_padded);
* ```
*
* This will parse the JSON string and return an object representation. By default, we
* use the simdjson::ondemand::parser::get_parser() instance. A parser instance should
* be used for just one document at a time.
*
* You can also pass you own parser instance:
* ```cpp
* simdjson::ondemand::parser parser;
* std::map<std::string, std::string> obj =
* simdjson::from(parser, R"({"key": "value"})"_padded);
* ```
* The parser instance can be reused.
*
* This functionality requires C++20 or better.
*/
static constexpr convert::internal::to_adaptor<> from{};
} // namespace simdjson
#endif // SIMDJSON_SUPPORTS_CONCEPTS
#endif // SIMDJSON_CONVERT_H
+109 -2
View File
@@ -52,11 +52,22 @@ inline simdjson_result<dom::element> simdjson_result<dom::array>::at_pointer(std
return at_pointer(json_pointer);
}
inline simdjson_result<std::vector<dom::element>> simdjson_result<dom::array>::at_path_with_wildcard(std::string_view json_path) const noexcept {
if (error()) {
return error();
}
return first.at_path_with_wildcard(json_path);
}
inline simdjson_result<dom::element> simdjson_result<dom::array>::at(size_t index) const noexcept {
if (error()) { return error(); }
return first.at(index);
}
inline std::vector<dom::element>& simdjson_result<dom::array>::get_values(std::vector<dom::element>& out) const noexcept {
return first.get_values(out);
}
namespace dom {
//
@@ -127,6 +138,93 @@ inline simdjson_result<element> array::at_path(std::string_view json_path) const
return at_pointer(json_pointer);
}
inline void array::process_json_path_of_child_elements(std::vector<element>::iterator& current, std::vector<element>::iterator& end, const std::string_view& path_suffix, std::vector<element>& accumulator) const noexcept {
if (current == end) {
return;
}
simdjson_result<std::vector<element>> result;
for (auto it = current; it != end; ++it) {
std::vector<element> child_result;
auto error = it->at_path_with_wildcard(path_suffix).get(child_result);
if(error) {
continue;
}
accumulator.reserve(accumulator.size() + child_result.size());
accumulator.insert(accumulator.end(),
std::make_move_iterator(child_result.begin()),
std::make_move_iterator(child_result.end()));
}
}
inline simdjson_result<std::vector<element>> array::at_path_with_wildcard(std::string_view json_path) const noexcept {
SIMDJSON_DEVELOPMENT_ASSERT(tape.usable()); // https://github.com/simdjson/simdjson/issues/1914
size_t i = 0;
// json_path.starts_with('$') requires C++20.
if (!json_path.empty() && json_path.front() == '$') {
i = 1;
}
if (i >= json_path.size() || (json_path[i] != '.' && json_path[i] != '[')) {
return INVALID_JSON_POINTER;
}
if (json_path.find("*") != std::string::npos) {
std::vector<element> child_values;
if (
(json_path.compare(i, 3, "[*]") == 0 && json_path.size() == i + 3) ||
(json_path.compare(i, 2,".*") == 0 && json_path.size() == i + 2)
) {
get_values(child_values);
return child_values;
}
std::pair<std::string_view, std::string_view> key_and_json_path = get_next_key_and_json_path(json_path);
std::string_view key = key_and_json_path.first;
json_path = key_and_json_path.second;
if (key.size() > 0) {
if (key == "*") {
get_values(child_values);
} else {
element pointer_result;
std::string json_pointer = std::string("/") + std::string(key);
auto error = at_pointer(json_pointer).get(pointer_result);
if (!error) {
child_values.emplace_back(pointer_result);
}
}
std::vector<element> result = {};
if (child_values.size() > 0) {
std::vector<element>::iterator child_values_begin = child_values.begin();
std::vector<element>::iterator child_values_end = child_values.end();
process_json_path_of_child_elements(child_values_begin, child_values_end, json_path, result);
}
return result;
} else {
return INVALID_JSON_POINTER;
}
} else {
element result;
auto error = at_path(json_path).get(result);
if (error) {
return error;
}
return std::vector<element>{std::move(result)};
}
}
inline simdjson_result<element> array::at(size_t index) const noexcept {
SIMDJSON_DEVELOPMENT_ASSERT(tape.usable()); // https://github.com/simdjson/simdjson/issues/1914
size_t i=0;
@@ -137,6 +235,15 @@ inline simdjson_result<element> array::at(size_t index) const noexcept {
return INDEX_OUT_OF_BOUNDS;
}
inline std::vector<element>& array::get_values(std::vector<element>& out) const noexcept {
out.reserve(this->size());
for (auto element : *this) {
out.emplace_back(element);
}
return out;
}
inline array::operator element() const noexcept {
return element(tape);
}
@@ -183,13 +290,13 @@ inline bool array::iterator::operator>(const array::iterator& other) const noexc
#include "simdjson/dom/element-inl.h"
#if defined(__cpp_lib_ranges)
#if SIMDJSON_SUPPORTS_RANGES
static_assert(std::ranges::view<simdjson::dom::array>);
static_assert(std::ranges::sized_range<simdjson::dom::array>);
#if SIMDJSON_EXCEPTIONS
static_assert(std::ranges::view<simdjson::simdjson_result<simdjson::dom::array>>);
static_assert(std::ranges::sized_range<simdjson::simdjson_result<simdjson::dom::array>>);
#endif // SIMDJSON_EXCEPTIONS
#endif // defined(__cpp_lib_ranges)
#endif // SIMDJSON_SUPPORTS_RANGES
#endif // SIMDJSON_ARRAY_INL_H
+27 -4
View File
@@ -1,6 +1,8 @@
#ifndef SIMDJSON_DOM_ARRAY_H
#define SIMDJSON_DOM_ARRAY_H
#include <vector>
#include "simdjson/dom/base.h"
#include "simdjson/internal/tape_ref.h"
@@ -108,6 +110,17 @@ public:
*/
inline simdjson_result<element> at_pointer(std::string_view json_pointer) const noexcept;
/**
* Recursive function which processes the json path of each child element
*/
inline void process_json_path_of_child_elements(std::vector<element>::iterator& current, std::vector<element>::iterator& end, const std::string_view& path_suffix, std::vector<element>& accumulator) const noexcept;
/**
* Adds support for JSONPath expression with wildcards '*'
*/
inline simdjson_result<std::vector<element>> at_path_with_wildcard(std::string_view json_path) const noexcept;
/**
* Get the value associated with the given JSONPath expression. We only support
* JSONPath queries that trivially convertible to JSON Pointer queries: key
@@ -141,6 +154,15 @@ public:
*/
inline simdjson_result<element> at(size_t index) const noexcept;
/**
* Gets the values of items in an array element
* This function has linear-time complexity: the values are checked one by one.
*
* @return The child elements of an array
*/
inline std::vector<element>& get_values(std::vector<element>& out) const noexcept;
/**
* Implicitly convert object to element
*/
@@ -167,8 +189,11 @@ public:
simdjson_inline simdjson_result(error_code error) noexcept; ///< @private
inline simdjson_result<dom::element> at_pointer(std::string_view json_pointer) const noexcept;
inline void process_json_path_of_child_elements(std::vector<dom::element>::iterator& current, std::vector<dom::element>::iterator& end, const std::string_view& path_suffix, std::vector<dom::element>& accumulator) const noexcept;
inline simdjson_result<std::vector<dom::element>> at_path_with_wildcard(std::string_view json_path) const noexcept;
inline simdjson_result<dom::element> at_path(std::string_view json_path) const noexcept;
inline simdjson_result<dom::element> at(size_t index) const noexcept;
inline std::vector<dom::element>& get_values(std::vector<dom::element>& out) const noexcept;
#if SIMDJSON_EXCEPTIONS
inline dom::array::iterator begin() const noexcept(false);
@@ -181,9 +206,7 @@ public:
} // namespace simdjson
#if defined(__cpp_lib_ranges)
#include <ranges>
#if SIMDJSON_SUPPORTS_RANGES
namespace std {
namespace ranges {
template<>
@@ -194,6 +217,6 @@ inline constexpr bool enable_view<simdjson::simdjson_result<simdjson::dom::array
#endif // SIMDJSON_EXCEPTIONS
} // namespace ranges
} // namespace std
#endif // defined(__cpp_lib_ranges)
#endif // SIMDJSON_SUPPORTS_RANGES
#endif // SIMDJSON_DOM_ARRAY_H
+20
View File
@@ -128,6 +128,12 @@ simdjson_inline simdjson_result<dom::element> simdjson_result<dom::element>::at_
if (json_pointer == "-1") { return INVALID_JSON_POINTER; }
return at_pointer(json_pointer);
}
simdjson_inline simdjson_result<std::vector<dom::element>> simdjson_result<dom::element>::at_path_with_wildcard(const std::string_view json_path) const noexcept {
if (error()) { return error(); }
return first.at_path_with_wildcard(json_path);
}
#ifndef SIMDJSON_DISABLE_DEPRECATED_API
[[deprecated("For standard compliance, use at_pointer instead, and prefix your pointers with a slash '/', see RFC6901 ")]]
simdjson_inline simdjson_result<dom::element> simdjson_result<dom::element>::at(const std::string_view json_pointer) const noexcept {
@@ -418,6 +424,20 @@ inline simdjson_result<element> element::at_pointer(std::string_view json_pointe
}
}
}
inline simdjson_result<std::vector<element>> element::at_path_with_wildcard(std::string_view json_path) const noexcept {
SIMDJSON_DEVELOPMENT_ASSERT(tape.usable()); // https://github.com/simdjson/simdjson/issues/1914
switch (tape.tape_ref_type()) {
case internal::tape_type::START_OBJECT:
return object(tape).at_path_with_wildcard(json_path);
case internal::tape_type::START_ARRAY:
return array(tape).at_path_with_wildcard(json_path);
default:
return std::vector<element>{};
}
}
inline simdjson_result<element> element::at_path(std::string_view json_path) const noexcept {
auto json_pointer = json_path_to_pointer_conversion(json_path);
if (json_pointer == "-1") { return INVALID_JSON_POINTER; }
+5
View File
@@ -1,6 +1,8 @@
#ifndef SIMDJSON_DOM_ELEMENT_H
#define SIMDJSON_DOM_ELEMENT_H
#include <vector>
#include "simdjson/dom/base.h"
#include "simdjson/dom/array.h"
@@ -399,6 +401,8 @@ public:
*/
inline simdjson_result<element> at_pointer(const std::string_view json_pointer) const noexcept;
inline simdjson_result<std::vector<element>> at_path_with_wildcard(const std::string_view json_path) const noexcept;
/**
* Get the value associated with the given JSONPath expression. We only support
* JSONPath queries that trivially convertible to JSON Pointer queries: key
@@ -544,6 +548,7 @@ public:
simdjson_inline simdjson_result<dom::element> operator[](const char *key) const noexcept;
simdjson_result<dom::element> operator[](int) const noexcept = delete;
simdjson_inline simdjson_result<dom::element> at_pointer(const std::string_view json_pointer) const noexcept;
simdjson_inline simdjson_result<std::vector<dom::element>> at_path_with_wildcard(const std::string_view json_path) const noexcept;
simdjson_inline simdjson_result<dom::element> at_path(const std::string_view json_path) const noexcept;
[[deprecated("For standard compliance, use at_pointer instead, and prefix your pointers with a slash '/', see RFC6901 ")]]
simdjson_inline simdjson_result<dom::element> at(const std::string_view json_pointer) const noexcept;
+114 -2
View File
@@ -40,10 +40,19 @@ inline simdjson_result<dom::element> simdjson_result<dom::object>::at_path(std::
if (json_pointer == "-1") { return INVALID_JSON_POINTER; }
return at_pointer(json_pointer);
}
inline simdjson_result<std::vector<dom::element>> simdjson_result<dom::object>::at_path_with_wildcard(std::string_view json_path) const noexcept {
if (error()) {
return error();
}
return first.at_path_with_wildcard(json_path);
}
inline simdjson_result<dom::element> simdjson_result<dom::object>::at_key(std::string_view key) const noexcept {
if (error()) { return error(); }
return first.at_key(key);
}
inline std::vector<dom::element>& simdjson_result<dom::object>::get_values(std::vector<dom::element>& out) const noexcept {
return first.get_values(out);
}
inline simdjson_result<dom::element> simdjson_result<dom::object>::at_key_case_insensitive(std::string_view key) const noexcept {
if (error()) { return error(); }
return first.at_key_case_insensitive(key);
@@ -143,6 +152,97 @@ inline simdjson_result<element> object::at_path(std::string_view json_path) cons
return at_pointer(json_pointer);
}
inline void object::process_json_path_of_child_elements(std::vector<element>::iterator& current, std::vector<element>::iterator& end, const std::string_view& path_suffix, std::vector<element>& accumulator) const noexcept {
if (current == end) {
return;
}
simdjson_result<std::vector<element>> result;
for (auto it = current; it != end; ++it) {
std::vector<element> child_result;
auto error = it->at_path_with_wildcard(path_suffix).get(child_result);
if(error) {
continue;
}
accumulator.reserve(accumulator.size() + child_result.size());
accumulator.insert(accumulator.end(),
std::make_move_iterator(child_result.begin()),
std::make_move_iterator(child_result.end()));
}
}
inline simdjson_result<std::vector<element>> object::at_path_with_wildcard(std::string_view json_path) const noexcept {
SIMDJSON_DEVELOPMENT_ASSERT(tape.usable()); // https://github.com/simdjson/simdjson/issues/1914
size_t i = 0;
if (json_path.empty()) {
return INVALID_JSON_POINTER;
}
// if JSONPath starts with $, skip it
// json_path.starts_with('$') requires C++20.
if (json_path.front() == '$') {
i = 1;
}
if (i >= json_path.size() || (json_path[i] != '.' && json_path[i] != '[')) {
// expect json path to always start with $ but this isn't currently
// expected in jsonpathutil.h.
return INVALID_JSON_POINTER;
}
if (json_path.find("*") != std::string::npos) {
std::vector<element> child_values;
if (
(json_path.compare(i, 3, "[*]") == 0 && json_path.size() == i + 3) ||
(json_path.compare(i, 2,".*") == 0 && json_path.size() == i + 2)
) {
get_values(child_values);
return child_values;
}
std::pair<std::string_view, std::string_view> key_and_json_path = get_next_key_and_json_path(json_path);
std::string_view key = key_and_json_path.first;
json_path = key_and_json_path.second;
if (key.size() > 0) {
if (key == "*") {
get_values(child_values);
} else {
element pointer_result;
auto error = at_pointer(std::string("/") + std::string(key)).get(pointer_result);
if (!error) {
child_values.emplace_back(pointer_result);
}
}
std::vector<element> result = {};
if (child_values.size() > 0) {
std::vector<element>::iterator child_values_begin = child_values.begin();
std::vector<element>::iterator child_values_end = child_values.end();
process_json_path_of_child_elements(child_values_begin, child_values_end, json_path, result);
}
return result;
} else {
return INVALID_JSON_POINTER;
}
} else {
element result;
auto error = this->at_path(json_path).get(result);
if (error) {
return error;
}
return std::vector<element>{std::move(result)};
}
}
inline simdjson_result<element> object::at_key(std::string_view key) const noexcept {
iterator end_field = end();
for (iterator field = begin(); field != end_field; ++field) {
@@ -152,6 +252,18 @@ inline simdjson_result<element> object::at_key(std::string_view key) const noexc
}
return NO_SUCH_FIELD;
}
inline std::vector<element>& object::get_values(std::vector<element>& out) const noexcept {
iterator end_field = end();
iterator begin_field = begin();
out.reserve(std::distance(begin_field, end_field));
for (iterator field = begin_field; field != end_field; ++field) {
out.emplace_back(field.value());
}
return out;
}
// In case you wonder why we need this, please see
// https://github.com/simdjson/simdjson/issues/323
// People do seek keys in a case-insensitive manner.
@@ -263,13 +375,13 @@ inline key_value_pair::key_value_pair(std::string_view _key, element _value) noe
} // namespace simdjson
#if defined(__cpp_lib_ranges)
#if SIMDJSON_SUPPORTS_RANGES
static_assert(std::ranges::view<simdjson::dom::object>);
static_assert(std::ranges::sized_range<simdjson::dom::object>);
#if SIMDJSON_EXCEPTIONS
static_assert(std::ranges::view<simdjson::simdjson_result<simdjson::dom::object>>);
static_assert(std::ranges::sized_range<simdjson::simdjson_result<simdjson::dom::object>>);
#endif // SIMDJSON_EXCEPTIONS
#endif // defined(__cpp_lib_ranges)
#endif // SIMDJSON_SUPPORTS_RANGES
#endif // SIMDJSON_OBJECT_INL_H
+25 -4
View File
@@ -1,6 +1,8 @@
#ifndef SIMDJSON_DOM_OBJECT_H
#define SIMDJSON_DOM_OBJECT_H
#include <vector>
#include "simdjson/dom/base.h"
#include "simdjson/dom/element.h"
#include "simdjson/internal/tape_ref.h"
@@ -172,6 +174,16 @@ public:
*/
inline simdjson_result<element> at_pointer(std::string_view json_pointer) const noexcept;
/**
* Recursive function which processes the json path of each child element
*/
inline void process_json_path_of_child_elements(std::vector<element>::iterator& current, std::vector<element>::iterator& end, const std::string_view& path_suffix, std::vector<element>& accumulator) const noexcept;
/**
* Adds support for JSONPath expression with wildcards '*'
*/
inline simdjson_result<std::vector<element>> at_path_with_wildcard(std::string_view json_path) const noexcept;
/**
* Get the value associated with the given JSONPath expression. We only support
* JSONPath queries that trivially convertible to JSON Pointer queries: key
@@ -203,6 +215,14 @@ public:
*/
inline simdjson_result<element> at_key(std::string_view key) const noexcept;
/**
* Gets the values associated with keys of an object
* This function has linear-time complexity: the keys are checked one by one.
*
* @return the values associated with each key of an object
*/
inline std::vector<element>& get_values(std::vector<element>& out) const noexcept;
/**
* Get the value associated with the given key in a case-insensitive manner.
* It is only guaranteed to work over ASCII inputs.
@@ -261,8 +281,11 @@ public:
inline simdjson_result<dom::element> operator[](const char *key) const noexcept;
simdjson_result<dom::element> operator[](int) const noexcept = delete;
inline simdjson_result<dom::element> at_pointer(std::string_view json_pointer) const noexcept;
inline void process_json_path_of_child_elements(std::vector<dom::element>::iterator& current, std::vector<dom::element>::iterator& end, const std::string_view& path_suffix, std::vector<dom::element>& accumulator) const noexcept;
inline simdjson_result<std::vector<dom::element>> at_path_with_wildcard(std::string_view json_path_new) const noexcept;
inline simdjson_result<dom::element> at_path(std::string_view json_path) const noexcept;
inline simdjson_result<dom::element> at_key(std::string_view key) const noexcept;
inline std::vector<dom::element>& get_values(std::vector<dom::element>& out) const noexcept;
inline simdjson_result<dom::element> at_key_case_insensitive(std::string_view key) const noexcept;
#if SIMDJSON_EXCEPTIONS
@@ -274,9 +297,7 @@ public:
} // namespace simdjson
#if defined(__cpp_lib_ranges)
#include <ranges>
#if SIMDJSON_SUPPORTS_RANGES
namespace std {
namespace ranges {
template<>
@@ -287,6 +308,6 @@ inline constexpr bool enable_view<simdjson::simdjson_result<simdjson::dom::objec
#endif // SIMDJSON_EXCEPTIONS
} // namespace ranges
} // namespace std
#endif // defined(__cpp_lib_ranges)
#endif // SIMDJSON_SUPPORTS_RANGES
#endif // SIMDJSON_DOM_OBJECT_H
+5 -5
View File
@@ -35,11 +35,11 @@ inline bool parser::dump_raw_tape(std::ostream &os) const noexcept {
return valid ? doc.dump_raw_tape(os) : false;
}
inline simdjson_result<size_t> parser::read_file(const std::string &path) noexcept {
inline simdjson_result<size_t> parser::read_file(std::string_view path) noexcept {
// Open the file
SIMDJSON_PUSH_DISABLE_WARNINGS
SIMDJSON_DISABLE_DEPRECATED_WARNING // Disable CRT_SECURE warning on MSVC: manually verified this is safe
std::FILE *fp = std::fopen(path.c_str(), "rb");
std::FILE *fp = std::fopen(path.data(), "rb");
SIMDJSON_POP_DISABLE_WARNINGS
if (fp == nullptr) {
@@ -91,18 +91,18 @@ inline simdjson_result<size_t> parser::read_file(const std::string &path) noexce
return bytes_read;
}
inline simdjson_result<element> parser::load(const std::string &path) & noexcept {
inline simdjson_result<element> parser::load(std::string_view path) & noexcept {
return load_into_document(doc, path);
}
inline simdjson_result<element> parser::load_into_document(document& provided_doc, const std::string &path) & noexcept {
inline simdjson_result<element> parser::load_into_document(document& provided_doc, std::string_view path) & noexcept {
size_t len;
auto _error = read_file(path).get(len);
if (_error) { return _error; }
return parse_into_document(provided_doc, loaded_bytes.get(), len, false);
}
inline simdjson_result<document_stream> parser::load_many(const std::string &path, size_t batch_size) noexcept {
inline simdjson_result<document_stream> parser::load_many(std::string_view path, size_t batch_size) noexcept {
size_t len;
auto _error = read_file(path).get(len);
if (_error) { return _error; }
+6 -6
View File
@@ -102,8 +102,8 @@ public:
* - other json errors if parsing fails. You should not rely on these errors to always the same for the
* same document: they may vary under runtime dispatch (so they may vary depending on your system and hardware).
*/
inline simdjson_result<element> load(const std::string &path) & noexcept;
inline simdjson_result<element> load(const std::string &path) && = delete ;
inline simdjson_result<element> load(std::string_view path) & noexcept;
inline simdjson_result<element> load(std::string_view path) && = delete ;
/**
* Load a JSON document from a file into a provide document instance and return a temporary reference to it.
@@ -148,8 +148,8 @@ public:
* - other json errors if parsing fails. You should not rely on these errors to always the same for the
* same document: they may vary under runtime dispatch (so they may vary depending on your system and hardware).
*/
inline simdjson_result<element> load_into_document(document& doc, const std::string &path) & noexcept;
inline simdjson_result<element> load_into_document(document& doc, const std::string &path) && =delete;
inline simdjson_result<element> load_into_document(document& doc, std::string_view path) & noexcept;
inline simdjson_result<element> load_into_document(document& doc, std::string_view path) && =delete;
/**
* Parse a JSON document and return a temporary reference to it.
@@ -386,7 +386,7 @@ public:
* - other json errors if parsing fails. You should not rely on these errors to always the same for the
* same document: they may vary under runtime dispatch (so they may vary depending on your system and hardware).
*/
inline simdjson_result<document_stream> load_many(const std::string &path, size_t batch_size = dom::DEFAULT_BATCH_SIZE) noexcept;
inline simdjson_result<document_stream> load_many(std::string_view path, size_t batch_size = dom::DEFAULT_BATCH_SIZE) noexcept;
/**
* Parse a buffer containing many JSON documents.
@@ -657,7 +657,7 @@ private:
inline error_code ensure_capacity(document& doc, size_t desired_capacity) noexcept;
/** Read the file into loaded_bytes */
inline simdjson_result<size_t> read_file(const std::string &path) noexcept;
inline simdjson_result<size_t> read_file(std::string_view path) noexcept;
friend class parser::Iterator;
friend class document_stream;
+167 -136
View File
@@ -3,8 +3,8 @@
#define SIMDJSON_SERIALIZATION_INL_H
#include "simdjson/dom/base.h"
#include "simdjson/dom/serialization.h"
#include "simdjson/dom/parser.h"
#include "simdjson/dom/serialization.h"
#include "simdjson/internal/tape_type.h"
#include "simdjson/dom/array-inl.h"
@@ -16,7 +16,9 @@
namespace simdjson {
namespace dom {
inline bool parser::print_json(std::ostream &os) const noexcept {
if (!valid) { return false; }
if (!valid) {
return false;
}
simdjson::internal::string_builder<> sb;
sb.append(doc.root());
std::string_view answer = sb.str();
@@ -24,37 +26,51 @@ inline bool parser::print_json(std::ostream &os) const noexcept {
return true;
}
inline std::ostream& operator<<(std::ostream& out, simdjson::dom::element value) {
simdjson::internal::string_builder<> sb;
sb.append(value);
return (out << sb.str());
inline std::ostream &operator<<(std::ostream &out,
simdjson::dom::element value) {
simdjson::internal::string_builder<> sb;
sb.append(value);
return (out << sb.str());
}
#if SIMDJSON_EXCEPTIONS
inline std::ostream& operator<<(std::ostream& out, simdjson::simdjson_result<simdjson::dom::element> x) {
if (x.error()) { throw simdjson::simdjson_error(x.error()); }
return (out << x.value());
inline std::ostream &
operator<<(std::ostream &out,
simdjson::simdjson_result<simdjson::dom::element> x) {
if (x.error()) {
throw simdjson::simdjson_error(x.error());
}
return (out << x.value());
}
#endif
inline std::ostream& operator<<(std::ostream& out, simdjson::dom::array value) {
simdjson::internal::string_builder<> sb;
sb.append(value);
return (out << sb.str());
inline std::ostream &operator<<(std::ostream &out, simdjson::dom::array value) {
simdjson::internal::string_builder<> sb;
sb.append(value);
return (out << sb.str());
}
#if SIMDJSON_EXCEPTIONS
inline std::ostream& operator<<(std::ostream& out, simdjson::simdjson_result<simdjson::dom::array> x) {
if (x.error()) { throw simdjson::simdjson_error(x.error()); }
return (out << x.value());
inline std::ostream &
operator<<(std::ostream &out,
simdjson::simdjson_result<simdjson::dom::array> x) {
if (x.error()) {
throw simdjson::simdjson_error(x.error());
}
return (out << x.value());
}
#endif
inline std::ostream& operator<<(std::ostream& out, simdjson::dom::object value) {
simdjson::internal::string_builder<> sb;
sb.append(value);
return (out << sb.str());
inline std::ostream &operator<<(std::ostream &out,
simdjson::dom::object value) {
simdjson::internal::string_builder<> sb;
sb.append(value);
return (out << sb.str());
}
#if SIMDJSON_EXCEPTIONS
inline std::ostream& operator<<(std::ostream& out, simdjson::simdjson_result<simdjson::dom::object> x) {
if (x.error()) { throw simdjson::simdjson_error(x.error()); }
return (out << x.value());
inline std::ostream &
operator<<(std::ostream &out,
simdjson::simdjson_result<simdjson::dom::object> x) {
if (x.error()) {
throw simdjson::simdjson_error(x.error());
}
return (out << x.value());
}
#endif
@@ -69,8 +85,9 @@ namespace {
* We expect that most compilers will use 8 bytes for this data structure.
**/
struct escape_sequence {
uint8_t length;
const char string[7]; // technically, we only ever need 6 characters, we pad to 8
uint8_t length;
const char
string[7]; // technically, we only ever need 6 characters, we pad to 8
};
/**@private
* This converts a signed integer into a character sequence.
@@ -86,7 +103,7 @@ static char *fast_itoa(char *output, int64_t value) noexcept {
char buffer[20];
uint64_t value_positive;
// In general, negating a signed integer is unsafe.
if(value < 0) {
if (value < 0) {
*output++ = '-';
// Doing value_positive = -value; while avoiding
// undefined behavior warnings.
@@ -105,7 +122,7 @@ static char *fast_itoa(char *output, int64_t value) noexcept {
// A faster approach is possible if we expect large integers:
// unroll the loop (work in 100s, 1000s) and use some kind of
// memoization.
while(value_positive >= 10) {
while (value_positive >= 10) {
*write_pointer-- = char('0' + (value_positive % 10));
value_positive /= 10;
}
@@ -131,7 +148,7 @@ static char *fast_itoa(char *output, uint64_t value) noexcept {
// A faster approach is possible if we expect large integers:
// unroll the loop (work in 100s, 1000s) and use some kind of
// memoization.
while(value >= 10) {
while (value >= 10) {
*write_pointer-- = char('0' + (value % 10));
value /= 10;
};
@@ -141,7 +158,6 @@ static char *fast_itoa(char *output, uint64_t value) noexcept {
return output + len;
}
} // anonymous namespace
namespace internal {
@@ -149,193 +165,208 @@ namespace internal {
* Minifier/formatter code.
**/
template<class formatter>
template <class formatter>
simdjson_inline void base_formatter<formatter>::number(uint64_t x) {
char number_buffer[24];
char *newp = fast_itoa(number_buffer, x);
buffer.insert(buffer.end(), number_buffer, newp);
chars(number_buffer, newp);
}
template<class formatter>
template <class formatter>
simdjson_inline void base_formatter<formatter>::number(int64_t x) {
char number_buffer[24];
char *newp = fast_itoa(number_buffer, x);
buffer.insert(buffer.end(), number_buffer, newp);
chars(number_buffer, newp);
}
template<class formatter>
template <class formatter>
simdjson_inline void base_formatter<formatter>::number(double x) {
char number_buffer[24];
// Currently, passing the nullptr to the second argument is
// safe because our implementation does not check the second
// argument.
char *newp = internal::to_chars(number_buffer, nullptr, x);
buffer.insert(buffer.end(), number_buffer, newp);
chars(number_buffer, newp);
}
template<class formatter>
simdjson_inline void base_formatter<formatter>::start_array() { one_char('['); }
template <class formatter>
simdjson_inline void base_formatter<formatter>::start_array() {
one_char('[');
}
template <class formatter>
simdjson_inline void base_formatter<formatter>::end_array() {
one_char(']');
}
template<class formatter>
simdjson_inline void base_formatter<formatter>::end_array() { one_char(']'); }
template <class formatter>
simdjson_inline void base_formatter<formatter>::start_object() {
one_char('{');
}
template<class formatter>
simdjson_inline void base_formatter<formatter>::start_object() { one_char('{'); }
template <class formatter>
simdjson_inline void base_formatter<formatter>::end_object() {
one_char('}');
}
template<class formatter>
simdjson_inline void base_formatter<formatter>::end_object() { one_char('}'); }
template <class formatter>
simdjson_inline void base_formatter<formatter>::comma() {
one_char(',');
}
template<class formatter>
simdjson_inline void base_formatter<formatter>::comma() { one_char(','); }
template<class formatter>
template <class formatter>
simdjson_inline void base_formatter<formatter>::true_atom() {
const char * s = "true";
buffer.insert(buffer.end(), s, s + 4);
const char *s = "true";
chars(s, s + 4);
}
template<class formatter>
template <class formatter>
simdjson_inline void base_formatter<formatter>::false_atom() {
const char * s = "false";
buffer.insert(buffer.end(), s, s + 5);
const char *s = "false";
chars(s, s + 5);
}
template<class formatter>
template <class formatter>
simdjson_inline void base_formatter<formatter>::null_atom() {
const char * s = "null";
buffer.insert(buffer.end(), s, s + 4);
const char *s = "null";
chars(s, s + 4);
}
template<class formatter>
simdjson_inline void base_formatter<formatter>::one_char(char c) { buffer.push_back(c); }
template <class formatter>
simdjson_inline void base_formatter<formatter>::one_char(char c) {
buffer.push_back(c);
}
template<class formatter>
simdjson_inline void base_formatter<formatter>::key(std::string_view unescaped) {
template <class formatter>
simdjson_inline void base_formatter<formatter>::chars(const char *begin,
const char *end) {
buffer.append(begin, end);
}
template <class formatter>
simdjson_inline void
base_formatter<formatter>::key(std::string_view unescaped) {
string(unescaped);
one_char(':');
}
template<class formatter>
simdjson_inline void base_formatter<formatter>::string(std::string_view unescaped) {
template <class formatter>
simdjson_inline void
base_formatter<formatter>::string(std::string_view unescaped) {
one_char('\"');
size_t i = 0;
// Fast path for the case where we have no control character, no ", and no backslash.
// This should include most keys.
// Fast path for the case where we have no control character, no ", and no
// backslash. This should include most keys.
//
// We would like to use 'bool' but some compilers take offense to bitwise operation
// with bool types.
constexpr static char needs_escaping[] = {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
for(;i + 8 <= unescaped.length(); i += 8) {
// We would like to use 'bool' but some compilers take offense to bitwise
// operation with bool types.
constexpr static char needs_escaping[] = {
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
for (; i + 8 <= unescaped.length(); i += 8) {
// Poor's man vectorization. This could get much faster if we used SIMD.
//
// It is not the case that replacing '|' with '||' would be neutral performance-wise.
if(needs_escaping[uint8_t(unescaped[i])] | needs_escaping[uint8_t(unescaped[i+1])]
| needs_escaping[uint8_t(unescaped[i+2])] | needs_escaping[uint8_t(unescaped[i+3])]
| needs_escaping[uint8_t(unescaped[i+4])] | needs_escaping[uint8_t(unescaped[i+5])]
| needs_escaping[uint8_t(unescaped[i+6])] | needs_escaping[uint8_t(unescaped[i+7])]
) { break; }
// It is not the case that replacing '|' with '||' would be neutral
// performance-wise.
if (needs_escaping[uint8_t(unescaped[i])] |
needs_escaping[uint8_t(unescaped[i + 1])] |
needs_escaping[uint8_t(unescaped[i + 2])] |
needs_escaping[uint8_t(unescaped[i + 3])] |
needs_escaping[uint8_t(unescaped[i + 4])] |
needs_escaping[uint8_t(unescaped[i + 5])] |
needs_escaping[uint8_t(unescaped[i + 6])] |
needs_escaping[uint8_t(unescaped[i + 7])]) {
break;
}
}
for(;i < unescaped.length(); i++) {
if(needs_escaping[uint8_t(unescaped[i])]) { break; }
for (; i < unescaped.length(); i++) {
if (needs_escaping[uint8_t(unescaped[i])]) {
break;
}
}
// The following is also possible and omits a 256-byte table, but it is slower:
// for (; (i < unescaped.length()) && (uint8_t(unescaped[i]) > 0x1F)
// The following is also possible and omits a 256-byte table, but it is
// slower: for (; (i < unescaped.length()) && (uint8_t(unescaped[i]) > 0x1F)
// && (unescaped[i] != '\"') && (unescaped[i] != '\\'); i++) {}
// At least for long strings, the following should be fast. We could
// do better by integrating the checks and the insertion.
buffer.insert(buffer.end(), unescaped.data(), unescaped.data() + i);
chars(unescaped.data(), unescaped.data() + i);
// We caught a control character if we enter this loop (slow).
// Note that we are do not restart from the beginning, but rather we continue
// from the point where we encountered something that requires escaping.
for (; i < unescaped.length(); i++) {
switch (unescaped[i]) {
case '\"':
{
const char * s = "\\\"";
buffer.insert(buffer.end(), s, s + 2);
}
break;
case '\\':
{
const char * s = "\\\\";
buffer.insert(buffer.end(), s, s + 2);
}
break;
case '\"': {
const char *s = "\\\"";
chars(s, s + 2);
} break;
case '\\': {
const char *s = "\\\\";
chars(s, s + 2);
} break;
default:
if (uint8_t(unescaped[i]) <= 0x1F) {
// If packed, this uses 8 * 32 bytes.
// Note that we expect most compilers to embed this code in the data
// section.
constexpr static escape_sequence escaped[32] = {
{6, "\\u0000"}, {6, "\\u0001"}, {6, "\\u0002"}, {6, "\\u0003"},
{6, "\\u0004"}, {6, "\\u0005"}, {6, "\\u0006"}, {6, "\\u0007"},
{2, "\\b"}, {2, "\\t"}, {2, "\\n"}, {6, "\\u000b"},
{2, "\\f"}, {2, "\\r"}, {6, "\\u000e"}, {6, "\\u000f"},
{6, "\\u0010"}, {6, "\\u0011"}, {6, "\\u0012"}, {6, "\\u0013"},
{6, "\\u0014"}, {6, "\\u0015"}, {6, "\\u0016"}, {6, "\\u0017"},
{6, "\\u0018"}, {6, "\\u0019"}, {6, "\\u001a"}, {6, "\\u001b"},
{6, "\\u001c"}, {6, "\\u001d"}, {6, "\\u001e"}, {6, "\\u001f"}};
{6, "\\u0000"}, {6, "\\u0001"}, {6, "\\u0002"}, {6, "\\u0003"},
{6, "\\u0004"}, {6, "\\u0005"}, {6, "\\u0006"}, {6, "\\u0007"},
{2, "\\b"}, {2, "\\t"}, {2, "\\n"}, {6, "\\u000b"},
{2, "\\f"}, {2, "\\r"}, {6, "\\u000e"}, {6, "\\u000f"},
{6, "\\u0010"}, {6, "\\u0011"}, {6, "\\u0012"}, {6, "\\u0013"},
{6, "\\u0014"}, {6, "\\u0015"}, {6, "\\u0016"}, {6, "\\u0017"},
{6, "\\u0018"}, {6, "\\u0019"}, {6, "\\u001a"}, {6, "\\u001b"},
{6, "\\u001c"}, {6, "\\u001d"}, {6, "\\u001e"}, {6, "\\u001f"}};
auto u = escaped[uint8_t(unescaped[i])];
buffer.insert(buffer.end(), u.string, u.string + u.length);
chars(u.string, u.string + u.length);
} else {
one_char(unescaped[i]);
}
} // switch
} // for
} // for
one_char('\"');
}
template<class formatter>
inline void base_formatter<formatter>::clear() {
template <class formatter> inline void base_formatter<formatter>::clear() {
buffer.clear();
}
template<class formatter>
template <class formatter>
simdjson_inline std::string_view base_formatter<formatter>::str() const {
return std::string_view(buffer.data(), buffer.size());
return buffer.str();
}
simdjson_inline void mini_formatter::print_newline() {
return;
}
simdjson_inline void mini_formatter::print_newline() { return; }
simdjson_inline void mini_formatter::print_indents(size_t depth) {
(void)depth;
return;
(void)depth;
return;
}
simdjson_inline void mini_formatter::print_space() {
return;
}
simdjson_inline void mini_formatter::print_space() { return; }
simdjson_inline void pretty_formatter::print_newline() {
one_char('\n');
}
simdjson_inline void pretty_formatter::print_newline() { one_char('\n'); }
simdjson_inline void pretty_formatter::print_indents(size_t depth) {
if(this->indent_step <= 0) {
return;
}
for(size_t i = 0; i < this->indent_step * depth; i++) {
one_char(' ');
}
if (this->indent_step <= 0) {
return;
}
for (size_t i = 0; i < this->indent_step * depth; i++) {
one_char(' ');
}
}
simdjson_inline void pretty_formatter::print_space() {
one_char(' ');
}
simdjson_inline void pretty_formatter::print_space() { one_char(' '); }
/***
* String building code.
@@ -514,7 +545,8 @@ inline void string_builder<serializer>::append(simdjson::dom::array value) {
}
template <class serializer>
simdjson_inline void string_builder<serializer>::append(simdjson::dom::key_value_pair kv) {
simdjson_inline void
string_builder<serializer>::append(simdjson::dom::key_value_pair kv) {
format.key(kv.key);
append(kv.value);
}
@@ -529,7 +561,6 @@ simdjson_inline std::string_view string_builder<serializer>::str() const {
return format.str();
}
} // namespace internal
} // namespace simdjson
+116 -55
View File
@@ -5,8 +5,6 @@
#include "simdjson/dom/element.h"
#include "simdjson/dom/object.h"
#include <vector>
namespace simdjson {
/**
@@ -16,8 +14,7 @@ namespace simdjson {
*/
namespace internal {
template<class formatter>
class base_formatter {
template <class formatter> class base_formatter {
public:
/** Add a comma **/
simdjson_inline void comma();
@@ -56,24 +53,76 @@ public:
/** Prints one character **/
simdjson_inline void one_char(char c);
/** Prints characters in [begin, end) verbatim. **/
simdjson_inline void chars(const char *begin, const char *end);
simdjson_inline void call_print_newline() {
static_cast<formatter*>(this)->print_newline();
static_cast<formatter *>(this)->print_newline();
}
simdjson_inline void call_print_indents(size_t depth) {
static_cast<formatter*>(this)->print_indents(depth);
static_cast<formatter *>(this)->print_indents(depth);
}
simdjson_inline void call_print_space() {
static_cast<formatter*>(this)->print_space();
static_cast<formatter *>(this)->print_space();
}
protected:
// implementation details (subject to change)
/** Backing buffer **/
std::vector<char> buffer{}; // not ideal!
};
struct vector_with_small_buffer {
vector_with_small_buffer() = default;
~vector_with_small_buffer() { free_buffer(); }
vector_with_small_buffer(const vector_with_small_buffer &) = delete;
vector_with_small_buffer &
operator=(const vector_with_small_buffer &) = delete;
void clear() {
size = 0;
capacity = StaticCapacity;
free_buffer();
buffer = array;
}
simdjson_inline void push_back(char c) {
if (capacity < size + 1)
grow(capacity * 2);
buffer[size++] = c;
}
simdjson_inline void append(const char *begin, const char *end) {
const size_t new_size = size + (end - begin);
if (capacity < new_size)
// std::max(new_size, capacity * 2); is broken in tests on Windows
grow(new_size < capacity * 2 ? capacity * 2 : new_size);
std::copy(begin, end, buffer + size);
size = new_size;
}
std::string_view str() const { return std::string_view(buffer, size); }
private:
void free_buffer() {
if (buffer != array)
delete[] buffer;
}
void grow(size_t new_capacity) {
auto new_buffer = new char[new_capacity];
std::copy(buffer, buffer + size, new_buffer);
free_buffer();
buffer = new_buffer;
capacity = new_capacity;
}
static const size_t StaticCapacity = 64;
char array[StaticCapacity];
char *buffer = array;
size_t size = 0;
size_t capacity = StaticCapacity;
} buffer{};
};
/**
* @private This is the class that we expect to use with the string_builder
@@ -107,9 +156,11 @@ protected:
* by a "formatter" which handles the details. Thus
* the string_builder template could support both minification
* and prettification, and various other tradeoffs.
*
* This is not to be confused with the simdjson::builder::string_builder
* which is a different class.
*/
template <class formatter = mini_formatter>
class string_builder {
template <class formatter = mini_formatter> class string_builder {
public:
/** Construct an initially empty builder, would print the empty string **/
string_builder() = default;
@@ -131,11 +182,12 @@ public:
simdjson_inline std::string_view str() const;
/** Append a key_value_pair to the builder (to be printed) **/
simdjson_inline void append(simdjson::dom::key_value_pair value);
private:
formatter format{};
};
} // internal
} // namespace internal
namespace dom {
@@ -144,33 +196,43 @@ namespace dom {
*
* @param out The output stream.
* @param value The element.
* @throw if there is an error with the underlying output stream. simdjson itself will not throw.
* @throw if there is an error with the underlying output stream. simdjson
* itself will not throw.
*/
inline std::ostream& operator<<(std::ostream& out, simdjson::dom::element value);
inline std::ostream &operator<<(std::ostream &out,
simdjson::dom::element value);
#if SIMDJSON_EXCEPTIONS
inline std::ostream& operator<<(std::ostream& out, simdjson::simdjson_result<simdjson::dom::element> x);
inline std::ostream &
operator<<(std::ostream &out,
simdjson::simdjson_result<simdjson::dom::element> x);
#endif
/**
* Print JSON to an output stream.
*
* @param out The output stream.
* @param value The array.
* @throw if there is an error with the underlying output stream. simdjson itself will not throw.
* @throw if there is an error with the underlying output stream. simdjson
* itself will not throw.
*/
inline std::ostream& operator<<(std::ostream& out, simdjson::dom::array value);
inline std::ostream &operator<<(std::ostream &out, simdjson::dom::array value);
#if SIMDJSON_EXCEPTIONS
inline std::ostream& operator<<(std::ostream& out, simdjson::simdjson_result<simdjson::dom::array> x);
inline std::ostream &
operator<<(std::ostream &out,
simdjson::simdjson_result<simdjson::dom::array> x);
#endif
/**
* Print JSON to an output stream.
*
* @param out The output stream.
* @param value The object.
* @throw if there is an error with the underlying output stream. simdjson itself will not throw.
* @throw if there is an error with the underlying output stream. simdjson
* itself will not throw.
*/
inline std::ostream& operator<<(std::ostream& out, simdjson::dom::object value);
inline std::ostream &operator<<(std::ostream &out, simdjson::dom::object value);
#if SIMDJSON_EXCEPTIONS
inline std::ostream& operator<<(std::ostream& out, simdjson::simdjson_result<simdjson::dom::object> x);
inline std::ostream &
operator<<(std::ostream &out,
simdjson::simdjson_result<simdjson::dom::object> x);
#endif
} // namespace dom
@@ -182,47 +244,47 @@ inline std::ostream& operator<<(std::ostream& out, simdjson::simdjson_result<si
* cout << to_string(doc) << endl; // prints [1,2,3]
*
*/
template <class T>
std::string to_string(T x) {
// in C++, to_string is standard: http://www.cplusplus.com/reference/string/to_string/
// Currently minify and to_string are identical but in the future, they may
// differ.
simdjson::internal::string_builder<> sb;
sb.append(x);
std::string_view answer = sb.str();
return std::string(answer.data(), answer.size());
template <class T> std::string to_string(T x) {
// in C++, to_string is standard:
// http://www.cplusplus.com/reference/string/to_string/ Currently minify and
// to_string are identical but in the future, they may differ.
simdjson::internal::string_builder<> sb;
sb.append(x);
std::string_view answer = sb.str();
return std::string(answer.data(), answer.size());
}
#if SIMDJSON_EXCEPTIONS
template <class T>
std::string to_string(simdjson_result<T> x) {
if (x.error()) { throw simdjson_error(x.error()); }
return to_string(x.value());
template <class T> std::string to_string(simdjson_result<T> x) {
if (x.error()) {
throw simdjson_error(x.error());
}
return to_string(x.value());
}
#endif
/**
* Minifies a JSON element or document, printing the smallest possible valid JSON.
* Minifies a JSON element or document, printing the smallest possible valid
* JSON.
*
* dom::parser parser;
* element doc = parser.parse(" [ 1 , 2 , 3 ] "_padded);
* cout << minify(doc) << endl; // prints [1,2,3]
*
*/
template <class T>
std::string minify(T x) {
return to_string(x);
}
template <class T> std::string minify(T x) { return to_string(x); }
#if SIMDJSON_EXCEPTIONS
template <class T>
std::string minify(simdjson_result<T> x) {
if (x.error()) { throw simdjson_error(x.error()); }
return to_string(x.value());
template <class T> std::string minify(simdjson_result<T> x) {
if (x.error()) {
throw simdjson_error(x.error());
}
return to_string(x.value());
}
#endif
/**
* Prettifies a JSON element or document, printing the valid JSON with indentation.
* Prettifies a JSON element or document, printing the valid JSON with
* indentation.
*
* dom::parser parser;
* element doc = parser.parse(" [ 1 , 2 , 3 ] "_padded);
@@ -238,23 +300,22 @@ std::string minify(simdjson_result<T> x) {
* cout << prettify(doc) << endl;
*
*/
template <class T>
std::string prettify(T x) {
simdjson::internal::string_builder<simdjson::internal::pretty_formatter> sb;
sb.append(x);
std::string_view answer = sb.str();
return std::string(answer.data(), answer.size());
template <class T> std::string prettify(T x) {
simdjson::internal::string_builder<simdjson::internal::pretty_formatter> sb;
sb.append(x);
std::string_view answer = sb.str();
return std::string(answer.data(), answer.size());
}
#if SIMDJSON_EXCEPTIONS
template <class T>
std::string prettify(simdjson_result<T> x) {
if (x.error()) { throw simdjson_error(x.error()); }
return to_string(x.value());
template <class T> std::string prettify(simdjson_result<T> x) {
if (x.error()) {
throw simdjson_error(x.error());
}
return to_string(x.value());
}
#endif
} // namespace simdjson
#endif
+33 -18
View File
@@ -67,8 +67,38 @@ simdjson_inline error_code simdjson_result_base<T>::error() const noexcept {
return this->second;
}
template<typename T>
simdjson_inline bool simdjson_result_base<T>::has_value() const noexcept {
return this->error() == SUCCESS;
}
#if SIMDJSON_EXCEPTIONS
template<typename T>
simdjson_inline T& simdjson_result_base<T>::operator*() & noexcept(false) {
return this->value();
}
template<typename T>
simdjson_inline T&& simdjson_result_base<T>::operator*() && noexcept(false) {
return std::forward<internal::simdjson_result_base<T>>(*this).value();
}
template<typename T>
simdjson_inline T* simdjson_result_base<T>::operator->() noexcept(false) {
if (this->error()) { throw simdjson_error(this->error()); }
return &this->first;
}
template<typename T>
simdjson_inline const T* simdjson_result_base<T>::operator->() const noexcept(false) {
if (this->error()) { throw simdjson_error(this->error()); }
return &this->first;
}
template<typename T>
simdjson_inline T& simdjson_result_base<T>::value() & noexcept(false) {
if (error()) { throw simdjson_error(error()); }
@@ -93,6 +123,7 @@ simdjson_inline simdjson_result_base<T>::operator T&&() && noexcept(false) {
#endif // SIMDJSON_EXCEPTIONS
template<typename T>
simdjson_inline const T& simdjson_result_base<T>::value_unsafe() const& noexcept {
return this->first;
@@ -127,26 +158,10 @@ simdjson_inline void simdjson_result<T>::tie(T &value, error_code &error) && noe
std::forward<internal::simdjson_result_base<T>>(*this).tie(value, error);
}
template<typename T>
simdjson_warn_unused simdjson_inline error_code simdjson_result<T>::get(T &value) && noexcept {
return std::forward<internal::simdjson_result_base<T>>(*this).get(value);
}
template<typename T>
simdjson_warn_unused simdjson_inline error_code
simdjson_result<T>::get(std::string &value) && noexcept
#if SIMDJSON_SUPPORTS_DESERIALIZATION
requires (!std::is_same_v<T, std::string>)
#endif // SIMDJSON_SUPPORTS_DESERIALIZATION
{
// SFINAE : n'active que pour T = std::string_view
static_assert(std::is_same<T, std::string_view>::value, "simdjson_result<T>::get(std::string&) n'est disponible que pour T = std::string_view");
std::string_view v;
error_code error = std::forward<simdjson_result<T>>(*this).get(v);
if (!error) {
value.assign(v.data(), v.size());
}
return error;
simdjson_result<T>::get(T &value) && noexcept {
return std::forward<internal::simdjson_result_base<T>>(*this).get(value);
}
template<typename T>
+72 -8
View File
@@ -49,7 +49,8 @@ enum error_code {
SCALAR_DOCUMENT_AS_VALUE, ///< A scalar document is treated as a value.
OUT_OF_BOUNDS, ///< Attempted to access location outside of document.
TRAILING_CONTENT, ///< Unexpected trailing content in the JSON input
NUM_ERROR_CODES
OUT_OF_CAPACITY, ///< The capacity was exceeded, we cannot allocate enough memory.
NUM_ERROR_CODES ///< Placeholder for end of error code list.
};
/**
@@ -107,6 +108,10 @@ namespace internal {
/**
* The result of a simdjson operation that could fail.
*
* IMPORTANT: For the ondemand API, we use implementation_simdjson_result_base<T> as a base class
* to avoid some compilation issue. Thus, if you modify this class, please ensure that the ondemand
* implementation_simdjson_result_base<T> is also modified.
*
* Gives the option of reading error codes, or throwing an exception by casting to the desired result.
*
* This is a base class for implementations that want to add functions to the result type for
@@ -167,8 +172,27 @@ struct simdjson_result_base : protected std::pair<T, error_code> {
*/
simdjson_inline error_code error() const noexcept;
/**
* Whether there is a value.
*/
simdjson_inline bool has_value() const noexcept;
#if SIMDJSON_EXCEPTIONS
/**
* Dereference operator to access the contained value.
*
* @throw simdjson_error if there was an error.
*/
simdjson_inline T& operator*() & noexcept(false);
simdjson_inline T&& operator*() && noexcept(false);
/**
* Arrow operator to access members of the contained value.
*
* @throw simdjson_error if there was an error.
*/
simdjson_inline T* operator->() noexcept(false);
simdjson_inline const T* operator->() const noexcept(false);
/**
* Get the result value.
*
@@ -202,12 +226,42 @@ struct simdjson_result_base : protected std::pair<T, error_code> {
/**
* Get the result value. This function is safe if and only
* the error() method returns a value that evaluates to false.
* We discourage the use of value_unsafe().
*
* The recommended pattern is:
*
* T value; // where T is the type
* auto error = result.get(value);
* if (error) {
* // handle error
* }
*
* Or you may call 'value()' which will raise an exception
* in case of error:
*
* T value = result.value();
*/
simdjson_inline const T& value_unsafe() const& noexcept;
/**
* Take the result value (move it). This function is safe if and only
* the error() method returns a value that evaluates to false.
* We discourage the use of value_unsafe().
*
* The recommended pattern is:
*
* T value; // where T is the type
* auto error = result.get(value);
* if (error) {
* // handle error, return, exit, abort
* } else {
* // use value here.
* }
*
* Or you may call 'value()' which will raise an exception
* in case of error:
*
* T value = result.value();
*/
simdjson_inline T&& value_unsafe() && noexcept;
@@ -222,6 +276,7 @@ struct simdjson_result_base : protected std::pair<T, error_code> {
*/
template<typename T>
struct simdjson_result : public internal::simdjson_result_base<T> {
/**
* @private Create a new empty result with error = UNINITIALIZED.
*/
@@ -253,24 +308,33 @@ struct simdjson_result : public internal::simdjson_result_base<T> {
* @param value The variable to assign the value to. May not be set if there is an error.
*/
simdjson_warn_unused simdjson_inline error_code get(T &value) && noexcept;
//
/**
* Copy the value to a provided std::string, only enabled for std::string_view.
*
* @param value The variable to assign the value to. May not be set if there is an error.
*/
simdjson_warn_unused simdjson_inline error_code get(std::string &value) && noexcept
#if SIMDJSON_SUPPORTS_DESERIALIZATION
requires (!std::is_same_v<T, std::string>)
#endif // SIMDJSON_SUPPORTS_DESERIALIZATION
;
template <typename U = T>
simdjson_warn_unused simdjson_inline error_code get(std::string &value) && noexcept {
static_assert(std::is_same<U, std::string_view>::value, "SFINAE");
std::string_view v;
error_code error = std::forward<simdjson_result<T>>(*this).get(v);
if (!error) {
value.assign(v.data(), v.size());
}
return error;
}
/**
* The error.
*/
simdjson_inline error_code error() const noexcept;
#if SIMDJSON_EXCEPTIONS
#if SIMDJSON_EXCEPTIONS
using internal::simdjson_result_base<T>::operator*;
using internal::simdjson_result_base<T>::operator->;
/**
* Get the result value.
*
@@ -29,6 +29,24 @@ simdjson_inline backslash_and_quote backslash_and_quote::copy_and_find(const uin
return { src[0] };
}
struct escaping {
static constexpr uint32_t BYTES_PROCESSED = 1;
simdjson_inline static escaping copy_and_find(const uint8_t *src, uint8_t *dst);
simdjson_inline bool has_escape() { return escape_bits; }
simdjson_inline int escape_index() { return 0; }
bool escape_bits;
}; // struct escaping
simdjson_inline escaping escaping::copy_and_find(const uint8_t *src, uint8_t *dst) {
dst[0] = src[0];
return { (src[0] == '\\') || (src[0] == '"') || (src[0] < 32) };
}
} // unnamed namespace
} // namespace fallback
} // namespace simdjson
@@ -33,8 +33,37 @@ simdjson_inline error_code implementation_simdjson_result_base<T>::error() const
return this->second;
}
template<typename T>
simdjson_inline bool implementation_simdjson_result_base<T>::has_value() const noexcept {
return this->error() == SUCCESS;
}
#if SIMDJSON_EXCEPTIONS
template<typename T>
simdjson_inline T& implementation_simdjson_result_base<T>::operator*() & noexcept(false) {
return this->value();
}
template<typename T>
simdjson_inline T&& implementation_simdjson_result_base<T>::operator*() && noexcept(false) {
return std::forward<implementation_simdjson_result_base<T>>(*this).value();
}
template<typename T>
simdjson_inline T* implementation_simdjson_result_base<T>::operator->() noexcept(false) {
if (this->error()) { throw simdjson_error(this->error()); }
return &this->first;
}
template<typename T>
simdjson_inline const T* implementation_simdjson_result_base<T>::operator->() const noexcept(false) {
if (this->error()) { throw simdjson_error(this->error()); }
return &this->first;
}
template<typename T>
simdjson_inline T& implementation_simdjson_result_base<T>::value() & noexcept(false) {
if (error()) { throw simdjson_error(error()); }
@@ -74,6 +74,11 @@ struct implementation_simdjson_result_base {
*/
simdjson_inline error_code error() const noexcept;
/**
* Whether there is a value.
*/
simdjson_inline bool has_value() const noexcept;
#if SIMDJSON_EXCEPTIONS
/**
@@ -81,6 +86,16 @@ struct implementation_simdjson_result_base {
*
* @throw simdjson_error if there was an error.
*/
simdjson_inline T& operator*() & noexcept(false);
simdjson_inline T&& operator*() && noexcept(false);
/**
* Arrow operator to access members of the contained value.
*
* @throw simdjson_error if there was an error.
*/
simdjson_inline T* operator->() noexcept(false);
simdjson_inline const T* operator->() const noexcept(false);
simdjson_inline T& value() & noexcept(false);
/**
@@ -122,6 +137,7 @@ struct implementation_simdjson_result_base {
* the error() method returns a value that evaluates to false.
*/
simdjson_inline T&& value_unsafe() && noexcept;
protected:
/** users should never directly access first and second. **/
T first{}; /** Users should never directly access 'first'. **/
@@ -45,4 +45,9 @@
#include "simdjson/generic/ondemand/token_iterator-inl.h"
#include "simdjson/generic/ondemand/value_iterator-inl.h"
// JSON builder, ideally they should not be part of the ondemand directory
// but it is convenient for now to have them here.
#include "simdjson/generic/ondemand/json_string_builder.h"
#include "simdjson/generic/ondemand/json_string_builder-inl.h"
#include "simdjson/generic/ondemand/json_builder.h"
+52
View File
@@ -131,6 +131,37 @@ public:
* - INDEX_OUT_OF_BOUNDS if the array index is larger than an array length
*/
simdjson_inline simdjson_result<value> at(size_t index) noexcept;
#if SIMDJSON_SUPPORTS_CONCEPTS
/**
* Get this array as the given type.
*
* @param out This is set to a value of the given type, parsed from the JSON. If there is an error, this may not be initialized.
* @returns INCORRECT_TYPE If the JSON array is not of the given type.
* @returns SUCCESS If the parse succeeded and the out parameter was set to the value.
*/
template <typename T>
simdjson_inline error_code get(T &out)
noexcept(custom_deserializable<T, array> ? nothrow_custom_deserializable<T, array> : true) {
static_assert(custom_deserializable<T, array>);
return deserialize(*this, out);
}
/**
* Get this array as the given type.
*
* @returns A value of the given type, parsed from the JSON.
* @returns INCORRECT_TYPE If the JSON value is not the given type.
*/
template <typename T>
simdjson_inline simdjson_result<T> get()
noexcept(custom_deserializable<T, value> ? nothrow_custom_deserializable<T, value> : true)
{
static_assert(std::is_default_constructible<T>::value, "The specified type is not default constructible.");
T out{};
SIMDJSON_TRY(get<T>(out));
return out;
}
#endif // SIMDJSON_SUPPORTS_CONCEPTS
protected:
/**
* Go to the end of the array, no matter where you are right now.
@@ -209,7 +240,28 @@ public:
simdjson_inline simdjson_result<SIMDJSON_IMPLEMENTATION::ondemand::value> at_pointer(std::string_view json_pointer) noexcept;
simdjson_inline simdjson_result<SIMDJSON_IMPLEMENTATION::ondemand::value> at_path(std::string_view json_path) noexcept;
simdjson_inline simdjson_result<std::string_view> raw_json() noexcept;
#if SIMDJSON_SUPPORTS_CONCEPTS
// TODO: move this code into object-inl.h
template<typename T>
simdjson_inline simdjson_result<T> get() noexcept {
if (error()) { return error(); }
if constexpr (std::is_same_v<T, SIMDJSON_IMPLEMENTATION::ondemand::array>) {
return first;
}
return first.get<T>();
}
template<typename T>
simdjson_inline error_code get(T& out) noexcept {
if (error()) { return error(); }
if constexpr (std::is_same_v<T, SIMDJSON_IMPLEMENTATION::ondemand::array>) {
out = first;
} else {
SIMDJSON_TRY( first.get<T>(out) );
}
return SUCCESS;
}
#endif // SIMDJSON_SUPPORTS_CONCEPTS
};
} // namespace simdjson
@@ -36,6 +36,9 @@ simdjson_inline array_iterator &array_iterator::operator++() noexcept {
return *this;
}
simdjson_inline bool array_iterator::at_end() const noexcept {
return iter.at_end();
}
} // namespace ondemand
} // namespace SIMDJSON_IMPLEMENTATION
} // namespace simdjson
@@ -72,7 +75,9 @@ simdjson_inline simdjson_result<SIMDJSON_IMPLEMENTATION::ondemand::array_iterato
++(first);
return *this;
}
simdjson_inline bool simdjson_result<SIMDJSON_IMPLEMENTATION::ondemand::array_iterator>::at_end() const noexcept {
return !first.iter.is_valid() || first.at_end();
}
} // namespace simdjson
#endif // SIMDJSON_GENERIC_ONDEMAND_ARRAY_ITERATOR_INL_H
@@ -34,7 +34,8 @@ public:
*
* Part of the std::iterator interface.
*/
simdjson_inline simdjson_result<value> operator*() noexcept; // MUST ONLY BE CALLED ONCE PER ITERATION.
simdjson_inline simdjson_result<value>
operator*() noexcept; // MUST ONLY BE CALLED ONCE PER ITERATION.
/**
* Check if we are at the end of the JSON.
*
@@ -58,6 +59,11 @@ public:
*/
simdjson_inline array_iterator &operator++() noexcept;
/**
* Check if the array is at the end.
*/
simdjson_warn_unused simdjson_inline bool at_end() const noexcept;
private:
value_iterator iter{};
@@ -76,7 +82,6 @@ namespace simdjson {
template<>
struct simdjson_result<SIMDJSON_IMPLEMENTATION::ondemand::array_iterator> : public SIMDJSON_IMPLEMENTATION::implementation_simdjson_result_base<SIMDJSON_IMPLEMENTATION::ondemand::array_iterator> {
public:
simdjson_inline simdjson_result(SIMDJSON_IMPLEMENTATION::ondemand::array_iterator &&value) noexcept; ///< @private
simdjson_inline simdjson_result(error_code error) noexcept; ///< @private
simdjson_inline simdjson_result() noexcept = default;
@@ -89,6 +94,8 @@ public:
simdjson_inline bool operator==(const simdjson_result<SIMDJSON_IMPLEMENTATION::ondemand::array_iterator> &) const noexcept;
simdjson_inline bool operator!=(const simdjson_result<SIMDJSON_IMPLEMENTATION::ondemand::array_iterator> &) const noexcept;
simdjson_inline simdjson_result<SIMDJSON_IMPLEMENTATION::ondemand::array_iterator> &operator++() noexcept;
simdjson_warn_unused simdjson_inline bool at_end() const noexcept;
};
} // namespace simdjson
@@ -8,6 +8,7 @@
// Internal headers needed for ondemand generics.
// All includes not under simdjson/generic/ondemand must be here!
// Otherwise, amalgamation will fail.
#include "simdjson/concepts.h"
#include "simdjson/dom/base.h" // for MINIMAL_DOCUMENT_CAPACITY
#include "simdjson/implementation.h"
#include "simdjson/padded_string.h"
@@ -1,4 +1,4 @@
#if SIMDJSON_SUPPORTS_DESERIALIZATION
#if SIMDJSON_SUPPORTS_CONCEPTS
#ifndef SIMDJSON_ONDEMAND_DESERIALIZE_H
#ifndef SIMDJSON_CONDITIONAL_INCLUDE
@@ -77,7 +77,7 @@ template <typename T, typename ValT = SIMDJSON_IMPLEMENTATION::ondemand::value>
concept custom_deserializable = tag_invocable<deserialize_tag, ValT&, T&>;
template <typename T, typename ValT = SIMDJSON_IMPLEMENTATION::ondemand::value>
concept deserializable = custom_deserializable<T, ValT> || is_builtin_deserializable_v<T>;
concept deserializable = custom_deserializable<T, ValT> || is_builtin_deserializable_v<T> || concepts::optional_type<T>;
template <typename T, typename ValT = SIMDJSON_IMPLEMENTATION::ondemand::value>
concept nothrow_custom_deserializable = nothrow_tag_invocable<deserialize_tag, ValT&, T&>;
@@ -88,28 +88,44 @@ concept nothrow_deserializable = nothrow_custom_deserializable<T, ValT> || is_bu
/// Deserialize Tag
inline constexpr struct deserialize_tag {
using array_type = SIMDJSON_IMPLEMENTATION::ondemand::array;
using object_type = SIMDJSON_IMPLEMENTATION::ondemand::object;
using value_type = SIMDJSON_IMPLEMENTATION::ondemand::value;
using document_type = SIMDJSON_IMPLEMENTATION::ondemand::document;
using document_reference_type = SIMDJSON_IMPLEMENTATION::ondemand::document_reference;
// Customization Point for array
template <typename T>
requires custom_deserializable<T, value_type>
simdjson_warn_unused constexpr /* error_code */ auto operator()(array_type &object, T& output) const noexcept(nothrow_custom_deserializable<T, value_type>) {
return tag_invoke(*this, object, output);
}
// Customization Point for object
template <typename T>
requires custom_deserializable<T, value_type>
simdjson_warn_unused constexpr /* error_code */ auto operator()(object_type &object, T& output) const noexcept(nothrow_custom_deserializable<T, value_type>) {
return tag_invoke(*this, object, output);
}
// Customization Point for value
template <typename T>
requires custom_deserializable<T, value_type>
[[nodiscard]] constexpr /* error_code */ auto operator()(value_type &object, T& output) const noexcept(nothrow_custom_deserializable<T, value_type>) {
simdjson_warn_unused constexpr /* error_code */ auto operator()(value_type &object, T& output) const noexcept(nothrow_custom_deserializable<T, value_type>) {
return tag_invoke(*this, object, output);
}
// Customization Point for document
template <typename T>
requires custom_deserializable<T, document_type>
[[nodiscard]] constexpr /* error_code */ auto operator()(document_type &object, T& output) const noexcept(nothrow_custom_deserializable<T, document_type>) {
simdjson_warn_unused constexpr /* error_code */ auto operator()(document_type &object, T& output) const noexcept(nothrow_custom_deserializable<T, document_type>) {
return tag_invoke(*this, object, output);
}
// Customization Point for document reference
template <typename T>
requires custom_deserializable<T, document_reference_type>
[[nodiscard]] constexpr /* error_code */ auto operator()(document_reference_type &object, T& output) const noexcept(nothrow_custom_deserializable<T, document_reference_type>) {
simdjson_warn_unused constexpr /* error_code */ auto operator()(document_reference_type &object, T& output) const noexcept(nothrow_custom_deserializable<T, document_reference_type>) {
return tag_invoke(*this, object, output);
}
@@ -119,5 +135,5 @@ inline constexpr struct deserialize_tag {
} // namespace simdjson
#endif // SIMDJSON_ONDEMAND_DESERIALIZE_H
#endif // SIMDJSON_SUPPORTS_DESERIALIZATION
#endif // SIMDJSON_SUPPORTS_CONCEPTS
@@ -195,8 +195,8 @@ simdjson_inline document::operator object() & noexcept(false) { return get_objec
simdjson_inline document::operator uint64_t() noexcept(false) { return get_uint64(); }
simdjson_inline document::operator int64_t() noexcept(false) { return get_int64(); }
simdjson_inline document::operator double() noexcept(false) { return get_double(); }
simdjson_inline document::operator std::string_view() noexcept(false) { return get_string(false); }
simdjson_inline document::operator raw_json_string() noexcept(false) { return get_raw_json_string(); }
simdjson_inline document::operator std::string_view() noexcept(false) simdjson_lifetime_bound { return get_string(false); }
simdjson_inline document::operator raw_json_string() noexcept(false) simdjson_lifetime_bound { return get_raw_json_string(); }
simdjson_inline document::operator bool() noexcept(false) { return get_bool(); }
simdjson_inline document::operator value() noexcept(false) { return get_value(); }
+35 -16
View File
@@ -183,7 +183,7 @@ public:
*/
template <typename T>
simdjson_inline simdjson_result<T> get() &
#if SIMDJSON_SUPPORTS_DESERIALIZATION
#if SIMDJSON_SUPPORTS_CONCEPTS
noexcept(custom_deserializable<T, document> ? nothrow_custom_deserializable<T, document> : true)
#else
noexcept
@@ -206,7 +206,7 @@ public:
*/
template<typename T>
simdjson_inline simdjson_result<T> get() &&
#if SIMDJSON_SUPPORTS_DESERIALIZATION
#if SIMDJSON_SUPPORTS_CONCEPTS
noexcept(custom_deserializable<T, document> ? nothrow_custom_deserializable<T, document> : true)
#else
noexcept
@@ -224,18 +224,18 @@ public:
* Be mindful that the document instance must remain in scope while you are accessing object, array and value instances.
*
* @param out This is set to a value of the given type, parsed from the JSON. If there is an error, this may not be initialized.
* @returns INCORRECT_TYPE If the JSON value is not an object.
* @returns INCORRECT_TYPE If the JSON value is of the given type.
* @returns SUCCESS If the parse succeeded and the out parameter was set to the value.
*/
template<typename T>
simdjson_inline error_code get(T &out) &
#if SIMDJSON_SUPPORTS_DESERIALIZATION
#if SIMDJSON_SUPPORTS_CONCEPTS
noexcept(custom_deserializable<T, document> ? nothrow_custom_deserializable<T, document> : true)
#else
noexcept
#endif
{
#if SIMDJSON_SUPPORTS_DESERIALIZATION
#if SIMDJSON_SUPPORTS_CONCEPTS
if constexpr (custom_deserializable<T, document>) {
return deserialize(*this, out);
} else {
@@ -247,7 +247,7 @@ public:
static_cast<void>(out); // to get rid of unused errors
return UNINITIALIZED;
}
#else // SIMDJSON_SUPPORTS_DESERIALIZATION
#else // SIMDJSON_SUPPORTS_CONCEPTS
// Unless the simdjson library or the user provides an inline implementation, calling this method should
// immediately fail.
static_assert(!sizeof(T), "The get method with given type is not implemented by the simdjson library. "
@@ -257,7 +257,7 @@ public:
" You may also add support for custom types, see our documentation.");
static_cast<void>(out); // to get rid of unused errors
return UNINITIALIZED;
#endif // SIMDJSON_SUPPORTS_DESERIALIZATION
#endif // SIMDJSON_SUPPORTS_CONCEPTS
}
/** @overload template<typename T> error_code get(T &out) & noexcept */
@@ -322,7 +322,7 @@ public:
* time it parses a document or when it is destroyed.
* @exception simdjson_error(INCORRECT_TYPE) if the JSON value is not a string.
*/
simdjson_inline operator std::string_view() noexcept(false);
simdjson_inline operator std::string_view() noexcept(false) simdjson_lifetime_bound;
/**
* Cast this JSON value to a raw_json_string.
*
@@ -331,7 +331,7 @@ public:
* @returns A pointer to the raw JSON for the given string.
* @exception simdjson_error(INCORRECT_TYPE) if the JSON value is not a string.
*/
simdjson_inline operator raw_json_string() noexcept(false);
simdjson_inline operator raw_json_string() noexcept(false) simdjson_lifetime_bound;
/**
* Cast this JSON value to a bool.
*
@@ -481,11 +481,27 @@ public:
* E.g., you must still call "is_null()" to check that a value is null even if
* "type()" returns json_type::null.
*
* The answer can be one of
* simdjson::ondemand::json_type::object,
* simdjson::ondemand::json_type::array,
* simdjson::ondemand::json_type::string,
* simdjson::ondemand::json_type::number,
* simdjson::ondemand::json_type::boolean,
* simdjson::ondemand::json_type::null.
*
* Starting with simdjson 4.0, this function will return simdjson::ondemand::json_type::unknown
* given a bad token.
* This allows you to identify a case such as {"key": NaN} and identify the NaN value.
* The simdjson::ondemand::json_type::unknown value should only happen with non-valid JSON.
*
* NOTE: If you're only expecting a value to be one type (a typical case), it's generally
* better to just call .get_double, .get_string, etc. and check for INCORRECT_TYPE (or just
* let it throw an exception).
*
* @error TAPE_ERROR when the JSON value is a bad token like "}" "," or "alse".
* Prior to simdjson 4.0, this function would return an error given a bad token.
* Starting with simdjson 4.0, it will return simdjson::ondemand::json_type::unknown.
* This allows you to identify a case such as {"key": NaN} and identify the NaN value.
* The simdjson::ondemand::json_type::unknown value should only happen with non-valid JSON.
*/
simdjson_inline simdjson_result<json_type> type() noexcept;
@@ -775,7 +791,7 @@ public:
simdjson_inline simdjson_result<bool> is_null() noexcept;
template <typename T>
simdjson_inline simdjson_result<T> get() &
#if SIMDJSON_SUPPORTS_DESERIALIZATION
#if SIMDJSON_SUPPORTS_CONCEPTS
noexcept(custom_deserializable<T, document> ? nothrow_custom_deserializable<T, document> : true)
#else
noexcept
@@ -788,7 +804,7 @@ public:
}
template<typename T>
simdjson_inline simdjson_result<T> get() &&
#if SIMDJSON_SUPPORTS_DESERIALIZATION
#if SIMDJSON_SUPPORTS_CONCEPTS
noexcept(custom_deserializable<T, document> ? nothrow_custom_deserializable<T, document> : true)
#else
noexcept
@@ -811,13 +827,13 @@ public:
*/
template<typename T>
simdjson_inline error_code get(T &out) &
#if SIMDJSON_SUPPORTS_DESERIALIZATION
#if SIMDJSON_SUPPORTS_CONCEPTS
noexcept(custom_deserializable<T, document> ? nothrow_custom_deserializable<T, document_reference> : true)
#else
noexcept
#endif
{
#if SIMDJSON_SUPPORTS_DESERIALIZATION
#if SIMDJSON_SUPPORTS_CONCEPTS
if constexpr (custom_deserializable<T, document_reference>) {
return deserialize(*this, out);
} else {
@@ -829,7 +845,7 @@ public:
static_cast<void>(out); // to get rid of unused errors
return UNINITIALIZED;
}
#else // SIMDJSON_SUPPORTS_DESERIALIZATION
#else // SIMDJSON_SUPPORTS_CONCEPTS
// Unless the simdjson library or the user provides an inline implementation, calling this method should
// immediately fail.
static_assert(!sizeof(T), "The get method with given type is not implemented by the simdjson library. "
@@ -839,7 +855,7 @@ public:
" You may also add support for custom types, see our documentation.");
static_cast<void>(out); // to get rid of unused errors
return UNINITIALIZED;
#endif // SIMDJSON_SUPPORTS_DESERIALIZATION
#endif // SIMDJSON_SUPPORTS_CONCEPTS
}
/** @overload template<typename T> error_code get(T &out) & noexcept */
@@ -926,6 +942,9 @@ public:
template<typename T> simdjson_inline error_code get(T &out) & noexcept;
template<typename T> simdjson_inline error_code get(T &out) && noexcept;
#if SIMDJSON_EXCEPTIONS
using SIMDJSON_IMPLEMENTATION::implementation_simdjson_result_base<SIMDJSON_IMPLEMENTATION::ondemand::document>::operator*;
using SIMDJSON_IMPLEMENTATION::implementation_simdjson_result_base<SIMDJSON_IMPLEMENTATION::ondemand::document>::operator->;
template <class T, typename std::enable_if<std::is_same<T, SIMDJSON_IMPLEMENTATION::ondemand::document>::value == false>::type>
explicit simdjson_inline operator T() noexcept(false);
simdjson_inline operator SIMDJSON_IMPLEMENTATION::ondemand::array() & noexcept(false);
@@ -182,10 +182,19 @@ simdjson_inline document_stream::iterator& document_stream::iterator::operator++
return *this;
}
simdjson_inline bool document_stream::iterator::at_end() const noexcept {
return finished;
}
simdjson_inline bool document_stream::iterator::operator!=(const document_stream::iterator &other) const noexcept {
return finished != other.finished;
}
simdjson_inline bool document_stream::iterator::operator==(const document_stream::iterator &other) const noexcept {
return finished == other.finished;
}
simdjson_inline document_stream::iterator document_stream::begin() noexcept {
start();
// If there are no documents, we're finished.
@@ -131,6 +131,7 @@ public:
* Default constructor.
*/
simdjson_inline iterator() noexcept;
simdjson_inline iterator(const iterator &other) noexcept = default;
/**
* Get the current document (or error).
*/
@@ -144,6 +145,7 @@ public:
* @param other the end iterator to compare to.
*/
simdjson_inline bool operator!=(const iterator &other) const noexcept;
simdjson_inline bool operator==(const iterator &other) const noexcept;
/**
* @private
*
@@ -187,6 +189,11 @@ public:
*/
inline error_code error() const noexcept;
/**
* Returns whether the iterator is at the end.
*/
inline bool at_end() const noexcept;
private:
simdjson_inline iterator(document_stream *s, bool finished) noexcept;
/** The document_stream we're iterating through. */
@@ -198,6 +205,7 @@ public:
friend class document_stream;
friend class json_iterator;
};
using iterator = document_stream::iterator;
/**
* Start iterating the documents in the stream.
@@ -0,0 +1,300 @@
/**
* This file is part of the builder API. It is temporarily in the ondemand directory
* but we will move it to a builder directory later.
*/
#ifndef SIMDJSON_GENERIC_BUILDER_H
#ifndef SIMDJSON_CONDITIONAL_INCLUDE
#define SIMDJSON_GENERIC_STRING_BUILDER_H
#include "simdjson/generic/builder/json_string_builder.h"
#include "simdjson/concepts.h"
#endif // SIMDJSON_CONDITIONAL_INCLUDE
#if SIMDJSON_STATIC_REFLECTION
#include <charconv>
#include <cstring>
#include <meta>
#include <memory>
#include <optional>
#include <string_view>
#include <type_traits>
#include <utility>
// #include <static_reflection> // for std::define_static_string - header not available yet
namespace simdjson {
namespace SIMDJSON_IMPLEMENTATION {
namespace builder {
// Concept that checks if a type is a container but not a string (because
// strings handling must be handled differently)
template <typename T>
concept container_but_not_string =
requires(T a) {
{ a.size() } -> std::convertible_to<std::size_t>;
{
a[std::declval<std::size_t>()]
}; // check if elements are accessible for the subscript operator
} && !std::is_same_v<T, std::string> &&
!std::is_same_v<T, std::string_view> && !std::is_same_v<T, const char *>;
template <class T>
requires(container_but_not_string<T>)
constexpr void atom(string_builder &b, const T &t) {
if (t.size() == 0) {
b.append_raw("[]");
return;
}
b.append('[');
atom(b, t[0]);
for (size_t i = 1; i < t.size(); ++i) {
b.append(',');
atom(b, t[i]);
}
b.append(']');
}
template <class T>
requires(std::is_same_v<T, std::string> ||
std::is_same_v<T, std::string_view> ||
std::is_same_v<T, const char *> ||
std::is_same_v<T, char>)
constexpr void atom(string_builder &b, const T &t) {
b.escape_and_append_with_quotes(t);
}
template <concepts::string_view_keyed_map T>
constexpr void atom(string_builder &b, const T &m) {
if (m.empty()) {
b.append_raw("{}");
return;
}
b.append('{');
bool first = true;
for (const auto& [key, value] : m) {
if (!first) {
b.append(',');
}
first = false;
// Keys must be convertible to string_view per the concept
b.escape_and_append_with_quotes(key);
b.append(':');
atom(b, value);
}
b.append('}');
}
template<typename number_type,
typename = typename std::enable_if<std::is_arithmetic<number_type>::value && !std::is_same_v<number_type, char>>::type>
constexpr void atom(string_builder &b, const number_type t) {
b.append(t);
}
template <class T>
requires(std::is_class_v<T> && !container_but_not_string<T> &&
!concepts::string_view_keyed_map<T> &&
!concepts::optional_type<T> &&
!concepts::smart_pointer<T> &&
!concepts::appendable_containers<T> &&
!std::is_same_v<T, std::string> &&
!std::is_same_v<T, std::string_view> &&
!std::is_same_v<T, const char*> &&
!std::is_same_v<T, char>)
constexpr void atom(string_builder &b, const T &t) {
int i = 0;
b.append('{');
template for (constexpr auto dm : std::define_static_array(std::meta::nonstatic_data_members_of(^^T, std::meta::access_context::unchecked()))) {
if (i != 0)
b.append(',');
constexpr auto key = std::define_static_string(constevalutil::consteval_to_quoted_escaped(std::meta::identifier_of(dm)));
b.append_raw(key);
b.append(':');
atom(b, t.[:dm:]);
i++;
};
b.append('}');
}
// Support for optional types (std::optional, etc.)
template <concepts::optional_type T>
constexpr void atom(string_builder &b, const T &opt) {
if (opt) {
atom(b, opt.value());
} else {
b.append_raw("null");
}
}
// Support for smart pointers (std::unique_ptr, std::shared_ptr, etc.)
template <concepts::smart_pointer T>
constexpr void atom(string_builder &b, const T &ptr) {
if (ptr) {
atom(b, *ptr);
} else {
b.append_raw("null");
}
}
// Support for enums - serialize as string representation using expand approach from P2996R12
template <typename T>
requires(std::is_enum_v<T>)
void atom(string_builder &b, const T &e) {
#if SIMDJSON_STATIC_REFLECTION
constexpr auto enumerators = std::define_static_array(std::meta::enumerators_of(^^T));
template for (constexpr auto enum_val : enumerators) {
constexpr auto enum_str = std::define_static_string(constevalutil::consteval_to_quoted_escaped(std::meta::identifier_of(enum_val)));
if (e == [:enum_val:]) {
b.append_raw(enum_str);
return;
}
};
// Fallback to integer if enum value not found
atom(b, static_cast<std::underlying_type_t<T>>(e));
#else
// Fallback: serialize as integer if reflection not available
atom(b, static_cast<std::underlying_type_t<T>>(e));
#endif
}
// Support for appendable containers that don't have operator[] (sets, etc.)
template <concepts::appendable_containers T>
requires(!container_but_not_string<T> && !concepts::string_view_keyed_map<T> &&
!concepts::optional_type<T> && !concepts::smart_pointer<T> &&
!std::is_same_v<T, std::string> &&
!std::is_same_v<T, std::string_view> && !std::is_same_v<T, const char*>)
constexpr void atom(string_builder &b, const T &container) {
if (container.empty()) {
b.append_raw("[]");
return;
}
b.append('[');
bool first = true;
for (const auto& item : container) {
if (!first) {
b.append(',');
}
first = false;
atom(b, item);
}
b.append(']');
}
// append functions that delegate to atom functions for primitive types
template <class T>
requires(std::is_arithmetic_v<T> && !std::is_same_v<T, char>)
void append(string_builder &b, const T &t) {
atom(b, t);
}
template <class T>
requires(std::is_same_v<T, std::string> ||
std::is_same_v<T, std::string_view> ||
std::is_same_v<T, const char *> ||
std::is_same_v<T, char>)
void append(string_builder &b, const T &t) {
atom(b, t);
}
template <concepts::optional_type T>
void append(string_builder &b, const T &t) {
atom(b, t);
}
template <concepts::smart_pointer T>
void append(string_builder &b, const T &t) {
atom(b, t);
}
template <concepts::appendable_containers T>
requires(!container_but_not_string<T> && !concepts::string_view_keyed_map<T> &&
!concepts::optional_type<T> && !concepts::smart_pointer<T> &&
!std::is_same_v<T, std::string> &&
!std::is_same_v<T, std::string_view> && !std::is_same_v<T, const char*>)
void append(string_builder &b, const T &t) {
atom(b, t);
}
template <concepts::string_view_keyed_map T>
void append(string_builder &b, const T &t) {
atom(b, t);
}
// works for struct
template <class Z>
requires(std::is_class_v<Z> && !container_but_not_string<Z> &&
!concepts::string_view_keyed_map<Z> &&
!concepts::optional_type<Z> &&
!concepts::smart_pointer<Z> &&
!concepts::appendable_containers<Z> &&
!std::is_same_v<Z, std::string> &&
!std::is_same_v<Z, std::string_view> &&
!std::is_same_v<Z, const char*> &&
!std::is_same_v<Z, char>)
void append(string_builder &b, const Z &z) {
int i = 0;
b.append('{');
template for (constexpr auto dm : std::define_static_array(std::meta::nonstatic_data_members_of(^^Z, std::meta::access_context::unchecked()))) {
if (i != 0)
b.append(',');
constexpr auto key = std::define_static_string(constevalutil::consteval_to_quoted_escaped(std::meta::identifier_of(dm)));
b.append_raw(key);
b.append(':');
atom(b, z.[:dm:]);
i++;
};
b.append('}');
}
// works for container
template <class Z>
requires(container_but_not_string<Z>)
void append(string_builder &b, const Z &z) {
if (z.size() == 0) {
b.append_raw("[]");
return;
}
b.append('[');
atom(b, z[0]);
for (size_t i = 1; i < z.size(); ++i) {
b.append(',');
atom(b, z[i]);
}
b.append(']');
}
template <class Z>
simdjson_result<std::string> to_json_string(const Z &z, size_t initial_capacity = 1024) {
string_builder b(initial_capacity);
append(b, z);
std::string_view s;
if(auto e = b.view().get(s); e) { return e; }
return std::string(s);
}
template <class Z>
simdjson_error to_json(const Z &z, std::string &s) {
string_builder b;
append(b, z);
std::string_view view;
if(auto e = b.view().get(view); e) { return e; }
s.assign(view);
return SUCCESS;
}
template <class Z>
string_builder& operator<<(string_builder& b, const Z& z) {
append(b, z);
return b;
}
} // namespace builder
} // namespace SIMDJSON_IMPLEMENTATION
// Alias the function template to 'to' in the global namespace
template <class Z>
simdjson_result<std::string> to_json(const Z &z, size_t initial_capacity = 1024) {
return SIMDJSON_IMPLEMENTATION::builder::to_json_string(z, initial_capacity);
}
} // namespace simdjson
#endif // SIMDJSON_STATIC_REFLECTION
#endif
@@ -218,6 +218,8 @@ simdjson_inline void json_iterator::assert_valid_position(token_position positio
#ifndef SIMDJSON_CLANG_VISUAL_STUDIO
SIMDJSON_ASSUME( position >= &parser->implementation->structural_indexes[0] );
SIMDJSON_ASSUME( position < &parser->implementation->structural_indexes[parser->implementation->n_structural_indexes] );
#else
(void)position; // Suppress unused parameter warning
#endif
}
@@ -0,0 +1,686 @@
/**
* This file is part of the builder API. It is temporarily in the ondemand
* directory but we will move it to a builder directory later.
*/
#include <array>
#include <cstring>
#include <type_traits>
#ifndef SIMDJSON_GENERIC_STRING_BUILDER_INL_H
#ifndef SIMDJSON_CONDITIONAL_INCLUDE
#define SIMDJSON_GENERIC_STRING_BUILDER_INL_H
#include "simdjson/generic/builder/json_string_builder.h"
#endif // SIMDJSON_CONDITIONAL_INCLUDE
/*
* Empirically, we have found that an inlined optimization is important for
* performance. The following macros are not ideal. We should find a better
* way to inline the code.
*/
#if defined(__SSE2__) || defined(__x86_64__) || defined(__x86_64) || \
(defined(_M_AMD64) || defined(_M_X64) || \
(defined(_M_IX86_FP) && _M_IX86_FP == 2))
#ifndef SIMDJSON_EXPERIMENTAL_HAS_SSE2
#define SIMDJSON_EXPERIMENTAL_HAS_SSE2 1
#endif
#endif
#if defined(__aarch64__) || defined(_M_ARM64)
#ifndef SIMDJSON_EXPERIMENTAL_HAS_NEON
#define SIMDJSON_EXPERIMENTAL_HAS_NEON 1
#endif
#endif
#if SIMDJSON_EXPERIMENTAL_HAS_NEON
#include <arm_neon.h>
#endif
#if SIMDJSON_EXPERIMENTAL_HAS_SSE2
#include <emmintrin.h>
#endif
namespace simdjson {
namespace SIMDJSON_IMPLEMENTATION {
namespace builder {
static SIMDJSON_CONSTEXPR_LAMBDA std::array<uint8_t, 256> json_quotable_character = {
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
/**
A possible SWAR implementation of has_json_escapable_byte. It is not used because
it is slower than the current implementation. It is kept here for reference (to show
that we tried it).
inline bool has_json_escapable_byte(uint64_t x) {
uint64_t is_ascii = 0x8080808080808080ULL & ~x;
uint64_t xor2 = x ^ 0x0202020202020202ULL;
uint64_t lt32_or_eq34 = xor2 - 0x2121212121212121ULL;
uint64_t sub92 = x ^ 0x5C5C5C5C5C5C5C5CULL;
uint64_t eq92 = (sub92 - 0x0101010101010101ULL);
return ((lt32_or_eq34 | eq92) & is_ascii) != 0;
}
**/
SIMDJSON_CONSTEXPR_LAMBDA simdjson_inline bool
simple_needs_escaping(std::string_view v) {
for (char c : v) {
// a table lookup is faster than a series of comparisons
if(json_quotable_character[static_cast<uint8_t>(c)]) {
return true;
}
}
return false;
}
#if SIMDJSON_EXPERIMENTAL_HAS_NEON
simdjson_inline bool fast_needs_escaping(std::string_view view) {
if (view.size() < 16) {
return simple_needs_escaping(view);
}
size_t i = 0;
uint8x16_t running = vdupq_n_u8(0);
uint8x16_t v34 = vdupq_n_u8(34);
uint8x16_t v92 = vdupq_n_u8(92);
for (; i + 15 < view.size(); i += 16) {
uint8x16_t word = vld1q_u8((const uint8_t *)view.data() + i);
running = vorrq_u8(running, vceqq_u8(word, v34));
running = vorrq_u8(running, vceqq_u8(word, v92));
running = vorrq_u8(running, vcltq_u8(word, vdupq_n_u8(32)));
}
if (i < view.size()) {
uint8x16_t word =
vld1q_u8((const uint8_t *)view.data() + view.length() - 16);
running = vorrq_u8(running, vceqq_u8(word, v34));
running = vorrq_u8(running, vceqq_u8(word, v92));
running = vorrq_u8(running, vcltq_u8(word, vdupq_n_u8(32)));
}
return vmaxvq_u32(vreinterpretq_u32_u8(running)) != 0;
}
#elif SIMDJSON_EXPERIMENTAL_HAS_SSE2
simdjson_inline bool fast_needs_escaping(std::string_view view) {
if (view.size() < 16) {
return simple_needs_escaping(view);
}
size_t i = 0;
__m128i running = _mm_setzero_si128();
for (; i + 15 < view.size(); i += 16) {
__m128i word = _mm_loadu_si128(reinterpret_cast<const __m128i *>(view.data() + i));
running = _mm_or_si128(running, _mm_cmpeq_epi8(word, _mm_set1_epi8(34)));
running = _mm_or_si128(running, _mm_cmpeq_epi8(word, _mm_set1_epi8(92)));
running = _mm_or_si128(
running, _mm_cmpeq_epi8(_mm_subs_epu8(word, _mm_set1_epi8(31)),
_mm_setzero_si128()));
}
if (i < view.size()) {
__m128i word =
_mm_loadu_si128(reinterpret_cast<const __m128i *>(view.data() + view.length() - 16));
running = _mm_or_si128(running, _mm_cmpeq_epi8(word, _mm_set1_epi8(34)));
running = _mm_or_si128(running, _mm_cmpeq_epi8(word, _mm_set1_epi8(92)));
running = _mm_or_si128(
running, _mm_cmpeq_epi8(_mm_subs_epu8(word, _mm_set1_epi8(31)),
_mm_setzero_si128()));
}
return _mm_movemask_epi8(running) != 0;
}
#else
simdjson_inline bool fast_needs_escaping(std::string_view view) {
return simple_needs_escaping(view);
}
#endif
SIMDJSON_CONSTEXPR_LAMBDA inline size_t
find_next_json_quotable_character(const std::string_view view,
size_t location) noexcept {
for (auto pos = view.begin() + location; pos != view.end(); ++pos) {
if (json_quotable_character[static_cast<uint8_t>(*pos)]) {
return pos - view.begin();
}
}
return size_t(view.size());
}
SIMDJSON_CONSTEXPR_LAMBDA static std::string_view control_chars[] = {
"\\u0000", "\\u0001", "\\u0002", "\\u0003", "\\u0004", "\\u0005", "\\u0006",
"\\u0007", "\\b", "\\t", "\\n", "\\u000b", "\\f", "\\r",
"\\u000e", "\\u000f", "\\u0010", "\\u0011", "\\u0012", "\\u0013", "\\u0014",
"\\u0015", "\\u0016", "\\u0017", "\\u0018", "\\u0019", "\\u001a", "\\u001b",
"\\u001c", "\\u001d", "\\u001e", "\\u001f"};
// All Unicode characters may be placed within the quotation marks, except for the
// characters that MUST be escaped: quotation mark, reverse solidus, and the control
// characters (U+0000 through U+001F).
// There are two-character sequence escape representations of some popular characters:
// \", \\, \b, \f, \n, \r, \t.
SIMDJSON_CONSTEXPR_LAMBDA void escape_json_char(char c, char *&out) {
if (c == '"') {
memcpy(out, "\\\"", 2);
out += 2;
} else if (c == '\\') {
memcpy(out, "\\\\", 2);
out += 2;
} else {
std::string_view v = control_chars[uint8_t(c)];
memcpy(out, v.data(), v.size());
out += v.size();
}
}
inline size_t write_string_escaped(const std::string_view input, char *out) {
size_t mysize = input.size();
if (!fast_needs_escaping(input)) { // fast path!
memcpy(out, input.data(), input.size());
return input.size();
}
const char *const initout = out;
size_t location = find_next_json_quotable_character(input, 0);
memcpy(out, input.data(), location);
out += location;
escape_json_char(input[location], out);
location += 1;
while (location < mysize) {
size_t newlocation = find_next_json_quotable_character(input, location);
memcpy(out, input.data() + location, newlocation - location);
out += newlocation - location;
location = newlocation;
if (location == mysize) {
break;
}
escape_json_char(input[location], out);
location += 1;
}
return out - initout;
}
simdjson_inline string_builder::string_builder(size_t initial_capacity)
: buffer(new(std::nothrow) char[initial_capacity]), position(0),
capacity(buffer.get() != nullptr ? initial_capacity : 0),
is_valid(buffer.get() != nullptr) {}
simdjson_inline bool string_builder::capacity_check(size_t upcoming_bytes) {
// We use the convention that when is_valid is false, then the capacity and
// the position are 0.
// Most of the time, this function will return true.
if (simdjson_likely(upcoming_bytes <= capacity - position)) {
return true;
}
// check for overflow, most of the time there is no overflow
if (simdjson_likely(position + upcoming_bytes < position)) {
return false;
}
// We will rarely get here.
grow_buffer((std::max)(capacity * 2, position + upcoming_bytes));
// If the buffer allocation failed, we set is_valid to false.
return is_valid;
}
simdjson_inline void string_builder::grow_buffer(size_t desired_capacity) {
if (!is_valid) {
return;
}
std::unique_ptr<char[]> new_buffer(new (std::nothrow) char[desired_capacity]);
if (new_buffer.get() == nullptr) {
set_valid(false);
return;
}
std::memcpy(new_buffer.get(), buffer.get(), position);
buffer.swap(new_buffer);
capacity = desired_capacity;
}
simdjson_inline void string_builder::set_valid(bool valid) noexcept {
if (!valid) {
is_valid = false;
capacity = 0;
position = 0;
buffer.reset();
} else {
is_valid = true;
}
}
simdjson_inline size_t string_builder::size() const noexcept {
return position;
}
simdjson_inline void string_builder::append(char c) noexcept {
if (capacity_check(1)) {
buffer.get()[position++] = c;
}
}
simdjson_inline void string_builder::append_null() noexcept {
constexpr char null_literal[] = "null";
constexpr size_t null_len = sizeof(null_literal) - 1;
if (capacity_check(null_len)) {
std::memcpy(buffer.get() + position, null_literal, null_len);
position += null_len;
}
}
simdjson_inline void string_builder::clear() noexcept {
position = 0;
// if it was invalid, we should try to repair it
if (!is_valid) {
capacity = 0;
buffer.reset();
is_valid = true;
}
}
namespace internal {
template <typename number_type, typename = typename std::enable_if<
std::is_unsigned<number_type>::value>::type>
simdjson_really_inline int int_log2(number_type x) { return 63 - leading_zeroes(uint64_t(x) | 1); }
simdjson_really_inline int fast_digit_count_32(uint32_t x) {
static uint64_t table[] = {
4294967296, 8589934582, 8589934582, 8589934582, 12884901788,
12884901788, 12884901788, 17179868184, 17179868184, 17179868184,
21474826480, 21474826480, 21474826480, 21474826480, 25769703776,
25769703776, 25769703776, 30063771072, 30063771072, 30063771072,
34349738368, 34349738368, 34349738368, 34349738368, 38554705664,
38554705664, 38554705664, 41949672960, 41949672960, 41949672960,
42949672960, 42949672960};
return uint32_t((x + table[int_log2(x)]) >> 32);
}
simdjson_really_inline int fast_digit_count_64(uint64_t x) {
static uint64_t table[] = {9,
99,
999,
9999,
99999,
999999,
9999999,
99999999,
999999999,
9999999999,
99999999999,
999999999999,
9999999999999,
99999999999999,
999999999999999ULL,
9999999999999999ULL,
99999999999999999ULL,
999999999999999999ULL,
9999999999999999999ULL};
int y = (19 * int_log2(x) >> 6);
y += x > table[y];
return y + 1;
}
template <typename number_type, typename = typename std::enable_if<
std::is_unsigned<number_type>::value>::type>
simdjson_really_inline size_t digit_count(number_type v) noexcept {
static_assert(sizeof(number_type) == 8 || sizeof(number_type) == 4 ||
sizeof(number_type) == 2 || sizeof(number_type) == 1,
"We only support 8-bit, 16-bit, 32-bit and 64-bit numbers");
SIMDJSON_IF_CONSTEXPR(sizeof(number_type) <= 4) {
return fast_digit_count_32(static_cast<uint32_t>(v));
} else {
return fast_digit_count_64(static_cast<uint64_t>(v));
}
}
static const char decimal_table[200] = {
0x30, 0x30, 0x30, 0x31, 0x30, 0x32, 0x30, 0x33, 0x30, 0x34, 0x30, 0x35,
0x30, 0x36, 0x30, 0x37, 0x30, 0x38, 0x30, 0x39, 0x31, 0x30, 0x31, 0x31,
0x31, 0x32, 0x31, 0x33, 0x31, 0x34, 0x31, 0x35, 0x31, 0x36, 0x31, 0x37,
0x31, 0x38, 0x31, 0x39, 0x32, 0x30, 0x32, 0x31, 0x32, 0x32, 0x32, 0x33,
0x32, 0x34, 0x32, 0x35, 0x32, 0x36, 0x32, 0x37, 0x32, 0x38, 0x32, 0x39,
0x33, 0x30, 0x33, 0x31, 0x33, 0x32, 0x33, 0x33, 0x33, 0x34, 0x33, 0x35,
0x33, 0x36, 0x33, 0x37, 0x33, 0x38, 0x33, 0x39, 0x34, 0x30, 0x34, 0x31,
0x34, 0x32, 0x34, 0x33, 0x34, 0x34, 0x34, 0x35, 0x34, 0x36, 0x34, 0x37,
0x34, 0x38, 0x34, 0x39, 0x35, 0x30, 0x35, 0x31, 0x35, 0x32, 0x35, 0x33,
0x35, 0x34, 0x35, 0x35, 0x35, 0x36, 0x35, 0x37, 0x35, 0x38, 0x35, 0x39,
0x36, 0x30, 0x36, 0x31, 0x36, 0x32, 0x36, 0x33, 0x36, 0x34, 0x36, 0x35,
0x36, 0x36, 0x36, 0x37, 0x36, 0x38, 0x36, 0x39, 0x37, 0x30, 0x37, 0x31,
0x37, 0x32, 0x37, 0x33, 0x37, 0x34, 0x37, 0x35, 0x37, 0x36, 0x37, 0x37,
0x37, 0x38, 0x37, 0x39, 0x38, 0x30, 0x38, 0x31, 0x38, 0x32, 0x38, 0x33,
0x38, 0x34, 0x38, 0x35, 0x38, 0x36, 0x38, 0x37, 0x38, 0x38, 0x38, 0x39,
0x39, 0x30, 0x39, 0x31, 0x39, 0x32, 0x39, 0x33, 0x39, 0x34, 0x39, 0x35,
0x39, 0x36, 0x39, 0x37, 0x39, 0x38, 0x39, 0x39,
};
} // namespace internal
template <typename number_type, typename>
simdjson_inline void string_builder::append(number_type v) noexcept {
static_assert(std::is_same<number_type, bool>::value ||
std::is_integral<number_type>::value ||
std::is_floating_point<number_type>::value,
"Unsupported number type");
// If C++17 is available, we can 'if constexpr' here.
SIMDJSON_IF_CONSTEXPR(std::is_same<number_type, bool>::value) {
if (v) {
constexpr char true_literal[] = "true";
constexpr size_t true_len = sizeof(true_literal) - 1;
if (capacity_check(true_len)) {
std::memcpy(buffer.get() + position, true_literal, true_len);
position += true_len;
}
} else {
constexpr char false_literal[] = "false";
constexpr size_t false_len = sizeof(false_literal) - 1;
if (capacity_check(false_len)) {
std::memcpy(buffer.get() + position, false_literal, false_len);
position += false_len;
}
}
}
else SIMDJSON_IF_CONSTEXPR(std::is_unsigned<number_type>::value) {
constexpr size_t max_number_size = 20;
if (capacity_check(max_number_size)) {
using unsigned_type = typename std::make_unsigned<number_type>::type;
unsigned_type pv = static_cast<unsigned_type>(v);
size_t dc = internal::digit_count(pv);
char *write_pointer = buffer.get() + position + dc - 1;
while (pv >= 100) {
memcpy(write_pointer - 1, &internal::decimal_table[(pv % 100)*2], 2);
write_pointer -= 2;
pv /= 100;
}
if (pv >= 10) {
*write_pointer-- = char('0' + (pv % 10));
pv /= 10;
}
*write_pointer = char('0' + pv);
position += dc;
}
}
else SIMDJSON_IF_CONSTEXPR(std::is_integral<number_type>::value) {
constexpr size_t max_number_size = 20;
if (capacity_check(max_number_size)) {
using unsigned_type = typename std::make_unsigned<number_type>::type;
bool negative = v < 0;
unsigned_type pv = static_cast<unsigned_type>(v);
if (negative) {
pv = 0 - pv; // the 0 is for Microsoft
}
size_t dc = internal::digit_count(pv);
if (negative) {
buffer.get()[position++] = '-';
}
char *write_pointer = buffer.get() + position + dc - 1;
while (pv >= 100) {
memcpy(write_pointer - 1, &internal::decimal_table[(pv % 100)*2], 2);
write_pointer -= 2;
pv /= 100;
}
if (pv >= 10) {
*write_pointer-- = char('0' + (pv % 10));
pv /= 10;
}
*write_pointer = char('0' + pv);
position += dc;
}
}
else SIMDJSON_IF_CONSTEXPR(std::is_floating_point<number_type>::value) {
constexpr size_t max_number_size = 24;
if (capacity_check(max_number_size)) {
// We could specialize for float.
char *end = simdjson::internal::to_chars(buffer.get() + position, nullptr,
double(v));
position = end - buffer.get();
}
}
}
simdjson_inline void
string_builder::escape_and_append(std::string_view input) noexcept {
// escaping might turn a control character into \x00xx so 6 characters.
if (capacity_check(6 * input.size())) {
position += write_string_escaped(input, buffer.get() + position);
}
}
simdjson_inline void
string_builder::escape_and_append_with_quotes(std::string_view input) noexcept {
// escaping might turn a control character into \x00xx so 6 characters.
if (capacity_check(2 + 6 * input.size())) {
buffer.get()[position++] = '"';
position += write_string_escaped(input, buffer.get() + position);
buffer.get()[position++] = '"';
}
}
simdjson_inline void
string_builder::escape_and_append_with_quotes(char input) noexcept {
// escaping might turn a control character into \x00xx so 6 characters.
if (capacity_check(2 + 6 * 1)) {
buffer.get()[position++] = '"';
std::string_view cinput(&input, 1);
position += write_string_escaped(cinput, buffer.get() + position);
buffer.get()[position++] = '"';
}
}
simdjson_inline void string_builder::escape_and_append_with_quotes(const char* input) noexcept {
std::string_view cinput(input);
escape_and_append_with_quotes(cinput);
}
#if SIMDJSON_SUPPORTS_CONCEPTS
template<internal::fixed_string key>
simdjson_inline void string_builder::escape_and_append_with_quotes() noexcept {
escape_and_append_with_quotes(internal::string_constant<key>::value);
}
#endif
simdjson_inline void string_builder::append_raw(const char *c) noexcept {
size_t len = std::strlen(c);
append_raw(c, len);
}
simdjson_inline void
string_builder::append_raw(std::string_view input) noexcept {
if (capacity_check(input.size())) {
std::memcpy(buffer.get() + position, input.data(), input.size());
position += input.size();
}
}
simdjson_inline void string_builder::append_raw(const char *str,
size_t len) noexcept {
if (capacity_check(len)) {
std::memcpy(buffer.get() + position, str, len);
position += len;
}
}
#if SIMDJSON_SUPPORTS_CONCEPTS
// Support for optional types (std::optional, etc.)
template <concepts::optional_type T>
simdjson_inline void string_builder::append(const T &opt) {
if (opt) {
append(*opt);
} else {
append_null();
}
}
template <typename T>
requires(std::is_convertible<T, std::string_view>::value ||
std::is_same<T, const char*>::value )
simdjson_inline void string_builder::append(const T &value) {
escape_and_append_with_quotes(value);
}
#endif
#if SIMDJSON_SUPPORTS_RANGES && SIMDJSON_SUPPORTS_CONCEPTS
// Support for range-based appending (std::ranges::view, etc.)
template <std::ranges::range R>
requires (!std::is_convertible<R, std::string_view>::value)
simdjson_inline void string_builder::append(const R &range) noexcept {
auto it = std::ranges::begin(range);
auto end = std::ranges::end(range);
if constexpr (concepts::is_pair<typename R::value_type>) {
start_object();
if (it == end) {
end_object();
return; // Handle empty range
}
// Append first item without leading comma
append_key_value(it->first, it->second);
++it;
// Append remaining items with preceding commas
for (; it != end; ++it) {
append_comma();
append_key_value(it->first, it->second);
}
end_object();
} else {
start_array();
if (it == end) {
end_array();
return; // Handle empty range
}
// Append first item without leading comma
append(*it);
++it;
// Append remaining items with preceding commas
for (; it != end; ++it) {
append_comma();
append(*it);
}
end_array();
}
}
#endif
#if SIMDJSON_EXCEPTIONS
simdjson_inline string_builder::operator std::string() const noexcept(false) {
return std::string(std::string_view());
}
simdjson_inline string_builder::operator std::string_view() const
noexcept(false) simdjson_lifetime_bound {
return view();
}
#endif
simdjson_inline simdjson_result<std::string_view>
string_builder::view() const noexcept {
if (!is_valid) {
return simdjson::OUT_OF_CAPACITY;
}
return std::string_view(buffer.get(), position);
}
simdjson_inline simdjson_result<const char *> string_builder::c_str() noexcept {
if (capacity_check(1)) {
buffer.get()[position] = '\0';
return buffer.get();
}
return simdjson::OUT_OF_CAPACITY;
}
simdjson_inline bool string_builder::validate_unicode() const noexcept {
return simdjson::validate_utf8(buffer.get(), position);
}
simdjson_inline void string_builder::start_object() noexcept {
if (capacity_check(1)) {
buffer.get()[position++] = '{';
}
}
simdjson_inline void string_builder::end_object() noexcept {
if (capacity_check(1)) {
buffer.get()[position++] = '}';
}
}
simdjson_inline void string_builder::start_array() noexcept {
if (capacity_check(1)) {
buffer.get()[position++] = '[';
}
}
simdjson_inline void string_builder::end_array() noexcept {
if (capacity_check(1)) {
buffer.get()[position++] = ']';
}
}
simdjson_inline void string_builder::append_comma() noexcept {
if (capacity_check(1)) {
buffer.get()[position++] = ',';
}
}
simdjson_inline void string_builder::append_colon() noexcept {
if (capacity_check(1)) {
buffer.get()[position++] = ':';
}
}
template<typename key_type, typename value_type>
simdjson_inline void string_builder::append_key_value(key_type key, value_type value) noexcept {
static_assert(
std::is_same<key_type, const char*>::value ||
std::is_convertible<key_type, std::string_view>::value,
"Unsupported key type");
escape_and_append_with_quotes(key);
append_colon();
SIMDJSON_IF_CONSTEXPR(std::is_same<value_type, std::nullptr_t>::value) {
append_null();
} else SIMDJSON_IF_CONSTEXPR(std::is_same<value_type, char>::value) {
escape_and_append_with_quotes(value);
} else SIMDJSON_IF_CONSTEXPR(std::is_convertible<value_type, std::string_view>::value) {
escape_and_append_with_quotes(value);
} else SIMDJSON_IF_CONSTEXPR(std::is_same<value_type, const char*>::value) {
escape_and_append_with_quotes(value);
} else {
append(value);
}
}
#if SIMDJSON_SUPPORTS_CONCEPTS
template<internal::fixed_string key, typename value_type>
simdjson_inline void string_builder::append_key_value(value_type value) noexcept {
escape_and_append_with_quotes<key>();
append_colon();
SIMDJSON_IF_CONSTEXPR(std::is_same<value_type, std::nullptr_t>::value) {
append_null();
} else SIMDJSON_IF_CONSTEXPR(std::is_same<value_type, char>::value) {
escape_and_append_with_quotes(value);
} else SIMDJSON_IF_CONSTEXPR(std::is_convertible<value_type, std::string_view>::value) {
escape_and_append_with_quotes(value);
} else SIMDJSON_IF_CONSTEXPR(std::is_same<value_type, const char*>::value) {
escape_and_append_with_quotes(value);
} else {
append(value);
}
}
#endif
} // namespace builder
} // namespace SIMDJSON_IMPLEMENTATION
} // namespace simdjson
#endif // SIMDJSON_GENERIC_STRING_BUILDER_INL_H
@@ -0,0 +1,273 @@
/**
* This file is part of the builder API. It is temporarily in the ondemand directory
* but we will move it to a builder directory later.
*/
#ifndef SIMDJSON_GENERIC_STRING_BUILDER_H
#ifndef SIMDJSON_CONDITIONAL_INCLUDE
#define SIMDJSON_GENERIC_STRING_BUILDER_H
#include "simdjson/generic/implementation_simdjson_result_base.h"
#endif // SIMDJSON_CONDITIONAL_INCLUDE
namespace simdjson {
namespace SIMDJSON_IMPLEMENTATION {
namespace builder {
#if SIMDJSON_SUPPORTS_CONCEPTS
// Helper to create string constants
namespace internal {
template <std::size_t N>
struct fixed_string {
constexpr fixed_string(const char (&str)[N]) {
for (std::size_t i = 0; i < N; ++i) {
data[i] = str[i];
}
}
char data[N];
constexpr std::string_view view() const { return {data, N - 1}; }
};
template <fixed_string str>
struct string_constant {
static constexpr std::string_view value = str.view();
};
} // namespace internal
#endif // SIMDJSON_SUPPORTS_CONCEPTS
/**
* A builder for JSON strings representing documents. This is a low-level
* builder that is not meant to be used directly by end-users. Though it
* supports atomic types (Booleans, strings), it does not support composed
* types (arrays and objects).
*
* Ultimately, this class can support kernel-specific optimizations. E.g.,
* it may make use of SIMD instructions to escape strings faster.
*/
class string_builder {
public:
simdjson_inline string_builder(size_t initial_capacity = 1024);
/**
* Append number (includes Booleans). Booleans are mapped to the strings
* false and true. Numbers are converted to strings abiding by the JSON standard.
* Floating-point numbers are converted to the shortest string that 'correctly'
* represents the number.
*/
template<typename number_type,
typename = typename std::enable_if<std::is_arithmetic<number_type>::value>::type>
simdjson_inline void append(number_type v) noexcept;
/**
* Append character c.
*/
simdjson_inline void append(char c) noexcept;
/**
* Append the string 'null'.
*/
simdjson_inline void append_null() noexcept;
/**
* Clear the content.
*/
simdjson_inline void clear() noexcept;
/**
* Append the std::string_view, after escaping it.
* There is no UTF-8 validation.
*/
simdjson_inline void escape_and_append(std::string_view input) noexcept;
/**
* Append the std::string_view surrounded by double quotes, after escaping it.
* There is no UTF-8 validation.
*/
simdjson_inline void escape_and_append_with_quotes(std::string_view input) noexcept;
#if SIMDJSON_SUPPORTS_CONCEPTS
template<internal::fixed_string key>
simdjson_inline void escape_and_append_with_quotes() noexcept;
#endif
/**
* Append the character surrounded by double quotes, after escaping it.
* There is no UTF-8 validation.
*/
simdjson_inline void escape_and_append_with_quotes(char input) noexcept;
/**
* Append the character surrounded by double quotes, after escaping it.
* There is no UTF-8 validation.
*/
simdjson_inline void escape_and_append_with_quotes(const char* input) noexcept;
/**
* Append the C string directly, without escaping.
* There is no UTF-8 validation.
*/
simdjson_inline void append_raw(const char *c) noexcept;
/**
* Append "{" to the buffer.
*/
simdjson_inline void start_object() noexcept;
/**
* Append "}" to the buffer.
*/
simdjson_inline void end_object() noexcept;
/**
* Append "[" to the buffer.
*/
simdjson_inline void start_array() noexcept;
/**
* Append "]" to the buffer.
*/
simdjson_inline void end_array() noexcept;
/**
* Append "," to the buffer.
*/
simdjson_inline void append_comma() noexcept;
/**
* Append ":" to the buffer.
*/
simdjson_inline void append_colon() noexcept;
/**
* Append a key-value pair to the buffer.
* The key is escaped and surrounded by double quotes.
* The value is escaped if it is a string.
*/
template<typename key_type, typename value_type>
simdjson_inline void append_key_value(key_type key, value_type value) noexcept;
#if SIMDJSON_SUPPORTS_CONCEPTS
template<internal::fixed_string key, typename value_type>
simdjson_inline void append_key_value(value_type value) noexcept;
// Support for optional types (std::optional, etc.)
template <concepts::optional_type T>
simdjson_inline void append(const T &opt);
// Support for string-like types
template <typename T>
requires(std::is_convertible<T, std::string_view>::value ||
std::is_same<T, const char*>::value )
simdjson_inline void append(const T &value);
#endif
#if SIMDJSON_SUPPORTS_RANGES && SIMDJSON_SUPPORTS_CONCEPTS
// Support for range-based appending (std::ranges::view, etc.)
template <std::ranges::range R>
requires (!std::is_convertible<R, std::string_view>::value)
simdjson_inline void append(const R &range) noexcept;
#endif
/**
* Append the std::string_view directly, without escaping.
* There is no UTF-8 validation.
*/
simdjson_inline void append_raw(std::string_view input) noexcept;
/**
* Append len characters from str.
* There is no UTF-8 validation.
*/
simdjson_inline void append_raw(const char *str, size_t len) noexcept;
#if SIMDJSON_EXCEPTIONS
/**
* Creates an std::string from the written JSON buffer.
* Throws if memory allocation failed
*
* The result may not be valid UTF-8 if some of your content was not valid UTF-8.
* Use validate_unicode() to check the content if needed.
*/
simdjson_inline operator std::string() const noexcept(false);
/**
* Creates an std::string_view from the written JSON buffer.
* Throws if memory allocation failed.
*
* The result may not be valid UTF-8 if some of your content was not valid UTF-8.
* Use validate_unicode() to check the content if needed.
*/
simdjson_inline operator std::string_view() const noexcept(false) simdjson_lifetime_bound;
#endif
/**
* Returns a view on the written JSON buffer. Returns an error
* if memory allocation failed.
*
* The result may not be valid UTF-8 if some of your content was not valid UTF-8.
* Use validate_unicode() to check the content.
*/
simdjson_inline simdjson_result<std::string_view> view() const noexcept;
/**
* Appends the null character to the buffer and returns
* a pointer to the beginning of the written JSON buffer.
* Returns an error if memory allocation failed.
* The result is null-terminated.
*
* The result may not be valid UTF-8 if some of your content was not valid UTF-8.
* Use validate_unicode() to check the content.
*/
simdjson_inline simdjson_result<const char *> c_str() noexcept;
/**
* Return true if the content is valid UTF-8.
*/
simdjson_inline bool validate_unicode() const noexcept;
/**
* Returns the current size of the written JSON buffer.
* If an error occurred, returns 0.
*/
simdjson_inline size_t size() const noexcept;
private:
/**
* Returns true if we can write at least upcoming_bytes bytes.
* The underlying buffer is reallocated if needed. It is designed
* to be called before writing to the buffer. It should be fast.
*/
simdjson_inline bool capacity_check(size_t upcoming_bytes);
/**
* Grow the buffer to at least desired_capacity bytes.
* If the allocation fails, is_valid is set to false. We expect
* that this function would not be repeatedly called.
*/
simdjson_inline void grow_buffer(size_t desired_capacity);
/**
* We use this helper function to make sure that is_valid is kept consistent.
*/
simdjson_inline void set_valid(bool valid) noexcept;
std::unique_ptr<char[]> buffer{};
size_t position{0};
size_t capacity{0};
bool is_valid{true};
};
}
}
#if !SIMDJSON_STATIC_REFLECTION
// fallback implementation until we have static reflection
template <class Z>
simdjson_result<std::string> to_json(const Z &z, size_t initial_capacity = 1024) {
simdjson::SIMDJSON_IMPLEMENTATION::builder::string_builder b(initial_capacity);
b.append(z);
std::string_view s;
auto e = b.view().get(s);
if(e) { return e; }
return std::string(s);
}
#endif
} // namespace simdjson
#endif // SIMDJSON_GENERIC_STRING_BUILDER_H
@@ -15,6 +15,7 @@ namespace ondemand {
* The type of a JSON value.
*/
enum class json_type {
unknown=0,
// Start at 1 to catch uninitialized / default values more easily
array=1, ///< A JSON array ( [ 1, 2, 3 ... ] )
object, ///< A JSON object ( { "a": 1, "b" 2, ... } )
@@ -271,6 +271,7 @@ simdjson_inline simdjson_result<std::string_view> simdjson_result<SIMDJSON_IMPL
if (error()) { return error(); }
return first.raw_json();
}
} // namespace simdjson
#endif // SIMDJSON_GENERIC_ONDEMAND_OBJECT_INL_H
+51 -1
View File
@@ -202,6 +202,36 @@ public:
*/
simdjson_inline simdjson_result<std::string_view> raw_json() noexcept;
#if SIMDJSON_SUPPORTS_CONCEPTS
/**
* Get this object as the given type.
*
* @param out This is set to a value of the given type, parsed from the JSON. If there is an error, this may not be initialized.
* @returns INCORRECT_TYPE If the JSON object is not of the given type.
* @returns SUCCESS If the parse succeeded and the out parameter was set to the value.
*/
template <typename T>
simdjson_inline error_code get(T &out)
noexcept(custom_deserializable<T, object> ? nothrow_custom_deserializable<T, object> : true) {
static_assert(custom_deserializable<T, object>);
return deserialize(*this, out);
}
/**
* Get this array as the given type.
*
* @returns A value of the given type, parsed from the JSON.
* @returns INCORRECT_TYPE If the JSON value is not the given type.
*/
template <typename T>
simdjson_inline simdjson_result<T> get()
noexcept(custom_deserializable<T, value> ? nothrow_custom_deserializable<T, value> : true)
{
static_assert(std::is_default_constructible<T>::value, "The specified type is not default constructible.");
T out{};
SIMDJSON_TRY(get<T>(out));
return out;
}
#endif // SIMDJSON_SUPPORTS_CONCEPTS
protected:
/**
* Go to the end of the object, no matter where you are right now.
@@ -245,12 +275,32 @@ public:
simdjson_inline simdjson_result<SIMDJSON_IMPLEMENTATION::ondemand::value> operator[](std::string_view key) && noexcept;
simdjson_inline simdjson_result<SIMDJSON_IMPLEMENTATION::ondemand::value> at_pointer(std::string_view json_pointer) noexcept;
simdjson_inline simdjson_result<SIMDJSON_IMPLEMENTATION::ondemand::value> at_path(std::string_view json_path) noexcept;
inline simdjson_result<bool> reset() noexcept;
inline simdjson_result<bool> is_empty() noexcept;
inline simdjson_result<size_t> count_fields() & noexcept;
inline simdjson_result<std::string_view> raw_json() noexcept;
#if SIMDJSON_SUPPORTS_CONCEPTS
// TODO: move this code into object-inl.h
template<typename T>
simdjson_inline simdjson_result<T> get() noexcept {
if (error()) { return error(); }
if constexpr (std::is_same_v<T, SIMDJSON_IMPLEMENTATION::ondemand::object>) {
return first;
}
return first.get<T>();
}
template<typename T>
simdjson_inline error_code get(T& out) noexcept {
if (error()) { return error(); }
if constexpr (std::is_same_v<T, SIMDJSON_IMPLEMENTATION::ondemand::object>) {
out = first;
} else {
SIMDJSON_TRY( first.get<T>(out) );
}
return SUCCESS;
}
#endif // SIMDJSON_SUPPORTS_CONCEPTS
};
} // namespace simdjson
+44 -10
View File
@@ -49,7 +49,7 @@ simdjson_inline simdjson_warn_unused bool parser::string_buffer_overflow(const u
#endif
simdjson_warn_unused simdjson_inline simdjson_result<document> parser::iterate(padded_string_view json) & noexcept {
if (json.padding() < SIMDJSON_PADDING) { return INSUFFICIENT_PADDING; }
if (!json.has_sufficient_padding()) { return INSUFFICIENT_PADDING; }
json.remove_utf8_bom();
@@ -65,7 +65,7 @@ simdjson_warn_unused simdjson_inline simdjson_result<document> parser::iterate(p
#ifdef SIMDJSON_EXPERIMENTAL_ALLOW_INCOMPLETE_JSON
simdjson_warn_unused simdjson_inline simdjson_result<document> parser::iterate_allow_incomplete_json(padded_string_view json) & noexcept {
if (json.padding() < SIMDJSON_PADDING) { return INSUFFICIENT_PADDING; }
if (!json.has_sufficient_padding()) { return INSUFFICIENT_PADDING; }
json.remove_utf8_bom();
@@ -97,10 +97,7 @@ simdjson_warn_unused simdjson_inline simdjson_result<document> parser::iterate(s
}
simdjson_warn_unused simdjson_inline simdjson_result<document> parser::iterate(std::string &json) & noexcept {
if(json.capacity() - json.size() < SIMDJSON_PADDING) {
json.reserve(json.size() + SIMDJSON_PADDING);
}
return iterate(padded_string_view(json));
return iterate(pad_with_reserve(json));
}
simdjson_warn_unused simdjson_inline simdjson_result<document> parser::iterate(const std::string &json) & noexcept {
@@ -122,7 +119,7 @@ simdjson_warn_unused simdjson_inline simdjson_result<document> parser::iterate(c
}
simdjson_warn_unused simdjson_inline simdjson_result<json_iterator> parser::iterate_raw(padded_string_view json) & noexcept {
if (json.padding() < SIMDJSON_PADDING) { return INSUFFICIENT_PADDING; }
if (!json.has_sufficient_padding()) { return INSUFFICIENT_PADDING; }
json.remove_utf8_bom();
@@ -137,6 +134,7 @@ simdjson_warn_unused simdjson_inline simdjson_result<json_iterator> parser::iter
}
inline simdjson_result<document_stream> parser::iterate_many(const uint8_t *buf, size_t len, size_t batch_size, bool allow_comma_separated) noexcept {
// Warning: no check is done on the buffer padding. We trust the user.
if(batch_size < MINIMAL_BATCH_SIZE) { batch_size = MINIMAL_BATCH_SIZE; }
if((len >= 3) && (std::memcmp(buf, "\xEF\xBB\xBF", 3) == 0)) {
buf += 3;
@@ -145,16 +143,24 @@ inline simdjson_result<document_stream> parser::iterate_many(const uint8_t *buf,
if(allow_comma_separated && batch_size < len) { batch_size = len; }
return document_stream(*this, buf, len, batch_size, allow_comma_separated);
}
inline simdjson_result<document_stream> parser::iterate_many(const char *buf, size_t len, size_t batch_size, bool allow_comma_separated) noexcept {
// Warning: no check is done on the buffer padding. We trust the user.
return iterate_many(reinterpret_cast<const uint8_t *>(buf), len, batch_size, allow_comma_separated);
}
inline simdjson_result<document_stream> parser::iterate_many(const std::string &s, size_t batch_size, bool allow_comma_separated) noexcept {
inline simdjson_result<document_stream> parser::iterate_many(padded_string_view s, size_t batch_size, bool allow_comma_separated) noexcept {
if (!s.has_sufficient_padding()) { return INSUFFICIENT_PADDING; }
return iterate_many(s.data(), s.length(), batch_size, allow_comma_separated);
}
inline simdjson_result<document_stream> parser::iterate_many(const padded_string &s, size_t batch_size, bool allow_comma_separated) noexcept {
return iterate_many(s.data(), s.length(), batch_size, allow_comma_separated);
return iterate_many(padded_string_view(s), batch_size, allow_comma_separated);
}
inline simdjson_result<document_stream> parser::iterate_many(const std::string &s, size_t batch_size, bool allow_comma_separated) noexcept {
return iterate_many(padded_string_view(s), batch_size, allow_comma_separated);
}
inline simdjson_result<document_stream> parser::iterate_many(std::string &s, size_t batch_size, bool allow_comma_separated) noexcept {
return iterate_many(pad(s), batch_size, allow_comma_separated);
}
simdjson_pure simdjson_inline size_t parser::capacity() const noexcept {
return _capacity;
}
@@ -189,6 +195,34 @@ simdjson_inline simdjson_warn_unused simdjson_result<std::string_view> parser::u
return result;
}
simdjson_inline simdjson_warn_unused ondemand::parser& parser::get_parser() {
return *parser::get_parser_instance();
}
simdjson_inline bool release_parser() {
auto &parser_instance = parser::get_threadlocal_parser_if_exists();
if (parser_instance) {
parser_instance.reset();
return true;
}
return false;
}
simdjson_inline simdjson_warn_unused std::unique_ptr<ondemand::parser>& parser::get_parser_instance() {
std::unique_ptr<ondemand::parser>& parser_instance = get_threadlocal_parser_if_exists();
if (!parser_instance) {
parser_instance.reset(new ondemand::parser());
}
return parser_instance;
}
simdjson_inline simdjson_warn_unused std::unique_ptr<ondemand::parser>& parser::get_threadlocal_parser_if_exists() {
// @the-moisrex points out that this could be implemented with std::optional (C++17).
thread_local std::unique_ptr<ondemand::parser> parser_instance = nullptr;
return parser_instance;
}
} // namespace ondemand
} // namespace SIMDJSON_IMPLEMENTATION
} // namespace simdjson
+43 -7
View File
@@ -7,6 +7,7 @@
#endif // SIMDJSON_CONDITIONAL_INCLUDE
#include <memory>
#include <thread>
namespace simdjson {
namespace SIMDJSON_IMPLEMENTATION {
@@ -125,7 +126,9 @@ public:
simdjson_warn_unused simdjson_result<document> iterate(std::string_view json, size_t capacity) & noexcept;
/** @overload simdjson_result<document> iterate(padded_string_view json) & noexcept */
simdjson_warn_unused simdjson_result<document> iterate(const std::string &json) & noexcept;
/** @overload simdjson_result<document> iterate(padded_string_view json) & noexcept */
/** @overload simdjson_result<document> iterate(padded_string_view json) & noexcept
The string instance might be have its capacity extended. Note that this can still
result in AddressSanitizer: container-overflow in some cases. */
simdjson_warn_unused simdjson_result<document> iterate(std::string &json) & noexcept;
/** @overload simdjson_result<document> iterate(padded_string_view json) & noexcept */
simdjson_warn_unused simdjson_result<document> iterate(const simdjson_result<padded_string> &json) & noexcept;
@@ -213,6 +216,11 @@ public:
* Setting batch_size to excessively large or excessively small values may impact negatively the
* performance.
*
* ### Threads
*
* When compiled with SIMDJSON_THREADS_ENABLED, this method will use a single thread under the
* hood to do some lookahead.
*
* ### REQUIRED: Buffer Padding
*
* The buffer must have at least SIMDJSON_PADDING extra allocated bytes. It does not matter what
@@ -220,10 +228,10 @@ public:
* using a sanitizer that verifies that no uninitialized byte is read, then you should initialize the
* SIMDJSON_PADDING bytes to avoid runtime warnings.
*
* ### Threads
* This is checked automatically with all iterate_many function calls, except for the two
* that take pointers (const char* or const uint8_t*).
*
* When compiled with SIMDJSON_THREADS_ENABLED, this method will use a single thread under the
* hood to do some lookahead.
* ### Threads
*
* ### Parser Capacity
*
@@ -249,14 +257,16 @@ public:
*/
inline simdjson_result<document_stream> iterate_many(const uint8_t *buf, size_t len, size_t batch_size = DEFAULT_BATCH_SIZE, bool allow_comma_separated = false) noexcept;
/** @overload parse_many(const uint8_t *buf, size_t len, size_t batch_size) */
inline simdjson_result<document_stream> iterate_many(padded_string_view json, size_t batch_size = DEFAULT_BATCH_SIZE, bool allow_comma_separated = false) noexcept;
/** @overload parse_many(const uint8_t *buf, size_t len, size_t batch_size) */
inline simdjson_result<document_stream> iterate_many(const char *buf, size_t len, size_t batch_size = DEFAULT_BATCH_SIZE, bool allow_comma_separated = false) noexcept;
/** @overload parse_many(const uint8_t *buf, size_t len, size_t batch_size) */
inline simdjson_result<document_stream> iterate_many(const std::string &s, size_t batch_size = DEFAULT_BATCH_SIZE, bool allow_comma_separated = false) noexcept;
inline simdjson_result<document_stream> iterate_many(const std::string &&s, size_t batch_size, bool allow_comma_separated = false) = delete;// unsafe
/** @overload parse_many(const uint8_t *buf, size_t len, size_t batch_size)
the string might be automatically padded with up to SIMDJSON_PADDING whitespace characters */
inline simdjson_result<document_stream> iterate_many(std::string &s, size_t batch_size = DEFAULT_BATCH_SIZE, bool allow_comma_separated = false) noexcept;
/** @overload parse_many(const uint8_t *buf, size_t len, size_t batch_size) */
inline simdjson_result<document_stream> iterate_many(const padded_string &s, size_t batch_size = DEFAULT_BATCH_SIZE, bool allow_comma_separated = false) noexcept;
inline simdjson_result<document_stream> iterate_many(const padded_string &&s, size_t batch_size, bool allow_comma_separated = false) = delete;// unsafe
/** @private We do not want to allow implicit conversion from C string to std::string. */
simdjson_result<document_stream> iterate_many(const char *buf, size_t batch_size = DEFAULT_BATCH_SIZE) noexcept = delete;
@@ -358,13 +368,39 @@ public:
bool string_buffer_overflow(const uint8_t *string_buf_loc) const noexcept;
#endif
/**
* Get a unique parser instance corresponding to the current thread.
* This instance can be safely used within the current thread, but it should
* not be passed to other threads.
*
* A parser should only be used for one document at a time.
*
* Our simdjson::from functions use this parser instance.
*
* You can free the related parser by calling release_parser().
*/
static simdjson_inline simdjson_warn_unused ondemand::parser& get_parser();
/**
* Release the parser instance initialized by get_parser() and all the
* associated resources (memory). Returns true if a parser instance
* was released.
*/
static simdjson_inline bool release_parser();
private:
friend bool release_parser();
friend ondemand::parser& get_parser();
/** Get the thread-local parser instance, allocates it if needed */
static simdjson_inline simdjson_warn_unused std::unique_ptr<ondemand::parser>& get_parser_instance();
/** Get the thread-local parser instance, it might be null */
static simdjson_inline simdjson_warn_unused std::unique_ptr<ondemand::parser>& get_threadlocal_parser_if_exists();
/** @private [for benchmarking access] The implementation to use */
std::unique_ptr<simdjson::internal::dom_parser_implementation> implementation{};
size_t _capacity{0};
size_t _max_capacity;
size_t _max_depth{DEFAULT_MAX_DEPTH};
std::unique_ptr<uint8_t[]> string_buf{};
#if SIMDJSON_DEVELOPMENT_CHECKS
std::unique_ptr<token_position[]> start_positions{};
#endif

Some files were not shown because too many files have changed in this diff Show More