Skip to content

Commit

Permalink
append_arrays compile time option (#1549)
Browse files Browse the repository at this point in the history
  • Loading branch information
stephenberry authored Jan 4, 2025
1 parent 77bb2c3 commit 752515f
Show file tree
Hide file tree
Showing 5 changed files with 117 additions and 29 deletions.
35 changes: 35 additions & 0 deletions docs/wrappers.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ Glaze provides a number of wrappers that indicate at compile time how a value sh
## Available Wrappers

```c++
glz::append_arrays<&T::x> // When reading into an array that is appendable, the new data will be appended rather than overwrite
glz::bools_as_numbers<&T::x> // Read and write booleans as numbers
glz::quoted_num<&T::x> // Read and write numbers as strings
glz::quoted<&T::x> // Read a value as a string and unescape, to avoid the user having to parse twice
Expand All @@ -29,6 +30,40 @@ glz::manage<&T::x, &T::read_x, &T::write_x> // Calls read_x() after reading x an

`glz::opts` is the compile time options struct passed to most of Glaze functions to configure read/write behavior. Often wrappers are associated with compile time options and can also be set via `glz::opts`. For example, the `glz::quoted_num` wrapper is associated with the `quoted_num` boolean in `glz::opts`.

## append_arrays

When reading into an array that is appendable, the new data will be appended rather than overwrite

Associated option: `glz::opts{.append_arrays = true};`

```c++
struct append_obj
{
std::vector<std::string> names{};
std::vector<std::array<int, 2>> arrays{};
};

template <>
struct glz::meta<append_obj>
{
using T = append_obj;
static constexpr auto value = object("names", append_arrays<&T::names>, "arrays", append_arrays<&T::arrays>);
};
```

In use:

```c++
append_obj obj{};
expect(not glz::read_json(obj, R"({"names":["Bob"],"arrays":[[0,0]]})"));
expect(obj.names == std::vector<std::string>{"Bob"});
expect(obj.arrays == std::vector<std::array<int, 2>>{{0,0}});

expect(not glz::read_json(obj, R"({"names":["Liz"],"arrays":[[1,1]]})"));
expect(obj.names == std::vector<std::string>{"Bob", "Liz"});
expect(obj.arrays == std::vector<std::array<int, 2>>{{0,0},{1,1}});
```
## bools_as_numbers
Read and write booleans as numbers
Expand Down
1 change: 1 addition & 0 deletions include/glaze/core/opts.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ namespace glz
char indentation_char = ' '; // Prettified JSON indentation char
uint8_t indentation_width = 3; // Prettified JSON indentation size
bool_t new_lines_in_arrays = true; // Whether prettified arrays should have new lines for each element
bool_t append_arrays = false; // When reading into an array the data will be appended if the type supports it
bool_t shrink_to_fit = false; // Shrinks dynamic containers to new size to save memory
bool_t write_type_info = true; // Write type info for meta objects in variants
bool_t error_on_missing_keys = false; // Require all non nullable keys to be present in the object. Use
Expand Down
4 changes: 4 additions & 0 deletions include/glaze/core/wrappers.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,10 @@ namespace glz
return [](auto&& v) { return custom_t{v, From, To}; };
}
}

// When reading into an array that is appendable, the new data will be appended rather than overwrite
template <auto MemPtr>
constexpr auto append_arrays = detail::opts_wrapper<MemPtr, &opts::append_arrays>();

// Read and write booleans as numbers
template <auto MemPtr>
Expand Down
62 changes: 33 additions & 29 deletions include/glaze/json/read.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -1272,7 +1272,7 @@ namespace glz
if (*it == ']') {
GLZ_SUB_LEVEL;
++it;
if constexpr (resizable<T>) {
if constexpr (resizable<T> && not Opts.append_arrays) {
value.clear();

if constexpr (Opts.shrink_to_fit) {
Expand All @@ -1284,42 +1284,46 @@ namespace glz

const size_t ws_size = size_t(it - ws_start);

const auto n = value.size();
static constexpr bool should_append = resizable<T> && Opts.append_arrays;
if constexpr (not should_append)
{
const auto n = value.size();

auto value_it = value.begin();
auto value_it = value.begin();

for (size_t i = 0; i < n; ++i) {
read<JSON>::op<ws_handled<Opts>()>(*value_it++, ctx, it, end);
if (bool(ctx.error)) [[unlikely]]
return;
GLZ_SKIP_WS();
if (*it == ',') {
++it;
for (size_t i = 0; i < n; ++i) {
read<JSON>::op<ws_handled<Opts>()>(*value_it++, ctx, it, end);
if (bool(ctx.error)) [[unlikely]]
return;
GLZ_SKIP_WS();
if (*it == ',') {
++it;

if constexpr (!Opts.minified) {
if (ws_size && ws_size < size_t(end - it)) {
skip_matching_ws(ws_start, it, ws_size);
if constexpr (!Opts.minified) {
if (ws_size && ws_size < size_t(end - it)) {
skip_matching_ws(ws_start, it, ws_size);
}
}
}

GLZ_SKIP_WS();
}
else if (*it == ']') {
GLZ_SUB_LEVEL;
++it;
if constexpr (erasable<T>) {
value.erase(value_it,
value.end()); // use erase rather than resize for non-default constructible elements
GLZ_SKIP_WS();
}
else if (*it == ']') {
GLZ_SUB_LEVEL;
++it;
if constexpr (erasable<T>) {
value.erase(value_it,
value.end()); // use erase rather than resize for non-default constructible elements

if constexpr (Opts.shrink_to_fit) {
value.shrink_to_fit();
if constexpr (Opts.shrink_to_fit) {
value.shrink_to_fit();
}
}
return;
}
else [[unlikely]] {
ctx.error = error_code::expected_bracket;
return;
}
return;
}
else [[unlikely]] {
ctx.error = error_code::expected_bracket;
return;
}
}

Expand Down
44 changes: 44 additions & 0 deletions tests/json_test/json_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -10141,6 +10141,50 @@ suite meta_keys_for_struct = [] {
};
};

struct append_obj
{
std::vector<std::string> names{};
std::vector<std::array<int, 2>> arrays{};
};

template <>
struct glz::meta<append_obj>
{
using T = append_obj;
static constexpr auto value = object("names", append_arrays<&T::names>, "arrays", append_arrays<&T::arrays>);
};

suite append_arrays_tests = [] {
"append_arrays vector"_test = [] {
std::vector<int> v{};
constexpr glz::opts append_opts{.append_arrays = true};
expect(not glz::read<append_opts>(v, "[1,2,3]"));
expect(v == std::vector<int>{1,2,3});
expect(not glz::read<append_opts>(v, "[4,5,6]"));
expect(v == std::vector<int>{1,2,3,4,5,6});
};

"append_arrays deque"_test = [] {
std::deque<int> v{};
constexpr glz::opts append_opts{.append_arrays = true};
expect(not glz::read<append_opts>(v, "[1,2,3]"));
expect(v == std::deque<int>{1,2,3});
expect(not glz::read<append_opts>(v, "[4,5,6]"));
expect(v == std::deque<int>{1,2,3,4,5,6});
};

"append_arrays append_obj"_test = [] {
append_obj obj{};
expect(not glz::read_json(obj, R"({"names":["Bob"],"arrays":[[0,0]]})"));
expect(obj.names == std::vector<std::string>{"Bob"});
expect(obj.arrays == std::vector<std::array<int, 2>>{{0,0}});

expect(not glz::read_json(obj, R"({"names":["Liz"],"arrays":[[1,1]]})"));
expect(obj.names == std::vector<std::string>{"Bob", "Liz"});
expect(obj.arrays == std::vector<std::array<int, 2>>{{0,0},{1,1}});
};
};

int main()
{
trace.end("json_test");
Expand Down

0 comments on commit 752515f

Please sign in to comment.