diff --git a/.travis.yml b/.travis.yml index 357c9e593..629bec876 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,6 +1,7 @@ language: cpp -sudo: false +dist: trusty +sudo: required # from http://stackoverflow.com/a/32127147/266378 matrix: @@ -56,3 +57,11 @@ script: - make CXX=$COMPILER CXXFLAGS="-lstdc++" - ./json_unit "*" - valgrind --error-exitcode=1 --leak-check=full ./json_unit + +notifications: + webhooks: + urls: + - https://webhooks.gitter.im/e/f1196addb0e97a5ff396 + on_success: change + on_failure: always + on_start: never diff --git a/ChangeLog.md b/ChangeLog.md new file mode 100644 index 000000000..81990a7b9 --- /dev/null +++ b/ChangeLog.md @@ -0,0 +1,181 @@ +# Change Log +All notable changes to this project will be documented in this file. This project adheres to [Semantic Versioning](http://semver.org/). + +## [Unreleased](https://github.com/nlohmann/json/tree/HEAD) + +[Full Changelog](https://github.com/nlohmann/json/compare/v1.1.0...HEAD) + +- Issue \#178 - Extending support to full uint64\_t/int64\_t range and unsigned type \(updated\) [\#193](https://github.com/nlohmann/json/pull/193) ([twelsby](https://github.com/twelsby)) + +- GCC/clang floating point parsing bug in strtod\(\) [\#195](https://github.com/nlohmann/json/issues/195) +- Floating point exceptions [\#181](https://github.com/nlohmann/json/issues/181) +- Implicit assignment to std::string fails [\#144](https://github.com/nlohmann/json/issues/144) +- Issue \#195 - update Travis to Trusty due to gcc/clang strtod\(\) bug [\#196](https://github.com/nlohmann/json/pull/196) ([twelsby](https://github.com/twelsby)) + +- Integer conversion to unsigned [\#178](https://github.com/nlohmann/json/issues/178) + +- Fix broken link [\#197](https://github.com/nlohmann/json/pull/197) ([vog](https://github.com/vog)) + +## [v1.1.0](https://github.com/nlohmann/json/releases/tag/v1.1.0) (2016-01-24) +[Full Changelog](https://github.com/nlohmann/json/compare/v1.0.0...v1.1.0) + +- JSON performance benchmark comparision [\#177](https://github.com/nlohmann/json/issues/177) +- Since re2c is often ignored in pull requests, it may make sense to make a contributing.md file [\#175](https://github.com/nlohmann/json/issues/175) +- Add assertions [\#168](https://github.com/nlohmann/json/issues/168) +- range based for loop for objects [\#83](https://github.com/nlohmann/json/issues/83) +- Implementation of get\_ref\(\) [\#184](https://github.com/nlohmann/json/pull/184) ([dariomt](https://github.com/dariomt)) + +- Small error in pull \#185 [\#194](https://github.com/nlohmann/json/issues/194) +- Bugs in miloyip/nativejson-benchmark: floating-point parsing [\#186](https://github.com/nlohmann/json/issues/186) +- Cannot index by key of type static constexpr const char\* [\#171](https://github.com/nlohmann/json/issues/171) +- Fixed Issue \#171 - added two extra template overloads of operator\[\] for T\* arguments [\#189](https://github.com/nlohmann/json/pull/189) ([twelsby](https://github.com/twelsby)) +- Fixed issue \#167 - removed operator ValueType\(\) condition for VS2015 [\#188](https://github.com/nlohmann/json/pull/188) ([twelsby](https://github.com/twelsby)) + +- Floating point equality [\#185](https://github.com/nlohmann/json/issues/185) +- Unused variables in catch [\#180](https://github.com/nlohmann/json/issues/180) +- Typo in documentation [\#179](https://github.com/nlohmann/json/issues/179) +- Android? [\#172](https://github.com/nlohmann/json/issues/172) +- MSVC 2015 build fails when attempting to compare object\_t [\#167](https://github.com/nlohmann/json/issues/167) +- Member detector is not portable [\#166](https://github.com/nlohmann/json/issues/166) +- Unnecessary const\_cast [\#162](https://github.com/nlohmann/json/issues/162) +- Consider submitting this to the Boost Library Incubator [\#66](https://github.com/nlohmann/json/issues/66) + +- Fixed Issue \#186 - add strto\(f|d|ld\) overload wrappers, "-0.0" special case and FP trailing zero [\#191](https://github.com/nlohmann/json/pull/191) ([twelsby](https://github.com/twelsby)) +- Issue \#185 - remove approx\(\) and use \#pragma to kill warnings [\#190](https://github.com/nlohmann/json/pull/190) ([twelsby](https://github.com/twelsby)) +- Fixed some typos in CONTRIBUTING.md [\#182](https://github.com/nlohmann/json/pull/182) ([nibroc](https://github.com/nibroc)) + +## [v1.0.0](https://github.com/nlohmann/json/releases/tag/v1.0.0) (2015-12-27) +[Full Changelog](https://github.com/nlohmann/json/compare/v1.0.0-rc1...v1.0.0) + +- add key name to exception [\#160](https://github.com/nlohmann/json/issues/160) +- prevent json.hpp from emitting compiler warnings [\#154](https://github.com/nlohmann/json/issues/154) +- json::parse\(string\) does not check utf8 bom [\#152](https://github.com/nlohmann/json/issues/152) +- unsigned 64bit values output as signed [\#151](https://github.com/nlohmann/json/issues/151) +- Wish feature: json5 [\#150](https://github.com/nlohmann/json/issues/150) +- overload of at\(\) with default value [\#133](https://github.com/nlohmann/json/issues/133) +- Memory leak in face of exceptions [\#118](https://github.com/nlohmann/json/issues/118) +- Find and Count for arrays [\#117](https://github.com/nlohmann/json/issues/117) +- dynamically constructing an arbitrarily nested object [\#114](https://github.com/nlohmann/json/issues/114) +- object field accessors [\#103](https://github.com/nlohmann/json/issues/103) +- v8pp and json [\#95](https://github.com/nlohmann/json/issues/95) +- Wishlist [\#65](https://github.com/nlohmann/json/issues/65) +- Windows/Visual Studio \(through 2013\) is unsupported [\#62](https://github.com/nlohmann/json/issues/62) + +- Bug in basic\_json::operator\[\] const overload [\#135](https://github.com/nlohmann/json/issues/135) +- wrong enable\_if for const pointer \(instead of pointer-to-const\) [\#134](https://github.com/nlohmann/json/issues/134) +- Visual Studio 14 Debug assertion failed [\#125](https://github.com/nlohmann/json/issues/125) +- Compile error with g++ 4.9.3 cygwin 64-bit [\#112](https://github.com/nlohmann/json/issues/112) +- error: unterminated raw string [\#109](https://github.com/nlohmann/json/issues/109) +- \[clang-3.6.2\] string/sstream with number to json issue [\#107](https://github.com/nlohmann/json/issues/107) + +- Getting member discarding qualifyer [\#159](https://github.com/nlohmann/json/issues/159) +- basic\_json::iterator::value\(\) output includes quotes while basic\_json::iterator::key\(\) doesn't [\#158](https://github.com/nlohmann/json/issues/158) +- Indexing `const basic\_json\<\>` with `const basic\_string\` [\#157](https://github.com/nlohmann/json/issues/157) +- token\_type\_name\(token\_type t\): not all control paths return a value [\#156](https://github.com/nlohmann/json/issues/156) +- Unable to compile on MSVC 2015 with SDL checking enabled: This function or variable may be unsafe. [\#149](https://github.com/nlohmann/json/issues/149) +- dump\(\) convert strings encoded by utf-8 to shift-jis on windows 10. [\#147](https://github.com/nlohmann/json/issues/147) +- Unable to get field names in a json object [\#145](https://github.com/nlohmann/json/issues/145) +- json.hpp:5746:32: error: 'to\_string' is not a member of 'std' [\#136](https://github.com/nlohmann/json/issues/136) +- Returning any data type [\#113](https://github.com/nlohmann/json/issues/113) +- vector\ copy constructor really weird [\#108](https://github.com/nlohmann/json/issues/108) +- maintaining order of keys during iteration [\#106](https://github.com/nlohmann/json/issues/106) + +- Replace sprintf with hex function, this fixes \#149 [\#153](https://github.com/nlohmann/json/pull/153) ([whackashoe](https://github.com/whackashoe)) +- Fix character skipping after a surrogate pair [\#146](https://github.com/nlohmann/json/pull/146) ([robertmrk](https://github.com/robertmrk)) +- Detect correctly pointer-to-const [\#137](https://github.com/nlohmann/json/pull/137) ([dariomt](https://github.com/dariomt)) +- disabled "CopyAssignable" test for MSVC in Debug mode, see \#125 [\#131](https://github.com/nlohmann/json/pull/131) ([dariomt](https://github.com/dariomt)) +- removed stream operator for iterator, resolution for \#125 [\#130](https://github.com/nlohmann/json/pull/130) ([dariomt](https://github.com/dariomt)) +- fixed typos in comments for examples [\#129](https://github.com/nlohmann/json/pull/129) ([dariomt](https://github.com/dariomt)) +- Remove superfluous inefficiency [\#126](https://github.com/nlohmann/json/pull/126) ([d-frey](https://github.com/d-frey)) +- remove invalid parameter '-stdlib=libc++' in CMakeLists.txt [\#124](https://github.com/nlohmann/json/pull/124) ([emvivre](https://github.com/emvivre)) +- exception-safe object creation, fixes \#118 [\#122](https://github.com/nlohmann/json/pull/122) ([d-frey](https://github.com/d-frey)) +- Fix small oversight. [\#121](https://github.com/nlohmann/json/pull/121) ([ColinH](https://github.com/ColinH)) +- Overload parse\(\) to accept an rvalue reference [\#120](https://github.com/nlohmann/json/pull/120) ([silverweed](https://github.com/silverweed)) +- Use the right variable name in doc string [\#115](https://github.com/nlohmann/json/pull/115) ([whoshuu](https://github.com/whoshuu)) + +## [v1.0.0-rc1](https://github.com/nlohmann/json/releases/tag/v1.0.0-rc1) (2015-07-26) +- Adjust wording to JSON RFC [\#97](https://github.com/nlohmann/json/issues/97) +- access by \(const\) reference [\#91](https://github.com/nlohmann/json/issues/91) +- is\_integer and is\_float tests [\#90](https://github.com/nlohmann/json/issues/90) +- MinGW have no std::to\_string [\#80](https://github.com/nlohmann/json/issues/80) +- erase elements using iterators [\#57](https://github.com/nlohmann/json/issues/57) +- Removing item from array [\#56](https://github.com/nlohmann/json/issues/56) +- Serialize/Deserialize like PHP? [\#55](https://github.com/nlohmann/json/issues/55) +- Document erase, count, and iterators key and value [\#52](https://github.com/nlohmann/json/issues/52) +- Supported compilers [\#50](https://github.com/nlohmann/json/issues/50) +- Use non-member begin/end [\#48](https://github.com/nlohmann/json/issues/48) +- Erase key [\#47](https://github.com/nlohmann/json/issues/47) +- Key iterator [\#46](https://github.com/nlohmann/json/issues/46) +- Add count member function [\#45](https://github.com/nlohmann/json/issues/45) +- Printing attribute names [\#39](https://github.com/nlohmann/json/issues/39) +- Avoid using spaces when encoding without pretty print [\#31](https://github.com/nlohmann/json/issues/31) +- Cannot encode long numbers [\#30](https://github.com/nlohmann/json/issues/30) +- Creating an empty array [\#27](https://github.com/nlohmann/json/issues/27) +- Custom allocator support [\#25](https://github.com/nlohmann/json/issues/25) +- create a header-only version [\#16](https://github.com/nlohmann/json/issues/16) +- Add to\_string overload for indentation [\#13](https://github.com/nlohmann/json/issues/13) +- move code into namespace [\#9](https://github.com/nlohmann/json/issues/9) +- free functions for explicit objects and arrays in initializer lists [\#8](https://github.com/nlohmann/json/issues/8) +- Test case coverage [\#2](https://github.com/nlohmann/json/issues/2) +- Parse streams incrementally. [\#40](https://github.com/nlohmann/json/pull/40) ([aburgh](https://github.com/aburgh)) + +- Binary string causes numbers to be dumped as hex [\#101](https://github.com/nlohmann/json/issues/101) +- failed to iterator json object with reverse\_iterator [\#100](https://github.com/nlohmann/json/issues/100) +- static analysis warnings [\#94](https://github.com/nlohmann/json/issues/94) +- reverse\_iterator operator inheritance problem [\#93](https://github.com/nlohmann/json/issues/93) +- Nonstandard integer type [\#89](https://github.com/nlohmann/json/issues/89) +- lexer::get\_number return NAN [\#82](https://github.com/nlohmann/json/issues/82) +- Handle infinity and NaN cases [\#70](https://github.com/nlohmann/json/issues/70) +- errors in g++-4.8.1 [\#68](https://github.com/nlohmann/json/issues/68) +- Double quotation mark is not parsed correctly [\#60](https://github.com/nlohmann/json/issues/60) +- Do not use std::to\_string [\#51](https://github.com/nlohmann/json/issues/51) +- Confused about iterating through json objects [\#49](https://github.com/nlohmann/json/issues/49) +- Problem getting vector \(array\) of strings [\#44](https://github.com/nlohmann/json/issues/44) +- Compilation error due to assuming that private=public [\#43](https://github.com/nlohmann/json/issues/43) +- Use of deprecated implicit copy constructor [\#42](https://github.com/nlohmann/json/issues/42) +- dumping a small number\_float just outputs 0.000000 [\#37](https://github.com/nlohmann/json/issues/37) +- Avoid using spaces when encoding without pretty print [\#31](https://github.com/nlohmann/json/issues/31) +- Cannot encode long numbers [\#30](https://github.com/nlohmann/json/issues/30) +- segmentation fault when iterating over empty arrays/objects [\#28](https://github.com/nlohmann/json/issues/28) +- Improper parsing of JSON string "\\" [\#17](https://github.com/nlohmann/json/issues/17) +- Don't return "const values" [\#15](https://github.com/nlohmann/json/issues/15) +- string parser does not recognize uncompliant strings [\#12](https://github.com/nlohmann/json/issues/12) +- free functions for explicit objects and arrays in initializer lists [\#8](https://github.com/nlohmann/json/issues/8) +- Runtime error in Travis job [\#1](https://github.com/nlohmann/json/issues/1) +- Fix compilation of json\_unit with GCC 5 [\#59](https://github.com/nlohmann/json/pull/59) ([dkopecek](https://github.com/dkopecek)) + +- Finish documenting the public interface in Doxygen [\#102](https://github.com/nlohmann/json/issues/102) +- 'noexcept' : unknown override specifier [\#99](https://github.com/nlohmann/json/issues/99) +- Keys when iterating over objects [\#67](https://github.com/nlohmann/json/issues/67) +- Complete brief documentation [\#61](https://github.com/nlohmann/json/issues/61) +- Get coverage back to 100% [\#58](https://github.com/nlohmann/json/issues/58) +- possible double-free in find function [\#11](https://github.com/nlohmann/json/issues/11) +- UTF-8 encoding/deconding/testing [\#10](https://github.com/nlohmann/json/issues/10) +- Add unit tests [\#4](https://github.com/nlohmann/json/issues/4) +- Drop C++98 support [\#3](https://github.com/nlohmann/json/issues/3) + +- Keyword 'inline' is useless when member functions are defined in headers [\#87](https://github.com/nlohmann/json/pull/87) ([ahamez](https://github.com/ahamez)) +- Remove useless typename [\#86](https://github.com/nlohmann/json/pull/86) ([ahamez](https://github.com/ahamez)) +- Avoid warning with Xcode's clang [\#85](https://github.com/nlohmann/json/pull/85) ([ahamez](https://github.com/ahamez)) +- Fix typos [\#73](https://github.com/nlohmann/json/pull/73) ([aqnouch](https://github.com/aqnouch)) +- Replace `default\_callback` function with `nullptr` and check for null… [\#72](https://github.com/nlohmann/json/pull/72) ([aburgh](https://github.com/aburgh)) +- support enum [\#71](https://github.com/nlohmann/json/pull/71) ([likebeta](https://github.com/likebeta)) +- Fix performance regression introduced with the parsing callback feature. [\#69](https://github.com/nlohmann/json/pull/69) ([aburgh](https://github.com/aburgh)) +- Improve the implementations of the comparission-operators [\#63](https://github.com/nlohmann/json/pull/63) ([Florianjw](https://github.com/Florianjw)) +- Feature/small float serialization [\#38](https://github.com/nlohmann/json/pull/38) ([jrandall](https://github.com/jrandall)) +- template version with re2c scanner [\#36](https://github.com/nlohmann/json/pull/36) ([nlohmann](https://github.com/nlohmann)) +- more descriptive documentation in example [\#33](https://github.com/nlohmann/json/pull/33) ([luxe](https://github.com/luxe)) +- Fix string conversion under Clang [\#26](https://github.com/nlohmann/json/pull/26) ([wancw](https://github.com/wancw)) +- Fixed dumping of strings [\#24](https://github.com/nlohmann/json/pull/24) ([Teemperor](https://github.com/Teemperor)) +- Added a remark to the readme that coverage is GCC only for now [\#23](https://github.com/nlohmann/json/pull/23) ([Teemperor](https://github.com/Teemperor)) +- Unicode escaping [\#22](https://github.com/nlohmann/json/pull/22) ([Teemperor](https://github.com/Teemperor)) +- Implemented the JSON spec for string parsing for everything but the \uXXXX escaping [\#21](https://github.com/nlohmann/json/pull/21) ([Teemperor](https://github.com/Teemperor)) +- add the std iterator typedefs to iterator and const\_iterator [\#19](https://github.com/nlohmann/json/pull/19) ([kirkshoop](https://github.com/kirkshoop)) +- Fixed escaped quotes [\#18](https://github.com/nlohmann/json/pull/18) ([Teemperor](https://github.com/Teemperor)) +- Fix double delete on std::bad\_alloc exception [\#14](https://github.com/nlohmann/json/pull/14) ([elliotgoodrich](https://github.com/elliotgoodrich)) +- Added CMake and lcov [\#6](https://github.com/nlohmann/json/pull/6) ([Teemperor](https://github.com/Teemperor)) +- Version 2.0 [\#5](https://github.com/nlohmann/json/pull/5) ([nlohmann](https://github.com/nlohmann)) + + + +\* *This Change Log was automatically generated by [github_changelog_generator](https://github.com/skywinder/Github-Changelog-Generator)* \ No newline at end of file diff --git a/Makefile b/Makefile index 71a1175fd..fdef78408 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,4 @@ -.PHONY: pretty clean +.PHONY: pretty clean ChangeLog.md # used programs RE2C = re2c @@ -59,3 +59,13 @@ pretty: json_benchmarks: benchmarks/benchmarks.cpp benchmarks/benchpress.hpp benchmarks/cxxopts.hpp src/json.hpp $(CXX) -std=c++11 $(CXXFLAGS) -O3 -flto -I src -I benchmarks $< $(LDFLAGS) -o $@ ./json_benchmarks + + +########################################################################## +# changelog +########################################################################## + +ChangeLog.md: + github_changelog_generator -o ChangeLog.md --simple-list --release-url https://github.com/nlohmann/json/releases/tag/%s + gsed -i 's|https://github.com/nlohmann/json/releases/tag/HEAD|https://github.com/nlohmann/json/tree/HEAD|' ChangeLog.md + gsed -i '2i All notable changes to this project will be documented in this file. This project adheres to [Semantic Versioning](http://semver.org/).' ChangeLog.md diff --git a/README.md b/README.md index 44c17bf00..4979a20a9 100644 --- a/README.md +++ b/README.md @@ -3,11 +3,12 @@ [![Build Status](https://travis-ci.org/nlohmann/json.svg?branch=master)](https://travis-ci.org/nlohmann/json) [![Build Status](https://ci.appveyor.com/api/projects/status/1acb366xfyg3qybk?svg=true)](https://ci.appveyor.com/project/nlohmann/json) [![Coverage Status](https://img.shields.io/coveralls/nlohmann/json.svg)](https://coveralls.io/r/nlohmann/json) -[![Try online](https://img.shields.io/badge/try-online-blue.svg)](http://melpon.org/wandbox/permlink/GnGKwji06WeVonlI) +[![Try online](https://img.shields.io/badge/try-online-blue.svg)](http://melpon.org/wandbox/permlink/WSW3gHHE4UcZ9K3G) [![Documentation Status](https://img.shields.io/badge/docs-doxygen-blue.svg)](http://nlohmann.github.io/json) [![GitHub license](https://img.shields.io/badge/license-MIT-blue.svg)](https://raw.githubusercontent.com/nlohmann/json/master/LICENSE.MIT) [![Github Releases](https://img.shields.io/github/release/nlohmann/json.svg)](https://github.com/nlohmann/json/releases) [![Github Issues](https://img.shields.io/github/issues/nlohmann/json.svg)](http://github.com/nlohmann/json/issues) +[![Gitter](https://badges.gitter.im/nlohmann/json.svg)](https://gitter.im/nlohmann/json?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge) ## Design goals @@ -21,11 +22,11 @@ There are myriads of [JSON](http://json.org) libraries out there, and each may e Other aspects were not so important to us: -- **Memory efficiency**. Each JSON object has an overhead of one pointer (the maximal size of a union) and one enumeration element (1 byte). The default generalization uses the following C++ data types: `std::string` for strings, `int64_t` or `double` for numbers, `std::map` for objects, `std::vector` for arrays, and `bool` for Booleans. However, you can template the generalized class `basic_json` to your needs. +- **Memory efficiency**. Each JSON object has an overhead of one pointer (the maximal size of a union) and one enumeration element (1 byte). The default generalization uses the following C++ data types: `std::string` for strings, `int64_t`, `uint64_t` or `double` for numbers, `std::map` for objects, `std::vector` for arrays, and `bool` for Booleans. However, you can template the generalized class `basic_json` to your needs. - **Speed**. We currently implement the parser as naive [recursive descent parser](http://en.wikipedia.org/wiki/Recursive_descent_parser) with hand coded string handling. It is fast enough, but a [LALR-parser](http://en.wikipedia.org/wiki/LALR_parser) with a decent regular expression processor should be even faster (but would consist of more files which makes the integration harder). -See the [https://github.com/nlohmann/json/blob/master/CONTRIBUTING.md#please-dont](contribution guidelines) for more information. +See the [contribution guidelines](https://github.com/nlohmann/json/blob/master/CONTRIBUTING.md#please-dont) for more information. ## Integration @@ -390,13 +391,15 @@ I deeply appreciate the help of the following people. - [406345](https://github.com/406345) fixed two small warnings. - [Glen Fernandes](https://github.com/glenfe) noted a potential portability problem in the `has_mapped_type` function. - [Corbin Hughes](https://github.com/nibroc) fixed some typos in the contribution guidelines. -- [twelsby](https://github.com/twelsby) fixed the array subscript operator, an issue that failed the MSVC build, and floating-point parsing/dumping. +- [twelsby](https://github.com/twelsby) fixed the array subscript operator, an issue that failed the MSVC build, and floating-point parsing/dumping. He further added support for unsigned integer numbers. +- [Volker Diels-Grabsch](https://github.com/vog) fixed a link in the README file. Thanks a lot for helping out! ## Notes -- The code contains numerous debug assertions which can be switched off by defining the preprocessor macro `NDEBUG`, see the [documentation of `assert`](http://en.cppreference.com/w/cpp/error/assert). +- The code contains numerous debug **assertions** which can be switched off by defining the preprocessor macro `NDEBUG`, see the [documentation of `assert`](http://en.cppreference.com/w/cpp/error/assert). +- As the exact type of a number is not defined in the [JSON specification](http://rfc7159.net/rfc7159), this library tries to choose the best fitting C++ number type automatically. As a result, the type `double` may be used to store numbers which may yield [**floating-point exceptions**](https://github.com/nlohmann/json/issues/181) in certain rare situations if floating-point exceptions have been unmasked in the calling code. These exceptions are not caused by the library and need to be fixed in the calling code, such as by re-masking the exceptions prior to calling library functions. ## Execute unit tests @@ -407,7 +410,7 @@ $ make $ ./json_unit "*" =============================================================================== -All tests passed (3343329 assertions in 29 test cases) +All tests passed (3344278 assertions in 29 test cases) ``` For more information, have a look at the file [.travis.yml](https://github.com/nlohmann/json/blob/master/.travis.yml). diff --git a/doc/ChangeLog.md b/doc/ChangeLog.md deleted file mode 100644 index a43b0e1ab..000000000 --- a/doc/ChangeLog.md +++ /dev/null @@ -1,45 +0,0 @@ -# JSON for Modern C++ - -## Version 1.0.0 - -- Release date: 2015-12-28 -- MD5: 331c30e4407ecdcf591e9e3ed85100d0 - -### Summary - -This is the first official release. Compared to the [prerelease version 1.0.0-rc1](https://github.com/nlohmann/json/releases/tag/v1.0.0-rc1), only a few minor improvements have been made: - -### Changes - -- *Changed*: A **UTF-8 byte order mark** is silently ignored. -- *Changed*: `sprintf` is no longer used. -- *Changed*: `iterator_wrapper` also works for const objects; note: the name may change! -- *Changed*: **Error messages** during deserialization have been improved. -- *Added*: The `parse` function now also works with type `std::istream&&`. -- *Added*: Function `value(key, default_value)` returns either a copy of an object's element at the specified key or a given default value if no element with the key exists. -- *Added*: Public functions are tagged with the version they were introduced. This shall allow for better **versioning** in the future. -- *Added*: All public functions and types are **documented** (see http://nlohmann.github.io/json/) including executable examples. -- *Added*: Allocation of all types (in particular arrays, strings, and objects) is now exception-safe. -- *Added*: They descriptions of thrown exceptions have been overworked and are part of the tests suite and documentation. - -## Version 1.0.0-rc1 - -- Release date: 2015-07-26 -- MD5: fac5948417ed49bfd0852a0e9dd36935 - -### Summary - -The 1.0.0 release should be the first "official" release after the initial announcement of the class in January 2015 via reddit ("0.1.0") and a heavily overworked second version ("0.2.0") in February. - -### Changes - -- *Changed:* In the generic class `basic_json`, all JSON value types (array, object, string, bool, integer number, and floating-point) are now **templated**. That is, you can choose whether you like a `std::list` for your arrays or an `std::unordered_map` for your objects. The specialization `json` sets some reasonable defaults. -- *Changed:* The library now consists of a **single header**, called `json.hpp`. Consequently, build systems such as Automake or CMake are not any longer required. -- *Changed:* The **deserialization** is now supported by a lexer generated with [re2c](http://re2c.org) from file [`src/json.hpp.re2c`](https://github.com/nlohmann/json/blob/master/src/json.hpp.re2c). As a result, we strictly follow the JSON specification. Note neither the tool re2c nor its input are required to use the class. -- *Added:* The library now satisfies the [**ReversibleContainer**](http://en.cppreference.com/w/cpp/concept/ReversibleContainer) requirement. It hence provides four different iterators (`iterator`, `const_iterator`, `reverse_iterator`, and `const_reverse_iterator`), comparison functions, `swap()`, `size()`, `max_size()`, and `empty()` member functions. -- *Added*: The class uses **user-defined allocators** which default to `std::allocator`, but can be templated via parameter `Allocator`. -- *Added:* To simplify pretty-printing, the `std::setw` **stream manipulator** has been overloaded to set the desired indentation. Pretty-printing a JSON object `j` is as simple as `std::cout << std::setw(4) << j << '\n'`. -- *Changed*: The type `json::value_t::number` is now called `json::value_t::number_integer` to be more symmetric compared to `json::value_t::number_float`. -- *Added*: The documentation is generated with Doxygen and hosted at [nlohmann.github.io/json](http://nlohmann.github.io/json/). Every public member function is thoroughly described including an example which also can be [tried online](http://melpon.org/wandbox/permlink/GnGKwji06WeVonlI). -- *Added*: The class is heavily unit-tested (3341774 assertions) and has a [line coverage of 100%](https://coveralls.io/github/nlohmann/json). With every commit, the code is compiled with g++ 4.9, g++ 5.0, Clang 3.6 (thanks to [Travis CI](https://travis-ci.org/nlohmann/json)), and Microsoft Visual Studio 14 2015 (thanks to [AppVeyor](https://ci.appveyor.com/project/nlohmann/json)). - diff --git a/doc/Doxyfile b/doc/Doxyfile index 9cca8a834..f1b3b3670 100644 --- a/doc/Doxyfile +++ b/doc/Doxyfile @@ -5,7 +5,7 @@ #--------------------------------------------------------------------------- DOXYFILE_ENCODING = UTF-8 PROJECT_NAME = "JSON for Modern C++" -PROJECT_NUMBER = 1.0.0 +PROJECT_NUMBER = 2.0.0 PROJECT_BRIEF = PROJECT_LOGO = OUTPUT_DIRECTORY = . diff --git a/doc/examples/README.link b/doc/examples/README.link index 017a2d006..dab9343fb 100644 --- a/doc/examples/README.link +++ b/doc/examples/README.link @@ -1 +1 @@ -online \ No newline at end of file +online \ No newline at end of file diff --git a/doc/examples/basic_json__value_t.output b/doc/examples/basic_json__value_t.output index 0a6269fbf..ea542caeb 100644 --- a/doc/examples/basic_json__value_t.output +++ b/doc/examples/basic_json__value_t.output @@ -1,7 +1,7 @@ null false 0 -0 +0.0 {} [] "" diff --git a/doc/examples/clear.output b/doc/examples/clear.output index 0a6269fbf..ea542caeb 100644 --- a/doc/examples/clear.output +++ b/doc/examples/clear.output @@ -1,7 +1,7 @@ null false 0 -0 +0.0 {} [] "" diff --git a/doc/examples/is_array.cpp b/doc/examples/is_array.cpp index a5a544f03..c436f38f4 100644 --- a/doc/examples/is_array.cpp +++ b/doc/examples/is_array.cpp @@ -8,6 +8,7 @@ int main() json j_null; json j_boolean = true; json j_number_integer = 17; + json j_number_unsigned_integer = 12345678987654321u; json j_number_float = 23.42; json j_object = {{"one", 1}, {"two", 2}}; json j_array = {1, 2, 4, 8, 16}; @@ -18,6 +19,7 @@ int main() std::cout << j_null.is_array() << '\n'; std::cout << j_boolean.is_array() << '\n'; std::cout << j_number_integer.is_array() << '\n'; + std::cout << j_number_unsigned_integer.is_array() << '\n'; std::cout << j_number_float.is_array() << '\n'; std::cout << j_object.is_array() << '\n'; std::cout << j_array.is_array() << '\n'; diff --git a/doc/examples/is_array.link b/doc/examples/is_array.link index e1aa234e7..4d5fdb07f 100644 --- a/doc/examples/is_array.link +++ b/doc/examples/is_array.link @@ -1 +1 @@ -online \ No newline at end of file +online \ No newline at end of file diff --git a/doc/examples/is_array.output b/doc/examples/is_array.output index 65ffc69e1..5b8cc40c9 100644 --- a/doc/examples/is_array.output +++ b/doc/examples/is_array.output @@ -3,5 +3,6 @@ false false false false +false true false diff --git a/doc/examples/is_boolean.cpp b/doc/examples/is_boolean.cpp index 0143b0bf4..24511a2fd 100644 --- a/doc/examples/is_boolean.cpp +++ b/doc/examples/is_boolean.cpp @@ -8,6 +8,7 @@ int main() json j_null; json j_boolean = true; json j_number_integer = 17; + json j_number_unsigned_integer = 12345678987654321u; json j_number_float = 23.42; json j_object = {{"one", 1}, {"two", 2}}; json j_array = {1, 2, 4, 8, 16}; @@ -18,6 +19,7 @@ int main() std::cout << j_null.is_boolean() << '\n'; std::cout << j_boolean.is_boolean() << '\n'; std::cout << j_number_integer.is_boolean() << '\n'; + std::cout << j_number_unsigned_integer.is_boolean() << '\n'; std::cout << j_number_float.is_boolean() << '\n'; std::cout << j_object.is_boolean() << '\n'; std::cout << j_array.is_boolean() << '\n'; diff --git a/doc/examples/is_boolean.link b/doc/examples/is_boolean.link index 3972af75b..5fda3d0a8 100644 --- a/doc/examples/is_boolean.link +++ b/doc/examples/is_boolean.link @@ -1 +1 @@ -online \ No newline at end of file +online \ No newline at end of file diff --git a/doc/examples/is_boolean.output b/doc/examples/is_boolean.output index 54f16b97f..721b3a5e2 100644 --- a/doc/examples/is_boolean.output +++ b/doc/examples/is_boolean.output @@ -5,3 +5,4 @@ false false false false +false diff --git a/doc/examples/is_discarded.cpp b/doc/examples/is_discarded.cpp index c71bf901c..7f6c8c07b 100644 --- a/doc/examples/is_discarded.cpp +++ b/doc/examples/is_discarded.cpp @@ -8,6 +8,7 @@ int main() json j_null; json j_boolean = true; json j_number_integer = 17; + json j_number_unsigned_integer = 12345678987654321u; json j_number_float = 23.42; json j_object = {{"one", 1}, {"two", 2}}; json j_array = {1, 2, 4, 8, 16}; @@ -18,6 +19,7 @@ int main() std::cout << j_null.is_discarded() << '\n'; std::cout << j_boolean.is_discarded() << '\n'; std::cout << j_number_integer.is_discarded() << '\n'; + std::cout << j_number_unsigned_integer.is_discarded() << '\n'; std::cout << j_number_float.is_discarded() << '\n'; std::cout << j_object.is_discarded() << '\n'; std::cout << j_array.is_discarded() << '\n'; diff --git a/doc/examples/is_discarded.link b/doc/examples/is_discarded.link index 389f90bbb..5f58643d7 100644 --- a/doc/examples/is_discarded.link +++ b/doc/examples/is_discarded.link @@ -1 +1 @@ -online \ No newline at end of file +online \ No newline at end of file diff --git a/doc/examples/is_discarded.output b/doc/examples/is_discarded.output index 28f6ac44f..485b16967 100644 --- a/doc/examples/is_discarded.output +++ b/doc/examples/is_discarded.output @@ -5,3 +5,4 @@ false false false false +false diff --git a/doc/examples/is_null.cpp b/doc/examples/is_null.cpp index d107f7302..d6a6de4c7 100644 --- a/doc/examples/is_null.cpp +++ b/doc/examples/is_null.cpp @@ -8,6 +8,7 @@ int main() json j_null; json j_boolean = true; json j_number_integer = 17; + json j_number_unsigned_integer = 12345678987654321u; json j_number_float = 23.42; json j_object = {{"one", 1}, {"two", 2}}; json j_array = {1, 2, 4, 8, 16}; @@ -18,6 +19,7 @@ int main() std::cout << j_null.is_null() << '\n'; std::cout << j_boolean.is_null() << '\n'; std::cout << j_number_integer.is_null() << '\n'; + std::cout << j_number_unsigned_integer.is_null() << '\n'; std::cout << j_number_float.is_null() << '\n'; std::cout << j_object.is_null() << '\n'; std::cout << j_array.is_null() << '\n'; diff --git a/doc/examples/is_null.link b/doc/examples/is_null.link index a2fb14074..39c5d2611 100644 --- a/doc/examples/is_null.link +++ b/doc/examples/is_null.link @@ -1 +1 @@ -online \ No newline at end of file +online \ No newline at end of file diff --git a/doc/examples/is_null.output b/doc/examples/is_null.output index 828ea8919..4cc64628d 100644 --- a/doc/examples/is_null.output +++ b/doc/examples/is_null.output @@ -5,3 +5,4 @@ false false false false +false diff --git a/doc/examples/is_number.cpp b/doc/examples/is_number.cpp index 992ec1838..29c21ffba 100644 --- a/doc/examples/is_number.cpp +++ b/doc/examples/is_number.cpp @@ -8,6 +8,7 @@ int main() json j_null; json j_boolean = true; json j_number_integer = 17; + json j_number_unsigned_integer = 12345678987654321u; json j_number_float = 23.42; json j_object = {{"one", 1}, {"two", 2}}; json j_array = {1, 2, 4, 8, 16}; @@ -18,6 +19,7 @@ int main() std::cout << j_null.is_number() << '\n'; std::cout << j_boolean.is_number() << '\n'; std::cout << j_number_integer.is_number() << '\n'; + std::cout << j_number_unsigned_integer.is_number() << '\n'; std::cout << j_number_float.is_number() << '\n'; std::cout << j_object.is_number() << '\n'; std::cout << j_array.is_number() << '\n'; diff --git a/doc/examples/is_number.link b/doc/examples/is_number.link index 1fc0fc866..090b5ca92 100644 --- a/doc/examples/is_number.link +++ b/doc/examples/is_number.link @@ -1 +1 @@ -online \ No newline at end of file +online \ No newline at end of file diff --git a/doc/examples/is_number.output b/doc/examples/is_number.output index b031a1411..06dbc2823 100644 --- a/doc/examples/is_number.output +++ b/doc/examples/is_number.output @@ -2,6 +2,7 @@ false false true true +true false false false diff --git a/doc/examples/is_number_float.cpp b/doc/examples/is_number_float.cpp index 3ede378fa..289428c3e 100644 --- a/doc/examples/is_number_float.cpp +++ b/doc/examples/is_number_float.cpp @@ -8,6 +8,7 @@ int main() json j_null; json j_boolean = true; json j_number_integer = 17; + json j_number_unsigned_integer = 12345678987654321u; json j_number_float = 23.42; json j_object = {{"one", 1}, {"two", 2}}; json j_array = {1, 2, 4, 8, 16}; @@ -18,6 +19,7 @@ int main() std::cout << j_null.is_number_float() << '\n'; std::cout << j_boolean.is_number_float() << '\n'; std::cout << j_number_integer.is_number_float() << '\n'; + std::cout << j_number_unsigned_integer.is_number_float() << '\n'; std::cout << j_number_float.is_number_float() << '\n'; std::cout << j_object.is_number_float() << '\n'; std::cout << j_array.is_number_float() << '\n'; diff --git a/doc/examples/is_number_float.link b/doc/examples/is_number_float.link index ef3aaf331..7ed767603 100644 --- a/doc/examples/is_number_float.link +++ b/doc/examples/is_number_float.link @@ -1 +1 @@ -online \ No newline at end of file +online \ No newline at end of file diff --git a/doc/examples/is_number_float.output b/doc/examples/is_number_float.output index c24702822..09afae4c9 100644 --- a/doc/examples/is_number_float.output +++ b/doc/examples/is_number_float.output @@ -1,6 +1,7 @@ false false false +false true false false diff --git a/doc/examples/is_number_integer.cpp b/doc/examples/is_number_integer.cpp index 281bf432c..d7a38b6ad 100644 --- a/doc/examples/is_number_integer.cpp +++ b/doc/examples/is_number_integer.cpp @@ -8,6 +8,7 @@ int main() json j_null; json j_boolean = true; json j_number_integer = 17; + json j_number_unsigned_integer = 12345678987654321u; json j_number_float = 23.42; json j_object = {{"one", 1}, {"two", 2}}; json j_array = {1, 2, 4, 8, 16}; @@ -18,6 +19,7 @@ int main() std::cout << j_null.is_number_integer() << '\n'; std::cout << j_boolean.is_number_integer() << '\n'; std::cout << j_number_integer.is_number_integer() << '\n'; + std::cout << j_number_unsigned_integer.is_number_integer() << '\n'; std::cout << j_number_float.is_number_integer() << '\n'; std::cout << j_object.is_number_integer() << '\n'; std::cout << j_array.is_number_integer() << '\n'; diff --git a/doc/examples/is_number_integer.link b/doc/examples/is_number_integer.link index bbd368f04..fae1ef433 100644 --- a/doc/examples/is_number_integer.link +++ b/doc/examples/is_number_integer.link @@ -1 +1 @@ -online \ No newline at end of file +online \ No newline at end of file diff --git a/doc/examples/is_number_integer.output b/doc/examples/is_number_integer.output index 26051afd7..be0f73936 100644 --- a/doc/examples/is_number_integer.output +++ b/doc/examples/is_number_integer.output @@ -1,6 +1,7 @@ false false true +true false false false diff --git a/doc/examples/is_number_unsigned.cpp b/doc/examples/is_number_unsigned.cpp new file mode 100644 index 000000000..185d93de7 --- /dev/null +++ b/doc/examples/is_number_unsigned.cpp @@ -0,0 +1,27 @@ +#include + +using namespace nlohmann; + +int main() +{ + // create JSON values + json j_null; + json j_boolean = true; + json j_number_integer = 17; + json j_number_unsigned_integer = 12345678987654321u; + json j_number_float = 23.42; + json j_object = {{"one", 1}, {"two", 2}}; + json j_array = {1, 2, 4, 8, 16}; + json j_string = "Hello, world"; + + // call is_number_unsigned() + std::cout << std::boolalpha; + std::cout << j_null.is_number_unsigned() << '\n'; + std::cout << j_boolean.is_number_unsigned() << '\n'; + std::cout << j_number_integer.is_number_unsigned() << '\n'; + std::cout << j_number_unsigned_integer.is_number_unsigned() << '\n'; + std::cout << j_number_float.is_number_unsigned() << '\n'; + std::cout << j_object.is_number_unsigned() << '\n'; + std::cout << j_array.is_number_unsigned() << '\n'; + std::cout << j_string.is_number_unsigned() << '\n'; +} diff --git a/doc/examples/is_number_unsigned.link b/doc/examples/is_number_unsigned.link new file mode 100644 index 000000000..1877ced96 --- /dev/null +++ b/doc/examples/is_number_unsigned.link @@ -0,0 +1 @@ +online \ No newline at end of file diff --git a/doc/examples/is_number_unsigned.output b/doc/examples/is_number_unsigned.output new file mode 100644 index 000000000..fdf264e0c --- /dev/null +++ b/doc/examples/is_number_unsigned.output @@ -0,0 +1,8 @@ +false +false +false +true +false +false +false +false diff --git a/doc/examples/is_object.cpp b/doc/examples/is_object.cpp index 268c02244..b85253a3b 100644 --- a/doc/examples/is_object.cpp +++ b/doc/examples/is_object.cpp @@ -9,6 +9,7 @@ int main() json j_boolean = true; json j_number_integer = 17; json j_number_float = 23.42; + json j_number_unsigned_integer = 12345678987654321u; json j_object = {{"one", 1}, {"two", 2}}; json j_array = {1, 2, 4, 8, 16}; json j_string = "Hello, world"; @@ -18,6 +19,7 @@ int main() std::cout << j_null.is_object() << '\n'; std::cout << j_boolean.is_object() << '\n'; std::cout << j_number_integer.is_object() << '\n'; + std::cout << j_number_unsigned_integer.is_object() << '\n'; std::cout << j_number_float.is_object() << '\n'; std::cout << j_object.is_object() << '\n'; std::cout << j_array.is_object() << '\n'; diff --git a/doc/examples/is_object.link b/doc/examples/is_object.link index 5ab6384fc..549bc0c0f 100644 --- a/doc/examples/is_object.link +++ b/doc/examples/is_object.link @@ -1 +1 @@ -online \ No newline at end of file +online \ No newline at end of file diff --git a/doc/examples/is_object.output b/doc/examples/is_object.output index 8416200cd..e041e3922 100644 --- a/doc/examples/is_object.output +++ b/doc/examples/is_object.output @@ -2,6 +2,7 @@ false false false false +false true false false diff --git a/doc/examples/is_primitive.cpp b/doc/examples/is_primitive.cpp index ed5d8b0bb..3f45b95a5 100644 --- a/doc/examples/is_primitive.cpp +++ b/doc/examples/is_primitive.cpp @@ -9,6 +9,7 @@ int main() json j_boolean = true; json j_number_integer = 17; json j_number_float = 23.42; + json j_number_unsigned_integer = 12345678987654321u; json j_object = {{"one", 1}, {"two", 2}}; json j_array = {1, 2, 4, 8, 16}; json j_string = "Hello, world"; @@ -18,6 +19,7 @@ int main() std::cout << j_null.is_primitive() << '\n'; std::cout << j_boolean.is_primitive() << '\n'; std::cout << j_number_integer.is_primitive() << '\n'; + std::cout << j_number_unsigned_integer.is_primitive() << '\n'; std::cout << j_number_float.is_primitive() << '\n'; std::cout << j_object.is_primitive() << '\n'; std::cout << j_array.is_primitive() << '\n'; diff --git a/doc/examples/is_primitive.link b/doc/examples/is_primitive.link index f6918a96d..27167a3a4 100644 --- a/doc/examples/is_primitive.link +++ b/doc/examples/is_primitive.link @@ -1 +1 @@ -online \ No newline at end of file +online \ No newline at end of file diff --git a/doc/examples/is_primitive.output b/doc/examples/is_primitive.output index 2c1bd2528..635db6e34 100644 --- a/doc/examples/is_primitive.output +++ b/doc/examples/is_primitive.output @@ -2,6 +2,7 @@ true true true true +true false false true diff --git a/doc/examples/is_string.cpp b/doc/examples/is_string.cpp index 6bcbcec51..6020fb668 100644 --- a/doc/examples/is_string.cpp +++ b/doc/examples/is_string.cpp @@ -9,6 +9,7 @@ int main() json j_boolean = true; json j_number_integer = 17; json j_number_float = 23.42; + json j_number_unsigned_integer = 12345678987654321u; json j_object = {{"one", 1}, {"two", 2}}; json j_array = {1, 2, 4, 8, 16}; json j_string = "Hello, world"; @@ -18,6 +19,7 @@ int main() std::cout << j_null.is_string() << '\n'; std::cout << j_boolean.is_string() << '\n'; std::cout << j_number_integer.is_string() << '\n'; + std::cout << j_number_unsigned_integer.is_string() << '\n'; std::cout << j_number_float.is_string() << '\n'; std::cout << j_object.is_string() << '\n'; std::cout << j_array.is_string() << '\n'; diff --git a/doc/examples/is_string.link b/doc/examples/is_string.link index 7ba95c364..1e8d54fa3 100644 --- a/doc/examples/is_string.link +++ b/doc/examples/is_string.link @@ -1 +1 @@ -online \ No newline at end of file +online \ No newline at end of file diff --git a/doc/examples/is_string.output b/doc/examples/is_string.output index 2cead02b6..672eb4385 100644 --- a/doc/examples/is_string.output +++ b/doc/examples/is_string.output @@ -4,4 +4,5 @@ false false false false +false true diff --git a/doc/examples/is_structured.cpp b/doc/examples/is_structured.cpp index 13c183e80..40a7cb332 100644 --- a/doc/examples/is_structured.cpp +++ b/doc/examples/is_structured.cpp @@ -9,6 +9,7 @@ int main() json j_boolean = true; json j_number_integer = 17; json j_number_float = 23.42; + json j_number_unsigned_integer = 12345678987654321u; json j_object = {{"one", 1}, {"two", 2}}; json j_array = {1, 2, 4, 8, 16}; json j_string = "Hello, world"; @@ -18,6 +19,7 @@ int main() std::cout << j_null.is_structured() << '\n'; std::cout << j_boolean.is_structured() << '\n'; std::cout << j_number_integer.is_structured() << '\n'; + std::cout << j_number_unsigned_integer.is_structured() << '\n'; std::cout << j_number_float.is_structured() << '\n'; std::cout << j_object.is_structured() << '\n'; std::cout << j_array.is_structured() << '\n'; diff --git a/doc/examples/is_structured.link b/doc/examples/is_structured.link index 9b5abfdf3..6cc3c9bba 100644 --- a/doc/examples/is_structured.link +++ b/doc/examples/is_structured.link @@ -1 +1 @@ -online \ No newline at end of file +online \ No newline at end of file diff --git a/doc/examples/is_structured.output b/doc/examples/is_structured.output index d251407d8..e1186dd8f 100644 --- a/doc/examples/is_structured.output +++ b/doc/examples/is_structured.output @@ -2,6 +2,7 @@ false false false false +false true true false diff --git a/doc/examples/operator__equal.output b/doc/examples/operator__equal.output index e9dfd7551..780673556 100644 --- a/doc/examples/operator__equal.output +++ b/doc/examples/operator__equal.output @@ -1,4 +1,4 @@ [1,2,3] == [1,2,4] false {"A":"a","B":"b"} == {"A":"a","B":"b"} true -17 == 17 true +17 == 17.0 true "foo" == "bar" false diff --git a/doc/examples/operator__notequal.output b/doc/examples/operator__notequal.output index ddd838b4a..9eba626e3 100644 --- a/doc/examples/operator__notequal.output +++ b/doc/examples/operator__notequal.output @@ -1,4 +1,4 @@ [1,2,3] == [1,2,4] true {"A":"a","B":"b"} == {"A":"a","B":"b"} false -17 == 17 false +17 == 17.0 false "foo" == "bar" true diff --git a/src/json.hpp b/src/json.hpp index d188a91fc..bf1700d89 100644 --- a/src/json.hpp +++ b/src/json.hpp @@ -32,7 +32,7 @@ Class @ref nlohmann::basic_json is a good entry point for the documentation. @author [Niels Lohmann](http://nlohmann.me) @see https://github.com/nlohmann/json to download the source code -@version 1.0.0 +@version 2.0.0 */ #ifndef NLOHMANN_JSON_HPP @@ -124,8 +124,8 @@ default; will be used in @ref string_t) in @ref boolean_t) @tparam NumberIntegerType type for JSON integer numbers (@c `int64_t` by default; will be used in @ref number_integer_t) -@tparam NumberUnsignedType type for JSON unsigned integer numbers (@c `uint64_t` by -default; will be used in @ref number_unsigned_t) +@tparam NumberUnsignedType type for JSON unsigned integer numbers (@c +`uint64_t` by default; will be used in @ref number_unsigned_t) @tparam NumberFloatType type for JSON floating-point numbers (@c `double` by default; will be used in @ref number_float_t) @tparam AllocatorType type of the allocator to use (@c `std::allocator` by @@ -218,13 +218,11 @@ class basic_json /// the type of an element reference using reference = value_type&; - /// the type of an element const reference using const_reference = const value_type&; /// a type to represent differences between iterators using difference_type = std::ptrdiff_t; - /// a type to represent container sizes using size_type = std::size_t; @@ -340,6 +338,14 @@ class basic_json @sa @ref array_t -- type for an array value @since version 1.0.0 + + @note The order name/value pairs are added to the object is *not* preserved + by the library. Therefore, iterating an object may return name/value pairs + in a different order than they were originally stored. In fact, keys will + be traversed in alphabetical order as `std::map` with `std::less` is used + by default. Please note this behavior conforms to [RFC + 7159](http://rfc7159.net/rfc7159), because any order implements the + specified "unordered" nature of JSON objects. */ using object_t = ObjectType permitted. This description includes both integer and floating-point numbers. However, - C++ allows more precise storage if it is known whether the number is a + C++ allows more precise storage if it is known whether the number is a signed integer, an unsigned integer or a floating-point number. Therefore, - three different types, @ref number_integer_t, @ref number_unsigned_t and + three different types, @ref number_integer_t, @ref number_unsigned_t and @ref number_float_t are used. To store integer numbers in C++, a type is defined by the template @@ -550,9 +556,9 @@ class basic_json > permitted. This description includes both integer and floating-point numbers. However, - C++ allows more precise storage if it is known whether the number is a + C++ allows more precise storage if it is known whether the number is a signed integer, an unsigned integer or a floating-point number. Therefore, - three different types, @ref number_integer_t, @ref number_unsigned_t and + three different types, @ref number_integer_t, @ref number_unsigned_t and @ref number_float_t are used. To store unsigned integer numbers in C++, a type is defined by the template @@ -582,11 +588,11 @@ class basic_json > An implementation may set limits on the range and precision of numbers. When the default type is used, the maximal integer number that can be - stored is `18446744073709551615` (UINT64_MAX) and the minimal integer number - that can be stored is `0`. Integer numbers - that are out of range will yield over/underflow when used in a constructor. - During deserialization, too large or small integer numbers will be - automatically be stored as @ref number_integer_t or @ref number_float_t. + stored is `18446744073709551615` (UINT64_MAX) and the minimal integer + number that can be stored is `0`. Integer numbers that are out of range + will yield over/underflow when used in a constructor. During + deserialization, too large or small integer numbers will be automatically + be stored as @ref number_integer_t or @ref number_float_t. [RFC 7159](http://rfc7159.net/rfc7159) further states: > Note that when such software is used, numbers that are integers and are @@ -594,7 +600,7 @@ class basic_json > that implementations will agree exactly on their numeric values. As this range is a subrange (when considered in conjunction with the - number_integer_t type) of the exactly supported range [0, UINT64_MAX], this + number_integer_t type) of the exactly supported range [0, UINT64_MAX], this class's integer type is interoperable. #### Storage @@ -608,7 +614,7 @@ class basic_json @since version 2.0.0 */ using number_unsigned_t = NumberUnsignedType; - + /*! @brief a type for a number (floating-point) @@ -622,9 +628,9 @@ class basic_json > permitted. This description includes both integer and floating-point numbers. However, - C++ allows more precise storage if it is known whether the number is a + C++ allows more precise storage if it is known whether the number is a signed integer, an unsigned integer or a floating-point number. Therefore, - three different types, @ref number_integer_t, @ref number_unsigned_t and + three different types, @ref number_integer_t, @ref number_unsigned_t and @ref number_float_t are used. To store floating-point numbers in C++, a type is defined by the template @@ -694,7 +700,7 @@ class basic_json @since version 1.0.0 */ - enum class value_t : uint16_t + enum class value_t : uint8_t { null, ///< null value object, ///< object (unordered set of name/value pairs) @@ -704,12 +710,78 @@ class basic_json number_integer, ///< number value (integer) number_unsigned,///< number value (unsigned integer) number_float, ///< number value (floating-point) - discarded, ///< discarded by the the parser callback function - precision_mask = 0xFF + discarded ///< discarded by the the parser callback function }; private: + + /*! + @brief a type to hold JSON type information + + This bitfield type holds information about JSON types. It is internally + used to hold the basic JSON type enumeration, as well as additional + information in the case of values that have been parsed from a string + including whether of not it was created directly or parsed, and + in the case of floating point numbers the number of significant + figures in the original representaiton and if it was in exponential + form, if a '+' was included in the exponent and the capitilization of + the exponent marker. The sole purpose of this information is to permit + accurate round trips. + + @since version 2.0.0 + */ + union type_data_t { + struct { + uint16_t type : 4; + uint16_t parsed : 1; + uint16_t has_exp : 1; + uint16_t exp_plus : 1; + uint16_t exp_cap : 1; + uint16_t precision : 8; + } bits; + uint16_t data; + + // Return the type + value_t get() const + { + return static_cast(bits.type); + } + + // Test type for equality (ignore other fields) + bool operator==(const value_t& rhs) const + { + return static_cast(bits.type) == rhs; + } + + // Assignment + type_data_t & operator=(value_t rhs) + { + bits.type = static_cast(rhs); + return *this; + } + + // Construct from value_t + type_data_t(value_t t) noexcept + { + *reinterpret_cast(this) = 0; + bits.type = static_cast(t); + } + + // Default constructor + type_data_t() noexcept + { + data = 0; + bits.type = reinterpret_cast(value_t::null); + } + + // Copy constructor + type_data_t(const type_data_t& t) noexcept + { + data = t.data; + } + }; + /// helper for exception-safe object creation template static T* create(Args&& ... args) @@ -796,7 +868,7 @@ class basic_json number_integer = number_integer_t(0); break; } - + case value_t::number_unsigned: { number_unsigned = number_unsigned_t(0); @@ -1325,8 +1397,8 @@ class basic_json template::value and - std::numeric_limits::is_integer and - std::numeric_limits::is_signed, + std::numeric_limits::is_integer and + std::numeric_limits::is_signed, CompatibleNumberIntegerType>::type = 0> basic_json(const CompatibleNumberIntegerType val) noexcept @@ -1339,7 +1411,7 @@ class basic_json Create an unsigned integer number JSON value with a given content. - @tparam T helper type to compare number_unsigned_t and unsigned int + @tparam T helper type to compare number_unsigned_t and unsigned int (not visible in) the interface. @param[in] val an integer to create a JSON number from @@ -1360,14 +1432,14 @@ class basic_json basic_json(const number_unsigned_t val) : m_type(value_t::number_unsigned), m_value(val) {} - + /*! @brief create an unsigned number (implicit) Create an unsigned number JSON value with a given content. This constructor allows any type that can be used to construct values of type @ref - number_unsigned_t. Examples may include the types `unsigned int`, `uint32_t`, - or `unsigned short`. + number_unsigned_t. Examples may include the types `unsigned int`, + `uint32_t`, or `unsigned short`. @tparam CompatibleNumberUnsignedType an integer type which is compatible to @ref number_unsigned_t. @@ -1381,13 +1453,13 @@ class basic_json @since version 2.0.0 */ - template::value and - std::numeric_limits::is_integer and - !std::numeric_limits::is_signed, - CompatibleNumberUnsignedType>::type - = 0> + template < typename CompatibleNumberUnsignedType, typename + std::enable_if < + std::is_constructible::value and + std::numeric_limits::is_integer and + !std::numeric_limits::is_signed, + CompatibleNumberUnsignedType >::type + = 0 > basic_json(const CompatibleNumberUnsignedType val) noexcept : m_type(value_t::number_unsigned), m_value(static_cast(val)) @@ -1747,7 +1819,7 @@ class basic_json } // check if iterator range is complete for primitive values - switch (static_cast(static_cast(m_type) & static_cast(value_t::precision_mask))) + switch (m_type.get()) { case value_t::boolean: case value_t::number_float: @@ -1768,7 +1840,7 @@ class basic_json } } - switch (static_cast(static_cast(m_type) & static_cast(value_t::precision_mask))) + switch (m_type.get()) { case value_t::number_integer: { @@ -1776,7 +1848,7 @@ class basic_json m_value.number_integer = first.m_object->m_value.number_integer; break; } - + case value_t::number_unsigned: { assert(first.m_object != nullptr); @@ -1852,7 +1924,7 @@ class basic_json basic_json(const basic_json& other) : m_type(other.m_type) { - switch (static_cast(static_cast(m_type) & static_cast(value_t::precision_mask))) + switch (m_type.get()) { case value_t::object: { @@ -1886,7 +1958,7 @@ class basic_json m_value = other.m_value.number_integer; break; } - + case value_t::number_unsigned: { m_value = other.m_value.number_unsigned; @@ -1982,7 +2054,7 @@ class basic_json */ ~basic_json() { - switch (m_type) + switch (m_type.get()) { case value_t::object: { @@ -2082,7 +2154,7 @@ class basic_json */ value_t type() const noexcept { - return static_cast(static_cast(m_type) & static_cast(value_t::precision_mask)); + return m_type.get(); } /*! @@ -2178,9 +2250,10 @@ class basic_json @liveexample{The following code exemplifies @ref is_number for all JSON types.,is_number} - @sa @ref is_number_integer() -- check if value is an integer or unsigned + @sa @ref is_number_integer() -- check if value is an integer or unsigned integer number - @sa @ref is_number_unsigned() -- check if value is an unsigned integer number + @sa @ref is_number_unsigned() -- check if value is an unsigned integer + number @sa @ref is_number_float() -- check if value is a floating-point number @since version 1.0.0 @@ -2193,10 +2266,10 @@ class basic_json /*! @brief return whether value is an integer number - This function returns true iff the JSON value is an integer or unsigned + This function returns true iff the JSON value is an integer or unsigned integer number. This excludes floating-point values. - @return `true` if type is an integer or unsigned integer number, `false` + @return `true` if type is an integer or unsigned integer number, `false` otherwise. @complexity Constant. @@ -2205,7 +2278,8 @@ class basic_json JSON types.,is_number_integer} @sa @ref is_number() -- check if value is a number - @sa @ref is_number_unsigned() -- check if value is an unsigned integer number + @sa @ref is_number_unsigned() -- check if value is an unsigned integer + number @sa @ref is_number_float() -- check if value is a floating-point number @since version 1.0.0 @@ -2214,19 +2288,22 @@ class basic_json { return m_type == value_t::number_integer or m_type == value_t::number_unsigned; } - + /*! @brief return whether value is an unsigned integer number - This function returns true iff the JSON value is an unsigned integer number. - This excludes floating-point and (signed) integer values. + This function returns true iff the JSON value is an unsigned integer + number. This excludes floating-point and (signed) integer values. @return `true` if type is an unsigned integer number, `false` otherwise. @complexity Constant. + @liveexample{The following code exemplifies @ref is_number_unsigned for all + JSON types.,is_number_unsigned} + @sa @ref is_number() -- check if value is a number - @sa @ref is_number_integer() -- check if value is an integer or unsigned + @sa @ref is_number_integer() -- check if value is an integer or unsigned integer number @sa @ref is_number_float() -- check if value is a floating-point number @@ -2252,13 +2329,14 @@ class basic_json @sa @ref is_number() -- check if value is number @sa @ref is_number_integer() -- check if value is an integer number - @sa @ref is_number_unsigned() -- check if value is an unsigned integer number + @sa @ref is_number_unsigned() -- check if value is an unsigned integer + number @since version 1.0.0 */ bool is_number_float() const noexcept { - return (static_cast(static_cast(m_type) & static_cast(value_t::precision_mask))) == value_t::number_float; + return m_type == value_t::number_float; } /*! @@ -2359,7 +2437,7 @@ class basic_json */ operator value_t() const noexcept { - return (static_cast(static_cast(m_type) & static_cast(value_t::precision_mask))); + return m_type.get(); } /// @} @@ -2514,13 +2592,13 @@ class basic_json , int>::type = 0> T get_impl(T*) const { - switch (static_cast(static_cast(m_type) & static_cast(value_t::precision_mask))) + switch (m_type.get()) { case value_t::number_integer: { return static_cast(m_value.number_integer); } - + case value_t::number_unsigned: { return static_cast(m_value.number_unsigned); @@ -2610,7 +2688,7 @@ class basic_json { return is_number_integer() ? &m_value.number_integer : nullptr; } - + /// get a pointer to the value (unsigned number) number_unsigned_t* get_impl_ptr(number_unsigned_t*) noexcept { @@ -2622,7 +2700,7 @@ class basic_json { return is_number_unsigned() ? &m_value.number_unsigned : nullptr; } - + /// get a pointer to the value (floating-point number) number_float_t* get_impl_ptr(number_float_t*) noexcept { @@ -2720,7 +2798,7 @@ class basic_json @warning The pointer becomes invalid if the underlying JSON object changes. @tparam PointerType pointer type; must be a pointer to @ref array_t, @ref - object_t, @ref string_t, @ref boolean_t, @ref number_integer_t, + object_t, @ref string_t, @ref boolean_t, @ref number_integer_t, @ref number_unsigned_t, or @ref number_float_t. @return pointer to the internally stored JSON value if the requested @@ -2771,7 +2849,7 @@ class basic_json state. @tparam PointerType pointer type; must be a pointer to @ref array_t, @ref - object_t, @ref string_t, @ref boolean_t, @ref number_integer_t, + object_t, @ref string_t, @ref boolean_t, @ref number_integer_t, @ref number_unsigned_t, or @ref number_float_t. @return pointer to the internally stored JSON value if the requested @@ -2835,7 +2913,7 @@ class basic_json @liveexample{The example shows several calls to `get_ref()`.,get_ref} - @since version 1.0.1 + @since version 1.1.0 */ template::value - and not std::is_same::value + template < typename ValueType, typename + std::enable_if < + not std::is_pointer::value + and not std::is_same::value #ifndef _MSC_VER // Fix for issue #167 operator<< abiguity under VS2015 - and not std::is_same>::value + and not std::is_same>::value #endif - , int>::type = 0> + , int >::type = 0 > operator ValueType() const { // delegate the call to get<>() const @@ -3365,7 +3443,7 @@ class basic_json with range checking @sa @ref value() for access by value with a default value - @since version 1.0.1 + @since version 1.1.0 */ template reference operator[](T* key) @@ -3414,7 +3492,7 @@ class basic_json with range checking @sa @ref value() for access by value with a default value - @since version 1.0.1 + @since version 1.1.0 */ template const_reference operator[](T* key) const @@ -3646,7 +3724,7 @@ class basic_json InteratorType result = end(); - switch (static_cast(static_cast(m_type) & static_cast(value_t::precision_mask))) + switch (m_type.get()) { case value_t::boolean: case value_t::number_float: @@ -3665,7 +3743,7 @@ class basic_json m_value.string = nullptr; } - m_type = value_t::null; + m_type = type_data_t(value_t::null); break; } @@ -3752,7 +3830,7 @@ class basic_json InteratorType result = end(); - switch (static_cast(static_cast(m_type) & static_cast(value_t::precision_mask))) + switch (m_type.get()) { case value_t::boolean: case value_t::number_float: @@ -3771,7 +3849,7 @@ class basic_json m_value.string = nullptr; } - m_type = value_t::null; + m_type = type_data_t(value_t::null); break; } @@ -4260,7 +4338,7 @@ class basic_json */ bool empty() const noexcept { - switch (m_type) + switch (m_type.get()) { case value_t::null: { @@ -4318,7 +4396,7 @@ class basic_json */ size_type size() const noexcept { - switch (m_type) + switch (m_type.get()) { case value_t::null: { @@ -4380,7 +4458,7 @@ class basic_json */ size_type max_size() const noexcept { - switch (m_type) + switch (m_type.get()) { case value_t::array: { @@ -4439,7 +4517,7 @@ class basic_json */ void clear() noexcept { - switch (m_type) + switch (m_type.get()) { case value_t::number_integer: { @@ -4532,7 +4610,7 @@ class basic_json assert(m_value.array != nullptr); m_value.array->push_back(std::move(val)); // invalidate object - val.m_type = value_t::null; + val.m_type = type_data_t(value_t::null); } /*! @@ -5557,7 +5635,7 @@ class basic_json /// return the type as string string_t type_name() const { - switch (m_type) + switch (m_type.get()) { case value_t::null: return "null"; @@ -5763,7 +5841,7 @@ class basic_json // variable to hold indentation for recursive calls unsigned int new_indent = current_indent; - switch (static_cast(static_cast(m_type) & static_cast(value_t::precision_mask))) + switch (m_type.get()) { case value_t::object: { @@ -5874,25 +5952,58 @@ class basic_json case value_t::number_float: { - // If the number was parsed from a string use the same precision - // otherwise 15 digits of precision allows round-trip IEEE 754 - // string->double->string; to be safe, we read this value from - // std::numeric_limits::digits10 - int precision = static_cast(m_type) >> 8; - if (!precision) precision = std::numeric_limits::digits10; + // Buffer size: precision (2^8-1 = 255) + other ('-.e-xxx' = 7) + null (1) + char buf[263]; + int len; - // Special case for zero - use fixed precision to get "0.0" - if (m_value.number_float == 0) + if (m_type.bits.parsed) + { + // Number was parsed from a string so use the same precision + if (m_type.bits.has_exp) { - o << std::fixed << std::setprecision(1); + // Exponent - output in exponential form - handle capitalization of e/E + if (m_type.bits.exp_cap) + { + len = snprintf(buf, sizeof(buf), "%.*E", m_type.bits.precision, m_value.number_float) + 1; + } + else + { + len = snprintf(buf, sizeof(buf), "%.*e", m_type.bits.precision, m_value.number_float) + 1; + } + + // Remove '+' sign from the exponent if necessary + if (!m_type.bits.exp_plus) + { + if (static_cast(len) > sizeof(buf)) len = sizeof(buf); + for (size_t i = 0; i < static_cast(len); i++) + { + if (buf[i] == '+') + { + for (; i + 1 < static_cast(len); i++) buf[i] = buf[i + 1]; + } + } + } } else { - // std::defaultfloat not supported in gcc version < 5 - o.unsetf(std::ios_base::floatfield); - o << std::setprecision(precision); + // No exponent - output as a decimal + snprintf(buf, sizeof(buf), "%.*f", m_type.bits.precision, m_value.number_float); } - o << m_value.number_float; + } + else if (m_value.number_float == 0) + { + // Special case for zero - use fixed precision to get "0.0" + snprintf(buf, sizeof(buf), "%#.1f", m_value.number_float); + } + else + { + // Otherwise 15 digits of precision allows round-trip IEEE 754 + // string->double->string; to be safe, we read this value from + // std::numeric_limits::digits10 + snprintf(buf, sizeof(buf), "%.*g", std::numeric_limits::digits10, m_value.number_float); + } + + o << buf; return; } @@ -5916,7 +6027,7 @@ class basic_json ////////////////////// /// the type of the current element - value_t m_type = value_t::null; + type_data_t m_type = type_data_t(value_t::null); /// the value of the current element json_value m_value = {}; @@ -6140,7 +6251,7 @@ class basic_json { assert(m_object != nullptr); - switch (m_object->m_type) + switch (m_object->m_type.get()) { case basic_json::value_t::object: { @@ -6167,7 +6278,7 @@ class basic_json { assert(m_object != nullptr); - switch (m_object->m_type) + switch (m_object->m_type.get()) { case basic_json::value_t::object: { @@ -6213,7 +6324,7 @@ class basic_json { assert(m_object != nullptr); - switch (m_object->m_type) + switch (m_object->m_type.get()) { case basic_json::value_t::object: { @@ -6249,7 +6360,7 @@ class basic_json { assert(m_object != nullptr); - switch (m_object->m_type) + switch (m_object->m_type.get()) { case basic_json::value_t::object: { @@ -6279,7 +6390,7 @@ class basic_json { assert(m_object != nullptr); - switch (m_object->m_type) + switch (m_object->m_type.get()) { case basic_json::value_t::object: { @@ -6319,7 +6430,7 @@ class basic_json { assert(m_object != nullptr); - switch (m_object->m_type) + switch (m_object->m_type.get()) { case basic_json::value_t::object: { @@ -6362,7 +6473,7 @@ class basic_json { assert(m_object != nullptr); - switch (m_object->m_type) + switch (m_object->m_type.get()) { case basic_json::value_t::object: { @@ -6399,7 +6510,7 @@ class basic_json { assert(m_object != nullptr); - switch (m_object->m_type) + switch (m_object->m_type.get()) { case basic_json::value_t::object: { @@ -6434,7 +6545,7 @@ class basic_json assert(m_object != nullptr); - switch (m_object->m_type) + switch (m_object->m_type.get()) { case basic_json::value_t::object: { @@ -6470,7 +6581,7 @@ class basic_json assert(m_object != nullptr); - switch (m_object->m_type) + switch (m_object->m_type.get()) { case basic_json::value_t::object: { @@ -6512,7 +6623,7 @@ class basic_json { assert(m_object != nullptr); - switch (m_object->m_type) + switch (m_object->m_type.get()) { case basic_json::value_t::object: { @@ -6562,7 +6673,7 @@ class basic_json { assert(m_object != nullptr); - switch (m_object->m_type) + switch (m_object->m_type.get()) { case basic_json::value_t::object: { @@ -6586,7 +6697,7 @@ class basic_json { assert(m_object != nullptr); - switch (m_object->m_type) + switch (m_object->m_type.get()) { case basic_json::value_t::object: { @@ -7654,10 +7765,10 @@ basic_json_parser_64: @return the floating point number - @warning This function uses `std::strtof`, `std::strtod`, or - `std::strtold` which use the current C locale to determine which - character is used as decimal point character. This may yield to parse - errors if the locale does not used `.`. + @bug This function uses `std::strtof`, `std::strtod`, or `std::strtold` + which use the current C locale to determine which character is used as + decimal point character. This may yield to parse errors if the locale + does not used `.`. */ long double str_to_float_t(long double* /* type */, char** endptr) const { @@ -7705,127 +7816,165 @@ basic_json_parser_64: /*! @brief static_cast between two types and indicate if it results in error - This function performs a static_cast between @a source and @a dest. It + This function performs a static_cast between @a source and @a dest. It then checks if a static_cast back to @a dest produces an error. - @param[in] source the value to cast from + @param[in] source the value to cast from @param[out] dest the value to cast to @return @a true if the cast was performed without error, @a false otherwise */ template - bool attempt_cast(T_A source, T_B & dest) const + bool attempt_cast(T_A source, T_B& dest) const { dest = static_cast(source); return (source == static_cast(dest)); } + /*! + @brief peek ahead and guess the number type and floating point representation + + This function scans the number to identify the number type. In addition it + counts the significant figures after the decimal point, whether the + number is in exponential or decimal form, the capitalization of the + exponent marker, and if the optional '+' is present in the exponent. This + information is necessary to perform accurate round trips of floating point + numbers. + + @param[out] type @ref type_data_t object to receive the type information. + */ + void guess_type(type_data_t & type) const + { + const lexer::lexer_char_t *curptr = m_start; + type.bits.parsed = true; + + uint8_t found_radix_point = 0; + uint8_t precision = 0; + + // Look for sign + if (*curptr == '-') { + type = value_t::number_integer; + curptr++; + } + else { + type = value_t::number_unsigned; + if (*curptr == '+') curptr++; + } + + // Count the significant figures + for (; curptr < m_cursor; curptr++) + { + + // Quickly skip tests if a digit + if (*curptr < '0' || *curptr > '9') + { + if (*curptr == '.') + { + // Don't count '.' but change to float + type = value_t::number_float; + + // Reset precision count + precision = 0; + found_radix_point = 0xFF; + continue; + } + // Assume exponent (if not it is a bad number and will fail + // parse anyway - could throw here instead): change to + // float, stop counting and record exponent details + type = value_t::number_float; + type.bits.has_exp = true; + + // Exponent capitalization + type.bits.exp_cap = (*curptr == 'E'); + + // Exponent '+' sign + type.bits.exp_plus = (*(++curptr) == '+'); + break; + } + precision++; + } + + // If no radix was found then precision would now be set to + // the number of digits, which is wrong - clear it + type.bits.precision = precision & found_radix_point; + } + /*! @brief return number value for number tokens - This function translates the last token into the most appropriate - number type (either integer, unsigned integer or floating point), - which is passed back to the caller via the result parameter. The pointer - @a m_start points to the beginning of the parsed number. We first examine - the first character to determine the sign of the number and then pass - this pointer to either @a std::strtoull (if positive) or @a std::strtoll - (if negative), both of which set @a endptr to the first character past the - converted number. If this pointer is not the same as @a m_cursor, then - either more or less characters have been used during the comparison. - - This can happen for inputs like "01" which will be treated like number 0 - followed by number 1. This will also occur for valid floating point - inputs like "12e3" will be incorrectly read as 12. Numbers that are too - large or too small for a signed/unsigned long long will cause a range - error (@a errno set to ERANGE). The parsed number is cast to a @ref - number_integer_t/@ref number_unsigned_t using the helper function @ref attempt_cast, - which returns @a false if the cast could not be peformed without error. + This function translates the last token into the most appropriate number + type (either integer, unsigned integer or floating point), which is + passed back to the caller via the result parameter. - In any of these cases (more/less characters read, range error or a cast - error) the pointer is passed to @a std:strtod, which also sets @a endptr to the - first character past the converted number. The resulting @ref number_float_t - is then cast to a @ref number_integer_t/@ref number_unsigned_t using - @ref attempt_cast and if no error occurs is stored in that form, otherwise - it is stored as a @ref number_float_t. - - A final comparison is made of @a endptr and if still not the same as - @ref m_cursor a bad input is assumed and @a result parameter is set to NAN. + First @ref guess_type() is called to determine the type and to retrieve + information about the floating point representation (if applicable) + that can be used to accurately render the number to a string later. + + Depending on the type, either @a std::strtoull (if number_unsigned_t) or + @a std::strtoll (if number_integer_t) is then called to attempt to parse the + number as an integer. Numbers that are too large or too small for a + signed/unsigned long long will cause a range error (@a errno set to ERANGE). + The parsed number is cast to a @ref number_integer_t/@ref number_unsigned_t + using the helper function @ref attempt_cast, which returns @a false if the + cast could not be peformed without error. + + In either of these cases (range error or a cast error) the number is parsed + using @a std:strtod (or @a std:strtof or @a std::strtold), which sets + @a endptr to the first character past the converted number. If it is not + the same as @ref m_cursor a bad input is assumed and @a result parameter is + set to NAN. @param[out] result @ref basic_json object to receive the number, or NAN if the - conversion read past the current token. The latter case needs to be + conversion read past the current token. The latter case needs to be treated by the caller function. */ void get_number(basic_json& result) const { assert(m_start != nullptr); - // Count the significant figures - int precision = 0; - { - const lexer::lexer_char_t *curptr; - - // Assume unsigned integer for now - result.m_type = value_t::number_unsigned; - for (curptr = m_start; curptr < m_cursor; curptr++) { - switch (*curptr) { - case '-': - // Found minus sign: change to integer - result.m_type = value_t::number_integer; - case '.': - // Don't count either '.' or '-' - continue; - case 'e': - case 'E': - // Found exponent: change to float and stop counting - result.m_type = value_t::number_float; - break; - default: - // Found a signficant figure - precision++; - continue; - } - break; - } - - // Characters after number - shouldn't happen, but try parsing as float - if (curptr != m_cursor) result.m_type = value_t::number_float; - } + guess_type(result.m_type); errno = 0; - typename string_t::value_type* endptr = 0; - // Attempt to parse it as an integer - first checking for a negative number + // Attempt to parse it as an integer if (result.m_type == value_t::number_unsigned) { // Positive, parse with strtoull and attempt cast to number_unsigned_t - if (!attempt_cast(std::strtoull(reinterpret_cast(m_start), &endptr, 10), result.m_value.number_unsigned)) + if (!attempt_cast(std::strtoull(reinterpret_cast(m_start), NULL, + 10), result.m_value.number_unsigned)) + { result.m_type = value_t::number_float; // Cast failed due to overflow - store as float + } } else if (result.m_type == value_t::number_integer) { // Negative, parse with strtoll and attempt cast to number_integer_t - if (!attempt_cast(std::strtoll(reinterpret_cast(m_start), &endptr, 10), result.m_value.number_unsigned)) + if (!attempt_cast(std::strtoll(reinterpret_cast(m_start), NULL, + 10), result.m_value.number_integer)) + { result.m_type = value_t::number_float; // Cast failed due to overflow - store as float + } } // Check the end of the number was reached and no range error occurred - if (reinterpret_cast(endptr) != m_cursor || errno == ERANGE) result.m_type = value_t::number_float; + if (errno == ERANGE) result.m_type = value_t::number_float; if (result.m_type == value_t::number_float) { - // Either the number won't fit in an integer (range error from strtoull/strtoll or overflow on cast) or there was - // something else after the number, which could be an exponent + // Either the number won't fit in an integer (range error from + // strtoull/strtoll or overflow on cast) or there was something + // else after the number, which could be an exponent // Parse with strtod + typename string_t::value_type* endptr; result.m_value.number_float = str_to_float_t(static_cast(nullptr), &endptr); - // Add the precision bits - result.m_type = static_cast(static_cast(result.m_type) | (precision << 8)); - // Anything after the number is an error if (reinterpret_cast(endptr) != m_cursor && *m_cursor != '.') + { throw std::invalid_argument(std::string("parse error - ") + get_token() + " is not a number"); + } } } @@ -8197,5 +8346,3 @@ inline nlohmann::json operator "" _json(const char* s, std::size_t) #endif #endif - - diff --git a/src/json.hpp.re2c b/src/json.hpp.re2c index 6c8311d01..4ec693572 100644 --- a/src/json.hpp.re2c +++ b/src/json.hpp.re2c @@ -32,7 +32,7 @@ Class @ref nlohmann::basic_json is a good entry point for the documentation. @author [Niels Lohmann](http://nlohmann.me) @see https://github.com/nlohmann/json to download the source code -@version 1.0.0 +@version 2.0.0 */ #ifndef NLOHMANN_JSON_HPP @@ -124,8 +124,8 @@ default; will be used in @ref string_t) in @ref boolean_t) @tparam NumberIntegerType type for JSON integer numbers (@c `int64_t` by default; will be used in @ref number_integer_t) -@tparam NumberUnsignedType type for JSON unsigned integer numbers (@c `uint64_t` by -default; will be used in @ref number_unsigned_t) +@tparam NumberUnsignedType type for JSON unsigned integer numbers (@c +`uint64_t` by default; will be used in @ref number_unsigned_t) @tparam NumberFloatType type for JSON floating-point numbers (@c `double` by default; will be used in @ref number_float_t) @tparam AllocatorType type of the allocator to use (@c `std::allocator` by @@ -218,13 +218,11 @@ class basic_json /// the type of an element reference using reference = value_type&; - /// the type of an element const reference using const_reference = const value_type&; /// a type to represent differences between iterators using difference_type = std::ptrdiff_t; - /// a type to represent container sizes using size_type = std::size_t; @@ -340,6 +338,14 @@ class basic_json @sa @ref array_t -- type for an array value @since version 1.0.0 + + @note The order name/value pairs are added to the object is *not* preserved + by the library. Therefore, iterating an object may return name/value pairs + in a different order than they were originally stored. In fact, keys will + be traversed in alphabetical order as `std::map` with `std::less` is used + by default. Please note this behavior conforms to [RFC + 7159](http://rfc7159.net/rfc7159), because any order implements the + specified "unordered" nature of JSON objects. */ using object_t = ObjectType permitted. This description includes both integer and floating-point numbers. However, - C++ allows more precise storage if it is known whether the number is a + C++ allows more precise storage if it is known whether the number is a signed integer, an unsigned integer or a floating-point number. Therefore, - three different types, @ref number_integer_t, @ref number_unsigned_t and + three different types, @ref number_integer_t, @ref number_unsigned_t and @ref number_float_t are used. To store integer numbers in C++, a type is defined by the template @@ -550,9 +556,9 @@ class basic_json > permitted. This description includes both integer and floating-point numbers. However, - C++ allows more precise storage if it is known whether the number is a + C++ allows more precise storage if it is known whether the number is a signed integer, an unsigned integer or a floating-point number. Therefore, - three different types, @ref number_integer_t, @ref number_unsigned_t and + three different types, @ref number_integer_t, @ref number_unsigned_t and @ref number_float_t are used. To store unsigned integer numbers in C++, a type is defined by the template @@ -582,11 +588,11 @@ class basic_json > An implementation may set limits on the range and precision of numbers. When the default type is used, the maximal integer number that can be - stored is `18446744073709551615` (UINT64_MAX) and the minimal integer number - that can be stored is `0`. Integer numbers - that are out of range will yield over/underflow when used in a constructor. - During deserialization, too large or small integer numbers will be - automatically be stored as @ref number_integer_t or @ref number_float_t. + stored is `18446744073709551615` (UINT64_MAX) and the minimal integer + number that can be stored is `0`. Integer numbers that are out of range + will yield over/underflow when used in a constructor. During + deserialization, too large or small integer numbers will be automatically + be stored as @ref number_integer_t or @ref number_float_t. [RFC 7159](http://rfc7159.net/rfc7159) further states: > Note that when such software is used, numbers that are integers and are @@ -594,7 +600,7 @@ class basic_json > that implementations will agree exactly on their numeric values. As this range is a subrange (when considered in conjunction with the - number_integer_t type) of the exactly supported range [0, UINT64_MAX], this + number_integer_t type) of the exactly supported range [0, UINT64_MAX], this class's integer type is interoperable. #### Storage @@ -608,7 +614,7 @@ class basic_json @since version 2.0.0 */ using number_unsigned_t = NumberUnsignedType; - + /*! @brief a type for a number (floating-point) @@ -622,9 +628,9 @@ class basic_json > permitted. This description includes both integer and floating-point numbers. However, - C++ allows more precise storage if it is known whether the number is a + C++ allows more precise storage if it is known whether the number is a signed integer, an unsigned integer or a floating-point number. Therefore, - three different types, @ref number_integer_t, @ref number_unsigned_t and + three different types, @ref number_integer_t, @ref number_unsigned_t and @ref number_float_t are used. To store floating-point numbers in C++, a type is defined by the template @@ -694,7 +700,7 @@ class basic_json @since version 1.0.0 */ - enum class value_t : uint16_t + enum class value_t : uint8_t { null, ///< null value object, ///< object (unordered set of name/value pairs) @@ -704,12 +710,78 @@ class basic_json number_integer, ///< number value (integer) number_unsigned,///< number value (unsigned integer) number_float, ///< number value (floating-point) - discarded, ///< discarded by the the parser callback function - precision_mask = 0xFF + discarded ///< discarded by the the parser callback function }; private: + + /*! + @brief a type to hold JSON type information + + This bitfield type holds information about JSON types. It is internally + used to hold the basic JSON type enumeration, as well as additional + information in the case of values that have been parsed from a string + including whether of not it was created directly or parsed, and + in the case of floating point numbers the number of significant + figures in the original representaiton and if it was in exponential + form, if a '+' was included in the exponent and the capitilization of + the exponent marker. The sole purpose of this information is to permit + accurate round trips. + + @since version 2.0.0 + */ + union type_data_t { + struct { + uint16_t type : 4; + uint16_t parsed : 1; + uint16_t has_exp : 1; + uint16_t exp_plus : 1; + uint16_t exp_cap : 1; + uint16_t precision : 8; + } bits; + uint16_t data; + + // Return the type + value_t get() const + { + return static_cast(bits.type); + } + + // Test type for equality (ignore other fields) + bool operator==(const value_t& rhs) const + { + return static_cast(bits.type) == rhs; + } + + // Assignment + type_data_t & operator=(value_t rhs) + { + bits.type = static_cast(rhs); + return *this; + } + + // Construct from value_t + type_data_t(value_t t) noexcept + { + *reinterpret_cast(this) = 0; + bits.type = static_cast(t); + } + + // Default constructor + type_data_t() noexcept + { + data = 0; + bits.type = reinterpret_cast(value_t::null); + } + + // Copy constructor + type_data_t(const type_data_t& t) noexcept + { + data = t.data; + } + }; + /// helper for exception-safe object creation template static T* create(Args&& ... args) @@ -796,7 +868,7 @@ class basic_json number_integer = number_integer_t(0); break; } - + case value_t::number_unsigned: { number_unsigned = number_unsigned_t(0); @@ -1325,8 +1397,8 @@ class basic_json template::value and - std::numeric_limits::is_integer and - std::numeric_limits::is_signed, + std::numeric_limits::is_integer and + std::numeric_limits::is_signed, CompatibleNumberIntegerType>::type = 0> basic_json(const CompatibleNumberIntegerType val) noexcept @@ -1339,7 +1411,7 @@ class basic_json Create an unsigned integer number JSON value with a given content. - @tparam T helper type to compare number_unsigned_t and unsigned int + @tparam T helper type to compare number_unsigned_t and unsigned int (not visible in) the interface. @param[in] val an integer to create a JSON number from @@ -1360,14 +1432,14 @@ class basic_json basic_json(const number_unsigned_t val) : m_type(value_t::number_unsigned), m_value(val) {} - + /*! @brief create an unsigned number (implicit) Create an unsigned number JSON value with a given content. This constructor allows any type that can be used to construct values of type @ref - number_unsigned_t. Examples may include the types `unsigned int`, `uint32_t`, - or `unsigned short`. + number_unsigned_t. Examples may include the types `unsigned int`, + `uint32_t`, or `unsigned short`. @tparam CompatibleNumberUnsignedType an integer type which is compatible to @ref number_unsigned_t. @@ -1381,13 +1453,13 @@ class basic_json @since version 2.0.0 */ - template::value and - std::numeric_limits::is_integer and - !std::numeric_limits::is_signed, - CompatibleNumberUnsignedType>::type - = 0> + template < typename CompatibleNumberUnsignedType, typename + std::enable_if < + std::is_constructible::value and + std::numeric_limits::is_integer and + !std::numeric_limits::is_signed, + CompatibleNumberUnsignedType >::type + = 0 > basic_json(const CompatibleNumberUnsignedType val) noexcept : m_type(value_t::number_unsigned), m_value(static_cast(val)) @@ -1747,7 +1819,7 @@ class basic_json } // check if iterator range is complete for primitive values - switch (static_cast(static_cast(m_type) & static_cast(value_t::precision_mask))) + switch (m_type.get()) { case value_t::boolean: case value_t::number_float: @@ -1768,7 +1840,7 @@ class basic_json } } - switch (static_cast(static_cast(m_type) & static_cast(value_t::precision_mask))) + switch (m_type.get()) { case value_t::number_integer: { @@ -1776,7 +1848,7 @@ class basic_json m_value.number_integer = first.m_object->m_value.number_integer; break; } - + case value_t::number_unsigned: { assert(first.m_object != nullptr); @@ -1852,7 +1924,7 @@ class basic_json basic_json(const basic_json& other) : m_type(other.m_type) { - switch (static_cast(static_cast(m_type) & static_cast(value_t::precision_mask))) + switch (m_type.get()) { case value_t::object: { @@ -1886,7 +1958,7 @@ class basic_json m_value = other.m_value.number_integer; break; } - + case value_t::number_unsigned: { m_value = other.m_value.number_unsigned; @@ -1982,7 +2054,7 @@ class basic_json */ ~basic_json() { - switch (m_type) + switch (m_type.get()) { case value_t::object: { @@ -2082,7 +2154,7 @@ class basic_json */ value_t type() const noexcept { - return static_cast(static_cast(m_type) & static_cast(value_t::precision_mask)); + return m_type.get(); } /*! @@ -2178,9 +2250,10 @@ class basic_json @liveexample{The following code exemplifies @ref is_number for all JSON types.,is_number} - @sa @ref is_number_integer() -- check if value is an integer or unsigned + @sa @ref is_number_integer() -- check if value is an integer or unsigned integer number - @sa @ref is_number_unsigned() -- check if value is an unsigned integer number + @sa @ref is_number_unsigned() -- check if value is an unsigned integer + number @sa @ref is_number_float() -- check if value is a floating-point number @since version 1.0.0 @@ -2193,10 +2266,10 @@ class basic_json /*! @brief return whether value is an integer number - This function returns true iff the JSON value is an integer or unsigned + This function returns true iff the JSON value is an integer or unsigned integer number. This excludes floating-point values. - @return `true` if type is an integer or unsigned integer number, `false` + @return `true` if type is an integer or unsigned integer number, `false` otherwise. @complexity Constant. @@ -2205,7 +2278,8 @@ class basic_json JSON types.,is_number_integer} @sa @ref is_number() -- check if value is a number - @sa @ref is_number_unsigned() -- check if value is an unsigned integer number + @sa @ref is_number_unsigned() -- check if value is an unsigned integer + number @sa @ref is_number_float() -- check if value is a floating-point number @since version 1.0.0 @@ -2214,19 +2288,22 @@ class basic_json { return m_type == value_t::number_integer or m_type == value_t::number_unsigned; } - + /*! @brief return whether value is an unsigned integer number - This function returns true iff the JSON value is an unsigned integer number. - This excludes floating-point and (signed) integer values. + This function returns true iff the JSON value is an unsigned integer + number. This excludes floating-point and (signed) integer values. @return `true` if type is an unsigned integer number, `false` otherwise. @complexity Constant. + @liveexample{The following code exemplifies @ref is_number_unsigned for all + JSON types.,is_number_unsigned} + @sa @ref is_number() -- check if value is a number - @sa @ref is_number_integer() -- check if value is an integer or unsigned + @sa @ref is_number_integer() -- check if value is an integer or unsigned integer number @sa @ref is_number_float() -- check if value is a floating-point number @@ -2252,13 +2329,14 @@ class basic_json @sa @ref is_number() -- check if value is number @sa @ref is_number_integer() -- check if value is an integer number - @sa @ref is_number_unsigned() -- check if value is an unsigned integer number + @sa @ref is_number_unsigned() -- check if value is an unsigned integer + number @since version 1.0.0 */ bool is_number_float() const noexcept { - return (static_cast(static_cast(m_type) & static_cast(value_t::precision_mask))) == value_t::number_float; + return m_type == value_t::number_float; } /*! @@ -2359,7 +2437,7 @@ class basic_json */ operator value_t() const noexcept { - return (static_cast(static_cast(m_type) & static_cast(value_t::precision_mask))); + return m_type.get(); } /// @} @@ -2514,13 +2592,13 @@ class basic_json , int>::type = 0> T get_impl(T*) const { - switch (static_cast(static_cast(m_type) & static_cast(value_t::precision_mask))) + switch (m_type.get()) { case value_t::number_integer: { return static_cast(m_value.number_integer); } - + case value_t::number_unsigned: { return static_cast(m_value.number_unsigned); @@ -2610,7 +2688,7 @@ class basic_json { return is_number_integer() ? &m_value.number_integer : nullptr; } - + /// get a pointer to the value (unsigned number) number_unsigned_t* get_impl_ptr(number_unsigned_t*) noexcept { @@ -2622,7 +2700,7 @@ class basic_json { return is_number_unsigned() ? &m_value.number_unsigned : nullptr; } - + /// get a pointer to the value (floating-point number) number_float_t* get_impl_ptr(number_float_t*) noexcept { @@ -2720,7 +2798,7 @@ class basic_json @warning The pointer becomes invalid if the underlying JSON object changes. @tparam PointerType pointer type; must be a pointer to @ref array_t, @ref - object_t, @ref string_t, @ref boolean_t, @ref number_integer_t, + object_t, @ref string_t, @ref boolean_t, @ref number_integer_t, @ref number_unsigned_t, or @ref number_float_t. @return pointer to the internally stored JSON value if the requested @@ -2771,7 +2849,7 @@ class basic_json state. @tparam PointerType pointer type; must be a pointer to @ref array_t, @ref - object_t, @ref string_t, @ref boolean_t, @ref number_integer_t, + object_t, @ref string_t, @ref boolean_t, @ref number_integer_t, @ref number_unsigned_t, or @ref number_float_t. @return pointer to the internally stored JSON value if the requested @@ -2835,7 +2913,7 @@ class basic_json @liveexample{The example shows several calls to `get_ref()`.,get_ref} - @since version 1.0.1 + @since version 1.1.0 */ template::value - and not std::is_same::value + template < typename ValueType, typename + std::enable_if < + not std::is_pointer::value + and not std::is_same::value #ifndef _MSC_VER // Fix for issue #167 operator<< abiguity under VS2015 - and not std::is_same>::value + and not std::is_same>::value #endif - , int>::type = 0> + , int >::type = 0 > operator ValueType() const { // delegate the call to get<>() const @@ -3365,7 +3443,7 @@ class basic_json with range checking @sa @ref value() for access by value with a default value - @since version 1.0.1 + @since version 1.1.0 */ template reference operator[](T* key) @@ -3414,7 +3492,7 @@ class basic_json with range checking @sa @ref value() for access by value with a default value - @since version 1.0.1 + @since version 1.1.0 */ template const_reference operator[](T* key) const @@ -3646,7 +3724,7 @@ class basic_json InteratorType result = end(); - switch (static_cast(static_cast(m_type) & static_cast(value_t::precision_mask))) + switch (m_type.get()) { case value_t::boolean: case value_t::number_float: @@ -3665,7 +3743,7 @@ class basic_json m_value.string = nullptr; } - m_type = value_t::null; + m_type = type_data_t(value_t::null); break; } @@ -3752,7 +3830,7 @@ class basic_json InteratorType result = end(); - switch (static_cast(static_cast(m_type) & static_cast(value_t::precision_mask))) + switch (m_type.get()) { case value_t::boolean: case value_t::number_float: @@ -3771,7 +3849,7 @@ class basic_json m_value.string = nullptr; } - m_type = value_t::null; + m_type = type_data_t(value_t::null); break; } @@ -4260,7 +4338,7 @@ class basic_json */ bool empty() const noexcept { - switch (m_type) + switch (m_type.get()) { case value_t::null: { @@ -4318,7 +4396,7 @@ class basic_json */ size_type size() const noexcept { - switch (m_type) + switch (m_type.get()) { case value_t::null: { @@ -4380,7 +4458,7 @@ class basic_json */ size_type max_size() const noexcept { - switch (m_type) + switch (m_type.get()) { case value_t::array: { @@ -4439,7 +4517,7 @@ class basic_json */ void clear() noexcept { - switch (m_type) + switch (m_type.get()) { case value_t::number_integer: { @@ -4532,7 +4610,7 @@ class basic_json assert(m_value.array != nullptr); m_value.array->push_back(std::move(val)); // invalidate object - val.m_type = value_t::null; + val.m_type = type_data_t(value_t::null); } /*! @@ -5557,7 +5635,7 @@ class basic_json /// return the type as string string_t type_name() const { - switch (m_type) + switch (m_type.get()) { case value_t::null: return "null"; @@ -5763,7 +5841,7 @@ class basic_json // variable to hold indentation for recursive calls unsigned int new_indent = current_indent; - switch (static_cast(static_cast(m_type) & static_cast(value_t::precision_mask))) + switch (m_type.get()) { case value_t::object: { @@ -5874,25 +5952,58 @@ class basic_json case value_t::number_float: { - // If the number was parsed from a string use the same precision - // otherwise 15 digits of precision allows round-trip IEEE 754 - // string->double->string; to be safe, we read this value from - // std::numeric_limits::digits10 - int precision = static_cast(m_type) >> 8; - if (!precision) precision = std::numeric_limits::digits10; + // Buffer size: precision (2^8-1 = 255) + other ('-.e-xxx' = 7) + null (1) + char buf[263]; + int len; - // Special case for zero - use fixed precision to get "0.0" - if (m_value.number_float == 0) + if (m_type.bits.parsed) + { + // Number was parsed from a string so use the same precision + if (m_type.bits.has_exp) { - o << std::fixed << std::setprecision(1); + // Exponent - output in exponential form - handle capitalization of e/E + if (m_type.bits.exp_cap) + { + len = snprintf(buf, sizeof(buf), "%.*E", m_type.bits.precision, m_value.number_float) + 1; + } + else + { + len = snprintf(buf, sizeof(buf), "%.*e", m_type.bits.precision, m_value.number_float) + 1; + } + + // Remove '+' sign from the exponent if necessary + if (!m_type.bits.exp_plus) + { + if (static_cast(len) > sizeof(buf)) len = sizeof(buf); + for (size_t i = 0; i < static_cast(len); i++) + { + if (buf[i] == '+') + { + for (; i + 1 < static_cast(len); i++) buf[i] = buf[i + 1]; + } + } + } } else { - // std::defaultfloat not supported in gcc version < 5 - o.unsetf(std::ios_base::floatfield); - o << std::setprecision(precision); + // No exponent - output as a decimal + snprintf(buf, sizeof(buf), "%.*f", m_type.bits.precision, m_value.number_float); } - o << m_value.number_float; + } + else if (m_value.number_float == 0) + { + // Special case for zero - use fixed precision to get "0.0" + snprintf(buf, sizeof(buf), "%#.1f", m_value.number_float); + } + else + { + // Otherwise 15 digits of precision allows round-trip IEEE 754 + // string->double->string; to be safe, we read this value from + // std::numeric_limits::digits10 + snprintf(buf, sizeof(buf), "%.*g", std::numeric_limits::digits10, m_value.number_float); + } + + o << buf; return; } @@ -5916,7 +6027,7 @@ class basic_json ////////////////////// /// the type of the current element - value_t m_type = value_t::null; + type_data_t m_type = type_data_t(value_t::null); /// the value of the current element json_value m_value = {}; @@ -6140,7 +6251,7 @@ class basic_json { assert(m_object != nullptr); - switch (m_object->m_type) + switch (m_object->m_type.get()) { case basic_json::value_t::object: { @@ -6167,7 +6278,7 @@ class basic_json { assert(m_object != nullptr); - switch (m_object->m_type) + switch (m_object->m_type.get()) { case basic_json::value_t::object: { @@ -6213,7 +6324,7 @@ class basic_json { assert(m_object != nullptr); - switch (m_object->m_type) + switch (m_object->m_type.get()) { case basic_json::value_t::object: { @@ -6249,7 +6360,7 @@ class basic_json { assert(m_object != nullptr); - switch (m_object->m_type) + switch (m_object->m_type.get()) { case basic_json::value_t::object: { @@ -6279,7 +6390,7 @@ class basic_json { assert(m_object != nullptr); - switch (m_object->m_type) + switch (m_object->m_type.get()) { case basic_json::value_t::object: { @@ -6319,7 +6430,7 @@ class basic_json { assert(m_object != nullptr); - switch (m_object->m_type) + switch (m_object->m_type.get()) { case basic_json::value_t::object: { @@ -6362,7 +6473,7 @@ class basic_json { assert(m_object != nullptr); - switch (m_object->m_type) + switch (m_object->m_type.get()) { case basic_json::value_t::object: { @@ -6399,7 +6510,7 @@ class basic_json { assert(m_object != nullptr); - switch (m_object->m_type) + switch (m_object->m_type.get()) { case basic_json::value_t::object: { @@ -6434,7 +6545,7 @@ class basic_json assert(m_object != nullptr); - switch (m_object->m_type) + switch (m_object->m_type.get()) { case basic_json::value_t::object: { @@ -6470,7 +6581,7 @@ class basic_json assert(m_object != nullptr); - switch (m_object->m_type) + switch (m_object->m_type.get()) { case basic_json::value_t::object: { @@ -6512,7 +6623,7 @@ class basic_json { assert(m_object != nullptr); - switch (m_object->m_type) + switch (m_object->m_type.get()) { case basic_json::value_t::object: { @@ -6562,7 +6673,7 @@ class basic_json { assert(m_object != nullptr); - switch (m_object->m_type) + switch (m_object->m_type.get()) { case basic_json::value_t::object: { @@ -6586,7 +6697,7 @@ class basic_json { assert(m_object != nullptr); - switch (m_object->m_type) + switch (m_object->m_type.get()) { case basic_json::value_t::object: { @@ -7336,10 +7447,10 @@ class basic_json @return the floating point number - @warning This function uses `std::strtof`, `std::strtod`, or - `std::strtold` which use the current C locale to determine which - character is used as decimal point character. This may yield to parse - errors if the locale does not used `.`. + @bug This function uses `std::strtof`, `std::strtod`, or `std::strtold` + which use the current C locale to determine which character is used as + decimal point character. This may yield to parse errors if the locale + does not used `.`. */ long double str_to_float_t(long double* /* type */, char** endptr) const { @@ -7387,127 +7498,165 @@ class basic_json /*! @brief static_cast between two types and indicate if it results in error - This function performs a static_cast between @a source and @a dest. It + This function performs a static_cast between @a source and @a dest. It then checks if a static_cast back to @a dest produces an error. - @param[in] source the value to cast from + @param[in] source the value to cast from @param[out] dest the value to cast to @return @a true if the cast was performed without error, @a false otherwise */ template - bool attempt_cast(T_A source, T_B & dest) const + bool attempt_cast(T_A source, T_B& dest) const { dest = static_cast(source); return (source == static_cast(dest)); } + /*! + @brief peek ahead and guess the number type and floating point representation + + This function scans the number to identify the number type. In addition it + counts the significant figures after the decimal point, whether the + number is in exponential or decimal form, the capitalization of the + exponent marker, and if the optional '+' is present in the exponent. This + information is necessary to perform accurate round trips of floating point + numbers. + + @param[out] type @ref type_data_t object to receive the type information. + */ + void guess_type(type_data_t & type) const + { + const lexer::lexer_char_t *curptr = m_start; + type.bits.parsed = true; + + uint8_t found_radix_point = 0; + uint8_t precision = 0; + + // Look for sign + if (*curptr == '-') { + type = value_t::number_integer; + curptr++; + } + else { + type = value_t::number_unsigned; + if (*curptr == '+') curptr++; + } + + // Count the significant figures + for (; curptr < m_cursor; curptr++) + { + + // Quickly skip tests if a digit + if (*curptr < '0' || *curptr > '9') + { + if (*curptr == '.') + { + // Don't count '.' but change to float + type = value_t::number_float; + + // Reset precision count + precision = 0; + found_radix_point = 0xFF; + continue; + } + // Assume exponent (if not it is a bad number and will fail + // parse anyway - could throw here instead): change to + // float, stop counting and record exponent details + type = value_t::number_float; + type.bits.has_exp = true; + + // Exponent capitalization + type.bits.exp_cap = (*curptr == 'E'); + + // Exponent '+' sign + type.bits.exp_plus = (*(++curptr) == '+'); + break; + } + precision++; + } + + // If no radix was found then precision would now be set to + // the number of digits, which is wrong - clear it + type.bits.precision = precision & found_radix_point; + } + /*! @brief return number value for number tokens - This function translates the last token into the most appropriate - number type (either integer, unsigned integer or floating point), - which is passed back to the caller via the result parameter. The pointer - @a m_start points to the beginning of the parsed number. We first examine - the first character to determine the sign of the number and then pass - this pointer to either @a std::strtoull (if positive) or @a std::strtoll - (if negative), both of which set @a endptr to the first character past the - converted number. If this pointer is not the same as @a m_cursor, then - either more or less characters have been used during the comparison. - - This can happen for inputs like "01" which will be treated like number 0 - followed by number 1. This will also occur for valid floating point - inputs like "12e3" will be incorrectly read as 12. Numbers that are too - large or too small for a signed/unsigned long long will cause a range - error (@a errno set to ERANGE). The parsed number is cast to a @ref - number_integer_t/@ref number_unsigned_t using the helper function @ref attempt_cast, - which returns @a false if the cast could not be peformed without error. + This function translates the last token into the most appropriate number + type (either integer, unsigned integer or floating point), which is + passed back to the caller via the result parameter. - In any of these cases (more/less characters read, range error or a cast - error) the pointer is passed to @a std:strtod, which also sets @a endptr to the - first character past the converted number. The resulting @ref number_float_t - is then cast to a @ref number_integer_t/@ref number_unsigned_t using - @ref attempt_cast and if no error occurs is stored in that form, otherwise - it is stored as a @ref number_float_t. - - A final comparison is made of @a endptr and if still not the same as - @ref m_cursor a bad input is assumed and @a result parameter is set to NAN. + First @ref guess_type() is called to determine the type and to retrieve + information about the floating point representation (if applicable) + that can be used to accurately render the number to a string later. + + Depending on the type, either @a std::strtoull (if number_unsigned_t) or + @a std::strtoll (if number_integer_t) is then called to attempt to parse the + number as an integer. Numbers that are too large or too small for a + signed/unsigned long long will cause a range error (@a errno set to ERANGE). + The parsed number is cast to a @ref number_integer_t/@ref number_unsigned_t + using the helper function @ref attempt_cast, which returns @a false if the + cast could not be peformed without error. + + In either of these cases (range error or a cast error) the number is parsed + using @a std:strtod (or @a std:strtof or @a std::strtold), which sets + @a endptr to the first character past the converted number. If it is not + the same as @ref m_cursor a bad input is assumed and @a result parameter is + set to NAN. @param[out] result @ref basic_json object to receive the number, or NAN if the - conversion read past the current token. The latter case needs to be + conversion read past the current token. The latter case needs to be treated by the caller function. */ void get_number(basic_json& result) const { assert(m_start != nullptr); - // Count the significant figures - int precision = 0; - { - const lexer::lexer_char_t *curptr; - - // Assume unsigned integer for now - result.m_type = value_t::number_unsigned; - for (curptr = m_start; curptr < m_cursor; curptr++) { - switch (*curptr) { - case '-': - // Found minus sign: change to integer - result.m_type = value_t::number_integer; - case '.': - // Don't count either '.' or '-' - continue; - case 'e': - case 'E': - // Found exponent: change to float and stop counting - result.m_type = value_t::number_float; - break; - default: - // Found a signficant figure - precision++; - continue; - } - break; - } - - // Characters after number - shouldn't happen, but try parsing as float - if (curptr != m_cursor) result.m_type = value_t::number_float; - } + guess_type(result.m_type); errno = 0; - typename string_t::value_type* endptr = 0; - // Attempt to parse it as an integer - first checking for a negative number + // Attempt to parse it as an integer if (result.m_type == value_t::number_unsigned) { // Positive, parse with strtoull and attempt cast to number_unsigned_t - if (!attempt_cast(std::strtoull(reinterpret_cast(m_start), &endptr, 10), result.m_value.number_unsigned)) + if (!attempt_cast(std::strtoull(reinterpret_cast(m_start), NULL, + 10), result.m_value.number_unsigned)) + { result.m_type = value_t::number_float; // Cast failed due to overflow - store as float + } } else if (result.m_type == value_t::number_integer) { // Negative, parse with strtoll and attempt cast to number_integer_t - if (!attempt_cast(std::strtoll(reinterpret_cast(m_start), &endptr, 10), result.m_value.number_unsigned)) + if (!attempt_cast(std::strtoll(reinterpret_cast(m_start), NULL, + 10), result.m_value.number_integer)) + { result.m_type = value_t::number_float; // Cast failed due to overflow - store as float + } } // Check the end of the number was reached and no range error occurred - if (reinterpret_cast(endptr) != m_cursor || errno == ERANGE) result.m_type = value_t::number_float; + if (errno == ERANGE) result.m_type = value_t::number_float; if (result.m_type == value_t::number_float) { - // Either the number won't fit in an integer (range error from strtoull/strtoll or overflow on cast) or there was - // something else after the number, which could be an exponent + // Either the number won't fit in an integer (range error from + // strtoull/strtoll or overflow on cast) or there was something + // else after the number, which could be an exponent // Parse with strtod + typename string_t::value_type* endptr; result.m_value.number_float = str_to_float_t(static_cast(nullptr), &endptr); - // Add the precision bits - result.m_type = static_cast(static_cast(result.m_type) | (precision << 8)); - // Anything after the number is an error if (reinterpret_cast(endptr) != m_cursor && *m_cursor != '.') + { throw std::invalid_argument(std::string("parse error - ") + get_token() + " is not a number"); + } } } @@ -7879,5 +8028,3 @@ inline nlohmann::json operator "" _json(const char* s, std::size_t) #endif #endif - - diff --git a/test/json_roundtrip/roundtrip28.json b/test/json_roundtrip/roundtrip28.json new file mode 100644 index 000000000..749ffaf93 --- /dev/null +++ b/test/json_roundtrip/roundtrip28.json @@ -0,0 +1 @@ +[4.940656458412e-324] \ No newline at end of file diff --git a/test/json_roundtrip/roundtrip29.json b/test/json_roundtrip/roundtrip29.json new file mode 100644 index 000000000..4bf65ec4b --- /dev/null +++ b/test/json_roundtrip/roundtrip29.json @@ -0,0 +1 @@ +[2.2250738585072e-308] \ No newline at end of file diff --git a/test/json_roundtrip/roundtrip30.json b/test/json_roundtrip/roundtrip30.json new file mode 100644 index 000000000..fd357651b --- /dev/null +++ b/test/json_roundtrip/roundtrip30.json @@ -0,0 +1 @@ +[1.2345E-30] \ No newline at end of file diff --git a/test/json_roundtrip/roundtrip31.json b/test/json_roundtrip/roundtrip31.json new file mode 100644 index 000000000..1428d1443 --- /dev/null +++ b/test/json_roundtrip/roundtrip31.json @@ -0,0 +1 @@ +[1.2345E+30] \ No newline at end of file diff --git a/test/json_roundtrip/roundtrip32.json b/test/json_roundtrip/roundtrip32.json new file mode 100644 index 000000000..d6f34011f --- /dev/null +++ b/test/json_roundtrip/roundtrip32.json @@ -0,0 +1 @@ +[1.2345e+30] \ No newline at end of file diff --git a/test/unit.cpp b/test/unit.cpp index 7ba03c084..615cd2e64 100644 --- a/test/unit.cpp +++ b/test/unit.cpp @@ -924,7 +924,8 @@ TEST_CASE("constructors") SECTION("object with error") { - CHECK_THROWS_AS(json::object({ {"one", 1}, {"two", 1u}, {"three", 2.2}, {"four", false}, 13 }), std::logic_error); + CHECK_THROWS_AS(json::object({ {"one", 1}, {"two", 1u}, {"three", 2.2}, {"four", false}, 13 }), + std::logic_error); CHECK_THROWS_WITH(json::object({ {"one", 1}, {"two", 1u}, {"three", 2.2}, {"four", false}, 13 }), "cannot create object from initializer list"); } @@ -2312,7 +2313,7 @@ TEST_CASE("value conversion") json::number_integer_t n = j.get(); CHECK(json(n) == j); } - + SECTION("number_unsigned_t") { json::number_unsigned_t n = j_unsigned.get(); @@ -3665,7 +3666,7 @@ TEST_CASE("element access") CHECK_THROWS_WITH(j_nonobject.at("foo"), "cannot use at() with number"); CHECK_THROWS_WITH(j_nonobject_const.at("foo"), "cannot use at() with number"); } - + SECTION("number (unsigned)") { json j_nonobject(json::value_t::number_unsigned); @@ -3675,7 +3676,7 @@ TEST_CASE("element access") CHECK_THROWS_WITH(j_nonobject.at("foo"), "cannot use at() with number"); CHECK_THROWS_WITH(j_nonobject_const.at("foo"), "cannot use at() with number"); } - + SECTION("number (floating-point)") { json j_nonobject(json::value_t::number_float); @@ -5435,7 +5436,7 @@ TEST_CASE("iterators") SECTION("object") { - json j = {{"A", 1},{"B", 2},{"C", 3}}; + json j = {{"A", 1}, {"B", 2}, {"C", 3}}; json j_const(j); SECTION("json + begin/end") @@ -9661,7 +9662,7 @@ TEST_CASE("parser class") // (2**53)-1 CHECK(json::parser("9007199254740991").parse().get() == 9007199254740991); } - + SECTION("over the edge cases") // issue #178 - Integer conversion to unsigned (incorrect handling of 64 bit integers) { // While RFC7159, Section 6 specifies a preference for support @@ -9672,7 +9673,7 @@ TEST_CASE("parser class") // i.e. -(2**63) -> (2**64)-1. // -(2**63) ** Note: compilers see negative literals as negated positive numbers (hence the -1)) - CHECK(json::parser("-9223372036854775808").parse().get() == -9223372036854775807-1); + CHECK(json::parser("-9223372036854775808").parse().get() == -9223372036854775807 - 1); // (2**63)-1 CHECK(json::parser("9223372036854775807").parse().get() == 9223372036854775807); // (2**64)-1 @@ -9718,14 +9719,17 @@ TEST_CASE("parser class") CHECK_THROWS_AS(json::parser("-0e-:").parse(), std::invalid_argument); CHECK_THROWS_AS(json::parser("-0f").parse(), std::invalid_argument); - CHECK_THROWS_WITH(json::parser("01").parse(), "parse error - 0 is not a number"); + CHECK_THROWS_WITH(json::parser("01").parse(), + "parse error - unexpected number literal; expected end of input"); CHECK_THROWS_WITH(json::parser("--1").parse(), "parse error - unexpected '-'"); - CHECK_THROWS_WITH(json::parser("1.").parse(), "parse error - unexpected '.'; expected end of input"); + CHECK_THROWS_WITH(json::parser("1.").parse(), + "parse error - unexpected '.'; expected end of input"); CHECK_THROWS_WITH(json::parser("1E").parse(), "parse error - unexpected 'E'; expected end of input"); CHECK_THROWS_WITH(json::parser("1E-").parse(), "parse error - unexpected 'E'; expected end of input"); - CHECK_THROWS_WITH(json::parser("1.E1").parse(), "parse error - unexpected '.'; expected end of input"); + CHECK_THROWS_WITH(json::parser("1.E1").parse(), + "parse error - unexpected '.'; expected end of input"); CHECK_THROWS_WITH(json::parser("-1E").parse(), "parse error - unexpected 'E'; expected end of input"); CHECK_THROWS_WITH(json::parser("-0E#").parse(), @@ -9767,7 +9771,8 @@ TEST_CASE("parser class") CHECK_THROWS_AS(json::parser("1E.").parse(), std::invalid_argument); CHECK_THROWS_AS(json::parser("1E/").parse(), std::invalid_argument); CHECK_THROWS_AS(json::parser("1E:").parse(), std::invalid_argument); - CHECK_THROWS_WITH(json::parser("0.").parse(), "parse error - unexpected '.'; expected end of input"); + CHECK_THROWS_WITH(json::parser("0.").parse(), + "parse error - unexpected '.'; expected end of input"); CHECK_THROWS_WITH(json::parser("-").parse(), "parse error - unexpected '-'"); CHECK_THROWS_WITH(json::parser("--").parse(), "parse error - unexpected '-'"); @@ -11765,7 +11770,12 @@ TEST_CASE("compliance tests from nativejson-benchmark") "test/json_roundtrip/roundtrip24.json", "test/json_roundtrip/roundtrip25.json", "test/json_roundtrip/roundtrip26.json", - "test/json_roundtrip/roundtrip27.json" + "test/json_roundtrip/roundtrip27.json", + "test/json_roundtrip/roundtrip28.json", + "test/json_roundtrip/roundtrip29.json", + "test/json_roundtrip/roundtrip30.json", + "test/json_roundtrip/roundtrip31.json", + "test/json_roundtrip/roundtrip32.json" }) { CAPTURE(filename); @@ -12076,14 +12086,15 @@ TEST_CASE("regression tests") SECTION("issue #89 - nonstandard integer type") { // create JSON class with nonstandard integer number type - using custom_json = nlohmann::basic_json; + using custom_json = + nlohmann::basic_json; custom_json j; j["int_1"] = 1; // we need to cast to int to compile with Catch - the value is int32_t CHECK(static_cast(j["int_1"]) == 1); // tests for correct handling of non-standard integers that overflow the type selected by the user - + // unsigned integer object creation - expected to wrap and still be stored as an integer j = 4294967296U; // 2^32 CHECK(static_cast(j.type()) == static_cast(custom_json::value_t::number_unsigned)); @@ -12092,17 +12103,17 @@ TEST_CASE("regression tests") // unsigned integer parsing - expected to overflow and be stored as a float j = custom_json::parse("4294967296"); // 2^32 CHECK(static_cast(j.type()) == static_cast(custom_json::value_t::number_float)); - CHECK(j.get() == 4294967296.0); + CHECK(j.get() == 4294967296.0f); // integer object creation - expected to wrap and still be stored as an integer j = -2147483649LL; // -2^31-1 CHECK(static_cast(j.type()) == static_cast(custom_json::value_t::number_integer)); - CHECK(j.get() == 2147483647.0); // Wrap + CHECK(j.get() == 2147483647.0f); // Wrap - // integer parsing - expected to overflow and be stored as a float - j = custom_json::parse("-2147483648"); // -2^31 + // integer parsing - expected to overflow and be stored as a float with rounding + j = custom_json::parse("-2147483649"); // -2^31 CHECK(static_cast(j.type()) == static_cast(custom_json::value_t::number_float)); - CHECK(j.get() == -2147483648.0); + CHECK(j.get() == -2147483650.0f); } SECTION("issue #93 reverse_iterator operator inheritance problem") @@ -12201,11 +12212,11 @@ TEST_CASE("regression tests") { CHECK(json::parse("\"\\ud80c\\udc60abc\"").get() == u8"\U00013060abc"); } - + SECTION("issue #171 - Cannot index by key of type static constexpr const char*") { json j; - + // Non-const access with key as "char []" char array_key[] = "Key1"; CHECK_NOTHROW(j[array_key] = 1); @@ -12263,12 +12274,8 @@ TEST_CASE("regression tests") j = json::parse("0.999999999999999944488848768742172978818416595458984374"); CHECK(j.get() == 0.99999999999999989); - // Test fails under GCC/clang due to strtod() error (may originate in libstdc++ - // but seems to have been fixed in the most current versions - just not on Travis) -#if !defined(__clang__) && !defined(__GNUC__) && !defined(__GNUG__) j = json::parse("1.00000000000000011102230246251565404236316680908203126"); CHECK(j.get() == 1.00000000000000022); -#endif j = json::parse("7205759403792793199999e-5"); CHECK(j.get() == 72057594037927928.0); @@ -12285,15 +12292,18 @@ TEST_CASE("regression tests") // create JSON class with nonstandard float number type // float - nlohmann::basic_json j_float = 1.23e25f; + nlohmann::basic_json j_float = + 1.23e25f; CHECK(j_float.get() == 1.23e25f); // double - nlohmann::basic_json j_double = 1.23e35f; + nlohmann::basic_json j_double = + 1.23e35f; CHECK(j_double.get() == 1.23e35f); // long double - nlohmann::basic_json j_long_double = 1.23e45L; + nlohmann::basic_json + j_long_double = 1.23e45L; CHECK(j_long_double.get() == 1.23e45L); } }