Merge 47acb302d2 into e830bc502f
This commit is contained in:
commit
b8560914e0
1
Makefile
1
Makefile
@ -25,6 +25,7 @@ SRCS = include/nlohmann/json.hpp \
|
||||
include/nlohmann/detail/output/binary_writer.hpp \
|
||||
include/nlohmann/detail/output/output_adapters.hpp \
|
||||
include/nlohmann/detail/output/serializer.hpp \
|
||||
include/nlohmann/detail/output/fancy_serializer.hpp \
|
||||
include/nlohmann/detail/value_t.hpp
|
||||
|
||||
UNAME = $(shell uname)
|
||||
|
||||
232
doc/StyledPrettyPrinting.md
Normal file
232
doc/StyledPrettyPrinting.md
Normal file
@ -0,0 +1,232 @@
|
||||
# Styled Pretty Printing
|
||||
|
||||
The library provides a pretty printer supporting flexible customization of the layout. There are two concepts used by the library:
|
||||
|
||||
* A _style_ defines how printed objects are formatted.
|
||||
* A _stylizer_ determines what style to use, based on the current context being printed.
|
||||
|
||||
At some level, this functionality is analogous to a mix between a source code formatter (like clang-format or astyle) and CSS.
|
||||
|
||||
## Styles -- Uniform Formatting
|
||||
|
||||
If you want the entire document to be formatted the same, all you need to do is provide a single `nlohmann::print_style` instance to the `nlohmann::styled_dump` function. There are three “presets” you can use, `print_style::preset_compact()`, `print_style::preset_one_line()`, and `print_style::multiline()`. When creating a new `print_style` object, the default matches `preset_compact`.
|
||||
|
||||
Here is how to use the presets along with `styled_dump`:
|
||||
|
||||
using nlohmann::json;
|
||||
using nlohmann::print_style;
|
||||
using nlohmann::styled_dump;
|
||||
|
||||
json j = {"foo", 1, 2, 3, false, {{"one", 1}}}
|
||||
|
||||
styled_dump(std::cout, j, print_style::preset_compact());
|
||||
// Result: ["foo",1,2,3,false,{"one":1}]
|
||||
|
||||
styled_dump(std::cout, j, print_style::preset_one_line());
|
||||
// Result: ["foo", 1, 2, 3, false, {"one": 1}]
|
||||
|
||||
styled_dump(std::cout, j, print_style::multiline());
|
||||
// Result (no comment symbols, of course):
|
||||
//[
|
||||
// "foo",
|
||||
// 1,
|
||||
// 2,
|
||||
// 3,
|
||||
// false,
|
||||
// {
|
||||
// "one": 1
|
||||
// }
|
||||
//]
|
||||
|
||||
Styles can, of course, be customized. The following fields are provided:
|
||||
|
||||
### `print_style::indent_step` and `print_style::indent_char`
|
||||
|
||||
These fields provide the step width and character to use for indentation. The step is in number of characters; e.g., don't set `indent_step` 8 and set `indent_char` to `'\t'`.
|
||||
|
||||
This parameter is ignored if no indentation occurs.
|
||||
|
||||
:warning: Currently, it is not possible to change indentation depending on context.
|
||||
|
||||
:question: Is that something we want to allow?
|
||||
|
||||
### `strings_maximum_length`
|
||||
|
||||
This will truncate strings internally that are too long. The value is the maximum length of the string, not counting the quotation marks (which are always printed). It cannot be 0.
|
||||
|
||||
If the string is short enough, it will be displayed in full. Otherwise, space permitting, it will print the longest prefix possible, followed by `...`, followed by the last character of the string. If it is too long even for that, it will leave off the last character, then the prefix entirely, and then start shortening the ellipsis.
|
||||
|
||||
Example:
|
||||
|
||||
print_style style;
|
||||
style.strings_maximum_length = 10;
|
||||
|
||||
json j = "The quick brown fox jumps over the lazy brown dog";
|
||||
styled_dump(std::cout, j, style);
|
||||
// Prints: "The qu...g" (including quotes)
|
||||
|
||||
### :exclamation: TODO: `list_maximum_length`
|
||||
|
||||
This is analogous to `strings_maximum_length`, but for lists. When this option takes effect, the result will not be valid JSON.
|
||||
|
||||
### `depth_limit`
|
||||
|
||||
This option specifies a maximum recursion depth beyond which objects should be elided, replaced with ellipses. A `0` value will print values at the top-level, but compound objects (arrays and objects) will print with ellipses internally.
|
||||
|
||||
Example:
|
||||
|
||||
print_style style = print_style::preset_one_line();
|
||||
style.depth_limit = 1;
|
||||
|
||||
json j = {1, {1}};
|
||||
styled_dump(std::cout, j, style);
|
||||
|
||||
### `space_after_colon`, `space_after_comma`
|
||||
|
||||
These Boolean values specify whether a space should be printed after their respective separators. The setting of these options makes the difference between `preset_compact` and `preset_one_line`.
|
||||
|
||||
:question: [Issue 229](https://github.com/nlohmann/json/issues/229) has a number of comments, and implementation work, with a similar structure to mine, but with strings that had what to print here. So instead of `space_after_colon`, there was a string the user would set to either `":"` or `": "` as appropriate. I could change to that approach, but I think I prefer this one. I am dogmatically enforcing that the setting not result in syntactically-incorrect JSON. Related to `multiline`, next, it means I don't have to look through that string for a `\n` to see if the next line needs to indent. The main thing I can think of here is maybe the user would sometimes want to print *multiple* spaces, or a tab -- but (i) that seems easy enough to add (especially multiple spaces), and (ii) I suspect the main reason to do this is alignment, and that would need to know more about context.
|
||||
|
||||
:question: Another alternative choice I could have made here (I think better-founded) is to combine these into one `space_after_separator`. This would sort of bring this in alignment with `multiline` -- e.g., there is no way (within a single style; it can be done with stylizers, later) to make objects multi-line and arrays single-line.
|
||||
|
||||
### `multiline`
|
||||
|
||||
This indicates whether to print newlines separating list entries or object key/value pairs. It is the difference between `preset_one_line` and `preset_multiline`.
|
||||
|
||||
### :exclamation: TODO: Other future attributes
|
||||
|
||||
Some other things I think could be wanted/useful:
|
||||
|
||||
* `ensure_ascii` -- this is currently a fixed argument passed through the serializers's `dump` function, but should probably be split out here
|
||||
* `array_max_length` -- inspired by [this comment](https://github.com/nlohmann/json/issues/229#issuecomment-300783790), where a user wanted to be able to print a nine-element list as a 3x3 array (three lines, three elements/line). This should be easy to do for simple cases, but I suspect that aligning into columns might be useful and that'd be harder.
|
||||
* `spaces_inside_braces`/`spaces_inside_brackets` -- `[ 1, 2 ]` and `{ "key": 1 }`
|
||||
* Anything that might affect the formatting of numbers?
|
||||
|
||||
## Stylizers -- Flexible Formatting
|
||||
|
||||
You may want to change the style used by the printer depending on the context or the object being printed. Stylizers provide a way to do this. Harking back to the analogy about code formatters and CSS, stylizers implement the CSS portion of that analogy. As the pretty printer performs its recursive traversal, it will query the stylizer to determine what style changes (if any) should be applied to the current subtree. (Like CSS, those changes only apply to that subtree, and are automatically unapplied when the subtree is finished.)
|
||||
|
||||
:exclamation: At the moment, the above description is a bit wrong -- the stylizer doesn't provide *changes*, it provides a complete new style. It's as if for every CSS selector you provide, you have to list all possible CSS attributes or let them be the default value; it can't cascade up from the context, *whatever* that might be. This should be fixed by the final version of this PR. Basically, fields of `print_style` will conceptually become `optional<.>`, where `none` indicates that the value should bubble up from the parent context. (In actuality, I'll be using something other than `optional` that will be both more compact and not be C++17. :-))
|
||||
|
||||
A stylizer stores a list of predicates on the current context and node. As the serializer visits each node in the tree, the stylizer will query each predicate in turn to determine if it wants to style the current node, appyling a corresponding style provided with the predicate if so.
|
||||
|
||||
:exclamation: Because of the prior point, this is also wrong. Right now it just stops at the first predicate that returns `true`, and takes its style. This actually in some sense means it's doing the exact opposite of what it will eventually do -- the *last* predicate to run will be the one that applies.
|
||||
|
||||
:question: Actually now that I type that out, maybe instead of predicates, it should just be a list of functions that are allowed to mutate, if they're interested, the style. Hmmm.
|
||||
|
||||
Each predicate can take either or both of two parameters:
|
||||
|
||||
* A `json_pointer` object providing the context to the current node.
|
||||
* The `json` object (or rather, a `const&` to it) that is the root of the current subtree.
|
||||
|
||||
Predicates and styles are registered with the function `register_style`.
|
||||
|
||||
In addition, there is a convenience function, `register_key_matcher_style`; given a string, this will generate a context predicate that will trigger when the current object is the value of the associated key. For example, given `"foo"`, it will trigger for (and hence style) `[1, 2]` in `{"foo": [1, 2], "bar": 7}`.
|
||||
|
||||
Both `register_style` and `register_key_matcher_style` can be used in a couple different ways, depending on how many changes to the style are needed:
|
||||
|
||||
* If a `print_style` object is already available, it can be passed as a second argument: `stylizer.register_style(pred, my_style)`
|
||||
* `register_style` and `register_key_matcher_style` return a mutable reference to the style that will be used when the predicate matches, so it can be used as, for example, `stylizer.register_style(pred) = print_style::preset_one_line()`
|
||||
* `last_registered_style()` returns a mutable reference to the last style added with a `register` call
|
||||
|
||||
:question: I think I want to remove the mutable reference return thing and remove `last_registered_style` entirely. Those were convenience things from before I had the presets, `last_registered_style` isn't even needed or used in my tests any more, and the second bullet point is only used once in a way that isn't trivial (`stylizer.register_style(...).space_after_comma = true`). Or I could remove `last_registered_style()` but leave the mutable return on `register`. Thoughts?
|
||||
|
||||
The predicate can be any object that is callable as `p(json)`, `p(json_pointer)`, or `p(json_pointer, json)`. (It probably expects a `const&` for each, as mentioned above.)
|
||||
|
||||
A stylizer can be provided a default `print_style` that is used for the root object.
|
||||
|
||||
:question: I feel like there's some uglyness here. To get the overloaded `register_style` functions, I had to do some template hackery to avoid ambiguous calls, *and* to avoid needing to go through two `std::function` calls. ("And" as in if you wanted to do either and keep the ability to pass both `pred(json)` and `pred(json_pointer)` using functions with the same name, you'd hit my problems.) I don't feel confident that this is as well-written as it could be. One problem I ran into was the use of `json_pointer`. (1) I had to add accessors so predicates can to get to the components of it (see examples, below). (2) I'm not happy with those accessors. Should it have `size()` and `[]` too? `begin` and `end`? If yes on `begin` and `end`, should they return `iterator` (and so `json_pointer` now has a mutable API) or `const_iterator`? (3) There are (apparent) implicit conversions both from `json` to `json_pointer` and vice versa, which made my template hackery on `register_style` a lot harder to figure out, though only slightly more complex to actually implement and explain. (Note: the conversion from `json` to `json_pointer` will fail, but it's a hard error and not SFIANE-friendly. Actually, now that I think about it -- disabling that conversion would be very plesant.)
|
||||
|
||||
:question: So one specific idea is to not use `json_pointer`. Another thought I had was that it could pass the entire line of `json` objects (or rather, `const*`) along with the textual path, in case that's useful to anyone.
|
||||
|
||||
Example of `register_key_matcher_style`:
|
||||
|
||||
print_stylizer stylizer(print_style::preset_multiline());
|
||||
stylizer.register_key_matcher_style("one line", print_style::preset_one_line());
|
||||
|
||||
json j = {
|
||||
{"one line", {1, 2}},
|
||||
{"two lines", {1, 2}}
|
||||
};
|
||||
styled_dump(std::cout, j, stylizer);
|
||||
|
||||
// Prints (no comments):
|
||||
//{
|
||||
// "one line": [1,2],
|
||||
// "two lines": [
|
||||
// 1,
|
||||
// 2
|
||||
// ]
|
||||
//}
|
||||
|
||||
Example of `register_style` with a context-based predicate:
|
||||
|
||||
print_stylizer stylizer(print_style::preset_multiline());
|
||||
|
||||
stylizer.register_style(
|
||||
[] (const json_pointer<json>& context)
|
||||
{
|
||||
// Matches if context[-2] is "each elem on one line"
|
||||
// -- so uses preset_one_line for *subobjects* of that.
|
||||
// (register_key_matcher_style would put the list
|
||||
// subobject on one line, but we want elements on multiple
|
||||
// lines.)
|
||||
return (context.cend() - context.cbegin() >= 2)
|
||||
&& (*(context.cend() - 2) == "each elem on one line");
|
||||
}
|
||||
) = print_style::preset_one_line();
|
||||
|
||||
json j = {
|
||||
{
|
||||
"each elem on one line", {
|
||||
{1, 2, 3, 4, 5},
|
||||
{1, 2, 3, 4, 5}
|
||||
},
|
||||
},
|
||||
{
|
||||
"fully multiline", {
|
||||
{1, 2, 3},
|
||||
}
|
||||
};
|
||||
|
||||
styled_dump(std::cout, j, stylizer);
|
||||
// Prints (no comments):
|
||||
//{
|
||||
// "each elem on one line": [
|
||||
// [1, 2, 3, 4, 5],
|
||||
// [1, 2, 3, 4, 5]
|
||||
// ],
|
||||
// "fully multiline": [
|
||||
// [
|
||||
// 1,
|
||||
// 2,
|
||||
// 3
|
||||
// ]
|
||||
// ]
|
||||
//}
|
||||
|
||||
Example of `register_style` with an object predicate:
|
||||
|
||||
print_stylizer stylizer;
|
||||
stylizer.get_default_style() = print_style::preset_multiline;
|
||||
|
||||
stylizer.register_style(
|
||||
[] (const json & j)
|
||||
{
|
||||
return j.type() == json::value_t::array;
|
||||
}
|
||||
) = print_style::preset_one_line;
|
||||
|
||||
json j = {
|
||||
{"an array", {1, 2, 3}},
|
||||
{"an object", {{"key", "val"}}}
|
||||
};
|
||||
|
||||
styled_dump(std::cout, j, stylizer);
|
||||
// Prints (no comment):
|
||||
//{
|
||||
// "an array": [1, 2, 3],
|
||||
// "an object": {
|
||||
// "key": "val"
|
||||
// }
|
||||
//}
|
||||
@ -196,7 +196,7 @@ boundaries compute_boundaries(FloatType value)
|
||||
constexpr int kMinExp = 1 - kBias;
|
||||
constexpr uint64_t kHiddenBit = uint64_t{1} << (kPrecision - 1); // = 2^(p-1)
|
||||
|
||||
using bits_type = typename std::conditional< kPrecision == 24, uint32_t, uint64_t >::type;
|
||||
using bits_type = typename std::conditional<kPrecision == 24, uint32_t, uint64_t>::type;
|
||||
|
||||
const uint64_t bits = reinterpret_bits<bits_type>(value);
|
||||
const uint64_t E = bits >> (kPrecision - 1);
|
||||
@ -607,7 +607,10 @@ inline void grisu2_digit_gen(char* buffer, int& length, int& decimal_exponent,
|
||||
// = ((p1 ) * 2^-e + (p2 )) * 2^e
|
||||
// = p1 + p2 * 2^e
|
||||
|
||||
const diyfp one(uint64_t{1} << -M_plus.e, M_plus.e);
|
||||
const diyfp one(uint64_t
|
||||
{
|
||||
1
|
||||
} << -M_plus.e, M_plus.e);
|
||||
|
||||
uint32_t p1 = static_cast<uint32_t>(M_plus.f >> -one.e); // p1 = f div 2^-e (Since -e >= 32, p1 fits into a 32-bit int.)
|
||||
uint64_t p2 = M_plus.f & (one.f - 1); // p2 = f mod 2^-e
|
||||
|
||||
@ -19,6 +19,9 @@ class json_pointer
|
||||
friend class basic_json;
|
||||
|
||||
public:
|
||||
typedef std::vector<std::string>::const_iterator const_iterator;
|
||||
typedef std::vector<std::string>::const_reverse_iterator const_reverse_iterator;
|
||||
|
||||
/*!
|
||||
@brief create JSON pointer
|
||||
|
||||
@ -75,6 +78,38 @@ class json_pointer
|
||||
return to_string();
|
||||
}
|
||||
|
||||
const_iterator cbegin() const
|
||||
{
|
||||
return reference_tokens.cbegin();
|
||||
}
|
||||
|
||||
const_iterator cend() const
|
||||
{
|
||||
return reference_tokens.cend();
|
||||
}
|
||||
|
||||
const_reverse_iterator crbegin() const
|
||||
{
|
||||
return reference_tokens.crbegin();
|
||||
}
|
||||
|
||||
const_reverse_iterator crend() const
|
||||
{
|
||||
return reference_tokens.crend();
|
||||
}
|
||||
|
||||
json_pointer appended(std::string const& next) const
|
||||
{
|
||||
json_pointer copy(*this);
|
||||
copy.reference_tokens.push_back(next);
|
||||
return copy;
|
||||
}
|
||||
|
||||
json_pointer appended(size_t next) const
|
||||
{
|
||||
return appended(std::to_string(next));
|
||||
}
|
||||
|
||||
/*!
|
||||
@param[in] s reference token to be converted into an array index
|
||||
|
||||
|
||||
552
include/nlohmann/detail/output/fancy_serializer.hpp
Normal file
552
include/nlohmann/detail/output/fancy_serializer.hpp
Normal file
@ -0,0 +1,552 @@
|
||||
#pragma once
|
||||
|
||||
#include <algorithm> // reverse, remove, fill, find, none_of
|
||||
#include <array> // array
|
||||
#include <cassert> // assert
|
||||
#include <ciso646> // and, or
|
||||
#include <clocale> // localeconv, lconv
|
||||
#include <cmath> // labs, isfinite, isnan, signbit
|
||||
#include <cstddef> // size_t, ptrdiff_t
|
||||
#include <cstdint> // uint8_t
|
||||
#include <cstdio> // snprintf
|
||||
#include <limits> // numeric_limits
|
||||
#include <string> // string
|
||||
#include <type_traits> // is_same
|
||||
#include <map>
|
||||
#include <sstream>
|
||||
#include <functional>
|
||||
#include <vector>
|
||||
|
||||
#include <nlohmann/detail/exceptions.hpp>
|
||||
#include <nlohmann/detail/conversions/to_chars.hpp>
|
||||
#include <nlohmann/detail/macro_scope.hpp>
|
||||
#include <nlohmann/detail/meta.hpp>
|
||||
#include <nlohmann/detail/output/output_adapters.hpp>
|
||||
#include <nlohmann/detail/value_t.hpp>
|
||||
#include <nlohmann/detail/output/primitive_serializer.hpp>
|
||||
#include <nlohmann/detail/json_pointer.hpp>
|
||||
|
||||
namespace nlohmann
|
||||
{
|
||||
|
||||
namespace details
|
||||
{
|
||||
// Some metaprogramming stuff. The point here is to distinguish
|
||||
// functions and function objects that take 'json' and
|
||||
// 'json_pointer<json>' as the first argument. This can't be done
|
||||
// conventionally because there are implicit conversions in both
|
||||
// directions, so a function type that matches one will match the
|
||||
// other. (The conversion from json to json_pointer doesn't really
|
||||
// exist if you try to use it, but it does in the SFIANE context.)
|
||||
//
|
||||
// So we define takes_argument<Func, Arg> to see if Func(Arg) is
|
||||
// not only legal but without undergoing any conversions on
|
||||
// Arg. That's where 'metawrapper' comes into play. We actually
|
||||
// check if Func(metawrapper<Arg>) is legal. That takes up the one
|
||||
// implicit conversion that's allowed.
|
||||
//
|
||||
// See also the uses below.
|
||||
|
||||
template<typename... Ts> struct make_void
|
||||
{
|
||||
typedef void type;
|
||||
};
|
||||
template<typename... Ts> using void_t = typename make_void<Ts...>::type;
|
||||
|
||||
template <typename T>
|
||||
struct metawrapper
|
||||
{
|
||||
operator T const& ();
|
||||
};
|
||||
|
||||
template <typename = void, typename F = void, typename ...Args>
|
||||
struct takes_arguments_impl : std::false_type { };
|
||||
|
||||
template <typename F, typename ...Args>
|
||||
struct takes_arguments_impl<void_t<decltype(std::declval<F>()(metawrapper<Args>()...))>, F, Args...> : std::true_type { };
|
||||
|
||||
template<typename F, typename ...Args>
|
||||
struct takes_arguments : takes_arguments_impl<void, F, Args...> { };
|
||||
}
|
||||
|
||||
struct print_style
|
||||
{
|
||||
unsigned int indent_step = 4;
|
||||
char indent_char = ' ';
|
||||
|
||||
unsigned int depth_limit = std::numeric_limits<unsigned>::max();
|
||||
|
||||
unsigned int strings_maximum_length = 0;
|
||||
|
||||
bool space_after_colon = false;
|
||||
bool space_after_comma = false;
|
||||
|
||||
bool multiline = false;
|
||||
|
||||
print_style() = default;
|
||||
|
||||
print_style(bool s_colon, bool s_comma, bool ml)
|
||||
: space_after_colon(s_colon), space_after_comma(s_comma), multiline(ml)
|
||||
{}
|
||||
|
||||
static const print_style preset_compact;
|
||||
static const print_style preset_one_line;
|
||||
static const print_style preset_multiline;
|
||||
};
|
||||
|
||||
const print_style print_style::preset_compact(false, false, false);
|
||||
const print_style print_style::preset_one_line(true, true, false);
|
||||
const print_style print_style::preset_multiline(true, true, true);
|
||||
|
||||
template<typename BasicJsonType>
|
||||
class basic_print_stylizer
|
||||
{
|
||||
public:
|
||||
using string_t = typename BasicJsonType::string_t;
|
||||
using json_pointer_t = json_pointer<BasicJsonType>;
|
||||
|
||||
using json_matcher_predicate = std::function<bool (const BasicJsonType&)>;
|
||||
using context_matcher_predicate = std::function<bool (const json_pointer_t&)>;
|
||||
using matcher_predicate = std::function<bool (const json_pointer_t&, const BasicJsonType&)>;
|
||||
|
||||
basic_print_stylizer(print_style const& ds)
|
||||
: default_style(ds)
|
||||
{}
|
||||
|
||||
basic_print_stylizer() = default;
|
||||
|
||||
public:
|
||||
const print_style& get_default_style() const
|
||||
{
|
||||
return default_style;
|
||||
}
|
||||
|
||||
print_style& get_default_style()
|
||||
{
|
||||
return default_style;
|
||||
}
|
||||
|
||||
const print_style* get_new_style_or_active(
|
||||
const json_pointer_t& pointer,
|
||||
const json& j,
|
||||
const print_style* active_style) const
|
||||
{
|
||||
for (auto const& pair : styles)
|
||||
{
|
||||
if (pair.first(pointer, j))
|
||||
{
|
||||
return &pair.second;
|
||||
}
|
||||
}
|
||||
return active_style;
|
||||
}
|
||||
|
||||
print_style& register_style(
|
||||
matcher_predicate p,
|
||||
print_style style = print_style())
|
||||
{
|
||||
styles.emplace_back(p, style);
|
||||
return styles.back().second;
|
||||
}
|
||||
|
||||
// Predicate is conceptually 'bool (json)' here
|
||||
template <typename Predicate>
|
||||
auto register_style(
|
||||
Predicate p,
|
||||
print_style style = print_style())
|
||||
-> typename std::enable_if<details::takes_arguments<Predicate, BasicJsonType>::value, print_style&>::type
|
||||
{
|
||||
auto wrapper = [p](const json_pointer_t&, const BasicJsonType & j)
|
||||
{
|
||||
return p(j);
|
||||
};
|
||||
styles.emplace_back(wrapper, style);
|
||||
return styles.back().second;
|
||||
}
|
||||
|
||||
// Predicate is conceptually 'bool (json_pointer)' here...
|
||||
//
|
||||
// ...But we have to 'json' instead (or rather, BasicJsonType)
|
||||
// because json has an apparent (in the SFIANE context) implicit
|
||||
// conversion from and two everything. Including
|
||||
// 'metawrapper<json_pointer>'. So if you pass 'bool (json)', it
|
||||
// will look like it can pass a metawrapper<json_pointer> to it
|
||||
template <typename Predicate>
|
||||
auto register_style(
|
||||
Predicate p,
|
||||
print_style style = print_style())
|
||||
-> typename std::enable_if < !details::takes_arguments<Predicate, BasicJsonType>::value, print_style& >::type
|
||||
{
|
||||
auto wrapper = [p](const json_pointer_t& c, const BasicJsonType&)
|
||||
{
|
||||
return p(c);
|
||||
};
|
||||
styles.emplace_back(wrapper, style);
|
||||
return styles.back().second;
|
||||
}
|
||||
|
||||
print_style& register_key_matcher_style(
|
||||
string_t str,
|
||||
print_style style = print_style())
|
||||
{
|
||||
return register_style([str](const json_pointer_t& pointer)
|
||||
{
|
||||
return (pointer.cbegin() != pointer.cend())
|
||||
&& (*pointer.crbegin() == str);
|
||||
},
|
||||
style);
|
||||
}
|
||||
|
||||
print_style& last_registered_style()
|
||||
{
|
||||
return styles.back().second;
|
||||
}
|
||||
|
||||
private:
|
||||
print_style default_style;
|
||||
std::vector<std::pair<matcher_predicate, print_style>> styles;
|
||||
};
|
||||
|
||||
namespace detail
|
||||
{
|
||||
///////////////////
|
||||
// serialization //
|
||||
///////////////////
|
||||
|
||||
template<typename BasicJsonType>
|
||||
class styled_serializer
|
||||
{
|
||||
using stylizer_t = basic_print_stylizer<BasicJsonType>;
|
||||
using primitive_serializer_t = primitive_serializer<BasicJsonType>;
|
||||
using string_t = typename BasicJsonType::string_t;
|
||||
using number_float_t = typename BasicJsonType::number_float_t;
|
||||
using number_integer_t = typename BasicJsonType::number_integer_t;
|
||||
using number_unsigned_t = typename BasicJsonType::number_unsigned_t;
|
||||
using json_pointer_t = json_pointer<BasicJsonType>;
|
||||
static constexpr uint8_t UTF8_ACCEPT = 0;
|
||||
static constexpr uint8_t UTF8_REJECT = 1;
|
||||
|
||||
public:
|
||||
/*!
|
||||
@param[in] s output stream to serialize to
|
||||
@param[in] ichar indentation character to use
|
||||
*/
|
||||
styled_serializer(output_adapter_t<char> s,
|
||||
const stylizer_t& st)
|
||||
: o(std::move(s)), stylizer(st),
|
||||
indent_string(512, st.get_default_style().indent_char)
|
||||
{}
|
||||
|
||||
// delete because of pointer members
|
||||
styled_serializer(const styled_serializer&) = delete;
|
||||
styled_serializer& operator=(const styled_serializer&) = delete;
|
||||
|
||||
void dump(const BasicJsonType& val, const bool ensure_ascii)
|
||||
{
|
||||
dump(val, ensure_ascii, 0, &stylizer.get_default_style(), json_pointer_t());
|
||||
}
|
||||
|
||||
private:
|
||||
/*!
|
||||
@brief internal implementation of the serialization function
|
||||
|
||||
This function is called by the public member function dump and organizes
|
||||
the serialization internally. The indentation level is propagated as
|
||||
additional parameter. In case of arrays and objects, the function is
|
||||
called recursively.
|
||||
|
||||
- strings and object keys are escaped using `escape_string()`
|
||||
- integer numbers are converted implicitly via `operator<<`
|
||||
- floating-point numbers are converted to a string using `"%g"` format
|
||||
|
||||
@param[in] val value to serialize
|
||||
@param[in] pretty_print whether the output shall be pretty-printed
|
||||
@param[in] depth the current recursive depth
|
||||
*/
|
||||
void dump(const BasicJsonType& val,
|
||||
const bool ensure_ascii,
|
||||
const unsigned int depth,
|
||||
const print_style* active_style,
|
||||
const json_pointer_t& context)
|
||||
{
|
||||
active_style = stylizer.get_new_style_or_active(context, val, active_style);
|
||||
|
||||
switch (val.m_type)
|
||||
{
|
||||
case value_t::object:
|
||||
{
|
||||
dump_object(val, ensure_ascii, depth, active_style, context);
|
||||
return;
|
||||
}
|
||||
|
||||
case value_t::array:
|
||||
{
|
||||
dump_array(val, ensure_ascii, depth, active_style, context);
|
||||
return;
|
||||
}
|
||||
|
||||
case value_t::string:
|
||||
{
|
||||
dump_string(*val.m_value.string, ensure_ascii, active_style);
|
||||
return;
|
||||
}
|
||||
|
||||
case value_t::boolean:
|
||||
{
|
||||
if (val.m_value.boolean)
|
||||
{
|
||||
o->write_characters("true", 4);
|
||||
}
|
||||
else
|
||||
{
|
||||
o->write_characters("false", 5);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
case value_t::number_integer:
|
||||
{
|
||||
prim_serializer.dump_integer(*o, val.m_value.number_integer);
|
||||
return;
|
||||
}
|
||||
|
||||
case value_t::number_unsigned:
|
||||
{
|
||||
prim_serializer.dump_integer(*o, val.m_value.number_unsigned);
|
||||
return;
|
||||
}
|
||||
|
||||
case value_t::number_float:
|
||||
{
|
||||
prim_serializer.dump_float(*o, val.m_value.number_float);
|
||||
return;
|
||||
}
|
||||
|
||||
case value_t::discarded:
|
||||
{
|
||||
o->write_characters("<discarded>", 11);
|
||||
return;
|
||||
}
|
||||
|
||||
case value_t::null:
|
||||
{
|
||||
o->write_characters("null", 4);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
template <typename Iterator>
|
||||
void dump_object_key_value(
|
||||
Iterator i, bool ensure_ascii, unsigned int depth,
|
||||
const print_style* active_style,
|
||||
const json_pointer_t& context)
|
||||
{
|
||||
const auto new_indent = (depth + 1) * active_style->indent_step * active_style->multiline;
|
||||
const int newline_len = active_style->space_after_colon;
|
||||
|
||||
o->write_characters(indent_string.c_str(), new_indent);
|
||||
o->write_character('\"');
|
||||
prim_serializer.dump_escaped(*o, i->first, ensure_ascii);
|
||||
o->write_characters("\": ", 2 + newline_len);
|
||||
dump(i->second, ensure_ascii, depth + 1, active_style, context.appended(i->first));
|
||||
}
|
||||
|
||||
void dump_object(const BasicJsonType& val,
|
||||
bool ensure_ascii,
|
||||
unsigned int depth,
|
||||
const print_style* active_style,
|
||||
const json_pointer_t& context)
|
||||
{
|
||||
if (val.m_value.object->empty())
|
||||
{
|
||||
o->write_characters("{}", 2);
|
||||
return;
|
||||
}
|
||||
else if (depth >= active_style->depth_limit)
|
||||
{
|
||||
o->write_characters("{...}", 5);
|
||||
return;
|
||||
}
|
||||
|
||||
// variable to hold indentation for recursive calls
|
||||
const auto old_indent = depth * active_style->indent_step * active_style->multiline;
|
||||
const auto new_indent = (depth + 1) * active_style->indent_step * active_style->multiline;
|
||||
if (JSON_UNLIKELY(indent_string.size() < new_indent))
|
||||
{
|
||||
indent_string.resize(indent_string.size() * 2, active_style->indent_char);
|
||||
}
|
||||
const int newline_len = (active_style->multiline ? 1 : 0);
|
||||
|
||||
o->write_characters("{\n", 1 + newline_len);
|
||||
|
||||
// first n-1 elements
|
||||
auto i = val.m_value.object->cbegin();
|
||||
for (std::size_t cnt = 0; cnt < val.m_value.object->size() - 1; ++cnt, ++i)
|
||||
{
|
||||
dump_object_key_value(i, ensure_ascii, depth, active_style, context);
|
||||
o->write_characters(",\n", 1 + newline_len);
|
||||
}
|
||||
|
||||
// last element
|
||||
assert(i != val.m_value.object->cend());
|
||||
assert(std::next(i) == val.m_value.object->cend());
|
||||
dump_object_key_value(i, ensure_ascii, depth, active_style, context);
|
||||
|
||||
o->write_characters("\n", newline_len);
|
||||
o->write_characters(indent_string.c_str(), old_indent);
|
||||
o->write_character('}');
|
||||
}
|
||||
|
||||
void dump_array(const BasicJsonType& val,
|
||||
bool ensure_ascii,
|
||||
unsigned int depth,
|
||||
const print_style* active_style,
|
||||
const json_pointer_t& context)
|
||||
{
|
||||
if (val.m_value.array->empty())
|
||||
{
|
||||
o->write_characters("[]", 2);
|
||||
return;
|
||||
}
|
||||
else if (depth >= active_style->depth_limit)
|
||||
{
|
||||
o->write_characters("[...]", 5);
|
||||
return;
|
||||
}
|
||||
|
||||
// variable to hold indentation for recursive calls
|
||||
const auto old_indent = depth * active_style->indent_step * active_style->multiline;;
|
||||
const auto new_indent = (depth + 1) * active_style->indent_step * active_style->multiline;;
|
||||
if (JSON_UNLIKELY(indent_string.size() < new_indent))
|
||||
{
|
||||
indent_string.resize(indent_string.size() * 2, active_style->indent_char);
|
||||
}
|
||||
const int newline_len = (active_style->multiline ? 1 : 0);
|
||||
|
||||
using pair = std::pair<const char*, int>;
|
||||
auto comma_string =
|
||||
active_style->multiline ? pair(",\n", 2) :
|
||||
active_style->space_after_comma ? pair(", ", 2) :
|
||||
pair(",", 1);
|
||||
|
||||
o->write_characters("[\n", 1 + newline_len);
|
||||
|
||||
// first n-1 elements
|
||||
for (auto i = val.m_value.array->cbegin();
|
||||
i != val.m_value.array->cend() - 1; ++i)
|
||||
{
|
||||
o->write_characters(indent_string.c_str(), new_indent);
|
||||
dump(*i, ensure_ascii, depth + 1, active_style,
|
||||
context.appended(i - val.m_value.array->cbegin()));
|
||||
o->write_characters(comma_string.first, comma_string.second);
|
||||
}
|
||||
|
||||
// last element
|
||||
assert(not val.m_value.array->empty());
|
||||
o->write_characters(indent_string.c_str(), new_indent);
|
||||
dump(val.m_value.array->back(), ensure_ascii, depth + 1, active_style,
|
||||
context.appended(val.m_value.array->size()));
|
||||
|
||||
o->write_characters("\n", newline_len);
|
||||
o->write_characters(indent_string.c_str(), old_indent);
|
||||
o->write_character(']');
|
||||
}
|
||||
|
||||
void dump_string(const string_t& str, bool ensure_ascii,
|
||||
const print_style* active_style)
|
||||
{
|
||||
o->write_character('\"');
|
||||
if (active_style->strings_maximum_length == 0)
|
||||
{
|
||||
prim_serializer.dump_escaped(*o, str, ensure_ascii);
|
||||
}
|
||||
else
|
||||
{
|
||||
std::stringstream ss;
|
||||
nlohmann::detail::output_adapter<char> o_string(ss);
|
||||
nlohmann::detail::output_adapter_t<char> oo_string = o_string;
|
||||
prim_serializer.dump_escaped(*oo_string, str, ensure_ascii);
|
||||
|
||||
std::string full_str = ss.str();
|
||||
if (full_str.size() <= active_style->strings_maximum_length)
|
||||
{
|
||||
o->write_characters(full_str.c_str(), full_str.size());
|
||||
}
|
||||
else
|
||||
{
|
||||
const unsigned start_len = [](unsigned int maxl)
|
||||
{
|
||||
if (maxl <= 3)
|
||||
{
|
||||
// There is only room for the ellipsis,
|
||||
// no characters from the string
|
||||
return 0u;
|
||||
}
|
||||
else if (maxl <= 5)
|
||||
{
|
||||
// With four allowed characters, we add in the
|
||||
// first from the string. With five, we add in
|
||||
// the *last* instead, so still just one at
|
||||
// the start.
|
||||
return 1u;
|
||||
}
|
||||
else
|
||||
{
|
||||
// We subtract three for the ellipsis
|
||||
// and one for the last character.
|
||||
return maxl - 4;
|
||||
}
|
||||
}(active_style->strings_maximum_length);
|
||||
|
||||
const unsigned end_len =
|
||||
active_style->strings_maximum_length >= 5 ? 1 : 0;
|
||||
|
||||
const unsigned ellipsis_length =
|
||||
active_style->strings_maximum_length >= 3
|
||||
? 3
|
||||
: active_style->strings_maximum_length;
|
||||
|
||||
o->write_characters(full_str.c_str(), start_len);
|
||||
o->write_characters("...", ellipsis_length);
|
||||
o->write_characters(full_str.c_str() + str.size() - end_len, end_len);
|
||||
}
|
||||
}
|
||||
o->write_character('\"');
|
||||
}
|
||||
|
||||
private:
|
||||
/// the output of the styled_serializer
|
||||
output_adapter_t<char> o = nullptr;
|
||||
|
||||
/// Used for serializing "base" objects. Strings are sort of
|
||||
/// counted in this, but not completely.
|
||||
primitive_serializer_t prim_serializer;
|
||||
|
||||
/// the indentation string
|
||||
string_t indent_string;
|
||||
|
||||
/// Output style
|
||||
const stylizer_t stylizer;
|
||||
};
|
||||
}
|
||||
|
||||
template<typename BasicJsonType>
|
||||
std::ostream& styled_dump(std::ostream& o, const BasicJsonType& j,
|
||||
basic_print_stylizer<BasicJsonType> const& stylizer)
|
||||
{
|
||||
// do the actual serialization
|
||||
detail::styled_serializer<BasicJsonType> s(detail::output_adapter<char>(o), stylizer);
|
||||
s.dump(j, false);
|
||||
return o;
|
||||
}
|
||||
|
||||
template<typename BasicJsonType>
|
||||
std::ostream& styled_dump(std::ostream& o, const BasicJsonType& j, print_style style)
|
||||
{
|
||||
basic_print_stylizer<BasicJsonType> stylizer(style);
|
||||
return styled_dump(o, j, stylizer);
|
||||
}
|
||||
|
||||
}
|
||||
414
include/nlohmann/detail/output/primitive_serializer.hpp
Normal file
414
include/nlohmann/detail/output/primitive_serializer.hpp
Normal file
@ -0,0 +1,414 @@
|
||||
#pragma once
|
||||
|
||||
#include <algorithm> // reverse, remove, fill, find, none_of
|
||||
#include <array> // array
|
||||
#include <cassert> // assert
|
||||
#include <ciso646> // and, or
|
||||
#include <clocale> // localeconv, lconv
|
||||
#include <cmath> // labs, isfinite, isnan, signbit
|
||||
#include <cstddef> // size_t, ptrdiff_t
|
||||
#include <cstdint> // uint8_t
|
||||
#include <cstdio> // snprintf
|
||||
#include <limits> // numeric_limits
|
||||
#include <string> // string
|
||||
#include <type_traits> // is_same
|
||||
|
||||
#include <nlohmann/detail/exceptions.hpp>
|
||||
#include <nlohmann/detail/conversions/to_chars.hpp>
|
||||
#include <nlohmann/detail/macro_scope.hpp>
|
||||
#include <nlohmann/detail/meta.hpp>
|
||||
#include <nlohmann/detail/output/output_adapters.hpp>
|
||||
#include <nlohmann/detail/value_t.hpp>
|
||||
|
||||
namespace nlohmann
|
||||
{
|
||||
namespace detail
|
||||
{
|
||||
///////////////////
|
||||
// serialization //
|
||||
///////////////////
|
||||
|
||||
template<typename BasicJsonType>
|
||||
class primitive_serializer
|
||||
{
|
||||
using string_t = typename BasicJsonType::string_t;
|
||||
using number_float_t = typename BasicJsonType::number_float_t;
|
||||
using number_integer_t = typename BasicJsonType::number_integer_t;
|
||||
using number_unsigned_t = typename BasicJsonType::number_unsigned_t;
|
||||
static constexpr uint8_t UTF8_ACCEPT = 0;
|
||||
static constexpr uint8_t UTF8_REJECT = 1;
|
||||
using output_adapter_protocol_t = output_adapter_protocol<char>;
|
||||
|
||||
public:
|
||||
/*!
|
||||
@param[in] s output stream to serialize to
|
||||
@param[in] ichar indentation character to use
|
||||
*/
|
||||
primitive_serializer()
|
||||
: loc(std::localeconv()),
|
||||
thousands_sep(loc->thousands_sep == nullptr ? '\0' : * (loc->thousands_sep)),
|
||||
decimal_point(loc->decimal_point == nullptr ? '\0' : * (loc->decimal_point))
|
||||
{}
|
||||
|
||||
// delete because of pointer members
|
||||
primitive_serializer(const primitive_serializer&) = delete;
|
||||
primitive_serializer& operator=(const primitive_serializer&) = delete;
|
||||
|
||||
/*!
|
||||
@brief dump escaped string
|
||||
|
||||
Escape a string by replacing certain special characters by a sequence of an
|
||||
escape character (backslash) and another character and other control
|
||||
characters by a sequence of "\u" followed by a four-digit hex
|
||||
representation. The escaped string is written to output stream @a o.
|
||||
|
||||
@param[in] s the string to escape
|
||||
@param[in] ensure_ascii whether to escape non-ASCII characters with
|
||||
\uXXXX sequences
|
||||
|
||||
@complexity Linear in the length of string @a s.
|
||||
*/
|
||||
void dump_escaped(output_adapter_protocol_t& o, const string_t& s, const bool ensure_ascii)
|
||||
{
|
||||
uint32_t codepoint;
|
||||
uint8_t state = UTF8_ACCEPT;
|
||||
std::size_t bytes = 0; // number of bytes written to string_buffer
|
||||
|
||||
for (std::size_t i = 0; i < s.size(); ++i)
|
||||
{
|
||||
const auto byte = static_cast<uint8_t>(s[i]);
|
||||
|
||||
switch (decode(state, codepoint, byte))
|
||||
{
|
||||
case UTF8_ACCEPT: // decode found a new code point
|
||||
{
|
||||
switch (codepoint)
|
||||
{
|
||||
case 0x08: // backspace
|
||||
{
|
||||
string_buffer[bytes++] = '\\';
|
||||
string_buffer[bytes++] = 'b';
|
||||
break;
|
||||
}
|
||||
|
||||
case 0x09: // horizontal tab
|
||||
{
|
||||
string_buffer[bytes++] = '\\';
|
||||
string_buffer[bytes++] = 't';
|
||||
break;
|
||||
}
|
||||
|
||||
case 0x0A: // newline
|
||||
{
|
||||
string_buffer[bytes++] = '\\';
|
||||
string_buffer[bytes++] = 'n';
|
||||
break;
|
||||
}
|
||||
|
||||
case 0x0C: // formfeed
|
||||
{
|
||||
string_buffer[bytes++] = '\\';
|
||||
string_buffer[bytes++] = 'f';
|
||||
break;
|
||||
}
|
||||
|
||||
case 0x0D: // carriage return
|
||||
{
|
||||
string_buffer[bytes++] = '\\';
|
||||
string_buffer[bytes++] = 'r';
|
||||
break;
|
||||
}
|
||||
|
||||
case 0x22: // quotation mark
|
||||
{
|
||||
string_buffer[bytes++] = '\\';
|
||||
string_buffer[bytes++] = '\"';
|
||||
break;
|
||||
}
|
||||
|
||||
case 0x5C: // reverse solidus
|
||||
{
|
||||
string_buffer[bytes++] = '\\';
|
||||
string_buffer[bytes++] = '\\';
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
{
|
||||
// escape control characters (0x00..0x1F) or, if
|
||||
// ensure_ascii parameter is used, non-ASCII characters
|
||||
if ((codepoint <= 0x1F) or (ensure_ascii and (codepoint >= 0x7F)))
|
||||
{
|
||||
if (codepoint <= 0xFFFF)
|
||||
{
|
||||
std::snprintf(string_buffer.data() + bytes, 7, "\\u%04x",
|
||||
static_cast<uint16_t>(codepoint));
|
||||
bytes += 6;
|
||||
}
|
||||
else
|
||||
{
|
||||
std::snprintf(string_buffer.data() + bytes, 13, "\\u%04x\\u%04x",
|
||||
static_cast<uint16_t>(0xD7C0 + (codepoint >> 10)),
|
||||
static_cast<uint16_t>(0xDC00 + (codepoint & 0x3FF)));
|
||||
bytes += 12;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// copy byte to buffer (all previous bytes
|
||||
// been copied have in default case above)
|
||||
string_buffer[bytes++] = s[i];
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// write buffer and reset index; there must be 13 bytes
|
||||
// left, as this is the maximal number of bytes to be
|
||||
// written ("\uxxxx\uxxxx\0") for one code point
|
||||
if (string_buffer.size() - bytes < 13)
|
||||
{
|
||||
o.write_characters(string_buffer.data(), bytes);
|
||||
bytes = 0;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case UTF8_REJECT: // decode found invalid UTF-8 byte
|
||||
{
|
||||
std::string sn(3, '\0');
|
||||
snprintf(&sn[0], sn.size(), "%.2X", byte);
|
||||
JSON_THROW(type_error::create(316, "invalid UTF-8 byte at index " + std::to_string(i) + ": 0x" + sn));
|
||||
}
|
||||
|
||||
default: // decode found yet incomplete multi-byte code point
|
||||
{
|
||||
if (not ensure_ascii)
|
||||
{
|
||||
// code point will not be escaped - copy byte to buffer
|
||||
string_buffer[bytes++] = s[i];
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (JSON_LIKELY(state == UTF8_ACCEPT))
|
||||
{
|
||||
// write buffer
|
||||
if (bytes > 0)
|
||||
{
|
||||
o.write_characters(string_buffer.data(), bytes);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// we finish reading, but do not accept: string was incomplete
|
||||
std::string sn(3, '\0');
|
||||
snprintf(&sn[0], sn.size(), "%.2X", static_cast<uint8_t>(s.back()));
|
||||
JSON_THROW(type_error::create(316, "incomplete UTF-8 string; last byte: 0x" + sn));
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief dump an integer
|
||||
|
||||
Dump a given integer to output stream @a o. Works internally with
|
||||
@a number_buffer.
|
||||
|
||||
@param[in] x integer number (signed or unsigned) to dump
|
||||
@tparam NumberType either @a number_integer_t or @a number_unsigned_t
|
||||
*/
|
||||
template<typename NumberType, detail::enable_if_t<
|
||||
std::is_same<NumberType, number_unsigned_t>::value or
|
||||
std::is_same<NumberType, number_integer_t>::value,
|
||||
int> = 0>
|
||||
void dump_integer(output_adapter_protocol_t& o, NumberType x)
|
||||
{
|
||||
// special case for "0"
|
||||
if (x == 0)
|
||||
{
|
||||
o.write_character('0');
|
||||
return;
|
||||
}
|
||||
|
||||
const bool is_negative = (x <= 0) and (x != 0); // see issue #755
|
||||
std::size_t i = 0;
|
||||
|
||||
while (x != 0)
|
||||
{
|
||||
// spare 1 byte for '\0'
|
||||
assert(i < number_buffer.size() - 1);
|
||||
|
||||
const auto digit = std::labs(static_cast<long>(x % 10));
|
||||
number_buffer[i++] = static_cast<char>('0' + digit);
|
||||
x /= 10;
|
||||
}
|
||||
|
||||
if (is_negative)
|
||||
{
|
||||
// make sure there is capacity for the '-'
|
||||
assert(i < number_buffer.size() - 2);
|
||||
number_buffer[i++] = '-';
|
||||
}
|
||||
|
||||
std::reverse(number_buffer.begin(), number_buffer.begin() + i);
|
||||
o.write_characters(number_buffer.data(), i);
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief dump a floating-point number
|
||||
|
||||
Dump a given floating-point number to output stream @a o. Works internally
|
||||
with @a number_buffer.
|
||||
|
||||
@param[in] x floating-point number to dump
|
||||
*/
|
||||
void dump_float(output_adapter_protocol_t& o, number_float_t x)
|
||||
{
|
||||
// NaN / inf
|
||||
if (not std::isfinite(x))
|
||||
{
|
||||
o.write_characters("null", 4);
|
||||
return;
|
||||
}
|
||||
|
||||
// If number_float_t is an IEEE-754 single or double precision number,
|
||||
// use the Grisu2 algorithm to produce short numbers which are
|
||||
// guaranteed to round-trip, using strtof and strtod, resp.
|
||||
//
|
||||
// NB: The test below works if <long double> == <double>.
|
||||
static constexpr bool is_ieee_single_or_double
|
||||
= (std::numeric_limits<number_float_t>::is_iec559 and std::numeric_limits<number_float_t>::digits == 24 and std::numeric_limits<number_float_t>::max_exponent == 128) or
|
||||
(std::numeric_limits<number_float_t>::is_iec559 and std::numeric_limits<number_float_t>::digits == 53 and std::numeric_limits<number_float_t>::max_exponent == 1024);
|
||||
|
||||
dump_float(o, x, std::integral_constant<bool, is_ieee_single_or_double>());
|
||||
}
|
||||
|
||||
private:
|
||||
void dump_float(output_adapter_protocol_t& o, number_float_t x, std::true_type /*is_ieee_single_or_double*/)
|
||||
{
|
||||
char* begin = number_buffer.data();
|
||||
char* end = ::nlohmann::detail::to_chars(begin, begin + number_buffer.size(), x);
|
||||
|
||||
o.write_characters(begin, static_cast<size_t>(end - begin));
|
||||
}
|
||||
|
||||
void dump_float(output_adapter_protocol_t& o, number_float_t x, std::false_type /*is_ieee_single_or_double*/)
|
||||
{
|
||||
// get number of digits for a float -> text -> float round-trip
|
||||
static constexpr auto d = std::numeric_limits<number_float_t>::max_digits10;
|
||||
|
||||
// the actual conversion
|
||||
std::ptrdiff_t len = snprintf(number_buffer.data(), number_buffer.size(), "%.*g", d, x);
|
||||
|
||||
// negative value indicates an error
|
||||
assert(len > 0);
|
||||
// check if buffer was large enough
|
||||
assert(static_cast<std::size_t>(len) < number_buffer.size());
|
||||
|
||||
// erase thousands separator
|
||||
if (thousands_sep != '\0')
|
||||
{
|
||||
const auto end = std::remove(number_buffer.begin(),
|
||||
number_buffer.begin() + len, thousands_sep);
|
||||
std::fill(end, number_buffer.end(), '\0');
|
||||
assert((end - number_buffer.begin()) <= len);
|
||||
len = (end - number_buffer.begin());
|
||||
}
|
||||
|
||||
// convert decimal point to '.'
|
||||
if (decimal_point != '\0' and decimal_point != '.')
|
||||
{
|
||||
const auto dec_pos = std::find(number_buffer.begin(), number_buffer.end(), decimal_point);
|
||||
if (dec_pos != number_buffer.end())
|
||||
{
|
||||
*dec_pos = '.';
|
||||
}
|
||||
}
|
||||
|
||||
o.write_characters(number_buffer.data(), static_cast<std::size_t>(len));
|
||||
|
||||
// determine if need to append ".0"
|
||||
const bool value_is_int_like =
|
||||
std::none_of(number_buffer.begin(), number_buffer.begin() + len + 1,
|
||||
[](char c)
|
||||
{
|
||||
return (c == '.' or c == 'e');
|
||||
});
|
||||
|
||||
if (value_is_int_like)
|
||||
{
|
||||
o.write_characters(".0", 2);
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief check whether a string is UTF-8 encoded
|
||||
|
||||
The function checks each byte of a string whether it is UTF-8 encoded. The
|
||||
result of the check is stored in the @a state parameter. The function must
|
||||
be called initially with state 0 (accept). State 1 means the string must
|
||||
be rejected, because the current byte is not allowed. If the string is
|
||||
completely processed, but the state is non-zero, the string ended
|
||||
prematurely; that is, the last byte indicated more bytes should have
|
||||
followed.
|
||||
|
||||
@param[in,out] state the state of the decoding
|
||||
@param[in,out] codep codepoint (valid only if resulting state is UTF8_ACCEPT)
|
||||
@param[in] byte next byte to decode
|
||||
@return new state
|
||||
|
||||
@note The function has been edited: a std::array is used.
|
||||
|
||||
@copyright Copyright (c) 2008-2009 Bjoern Hoehrmann <bjoern@hoehrmann.de>
|
||||
@sa http://bjoern.hoehrmann.de/utf-8/decoder/dfa/
|
||||
*/
|
||||
static uint8_t decode(uint8_t& state, uint32_t& codep, const uint8_t byte) noexcept
|
||||
{
|
||||
static const std::array<uint8_t, 400> utf8d =
|
||||
{
|
||||
{
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 00..1F
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 20..3F
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 40..5F
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 60..7F
|
||||
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, // 80..9F
|
||||
7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, // A0..BF
|
||||
8, 8, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, // C0..DF
|
||||
0xA, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x4, 0x3, 0x3, // E0..EF
|
||||
0xB, 0x6, 0x6, 0x6, 0x5, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, // F0..FF
|
||||
0x0, 0x1, 0x2, 0x3, 0x5, 0x8, 0x7, 0x1, 0x1, 0x1, 0x4, 0x6, 0x1, 0x1, 0x1, 0x1, // s0..s0
|
||||
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 0, 1, 0, 1, 1, 1, 1, 1, 1, // s1..s2
|
||||
1, 2, 1, 1, 1, 1, 1, 2, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, // s3..s4
|
||||
1, 2, 1, 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 3, 1, 3, 1, 1, 1, 1, 1, 1, // s5..s6
|
||||
1, 3, 1, 1, 1, 1, 1, 3, 1, 3, 1, 1, 1, 1, 1, 1, 1, 3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 // s7..s8
|
||||
}
|
||||
};
|
||||
|
||||
const uint8_t type = utf8d[byte];
|
||||
|
||||
codep = (state != UTF8_ACCEPT)
|
||||
? (byte & 0x3fu) | (codep << 6)
|
||||
: static_cast<uint32_t>(0xff >> type) & (byte);
|
||||
|
||||
state = utf8d[256u + state * 16u + type];
|
||||
return state;
|
||||
}
|
||||
|
||||
private:
|
||||
/// a (hopefully) large enough character buffer
|
||||
std::array<char, 64> number_buffer{{}};
|
||||
|
||||
/// the locale
|
||||
const std::lconv* loc = nullptr;
|
||||
/// the locale's thousand separator character
|
||||
const char thousands_sep = '\0';
|
||||
/// the locale's decimal point character
|
||||
const char decimal_point = '\0';
|
||||
|
||||
/// string buffer
|
||||
std::array<char, 512> string_buffer{{}};
|
||||
};
|
||||
}
|
||||
}
|
||||
@ -18,6 +18,7 @@
|
||||
#include <nlohmann/detail/macro_scope.hpp>
|
||||
#include <nlohmann/detail/meta.hpp>
|
||||
#include <nlohmann/detail/output/output_adapters.hpp>
|
||||
#include <nlohmann/detail/output/primitive_serializer.hpp>
|
||||
#include <nlohmann/detail/value_t.hpp>
|
||||
|
||||
namespace nlohmann
|
||||
@ -31,6 +32,7 @@ namespace detail
|
||||
template<typename BasicJsonType>
|
||||
class serializer
|
||||
{
|
||||
using primitive_serializer_t = primitive_serializer<BasicJsonType>;
|
||||
using string_t = typename BasicJsonType::string_t;
|
||||
using number_float_t = typename BasicJsonType::number_float_t;
|
||||
using number_integer_t = typename BasicJsonType::number_integer_t;
|
||||
@ -44,10 +46,7 @@ class serializer
|
||||
@param[in] ichar indentation character to use
|
||||
*/
|
||||
serializer(output_adapter_t<char> s, const char ichar)
|
||||
: o(std::move(s)), loc(std::localeconv()),
|
||||
thousands_sep(loc->thousands_sep == nullptr ? '\0' : * (loc->thousands_sep)),
|
||||
decimal_point(loc->decimal_point == nullptr ? '\0' : * (loc->decimal_point)),
|
||||
indent_char(ichar), indent_string(512, indent_char)
|
||||
: o(std::move(s)), indent_char(ichar), indent_string(512, indent_char)
|
||||
{}
|
||||
|
||||
// delete because of pointer members
|
||||
@ -94,7 +93,7 @@ class serializer
|
||||
const auto new_indent = current_indent + indent_step;
|
||||
if (JSON_UNLIKELY(indent_string.size() < new_indent))
|
||||
{
|
||||
indent_string.resize(indent_string.size() * 2, ' ');
|
||||
indent_string.resize(indent_string.size() * 2, indent_char);
|
||||
}
|
||||
|
||||
// first n-1 elements
|
||||
@ -103,7 +102,7 @@ class serializer
|
||||
{
|
||||
o->write_characters(indent_string.c_str(), new_indent);
|
||||
o->write_character('\"');
|
||||
dump_escaped(i->first, ensure_ascii);
|
||||
prim_serializer.dump_escaped(*o, i->first, ensure_ascii);
|
||||
o->write_characters("\": ", 3);
|
||||
dump(i->second, true, ensure_ascii, indent_step, new_indent);
|
||||
o->write_characters(",\n", 2);
|
||||
@ -114,7 +113,7 @@ class serializer
|
||||
assert(std::next(i) == val.m_value.object->cend());
|
||||
o->write_characters(indent_string.c_str(), new_indent);
|
||||
o->write_character('\"');
|
||||
dump_escaped(i->first, ensure_ascii);
|
||||
prim_serializer.dump_escaped(*o, i->first, ensure_ascii);
|
||||
o->write_characters("\": ", 3);
|
||||
dump(i->second, true, ensure_ascii, indent_step, new_indent);
|
||||
|
||||
@ -131,7 +130,7 @@ class serializer
|
||||
for (std::size_t cnt = 0; cnt < val.m_value.object->size() - 1; ++cnt, ++i)
|
||||
{
|
||||
o->write_character('\"');
|
||||
dump_escaped(i->first, ensure_ascii);
|
||||
prim_serializer.dump_escaped(*o, i->first, ensure_ascii);
|
||||
o->write_characters("\":", 2);
|
||||
dump(i->second, false, ensure_ascii, indent_step, current_indent);
|
||||
o->write_character(',');
|
||||
@ -141,7 +140,7 @@ class serializer
|
||||
assert(i != val.m_value.object->cend());
|
||||
assert(std::next(i) == val.m_value.object->cend());
|
||||
o->write_character('\"');
|
||||
dump_escaped(i->first, ensure_ascii);
|
||||
prim_serializer.dump_escaped(*o, i->first, ensure_ascii);
|
||||
o->write_characters("\":", 2);
|
||||
dump(i->second, false, ensure_ascii, indent_step, current_indent);
|
||||
|
||||
@ -167,7 +166,7 @@ class serializer
|
||||
const auto new_indent = current_indent + indent_step;
|
||||
if (JSON_UNLIKELY(indent_string.size() < new_indent))
|
||||
{
|
||||
indent_string.resize(indent_string.size() * 2, ' ');
|
||||
indent_string.resize(indent_string.size() * 2, indent_char);
|
||||
}
|
||||
|
||||
// first n-1 elements
|
||||
@ -213,7 +212,7 @@ class serializer
|
||||
case value_t::string:
|
||||
{
|
||||
o->write_character('\"');
|
||||
dump_escaped(*val.m_value.string, ensure_ascii);
|
||||
prim_serializer.dump_escaped(*o, *val.m_value.string, ensure_ascii);
|
||||
o->write_character('\"');
|
||||
return;
|
||||
}
|
||||
@ -233,19 +232,19 @@ class serializer
|
||||
|
||||
case value_t::number_integer:
|
||||
{
|
||||
dump_integer(val.m_value.number_integer);
|
||||
prim_serializer.dump_integer(*o, val.m_value.number_integer);
|
||||
return;
|
||||
}
|
||||
|
||||
case value_t::number_unsigned:
|
||||
{
|
||||
dump_integer(val.m_value.number_unsigned);
|
||||
prim_serializer.dump_integer(*o, val.m_value.number_unsigned);
|
||||
return;
|
||||
}
|
||||
|
||||
case value_t::number_float:
|
||||
{
|
||||
dump_float(val.m_value.number_float);
|
||||
prim_serializer.dump_float(*o, val.m_value.number_float);
|
||||
return;
|
||||
}
|
||||
|
||||
@ -263,369 +262,17 @@ class serializer
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
/*!
|
||||
@brief dump escaped string
|
||||
|
||||
Escape a string by replacing certain special characters by a sequence of an
|
||||
escape character (backslash) and another character and other control
|
||||
characters by a sequence of "\u" followed by a four-digit hex
|
||||
representation. The escaped string is written to output stream @a o.
|
||||
|
||||
@param[in] s the string to escape
|
||||
@param[in] ensure_ascii whether to escape non-ASCII characters with
|
||||
\uXXXX sequences
|
||||
|
||||
@complexity Linear in the length of string @a s.
|
||||
*/
|
||||
void dump_escaped(const string_t& s, const bool ensure_ascii)
|
||||
{
|
||||
uint32_t codepoint;
|
||||
uint8_t state = UTF8_ACCEPT;
|
||||
std::size_t bytes = 0; // number of bytes written to string_buffer
|
||||
|
||||
for (std::size_t i = 0; i < s.size(); ++i)
|
||||
{
|
||||
const auto byte = static_cast<uint8_t>(s[i]);
|
||||
|
||||
switch (decode(state, codepoint, byte))
|
||||
{
|
||||
case UTF8_ACCEPT: // decode found a new code point
|
||||
{
|
||||
switch (codepoint)
|
||||
{
|
||||
case 0x08: // backspace
|
||||
{
|
||||
string_buffer[bytes++] = '\\';
|
||||
string_buffer[bytes++] = 'b';
|
||||
break;
|
||||
}
|
||||
|
||||
case 0x09: // horizontal tab
|
||||
{
|
||||
string_buffer[bytes++] = '\\';
|
||||
string_buffer[bytes++] = 't';
|
||||
break;
|
||||
}
|
||||
|
||||
case 0x0A: // newline
|
||||
{
|
||||
string_buffer[bytes++] = '\\';
|
||||
string_buffer[bytes++] = 'n';
|
||||
break;
|
||||
}
|
||||
|
||||
case 0x0C: // formfeed
|
||||
{
|
||||
string_buffer[bytes++] = '\\';
|
||||
string_buffer[bytes++] = 'f';
|
||||
break;
|
||||
}
|
||||
|
||||
case 0x0D: // carriage return
|
||||
{
|
||||
string_buffer[bytes++] = '\\';
|
||||
string_buffer[bytes++] = 'r';
|
||||
break;
|
||||
}
|
||||
|
||||
case 0x22: // quotation mark
|
||||
{
|
||||
string_buffer[bytes++] = '\\';
|
||||
string_buffer[bytes++] = '\"';
|
||||
break;
|
||||
}
|
||||
|
||||
case 0x5C: // reverse solidus
|
||||
{
|
||||
string_buffer[bytes++] = '\\';
|
||||
string_buffer[bytes++] = '\\';
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
{
|
||||
// escape control characters (0x00..0x1F) or, if
|
||||
// ensure_ascii parameter is used, non-ASCII characters
|
||||
if ((codepoint <= 0x1F) or (ensure_ascii and (codepoint >= 0x7F)))
|
||||
{
|
||||
if (codepoint <= 0xFFFF)
|
||||
{
|
||||
std::snprintf(string_buffer.data() + bytes, 7, "\\u%04x",
|
||||
static_cast<uint16_t>(codepoint));
|
||||
bytes += 6;
|
||||
}
|
||||
else
|
||||
{
|
||||
std::snprintf(string_buffer.data() + bytes, 13, "\\u%04x\\u%04x",
|
||||
static_cast<uint16_t>(0xD7C0 + (codepoint >> 10)),
|
||||
static_cast<uint16_t>(0xDC00 + (codepoint & 0x3FF)));
|
||||
bytes += 12;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// copy byte to buffer (all previous bytes
|
||||
// been copied have in default case above)
|
||||
string_buffer[bytes++] = s[i];
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// write buffer and reset index; there must be 13 bytes
|
||||
// left, as this is the maximal number of bytes to be
|
||||
// written ("\uxxxx\uxxxx\0") for one code point
|
||||
if (string_buffer.size() - bytes < 13)
|
||||
{
|
||||
o->write_characters(string_buffer.data(), bytes);
|
||||
bytes = 0;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case UTF8_REJECT: // decode found invalid UTF-8 byte
|
||||
{
|
||||
std::string sn(3, '\0');
|
||||
snprintf(&sn[0], sn.size(), "%.2X", byte);
|
||||
JSON_THROW(type_error::create(316, "invalid UTF-8 byte at index " + std::to_string(i) + ": 0x" + sn));
|
||||
}
|
||||
|
||||
default: // decode found yet incomplete multi-byte code point
|
||||
{
|
||||
if (not ensure_ascii)
|
||||
{
|
||||
// code point will not be escaped - copy byte to buffer
|
||||
string_buffer[bytes++] = s[i];
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (JSON_LIKELY(state == UTF8_ACCEPT))
|
||||
{
|
||||
// write buffer
|
||||
if (bytes > 0)
|
||||
{
|
||||
o->write_characters(string_buffer.data(), bytes);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// we finish reading, but do not accept: string was incomplete
|
||||
std::string sn(3,'\0');
|
||||
snprintf(&sn[0], sn.size(), "%.2X", static_cast<uint8_t>(s.back()));
|
||||
JSON_THROW(type_error::create(316, "incomplete UTF-8 string; last byte: 0x" + sn));
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief dump an integer
|
||||
|
||||
Dump a given integer to output stream @a o. Works internally with
|
||||
@a number_buffer.
|
||||
|
||||
@param[in] x integer number (signed or unsigned) to dump
|
||||
@tparam NumberType either @a number_integer_t or @a number_unsigned_t
|
||||
*/
|
||||
template<typename NumberType, detail::enable_if_t<
|
||||
std::is_same<NumberType, number_unsigned_t>::value or
|
||||
std::is_same<NumberType, number_integer_t>::value,
|
||||
int> = 0>
|
||||
void dump_integer(NumberType x)
|
||||
{
|
||||
// special case for "0"
|
||||
if (x == 0)
|
||||
{
|
||||
o->write_character('0');
|
||||
return;
|
||||
}
|
||||
|
||||
const bool is_negative = (x <= 0) and (x != 0); // see issue #755
|
||||
std::size_t i = 0;
|
||||
|
||||
while (x != 0)
|
||||
{
|
||||
// spare 1 byte for '\0'
|
||||
assert(i < number_buffer.size() - 1);
|
||||
|
||||
const auto digit = std::labs(static_cast<long>(x % 10));
|
||||
number_buffer[i++] = static_cast<char>('0' + digit);
|
||||
x /= 10;
|
||||
}
|
||||
|
||||
if (is_negative)
|
||||
{
|
||||
// make sure there is capacity for the '-'
|
||||
assert(i < number_buffer.size() - 2);
|
||||
number_buffer[i++] = '-';
|
||||
}
|
||||
|
||||
std::reverse(number_buffer.begin(), number_buffer.begin() + i);
|
||||
o->write_characters(number_buffer.data(), i);
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief dump a floating-point number
|
||||
|
||||
Dump a given floating-point number to output stream @a o. Works internally
|
||||
with @a number_buffer.
|
||||
|
||||
@param[in] x floating-point number to dump
|
||||
*/
|
||||
void dump_float(number_float_t x)
|
||||
{
|
||||
// NaN / inf
|
||||
if (not std::isfinite(x))
|
||||
{
|
||||
o->write_characters("null", 4);
|
||||
return;
|
||||
}
|
||||
|
||||
// If number_float_t is an IEEE-754 single or double precision number,
|
||||
// use the Grisu2 algorithm to produce short numbers which are
|
||||
// guaranteed to round-trip, using strtof and strtod, resp.
|
||||
//
|
||||
// NB: The test below works if <long double> == <double>.
|
||||
static constexpr bool is_ieee_single_or_double
|
||||
= (std::numeric_limits<number_float_t>::is_iec559 and std::numeric_limits<number_float_t>::digits == 24 and std::numeric_limits<number_float_t>::max_exponent == 128) or
|
||||
(std::numeric_limits<number_float_t>::is_iec559 and std::numeric_limits<number_float_t>::digits == 53 and std::numeric_limits<number_float_t>::max_exponent == 1024);
|
||||
|
||||
dump_float(x, std::integral_constant<bool, is_ieee_single_or_double>());
|
||||
}
|
||||
|
||||
void dump_float(number_float_t x, std::true_type /*is_ieee_single_or_double*/)
|
||||
{
|
||||
char* begin = number_buffer.data();
|
||||
char* end = ::nlohmann::detail::to_chars(begin, begin + number_buffer.size(), x);
|
||||
|
||||
o->write_characters(begin, static_cast<size_t>(end - begin));
|
||||
}
|
||||
|
||||
void dump_float(number_float_t x, std::false_type /*is_ieee_single_or_double*/)
|
||||
{
|
||||
// get number of digits for a float -> text -> float round-trip
|
||||
static constexpr auto d = std::numeric_limits<number_float_t>::max_digits10;
|
||||
|
||||
// the actual conversion
|
||||
std::ptrdiff_t len = snprintf(number_buffer.data(), number_buffer.size(), "%.*g", d, x);
|
||||
|
||||
// negative value indicates an error
|
||||
assert(len > 0);
|
||||
// check if buffer was large enough
|
||||
assert(static_cast<std::size_t>(len) < number_buffer.size());
|
||||
|
||||
// erase thousands separator
|
||||
if (thousands_sep != '\0')
|
||||
{
|
||||
const auto end = std::remove(number_buffer.begin(),
|
||||
number_buffer.begin() + len, thousands_sep);
|
||||
std::fill(end, number_buffer.end(), '\0');
|
||||
assert((end - number_buffer.begin()) <= len);
|
||||
len = (end - number_buffer.begin());
|
||||
}
|
||||
|
||||
// convert decimal point to '.'
|
||||
if (decimal_point != '\0' and decimal_point != '.')
|
||||
{
|
||||
const auto dec_pos = std::find(number_buffer.begin(), number_buffer.end(), decimal_point);
|
||||
if (dec_pos != number_buffer.end())
|
||||
{
|
||||
*dec_pos = '.';
|
||||
}
|
||||
}
|
||||
|
||||
o->write_characters(number_buffer.data(), static_cast<std::size_t>(len));
|
||||
|
||||
// determine if need to append ".0"
|
||||
const bool value_is_int_like =
|
||||
std::none_of(number_buffer.begin(), number_buffer.begin() + len + 1,
|
||||
[](char c)
|
||||
{
|
||||
return (c == '.' or c == 'e');
|
||||
});
|
||||
|
||||
if (value_is_int_like)
|
||||
{
|
||||
o->write_characters(".0", 2);
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief check whether a string is UTF-8 encoded
|
||||
|
||||
The function checks each byte of a string whether it is UTF-8 encoded. The
|
||||
result of the check is stored in the @a state parameter. The function must
|
||||
be called initially with state 0 (accept). State 1 means the string must
|
||||
be rejected, because the current byte is not allowed. If the string is
|
||||
completely processed, but the state is non-zero, the string ended
|
||||
prematurely; that is, the last byte indicated more bytes should have
|
||||
followed.
|
||||
|
||||
@param[in,out] state the state of the decoding
|
||||
@param[in,out] codep codepoint (valid only if resulting state is UTF8_ACCEPT)
|
||||
@param[in] byte next byte to decode
|
||||
@return new state
|
||||
|
||||
@note The function has been edited: a std::array is used.
|
||||
|
||||
@copyright Copyright (c) 2008-2009 Bjoern Hoehrmann <bjoern@hoehrmann.de>
|
||||
@sa http://bjoern.hoehrmann.de/utf-8/decoder/dfa/
|
||||
*/
|
||||
static uint8_t decode(uint8_t& state, uint32_t& codep, const uint8_t byte) noexcept
|
||||
{
|
||||
static const std::array<uint8_t, 400> utf8d =
|
||||
{
|
||||
{
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 00..1F
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 20..3F
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 40..5F
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 60..7F
|
||||
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, // 80..9F
|
||||
7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, // A0..BF
|
||||
8, 8, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, // C0..DF
|
||||
0xA, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x4, 0x3, 0x3, // E0..EF
|
||||
0xB, 0x6, 0x6, 0x6, 0x5, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, // F0..FF
|
||||
0x0, 0x1, 0x2, 0x3, 0x5, 0x8, 0x7, 0x1, 0x1, 0x1, 0x4, 0x6, 0x1, 0x1, 0x1, 0x1, // s0..s0
|
||||
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 0, 1, 0, 1, 1, 1, 1, 1, 1, // s1..s2
|
||||
1, 2, 1, 1, 1, 1, 1, 2, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, // s3..s4
|
||||
1, 2, 1, 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 3, 1, 3, 1, 1, 1, 1, 1, 1, // s5..s6
|
||||
1, 3, 1, 1, 1, 1, 1, 3, 1, 3, 1, 1, 1, 1, 1, 1, 1, 3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 // s7..s8
|
||||
}
|
||||
};
|
||||
|
||||
const uint8_t type = utf8d[byte];
|
||||
|
||||
codep = (state != UTF8_ACCEPT)
|
||||
? (byte & 0x3fu) | (codep << 6)
|
||||
: static_cast<uint32_t>(0xff >> type) & (byte);
|
||||
|
||||
state = utf8d[256u + state * 16u + type];
|
||||
return state;
|
||||
}
|
||||
|
||||
private:
|
||||
/// the output of the serializer
|
||||
output_adapter_t<char> o = nullptr;
|
||||
|
||||
/// a (hopefully) large enough character buffer
|
||||
std::array<char, 64> number_buffer{{}};
|
||||
|
||||
/// the locale
|
||||
const std::lconv* loc = nullptr;
|
||||
/// the locale's thousand separator character
|
||||
const char thousands_sep = '\0';
|
||||
/// the locale's decimal point character
|
||||
const char decimal_point = '\0';
|
||||
|
||||
/// string buffer
|
||||
std::array<char, 512> string_buffer{{}};
|
||||
|
||||
/// the indentation character
|
||||
const char indent_char;
|
||||
/// the indentation string
|
||||
string_t indent_string;
|
||||
|
||||
/// used for serializing non-object non-arrays
|
||||
primitive_serializer_t prim_serializer;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@ -65,6 +65,7 @@ SOFTWARE.
|
||||
#include <nlohmann/detail/input/binary_reader.hpp>
|
||||
#include <nlohmann/detail/output/binary_writer.hpp>
|
||||
#include <nlohmann/detail/output/serializer.hpp>
|
||||
#include <nlohmann/detail/output/fancy_serializer.hpp>
|
||||
#include <nlohmann/detail/json_ref.hpp>
|
||||
#include <nlohmann/detail/json_pointer.hpp>
|
||||
#include <nlohmann/adl_serializer.hpp>
|
||||
@ -166,6 +167,7 @@ class basic_json
|
||||
friend ::nlohmann::json_pointer<basic_json>;
|
||||
friend ::nlohmann::detail::parser<basic_json>;
|
||||
friend ::nlohmann::detail::serializer<basic_json>;
|
||||
friend ::nlohmann::detail::styled_serializer<basic_json>;
|
||||
template<typename BasicJsonType>
|
||||
friend class ::nlohmann::detail::iter_impl;
|
||||
template<typename BasicJsonType, typename CharType>
|
||||
@ -7622,6 +7624,8 @@ class basic_json
|
||||
|
||||
/// @}
|
||||
};
|
||||
|
||||
using print_stylizer = basic_print_stylizer<json>;
|
||||
} // namespace nlohmann
|
||||
|
||||
///////////////////////
|
||||
@ -7667,7 +7671,7 @@ struct hash<nlohmann::json>
|
||||
/// @note: do not remove the space after '<',
|
||||
/// see https://github.com/nlohmann/json/pull/679
|
||||
template<>
|
||||
struct less< ::nlohmann::detail::value_t>
|
||||
struct less<::nlohmann::detail::value_t>
|
||||
{
|
||||
/*!
|
||||
@brief compare two value_t enum values
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -37,8 +37,10 @@ void check_escaped(const char* original, const char* escaped = "", const bool en
|
||||
void check_escaped(const char* original, const char* escaped, const bool ensure_ascii)
|
||||
{
|
||||
std::stringstream ss;
|
||||
json::serializer s(nlohmann::detail::output_adapter<char>(ss), ' ');
|
||||
s.dump_escaped(original, ensure_ascii);
|
||||
nlohmann::detail::output_adapter<char> o(ss);
|
||||
nlohmann::detail::output_adapter_t<char> oo = o;
|
||||
nlohmann::detail::primitive_serializer<json> s;
|
||||
s.dump_escaped(*oo, original, ensure_ascii);
|
||||
CHECK(ss.str() == escaped);
|
||||
}
|
||||
|
||||
|
||||
@ -1093,9 +1093,9 @@ TEST_CASE("value conversion")
|
||||
|
||||
SECTION("superfluous entries")
|
||||
{
|
||||
json j8 = {{0, 1, 2}, {1, 2, 3}, {2, 3, 4}};
|
||||
m2 = j8.get<std::map<int, int>>();
|
||||
CHECK(m == m2);
|
||||
json j8 = {{0, 1, 2}, {1, 2, 3}, {2, 3, 4}};
|
||||
m2 = j8.get<std::map<int, int>>();
|
||||
CHECK(m == m2);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
466
test/src/unit-fancy-serialization.cpp
Normal file
466
test/src/unit-fancy-serialization.cpp
Normal file
@ -0,0 +1,466 @@
|
||||
/*
|
||||
__ _____ _____ _____
|
||||
__| | __| | | | JSON for Modern C++ (test suite)
|
||||
| | |__ | | | | | | version 3.1.2
|
||||
|_____|_____|_____|_|___| https://github.com/nlohmann/json
|
||||
|
||||
Licensed under the MIT License <http://opensource.org/licenses/MIT>.
|
||||
SPDX-License-Identifier: MIT
|
||||
Copyright (c) 2018 Evan Driscoll <evaned@gmail.com>
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
*/
|
||||
|
||||
#include "catch.hpp"
|
||||
#include <iostream>
|
||||
|
||||
#include <nlohmann/json.hpp>
|
||||
|
||||
using nlohmann::json;
|
||||
using nlohmann::json_pointer;
|
||||
using nlohmann::styled_dump;
|
||||
using nlohmann::print_style;
|
||||
using nlohmann::print_stylizer;
|
||||
|
||||
// Chops off the first line (if empty, but if it *isn't* empty you're
|
||||
// probably using this wrong), measures the leading indent on the
|
||||
// *next* line, then chops that amount off of all subsequent lines.
|
||||
std::string dedent(const char* str)
|
||||
{
|
||||
std::stringstream out;
|
||||
std::stringstream ss(str);
|
||||
std::string line;
|
||||
bool first = true;
|
||||
int indent = -1;
|
||||
|
||||
while (getline(ss, line))
|
||||
{
|
||||
if (first && line.empty())
|
||||
{
|
||||
first = false;
|
||||
continue;
|
||||
}
|
||||
if (indent == -1)
|
||||
{
|
||||
indent = line.find_first_not_of(' ');
|
||||
assert(indent != std::string::npos);
|
||||
}
|
||||
out << line.c_str() + indent << "\n";
|
||||
}
|
||||
|
||||
std::string ans = out.str();
|
||||
if (ans[ans.size() - 1] == '\n' and str[strlen(str) - 1] != '\n')
|
||||
{
|
||||
ans.resize(ans.size() - 1);
|
||||
}
|
||||
|
||||
return ans;
|
||||
}
|
||||
|
||||
std::string styled_to_string(json j, print_style style = print_style())
|
||||
{
|
||||
std::stringstream ss;
|
||||
styled_dump(ss, j, style);
|
||||
return ss.str();
|
||||
}
|
||||
|
||||
std::string styled_to_string(json j, print_stylizer stylizer)
|
||||
{
|
||||
std::stringstream ss;
|
||||
styled_dump(ss, j, stylizer);
|
||||
return ss.str();
|
||||
}
|
||||
|
||||
TEST_CASE("serialization")
|
||||
{
|
||||
SECTION("primitives")
|
||||
{
|
||||
SECTION("null")
|
||||
{
|
||||
auto str = styled_to_string({});
|
||||
CHECK(str == "null");
|
||||
}
|
||||
|
||||
SECTION("true")
|
||||
{
|
||||
auto str = styled_to_string(true);
|
||||
CHECK(str == "true");
|
||||
}
|
||||
|
||||
SECTION("false")
|
||||
{
|
||||
auto str = styled_to_string(false);
|
||||
CHECK(str == "false");
|
||||
}
|
||||
|
||||
SECTION("integer")
|
||||
{
|
||||
auto str = styled_to_string(10);
|
||||
CHECK(str == "10");
|
||||
}
|
||||
|
||||
SECTION("floating point")
|
||||
{
|
||||
auto str = styled_to_string(7.5);
|
||||
CHECK(str == "7.5");
|
||||
}
|
||||
}
|
||||
|
||||
SECTION("strings")
|
||||
{
|
||||
SECTION("long strings usually print")
|
||||
{
|
||||
auto str = styled_to_string(
|
||||
"The quick brown fox jumps over the lazy brown dog");
|
||||
CHECK(str ==
|
||||
"\"The quick brown fox jumps over the lazy brown dog\"");
|
||||
}
|
||||
|
||||
SECTION("long strings can be shortened")
|
||||
{
|
||||
print_style style;
|
||||
style.strings_maximum_length = 10;
|
||||
|
||||
auto str = styled_to_string(
|
||||
"The quick brown fox jumps over the lazy brown dog",
|
||||
style);
|
||||
CHECK(str == "\"The qu...g\"");
|
||||
}
|
||||
|
||||
SECTION("requesting extremely short strings limits what is included")
|
||||
{
|
||||
const char* const quick = "The quick brown fox jumps over the lazy brown dog";
|
||||
|
||||
std::pair<unsigned, const char*> tests[] =
|
||||
{
|
||||
{5, "\"T...g\""},
|
||||
{4, "\"T...\""},
|
||||
{3, "\"...\""},
|
||||
{2, "\"..\""},
|
||||
{1, "\".\""},
|
||||
};
|
||||
|
||||
for (auto test : tests)
|
||||
{
|
||||
print_style style;
|
||||
style.strings_maximum_length = test.first;
|
||||
auto str = styled_to_string(quick, style);
|
||||
CHECK(str == test.second);
|
||||
}
|
||||
}
|
||||
|
||||
SECTION("But you cannot ask for a length of zero; that means unlimited")
|
||||
{
|
||||
print_style style;
|
||||
style.strings_maximum_length = 0;
|
||||
|
||||
auto str = styled_to_string(
|
||||
"The quick brown fox jumps over the lazy brown dog",
|
||||
style);
|
||||
CHECK(str ==
|
||||
"\"The quick brown fox jumps over the lazy brown dog\"");
|
||||
}
|
||||
|
||||
SECTION("\"Limiting\" to something long doesn't do anything")
|
||||
{
|
||||
print_style style;
|
||||
style.strings_maximum_length = 100;
|
||||
|
||||
auto str = styled_to_string(
|
||||
"The quick brown fox jumps over the lazy brown dog",
|
||||
style);
|
||||
CHECK(str ==
|
||||
"\"The quick brown fox jumps over the lazy brown dog\"");
|
||||
}
|
||||
|
||||
// TODO: Handle escape sequences. Figure out what we want the
|
||||
// behavior to be, first. :-)
|
||||
}
|
||||
|
||||
SECTION("maximum depth")
|
||||
{
|
||||
SECTION("recursing past the maximum depth with a list elides the subobjects")
|
||||
{
|
||||
print_style style;
|
||||
style.depth_limit = 1;
|
||||
|
||||
auto str_flat = styled_to_string({1, {1}}, style);
|
||||
CHECK(str_flat == "[1,[...]]");
|
||||
|
||||
style = print_style::preset_multiline;
|
||||
style.depth_limit = 1;
|
||||
auto str_lines = styled_to_string({1, {1}}, style);
|
||||
CHECK(str_lines == dedent(R"(
|
||||
[
|
||||
1,
|
||||
[...]
|
||||
])"));
|
||||
}
|
||||
|
||||
SECTION("recursing past the maximum depth with an object elides the subobjects")
|
||||
{
|
||||
print_style style;
|
||||
style.depth_limit = 1;
|
||||
|
||||
auto str_flat = styled_to_string({1, {{"one", 1}}}, style);
|
||||
CHECK(str_flat == "[1,{...}]");
|
||||
|
||||
style = print_style::preset_multiline;
|
||||
style.depth_limit = 1;
|
||||
auto str_lines = styled_to_string({1, {{"one", 1}}}, style);
|
||||
CHECK(str_lines == dedent(R"(
|
||||
[
|
||||
1,
|
||||
{...}
|
||||
])"));
|
||||
}
|
||||
}
|
||||
|
||||
SECTION("changing styles")
|
||||
{
|
||||
SECTION("can style objects of a key differently")
|
||||
{
|
||||
print_stylizer stylizer;
|
||||
stylizer.get_default_style() = print_style::preset_multiline;
|
||||
stylizer.register_key_matcher_style("one line");
|
||||
|
||||
auto str = styled_to_string(
|
||||
{
|
||||
{
|
||||
"one line", {1, 2}
|
||||
},
|
||||
{
|
||||
"two lines", {1, 2}
|
||||
}
|
||||
},
|
||||
stylizer);
|
||||
|
||||
CHECK(str == dedent(R"(
|
||||
{
|
||||
"one line": [1,2],
|
||||
"two lines": [
|
||||
1,
|
||||
2
|
||||
]
|
||||
})"));
|
||||
}
|
||||
|
||||
SECTION("changes propagate (unless overridden)")
|
||||
{
|
||||
print_stylizer stylizer;
|
||||
stylizer.get_default_style() = print_style::preset_multiline;
|
||||
stylizer.register_key_matcher_style("one line");
|
||||
|
||||
auto str = styled_to_string(
|
||||
{
|
||||
{
|
||||
"one line", {{"still one line", {1, 2}}}
|
||||
},
|
||||
},
|
||||
stylizer);
|
||||
|
||||
CHECK(str == dedent(R"(
|
||||
{
|
||||
"one line": {"still one line":[1,2]}
|
||||
})"));
|
||||
}
|
||||
|
||||
SECTION("example of more sophisticated context matcher")
|
||||
{
|
||||
print_stylizer stylizer;
|
||||
stylizer.get_default_style() = print_style::preset_multiline;
|
||||
|
||||
stylizer.register_style(
|
||||
[] (const json_pointer<json>& context)
|
||||
{
|
||||
// Matches if context[-2] is "each elem on one line"
|
||||
return (context.cend() - context.cbegin() >= 2)
|
||||
&& (*(context.cend() - 2) == "each elem on one line");
|
||||
}
|
||||
).space_after_comma = true;
|
||||
|
||||
auto str = styled_to_string(
|
||||
{
|
||||
{
|
||||
"each elem on one line", {
|
||||
{1, 2, 3, 4, 5},
|
||||
{1, 2, 3, 4, 5}
|
||||
},
|
||||
},
|
||||
{
|
||||
"fully multiline", {
|
||||
{1, 2, 3},
|
||||
}
|
||||
}
|
||||
},
|
||||
stylizer);
|
||||
|
||||
CHECK(str == dedent(R"(
|
||||
{
|
||||
"each elem on one line": [
|
||||
[1, 2, 3, 4, 5],
|
||||
[1, 2, 3, 4, 5]
|
||||
],
|
||||
"fully multiline": [
|
||||
[
|
||||
1,
|
||||
2,
|
||||
3
|
||||
]
|
||||
]
|
||||
})"));
|
||||
}
|
||||
|
||||
SECTION("example of more sophisticated json matcher")
|
||||
{
|
||||
print_stylizer stylizer;
|
||||
stylizer.get_default_style() = print_style::preset_multiline;
|
||||
|
||||
stylizer.register_style(
|
||||
[] (const json & j)
|
||||
{
|
||||
return j.type() == json::value_t::array;
|
||||
}
|
||||
) = print_style::preset_one_line;
|
||||
|
||||
auto str = styled_to_string(
|
||||
{
|
||||
{
|
||||
"an array", {1, 2, 3}
|
||||
},
|
||||
{
|
||||
"an object", {{"key", "val"}}
|
||||
}
|
||||
},
|
||||
stylizer);
|
||||
|
||||
CHECK(str == dedent(R"(
|
||||
{
|
||||
"an array": [1, 2, 3],
|
||||
"an object": {
|
||||
"key": "val"
|
||||
}
|
||||
})"));
|
||||
}
|
||||
}
|
||||
|
||||
SECTION("Spaces after commas are controllable separately from multiline")
|
||||
{
|
||||
SECTION("commas")
|
||||
{
|
||||
print_style style;
|
||||
style.space_after_comma = true;
|
||||
auto str = styled_to_string({1, 2, 3}, style);
|
||||
CHECK(str == "[1, 2, 3]");
|
||||
}
|
||||
|
||||
SECTION("colons")
|
||||
{
|
||||
print_style style;
|
||||
style.space_after_colon = true;
|
||||
auto str = styled_to_string({{"one", 1}}, style);
|
||||
CHECK(str == "{\"one\": 1}");
|
||||
}
|
||||
|
||||
SECTION("multiline can have no space")
|
||||
{
|
||||
print_style style = print_style::preset_multiline;
|
||||
style.space_after_colon = false;
|
||||
auto str = styled_to_string({{"one", 1}}, style);
|
||||
CHECK(str == dedent(R"(
|
||||
{
|
||||
"one":1
|
||||
})"));
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
SECTION("given width")
|
||||
{
|
||||
print_style style = print_style::preset_multiline;
|
||||
auto str = styled_to_string({"foo", 1, 2, 3, false, {{"one", 1}}}, style);
|
||||
CHECK(str == dedent(R"(
|
||||
[
|
||||
"foo",
|
||||
1,
|
||||
2,
|
||||
3,
|
||||
false,
|
||||
{
|
||||
"one": 1
|
||||
}
|
||||
])"));
|
||||
}
|
||||
|
||||
SECTION("given fill")
|
||||
{
|
||||
print_style style = print_style::preset_multiline;
|
||||
style.indent_step = 1;
|
||||
style.indent_char = '\t';
|
||||
|
||||
auto str = styled_to_string({"foo", 1, 2, 3, false, {{"one", 1}}}, style);
|
||||
CHECK(str ==
|
||||
"[\n"
|
||||
"\t\"foo\",\n"
|
||||
"\t1,\n"
|
||||
"\t2,\n"
|
||||
"\t3,\n"
|
||||
"\tfalse,\n"
|
||||
"\t{\n"
|
||||
"\t\t\"one\": 1\n"
|
||||
"\t}\n"
|
||||
"]"
|
||||
);
|
||||
}
|
||||
|
||||
SECTION("indent_char is honored for deep indents in lists")
|
||||
{
|
||||
print_style style = print_style::preset_multiline;
|
||||
style.indent_step = 300;
|
||||
style.indent_char = 'X';
|
||||
|
||||
auto str = styled_to_string({1, {1}}, style);
|
||||
|
||||
std::string indent(300, 'X');
|
||||
CHECK(str ==
|
||||
"[\n" +
|
||||
indent + "1,\n" +
|
||||
indent + "[\n" +
|
||||
indent + indent + "1\n" +
|
||||
indent + "]\n" +
|
||||
"]");
|
||||
}
|
||||
|
||||
SECTION("indent_char is honored for deep indents in objects")
|
||||
{
|
||||
print_style style = print_style::preset_multiline;
|
||||
style.indent_step = 300;
|
||||
style.indent_char = 'X';
|
||||
|
||||
auto str = styled_to_string({{"key", {{"key", 1}}}}, style);
|
||||
|
||||
std::string indent(300, 'X');
|
||||
CHECK(str ==
|
||||
"{\n" +
|
||||
indent + "\"key\": {\n" +
|
||||
indent + indent + "\"key\": 1\n" +
|
||||
indent + "}\n" +
|
||||
"}");
|
||||
}
|
||||
}
|
||||
@ -317,8 +317,8 @@ TEST_CASE("object inspection")
|
||||
SECTION("round trips")
|
||||
{
|
||||
for (const auto& s :
|
||||
{"3.141592653589793", "1000000000000000010E5"
|
||||
})
|
||||
{"3.141592653589793", "1000000000000000010E5"
|
||||
})
|
||||
{
|
||||
json j1 = json::parse(s);
|
||||
std::string s1 = j1.dump();
|
||||
|
||||
@ -441,6 +441,47 @@ TEST_CASE("JSON pointers")
|
||||
}
|
||||
}
|
||||
|
||||
SECTION("iterator access")
|
||||
{
|
||||
SECTION("forward iterator access")
|
||||
{
|
||||
auto ptr = "/foo/bar/c%d"_json_pointer;
|
||||
std::vector<std::string> components(ptr.cbegin(), ptr.cend());
|
||||
CHECK(components.size() == 3u);
|
||||
CHECK(components[0] == "foo");
|
||||
CHECK(components[1] == "bar");
|
||||
CHECK(components[2] == "c%d");
|
||||
}
|
||||
|
||||
SECTION("reverse iterator access")
|
||||
{
|
||||
auto ptr = "/foo/bar/c%d"_json_pointer;
|
||||
std::vector<std::string> rcomponents(ptr.crbegin(), ptr.crend());
|
||||
CHECK(rcomponents.size() == 3u);
|
||||
CHECK(rcomponents[2] == "foo");
|
||||
CHECK(rcomponents[1] == "bar");
|
||||
CHECK(rcomponents[0] == "c%d");
|
||||
}
|
||||
}
|
||||
|
||||
SECTION("appending")
|
||||
{
|
||||
SECTION("can append to empty string pointer")
|
||||
{
|
||||
auto ptr = ""_json_pointer;
|
||||
CHECK(ptr.to_string() == "");
|
||||
|
||||
ptr = ptr.appended("foo");
|
||||
CHECK(ptr.to_string() == "/foo");
|
||||
|
||||
ptr = ptr.appended("bar");
|
||||
CHECK(ptr.to_string() == "/foo/bar");
|
||||
|
||||
ptr = ptr.appended(10);
|
||||
CHECK(ptr.to_string() == "/foo/bar/10");
|
||||
}
|
||||
}
|
||||
|
||||
SECTION("conversion")
|
||||
{
|
||||
SECTION("array")
|
||||
|
||||
@ -45,19 +45,19 @@ void from_json(const json&, pod) noexcept;
|
||||
void from_json(const json&, pod_bis);
|
||||
static json j;
|
||||
|
||||
static_assert(noexcept(json{}), "");
|
||||
static_assert(noexcept(json {}), "");
|
||||
static_assert(noexcept(nlohmann::to_json(j, 2)), "");
|
||||
static_assert(noexcept(nlohmann::to_json(j, 2.5)), "");
|
||||
static_assert(noexcept(nlohmann::to_json(j, true)), "");
|
||||
static_assert(noexcept(nlohmann::to_json(j, test{})), "");
|
||||
static_assert(noexcept(nlohmann::to_json(j, pod{})), "");
|
||||
static_assert(not noexcept(nlohmann::to_json(j, pod_bis{})), "");
|
||||
static_assert(noexcept(nlohmann::to_json(j, test {})), "");
|
||||
static_assert(noexcept(nlohmann::to_json(j, pod {})), "");
|
||||
static_assert(not noexcept(nlohmann::to_json(j, pod_bis {})), "");
|
||||
static_assert(noexcept(json(2)), "");
|
||||
static_assert(noexcept(json(test{})), "");
|
||||
static_assert(noexcept(json(pod{})), "");
|
||||
static_assert(noexcept(json(test {})), "");
|
||||
static_assert(noexcept(json(pod {})), "");
|
||||
static_assert(noexcept(j.get<pod>()), "");
|
||||
static_assert(not noexcept(j.get<pod_bis>()), "");
|
||||
static_assert(noexcept(json(pod{})), "");
|
||||
static_assert(noexcept(json(pod {})), "");
|
||||
|
||||
TEST_CASE("runtime checks")
|
||||
{
|
||||
|
||||
@ -63,6 +63,37 @@ TEST_CASE("serialization")
|
||||
}
|
||||
}
|
||||
|
||||
SECTION("indent_char is honored for deep indents in lists")
|
||||
{
|
||||
std::stringstream ss;
|
||||
json j = {1, {1}};
|
||||
ss << std::setw(300) << std::setfill('X') << j;
|
||||
|
||||
std::string indent(300, 'X');
|
||||
CHECK(ss.str() ==
|
||||
"[\n" +
|
||||
indent + "1,\n" +
|
||||
indent + "[\n" +
|
||||
indent + indent + "1\n" +
|
||||
indent + "]\n" +
|
||||
"]");
|
||||
}
|
||||
|
||||
SECTION("indent_char is honored for deep indents in objects")
|
||||
{
|
||||
std::stringstream ss;
|
||||
json j = {{"key", {{"key", 1}}}};
|
||||
ss << std::setw(300) << std::setfill('X') << j;
|
||||
|
||||
std::string indent(300, 'X');
|
||||
CHECK(ss.str() ==
|
||||
"{\n" +
|
||||
indent + "\"key\": {\n" +
|
||||
indent + indent + "\"key\": 1\n" +
|
||||
indent + "}\n" +
|
||||
"}");
|
||||
}
|
||||
|
||||
SECTION("operator>>")
|
||||
{
|
||||
SECTION("no given width")
|
||||
|
||||
@ -585,7 +585,7 @@ struct pod_serializer
|
||||
std::is_pod<U>::value and std::is_class<U>::value, int>::type = 0>
|
||||
static void to_json(BasicJsonType& j, const T& t) noexcept
|
||||
{
|
||||
auto bytes = static_cast< const unsigned char*>(static_cast<const void*>(&t));
|
||||
auto bytes = static_cast<const unsigned char*>(static_cast<const void*>(&t));
|
||||
std::uint64_t value = bytes[0];
|
||||
for (auto i = 1; i < 8; ++i)
|
||||
value |= std::uint64_t{bytes[i]} << 8 * i;
|
||||
|
||||
Loading…
Reference in New Issue
Block a user