// Glaze Library // For the license information refer to glaze.hpp #pragma once #include "glaze/core/buffer_traits.hpp" #include "glaze/core/custom_meta.hpp" #include "glaze/core/opts.hpp" #include "glaze/core/reflect.hpp" #include "glaze/core/to.hpp" #include "glaze/core/wrappers.hpp" #include "glaze/core/write.hpp" #include "glaze/core/write_chars.hpp" #include "glaze/util/dump.hpp" #include "glaze/util/for_each.hpp" #include "glaze/util/parse.hpp" #include "glaze/util/variant.hpp" #include "glaze/yaml/common.hpp" #include "glaze/yaml/opts.hpp" namespace glz { template <> struct serialize { template static void op(T&& value, Ctx&& ctx, B&& b, IX&& ix) { to>::template op(std::forward(value), std::forward(ctx), std::forward(b), std::forward(ix)); } }; // glaze_value_t - unwrap custom value types template requires(glaze_value_t && !custom_write) struct to { template static void op(Value&& value, Ctx&& ctx, B&& b, IX&& ix) { using V = std::remove_cvref_t(), meta_wrapper_v))>; to::template op(get_member(std::forward(value), meta_wrapper_v), std::forward(ctx), std::forward(b), std::forward(ix)); } }; // nullable_like (std::optional, pointers) template struct to { template static void op(auto&& value, is_context auto&& ctx, auto&& b, auto& ix) { if (value) { serialize::op(*value, ctx, b, ix); } else { if (!ensure_space(ctx, b, ix + 8)) [[unlikely]] { return; } dump("null", b, ix); } } }; // Always-null literal types (std::nullptr_t, std::monostate, std::nullopt_t) template struct to { template static void op(auto&&, is_context auto&& ctx, B&& b, auto& ix) { if (!ensure_space(ctx, b, ix + 8)) [[unlikely]] { return; } dump("null", b, ix); } }; // boolean_like template struct to { template static void op(const bool value, is_context auto&& ctx, B&& b, auto& ix) { if (!ensure_space(ctx, b, ix + 8)) [[unlikely]] { return; } if constexpr (check_bools_as_numbers(Opts)) { if (value) { b[ix++] = '1'; } else { b[ix++] = '0'; } } else { if (value) { std::memcpy(&b[ix], "true", 4); ix += 4; } else { std::memcpy(&b[ix], "false", 5); ix += 5; } } } }; // num_t (integers and floats) template struct to { template static void op(auto&& value, is_context auto&& ctx, B&& b, auto& ix) { if (!ensure_space(ctx, b, ix + 32 + write_padding_bytes)) [[unlikely]] { return; } // YAML supports .inf, -.inf, and .nan for special float values if constexpr (std::floating_point>) { if (std::isnan(value)) { dump(".nan", b, ix); return; } else if (std::isinf(value)) { if (value < 0) { dump("-.inf", b, ix); } else { dump(".inf", b, ix); } return; } } write_chars::op(value, ctx, b, ix); } }; // char_t template struct to { template static void op(auto&& value, is_context auto&& ctx, B&& b, auto& ix) { if (!ensure_space(ctx, b, ix + 8)) [[unlikely]] { return; } dump('"', b, ix); b[ix++] = value; dump('"', b, ix); } }; namespace yaml { // Write a YAML double-quoted string with proper escaping template inline void write_double_quoted_string(std::string_view str, is_context auto&& ctx, B&& b, auto& ix) { // Estimate max size: original + quotes + escapes if (!ensure_space(ctx, b, ix + str.size() * 2 + 3 + write_padding_bytes)) [[unlikely]] { return; } dump('"', b, ix); for (char c : str) { switch (c) { case '"': dump("\\\"", b, ix); break; case '\\': dump("\\\\", b, ix); break; case '\n': dump("\\n", b, ix); break; case '\r': dump("\\r", b, ix); break; case '\t': dump("\\t", b, ix); break; case '\0': dump("\\0", b, ix); break; default: if (static_cast(c) < 0x20) { // Control characters - use hex escape dump("\\x", b, ix); constexpr char hex[] = "0123456789abcdef"; b[ix++] = hex[(c >> 4) & 0xF]; b[ix++] = hex[c & 0xF]; } else { b[ix++] = c; } break; } } dump('"', b, ix); } // Write a YAML single-quoted string (only ' needs escaping as '') template inline void write_single_quoted_string(std::string_view str, is_context auto&& ctx, B&& b, auto& ix) { if (!ensure_space(ctx, b, ix + str.size() * 2 + 3 + write_padding_bytes)) [[unlikely]] { return; } dump('\'', b, ix); for (char c : str) { if (c == '\'') { dump("''", b, ix); } else { b[ix++] = c; } } dump('\'', b, ix); } // Write a literal block scalar (|) template inline void write_literal_block(std::string_view str, is_context auto&& ctx, B&& b, auto& ix, int32_t indent_level, uint8_t indent_width, char chomping) { if (!ensure_space(ctx, b, ix + str.size() + 64 + write_padding_bytes)) [[unlikely]] { return; } if (chomping == '-' || chomping == '+') { dump('|', b, ix); dump(chomping, b, ix); dump('\n', b, ix); } else { dump("|\n", b, ix); } // Write each line with proper indentation size_t pos = 0; while (pos < str.size()) { // Write indentation const int32_t spaces = (indent_level + 1) * indent_width; if (!ensure_space(ctx, b, ix + spaces + 256)) [[unlikely]] { return; } for (int32_t i = 0; i < spaces; ++i) { b[ix++] = ' '; } // Find end of line size_t eol = str.find('\n', pos); if (eol == std::string_view::npos) { eol = str.size(); } // Write line content const size_t line_len = eol - pos; if (!ensure_space(ctx, b, ix + line_len + 8)) [[unlikely]] { return; } std::memcpy(&b[ix], &str[pos], line_len); ix += line_len; dump('\n', b, ix); pos = eol + 1; } } // Write string with appropriate style #ifdef _MSC_VER #pragma warning(push) #pragma warning(disable : 4702) // unreachable code from if constexpr #endif template inline void write_yaml_string(std::string_view str, is_context auto&& ctx, B&& b, auto& ix, int32_t indent_level = 0) { constexpr uint8_t indent_width = check_indent_width(yaml_opts{}); // Use literal block style for multiline strings if (str.find('\n') != std::string_view::npos) { // Block scalars are not valid inside flow collections ({...}, [...]). // Emit double-quoted escaped form in flow context. if constexpr (yaml::check_flow_style(Opts) || yaml::check_flow_context(Opts)) { write_double_quoted_string(str, ctx, b, ix); return; } // Block scalars have no escape mechanism, so characters like \r, \0, // and other control chars cannot be represented. Fall back to double-quoted. { bool has_unrepresentable = false; for (char c : str) { if (c == '\r' || c == '\0' || (static_cast(c) < 0x20 && c != '\n' && c != '\t')) { has_unrepresentable = true; break; } } if (has_unrepresentable) { write_double_quoted_string(str, ctx, b, ix); return; } } size_t trailing_newlines = 0; for (size_t i = str.size(); i > 0; --i) { if (str[i - 1] == '\n') { ++trailing_newlines; } else { break; } } char chomping = '\0'; if (trailing_newlines == 0) { chomping = '-'; } else if (trailing_newlines > 1) { chomping = '+'; } write_literal_block(str, ctx, b, ix, indent_level, indent_width, chomping); return; } // Check if string needs quoting if (yaml::needs_quoting(str)) { // Double-quoted style is required for strings with characters that need // escape sequences (\r, \0, control chars) since single-quoted strings // have no escape mechanism for these. bool needs_escapes = false; for (char c : str) { if (c == '\r' || c == '\0' || (static_cast(c) < 0x20 && c != '\t')) { needs_escapes = true; break; } } if (needs_escapes || str.find('\'') != std::string_view::npos) { write_double_quoted_string(str, ctx, b, ix); } else { write_single_quoted_string(str, ctx, b, ix); } } else { // Plain scalar if (!ensure_space(ctx, b, ix + str.size() + write_padding_bytes)) [[unlikely]] { return; } dump(str, b, ix); } } #ifdef _MSC_VER #pragma warning(pop) #endif } // namespace yaml // str_t (strings) template struct to { template static void op(auto&& value, is_context auto&& ctx, B&& b, auto& ix) { const sv str{value}; yaml::write_yaml_string(str, ctx, b, ix); } }; // Enum with glz::meta - writes string representation template requires((glaze_enum_t || (meta_keys && std::is_enum_v>)) && not custom_write) struct to { template static void op(auto&& value, is_context auto&& ctx, B&& b, auto& ix) { const sv str = get_enum_name(value); if (!str.empty()) { yaml::write_yaml_string(str, ctx, b, ix); } else [[unlikely]] { // Value doesn't have a mapped string, serialize as underlying number serialize::op(static_cast>(value), ctx, b, ix); } } }; // Raw enum (without glz::meta) - writes as underlying numeric type template requires(!meta_keys && std::is_enum_v> && !glaze_enum_t && !custom_write) struct to { template static void op(auto&& value, is_context auto&& ctx, Args&&... args) { serialize::op(static_cast>>(value), ctx, std::forward(args)...); } }; namespace yaml { // Forward declarations for helpers used in block sequences template inline void write_block_mapping_nested(T&& value, is_context auto&& ctx, B&& b, auto& ix, int32_t indent_level); template inline void write_block_mapping(T&& value, is_context auto&& ctx, B&& b, auto& ix, int32_t indent_level, bool skip_first_indent = false); // Forward declaration for tagged-variant block output (definition needs write_block_mapping). template inline void write_tagged_block_object(T&& inner, size_t index, is_context auto&& ctx, B&& b, auto& ix, int32_t indent_level, bool skip_first_indent); // Helper to check if a type is "simple" (writes on same line) template consteval bool is_simple_type() { using V = std::remove_cvref_t; return bool_t || num_t || str_t || char_t || std::is_enum_v || std::same_as; } // Runtime check if a variant currently holds a simple type template inline bool variant_holds_simple_type([[maybe_unused]] const T& value) { if constexpr (is_variant) { return std::visit( [](const auto& v) { using V = std::remove_cvref_t; return is_simple_type(); }, value); } else if constexpr (glaze_value_t) { // Unwrap glaze_value types (like glz::generic) to check the inner variant decltype(auto) inner = get_member(value, meta_wrapper_v); return variant_holds_simple_type(inner); } else { return is_simple_type(); } } // Compile-time check if a type is or wraps a variant template consteval bool is_or_wraps_variant() { if constexpr (is_variant) { return true; } else if constexpr (glaze_value_t) { using inner_t = std::decay_t(), meta_wrapper_v))>; return is_or_wraps_variant(); } else { return false; } } // Write a variant's held value in block context, ensuring strings get correct indent_level. template inline void write_variant_value(T&& value, is_context auto&& ctx, B&& b, auto& ix, int32_t indent_level) { using V = std::remove_cvref_t; if constexpr (is_variant) { std::visit( [&](auto&& inner) { using inner_t = std::remove_cvref_t; if constexpr (str_t) { write_yaml_string(sv{inner}, ctx, b, ix, indent_level); } else { serialize::op(inner, ctx, b, ix); } }, value); } else if constexpr (glaze_value_t) { write_variant_value(get_member(value, meta_wrapper_v), ctx, b, ix, indent_level); } else if constexpr (str_t) { write_yaml_string(sv{value}, ctx, b, ix, indent_level); } else { serialize::op(value, ctx, b, ix); } } // Write block-style sequence template inline void write_block_sequence(T&& value, is_context auto&& ctx, B&& b, auto& ix, int32_t indent_level) { constexpr uint8_t indent_width = check_indent_width(yaml_opts{}); bool is_empty = false; if constexpr (requires { value.empty(); }) { is_empty = value.empty(); } else if constexpr (requires { value.begin(); value.end(); }) { is_empty = (value.begin() == value.end()); } if (is_empty) { const int32_t spaces = indent_level * indent_width; if (!ensure_space(ctx, b, ix + spaces + 8)) [[unlikely]] { return; } for (int32_t i = 0; i < spaces; ++i) { b[ix++] = ' '; } dump("[]", b, ix); if (indent_level > 0) { dump('\n', b, ix); } return; } bool first = true; for (auto&& element : value) { if (bool(ctx.error)) [[unlikely]] return; using element_t = std::remove_cvref_t; if (!first) { // Nothing needed between elements - each starts with indented dash } first = false; // Write indentation and dash const int32_t spaces = indent_level * indent_width; if (!ensure_space(ctx, b, ix + spaces + 8)) [[unlikely]] { return; } for (int32_t i = 0; i < spaces; ++i) { b[ix++] = ' '; } dump('-', b, ix); if constexpr (str_t) { dump(' ', b, ix); write_yaml_string(sv{element}, ctx, b, ix, indent_level); dump('\n', b, ix); } else if constexpr (is_simple_type()) { dump(' ', b, ix); serialize::op(element, ctx, b, ix); dump('\n', b, ix); } else if constexpr (nullable_like) { using inner_t = std::remove_cvref_t; if (!element) { dump(' ', b, ix); dump("null", b, ix); dump('\n', b, ix); } else if constexpr (str_t) { dump(' ', b, ix); write_yaml_string(sv{*element}, ctx, b, ix, indent_level); dump('\n', b, ix); } else if constexpr (is_simple_type()) { dump(' ', b, ix); serialize::op(*element, ctx, b, ix); dump('\n', b, ix); } else { // Complex inner type - check for empty containers first bool wrote_empty = false; if constexpr (writable_map_t) { if (element->empty()) { dump(" {}\n", b, ix); wrote_empty = true; } } else if constexpr (writable_array_t) { if constexpr (requires { element->empty(); }) { if (element->empty()) { dump(" []\n", b, ix); wrote_empty = true; } } } if (!wrote_empty) { if constexpr (glaze_object_t || reflectable) { dump(' ', b, ix); write_block_mapping(*element, ctx, b, ix, indent_level + 1, true); } else { dump('\n', b, ix); write_block_mapping_nested(*element, ctx, b, ix, indent_level + 1); } } } } else if constexpr (is_or_wraps_variant()) { // For variants, check at runtime if they hold a simple type if (variant_holds_simple_type(element)) { dump(' ', b, ix); write_variant_value(element, ctx, b, ix, indent_level); dump('\n', b, ix); } else { // Complex variant content (maps/arrays/objects) - write in block style if constexpr (is_variant) { const size_t index = element.index(); std::visit( [&](auto&& inner) { using inner_t = std::remove_cvref_t; if constexpr (glaze_object_t || reflectable) { // Compact form: first key (or the discriminator tag) inline after dash dump(' ', b, ix); if constexpr (check_write_type_info(Opts) && not tag_v.empty()) { write_tagged_block_object(inner, index, ctx, b, ix, indent_level + 1, true); } else { write_block_mapping(inner, ctx, b, ix, indent_level + 1, true); } } else { if constexpr (writable_map_t) { if (inner.empty()) { dump(" {}\n", b, ix); return; } } else if constexpr (writable_array_t) { if constexpr (requires { inner.empty(); }) { if (inner.empty()) { dump(" []\n", b, ix); return; } } } dump('\n', b, ix); write_block_mapping_nested(inner, ctx, b, ix, indent_level + 1); } }, element); } else { // glaze_value_t wrapping a variant — simple types were already // handled by variant_holds_simple_type above, so the held value // is guaranteed to be a complex type (map/array/object). dump('\n', b, ix); write_block_mapping_nested(element, ctx, b, ix, indent_level + 1); } } } else if constexpr (glaze_object_t || reflectable) { // Compact form: first key inline after dash dump(' ', b, ix); write_block_mapping(element, ctx, b, ix, indent_level + 1, true); } else if constexpr (has_custom_meta_v) { // Types with top-level custom serialization produce scalar output - // write inline after dash dump(' ', b, ix); serialize::op(element, ctx, b, ix); dump('\n', b, ix); } else if constexpr (writable_map_t || writable_array_t || glaze_value_t) { // Containers and glaze_value_t (which may wrap containers) - // write on next line with increased indent dump('\n', b, ix); write_block_mapping_nested(element, ctx, b, ix, indent_level + 1); } else { // Other types (pairs, tuples, etc.) - write inline after dash dump(' ', b, ix); serialize::op(element, ctx, b, ix); dump('\n', b, ix); } } } // Write flow-style sequence template inline void write_flow_sequence(T&& value, is_context auto&& ctx, B&& b, auto& ix) { if (!ensure_space(ctx, b, ix + 8)) [[unlikely]] { return; } dump('[', b, ix); bool first = true; for (auto&& element : value) { if (bool(ctx.error)) [[unlikely]] return; if (!first) { dump(", ", b, ix); } first = false; serialize::op()>(element, ctx, b, ix); } dump(']', b, ix); } } // namespace yaml // writable_array_t (vectors, arrays, etc.) template struct to { template static void op(auto&& value, is_context auto&& ctx, B&& b, auto& ix) { if constexpr (yaml::check_flow_style(Opts) || yaml::check_flow_context(Opts)) { yaml::write_flow_sequence(value, ctx, b, ix); } else { // Get indent level from context if available, otherwise 0 int32_t indent_level = 0; if constexpr (requires { ctx.indent_level; }) { indent_level = ctx.indent_level; } yaml::write_block_sequence(value, ctx, b, ix, indent_level); } } }; // Tuples (std::tuple, glaze_array_t, tuple_t) template requires(glaze_array_t || tuple_t> || is_std_tuple) struct to { template static void op(auto&& value, is_context auto&& ctx, B&& b, auto& ix) { static constexpr auto N = []() constexpr { if constexpr (glaze_array_t>) { return glz::tuple_size_v>>; } else { return glz::tuple_size_v>; } }(); if constexpr (yaml::check_flow_style(Opts) || yaml::check_flow_context(Opts)) { // Flow style: [a, b, c] if (!ensure_space(ctx, b, ix + 8)) [[unlikely]] { return; } dump('[', b, ix); using V = std::decay_t; for_each([&]() { if (bool(ctx.error)) [[unlikely]] return; if constexpr (I != 0) { dump(", ", b, ix); } if constexpr (glaze_array_t) { serialize::op()>(get_member(value, glz::get(meta_v)), ctx, b, ix); } else if constexpr (is_std_tuple) { using element_t = core_t(value))>; to::template op()>(std::get(value), ctx, b, ix); } else { using element_t = core_t(value))>; to::template op()>(glz::get(value), ctx, b, ix); } }); dump(']', b, ix); } else { // Block style: - a\n- b\n- c constexpr uint8_t indent_width = yaml::check_indent_width(yaml::yaml_opts{}); int32_t indent_level = 0; if constexpr (requires { ctx.indent_level; }) { indent_level = ctx.indent_level; } using V = std::decay_t; for_each([&]() { if (bool(ctx.error)) [[unlikely]] return; // Write indentation and dash const int32_t spaces = indent_level * indent_width; if (!ensure_space(ctx, b, ix + spaces + 8)) [[unlikely]] { return; } for (int32_t i = 0; i < spaces; ++i) { b[ix++] = ' '; } dump("- ", b, ix); if constexpr (glaze_array_t) { using element_t = std::decay_t(meta_v)))>; if constexpr (yaml::is_simple_type()) { serialize::op(get_member(value, glz::get(meta_v)), ctx, b, ix); dump('\n', b, ix); } else { dump('\n', b, ix); serialize::op(get_member(value, glz::get(meta_v)), ctx, b, ix); } } else if constexpr (is_std_tuple) { using element_t = std::decay_t>>; if constexpr (yaml::is_simple_type()) { to::template op(std::get(value), ctx, b, ix); dump('\n', b, ix); } else { dump('\n', b, ix); to::template op(std::get(value), ctx, b, ix); } } else { using element_t = std::decay_t(value))>; if constexpr (yaml::is_simple_type()) { to::template op(glz::get(value), ctx, b, ix); dump('\n', b, ix); } else { dump('\n', b, ix); to::template op(glz::get(value), ctx, b, ix); } } }); } } }; // Pairs (std::pair) template struct to { template static void op(auto&& value, is_context auto&& ctx, B&& b, auto& ix) { const auto& [key, val] = value; using first_type = typename std::remove_cvref_t::first_type; using second_type = typename std::remove_cvref_t::second_type; if constexpr (yaml::check_flow_style(Opts) || yaml::check_flow_context(Opts)) { // Flow style: {key: value} if (!ensure_space(ctx, b, ix + 8)) [[unlikely]] { return; } dump('{', b, ix); // Write key if constexpr (str_t) { yaml::write_yaml_string(sv{key}, ctx, b, ix); } else { serialize::op()>(key, ctx, b, ix); } dump(": ", b, ix); // Write value serialize::op()>(val, ctx, b, ix); dump('}', b, ix); } else { // Block style: key: value constexpr uint8_t indent_width = yaml::check_indent_width(yaml::yaml_opts{}); int32_t indent_level = 0; if constexpr (requires { ctx.indent_level; }) { indent_level = ctx.indent_level; } // Write indentation const int32_t spaces = indent_level * indent_width; if (!ensure_space(ctx, b, ix + spaces + 64)) [[unlikely]] { return; } for (int32_t i = 0; i < spaces; ++i) { b[ix++] = ' '; } // Write key if constexpr (str_t) { yaml::write_yaml_string(sv{key}, ctx, b, ix); } else { serialize::op(key, ctx, b, ix); } dump(':', b, ix); if constexpr (yaml::is_simple_type()) { dump(' ', b, ix); serialize::op(val, ctx, b, ix); dump('\n', b, ix); } else { dump('\n', b, ix); serialize::op(val, ctx, b, ix); } } } }; namespace yaml { // Forward declaration for nested object helper template inline void write_block_mapping_nested(T&& value, is_context auto&& ctx, B&& b, auto& ix, int32_t indent_level); // Write block-style mapping template inline void write_block_mapping(T&& value, is_context auto&& ctx, B&& b, auto& ix, int32_t indent_level, bool skip_first_indent) { using V = std::remove_cvref_t; constexpr auto N = reflect::size; constexpr uint8_t indent_width = check_indent_width(yaml_opts{}); for_each([&]() { if (bool(ctx.error)) [[unlikely]] return; using val_t = field_t; if constexpr (!always_skipped) { static constexpr sv key = get(reflect::keys); // Get member value (supports both glaze_object_t and reflectable) auto&& member = [&]() -> decltype(auto) { if constexpr (glaze_object_t) { return get_member(value, get(reflect::values)); } else { return get(to_tie(value)); } }(); // Skip fields based on meta::skip (compile-time) and meta::skip_if (runtime) if constexpr (meta_has_skip) { static constexpr meta_context mctx{.op = operation::serialize}; if constexpr (meta::skip(reflect::keys[I], mctx)) return; } if constexpr (meta_has_skip_if) { static constexpr auto k = glz::get(reflect::keys); static constexpr meta_context mctx{.op = operation::serialize}; if (meta::skip_if(member, k, mctx)) return; } // Skip null members if configured if constexpr (nullable_like) { if constexpr (Opts.skip_null_members) { if (!member) { return; } } } if constexpr (check_skip_default_members(Opts) && has_skippable_default) { if (is_default_value(member)) return; } // Write indentation (skip for first field when in compact sequence context) if (skip_first_indent) { skip_first_indent = false; if (!ensure_space(ctx, b, ix + key.size() + 8)) [[unlikely]] { return; } } else { const int32_t spaces = indent_level * indent_width; if (!ensure_space(ctx, b, ix + spaces + key.size() + 8)) [[unlikely]] { return; } for (int32_t i = 0; i < spaces; ++i) { b[ix++] = ' '; } } // Write key dump(key, b, ix); dump(':', b, ix); // Handle empty containers inline (before type dispatch) if constexpr (range && !str_t && !is_simple_type() && has_empty) { if (member.empty()) { if constexpr (writable_map_t) { dump(" {}\n", b, ix); } else { dump(" []\n", b, ix); } return; } } if constexpr (is_simple_type()) { // Simple types go on same line dump(' ', b, ix); if constexpr (str_t) { yaml::write_yaml_string(sv{member}, ctx, b, ix, indent_level); } else { serialize::op(member, ctx, b, ix); } dump('\n', b, ix); } else if constexpr (nullable_like) { using inner_t = std::remove_cvref_t())>; if (!member) { // Null - always same line dump(' ', b, ix); serialize::op(member, ctx, b, ix); dump('\n', b, ix); } else if constexpr (is_simple_type() || str_t) { // Simple inner type - same line dump(' ', b, ix); if constexpr (str_t) { yaml::write_yaml_string(sv{*member}, ctx, b, ix, indent_level); } else { serialize::op(*member, ctx, b, ix); } dump('\n', b, ix); } else { // Complex inner type - check for empty containers first bool wrote_empty = false; if constexpr (writable_map_t) { if (member->empty()) { dump(" {}\n", b, ix); wrote_empty = true; } } else if constexpr (writable_array_t) { if constexpr (requires { member->empty(); }) { if (member->empty()) { dump(" []\n", b, ix); wrote_empty = true; } } } if (!wrote_empty) { // Non-empty complex inner type - next line with increased indent dump('\n', b, ix); if constexpr (requires { ctx.indent_level; }) { auto nested_ctx = ctx; nested_ctx.indent_level = indent_level + 1; serialize::op(*member, nested_ctx, b, ix); } else { write_block_mapping_nested(*member, ctx, b, ix, indent_level + 1); } } } } else if constexpr (is_or_wraps_variant()) { // Variants and variant-wrappers (e.g., glz::generic) may hold simple values if (variant_holds_simple_type(member)) { dump(' ', b, ix); write_variant_value(member, ctx, b, ix, indent_level); dump('\n', b, ix); } else { dump('\n', b, ix); if constexpr (requires { ctx.indent_level; }) { auto nested_ctx = ctx; nested_ctx.indent_level = indent_level + 1; serialize::op(member, nested_ctx, b, ix); } else { write_block_mapping_nested(member, ctx, b, ix, indent_level + 1); } } } else if constexpr (writable_map_t || writable_array_t || glaze_object_t || reflectable) { // Complex types go on next line with increased indent dump('\n', b, ix); // Create a modified context with incremented indent level if constexpr (requires { ctx.indent_level; }) { auto nested_ctx = ctx; nested_ctx.indent_level = indent_level + 1; serialize::op(member, nested_ctx, b, ix); } else { write_block_mapping_nested(member, ctx, b, ix, indent_level + 1); } } else { // All other types (custom_t, types with custom to/from, etc.) // are assumed to produce scalar values - write on same line dump(' ', b, ix); serialize::op(member, ctx, b, ix); dump('\n', b, ix); } } }); } // Helper for nested objects template inline void write_block_mapping_nested(T&& value, is_context auto&& ctx, B&& b, auto& ix, int32_t indent_level) { using V = std::remove_cvref_t; if constexpr (glaze_object_t || reflectable) { write_block_mapping(value, ctx, b, ix, indent_level); } else if constexpr (glaze_value_t) { // Unwrap glaze_value_t types (like glz::generic) and recurse write_block_mapping_nested(get_member(value, meta_wrapper_v), ctx, b, ix, indent_level); } else if constexpr (is_variant) { // Handle variants by visiting and recursing with the same indent level. A tagged // variant holding an object also emits its discriminator (meta::tag) entry. const size_t index = value.index(); std::visit( [&](auto&& inner) { using inner_t = std::remove_cvref_t; if constexpr ((glaze_object_t || reflectable) && check_write_type_info(Opts) && not tag_v.empty()) { write_tagged_block_object(inner, index, ctx, b, ix, indent_level, false); } else { write_block_mapping_nested(inner, ctx, b, ix, indent_level); } }, value); } else if constexpr (writable_array_t) { write_block_sequence(value, ctx, b, ix, indent_level); } else if constexpr (writable_map_t) { // Map handling constexpr uint8_t indent_width = check_indent_width(yaml_opts{}); for (auto&& [k, v] : value) { if (bool(ctx.error)) [[unlikely]] return; // Write indentation and key const int32_t spaces = indent_level * indent_width; if (!ensure_space(ctx, b, ix + spaces + 64)) [[unlikely]] { return; } for (int32_t i = 0; i < spaces; ++i) { b[ix++] = ' '; } // Write key using key_t = std::remove_cvref_t; if constexpr (str_t) { write_yaml_string(sv{k}, ctx, b, ix); } else { serialize::op(k, ctx, b, ix); } dump(':', b, ix); using val_t = std::remove_cvref_t; if constexpr (str_t) { dump(' ', b, ix); write_yaml_string(sv{v}, ctx, b, ix, indent_level); dump('\n', b, ix); } else if constexpr (is_simple_type()) { dump(' ', b, ix); serialize::op(v, ctx, b, ix); dump('\n', b, ix); } else if constexpr (nullable_like) { using inner_t = std::remove_cvref_t; if (!v) { dump(' ', b, ix); dump("null", b, ix); dump('\n', b, ix); } else if constexpr (str_t) { dump(' ', b, ix); write_yaml_string(sv{*v}, ctx, b, ix, indent_level); dump('\n', b, ix); } else if constexpr (is_simple_type()) { dump(' ', b, ix); serialize::op(*v, ctx, b, ix); dump('\n', b, ix); } else { // Complex inner type - check for empty containers first bool wrote_empty = false; if constexpr (writable_map_t) { if (v->empty()) { dump(" {}\n", b, ix); wrote_empty = true; } } else if constexpr (writable_array_t) { if constexpr (requires { v->empty(); }) { if (v->empty()) { dump(" []\n", b, ix); wrote_empty = true; } } } if (!wrote_empty) { dump('\n', b, ix); write_block_mapping_nested(*v, ctx, b, ix, indent_level + 1); } } } else if constexpr (is_or_wraps_variant()) { // For variants and types wrapping variants (like glz::generic), // check at runtime if they hold a simple type if (variant_holds_simple_type(v)) { dump(' ', b, ix); write_variant_value(v, ctx, b, ix, indent_level); dump('\n', b, ix); } else { dump('\n', b, ix); write_block_mapping_nested(v, ctx, b, ix, indent_level + 1); } } else { dump('\n', b, ix); write_block_mapping_nested(v, ctx, b, ix, indent_level + 1); } } } else { // Fallback for types not explicitly handled above. // Note: Variants with complex content are handled in the map value loop above // (lines 785-796), so they don't reach here. This fallback handles edge cases // like custom types. The newline is safe because serialize::op for simple // types doesn't add trailing newlines. serialize::op(value, ctx, b, ix); dump('\n', b, ix); } } // Write the comma-separated "key: value" entries of a flow mapping (no surrounding braces). // `first` tracks whether a leading ", " separator is needed, letting callers prepend // entries (e.g. a tagged variant's discriminator) before the object's own members. template inline void write_flow_mapping_members(T&& value, is_context auto&& ctx, B&& b, auto& ix, bool& first) { using V = std::remove_cvref_t; constexpr auto N = reflect::size; for_each([&]() { if (bool(ctx.error)) [[unlikely]] return; using val_t = field_t; if constexpr (!always_skipped) { static constexpr sv key = get(reflect::keys); // Get member value (supports both glaze_object_t and reflectable) auto&& member = [&]() -> decltype(auto) { if constexpr (glaze_object_t) { return get_member(value, get(reflect::values)); } else { return get(to_tie(value)); } }(); // Skip null members if configured if constexpr (nullable_like) { if constexpr (Opts.skip_null_members) { if (!member) { return; } } } if constexpr (check_skip_default_members(Opts) && has_skippable_default) { if (is_default_value(member)) return; } if (!first) { dump(", ", b, ix); } first = false; if (!ensure_space(ctx, b, ix + key.size() + 8)) [[unlikely]] { return; } dump(key, b, ix); dump(": ", b, ix); serialize::op()>(member, ctx, b, ix); } }); } // Write flow-style mapping template inline void write_flow_mapping(T&& value, is_context auto&& ctx, B&& b, auto& ix) { if (!ensure_space(ctx, b, ix + 8)) [[unlikely]] { return; } dump('{', b, ix); bool first = true; write_flow_mapping_members(value, ctx, b, ix, first); dump('}', b, ix); } // Write a tagged variant's discriminator value (the meta::ids entry for `index`). template inline void write_variant_tag_id(size_t index, is_context auto&& ctx, B&& b, auto& ix, int32_t indent_level) { using id_type = std::decay_t[0])>; if constexpr (std::integral) { serialize::op(ids_v[index], ctx, b, ix); } else { write_yaml_string(sv{ids_v[index]}, ctx, b, ix, indent_level); } } // Write a tagged variant alternative that holds an object as a block mapping, emitting the // discriminator entry (meta::tag: id) ahead of the object's own members so it round-trips. template inline void write_tagged_block_object(T&& inner, size_t index, is_context auto&& ctx, B&& b, auto& ix, int32_t indent_level, bool skip_first_indent) { constexpr uint8_t indent_width = check_indent_width(yaml_opts{}); static constexpr sv tag = tag_v; if (!skip_first_indent) { const int32_t spaces = indent_level * indent_width; if (!ensure_space(ctx, b, ix + spaces + tag.size() + 8)) [[unlikely]] { return; } for (int32_t i = 0; i < spaces; ++i) { b[ix++] = ' '; } } dump(tag, b, ix); dump(": ", b, ix); write_variant_tag_id(index, ctx, b, ix, indent_level); dump('\n', b, ix); // Remaining members are emitted at the mapping's indent (the tag occupied the first line). write_block_mapping(inner, ctx, b, ix, indent_level, false); } // Write a tagged variant alternative that holds an object as a flow mapping // ({tag: id, key: value, ...}). template inline void write_tagged_flow_object(T&& inner, size_t index, is_context auto&& ctx, B&& b, auto& ix) { static constexpr sv tag = tag_v; if (!ensure_space(ctx, b, ix + tag.size() + 8)) [[unlikely]] { return; } dump('{', b, ix); dump(tag, b, ix); dump(": ", b, ix); write_variant_tag_id(index, ctx, b, ix, 0); bool first = false; // tag already written, so members need a leading ", " write_flow_mapping_members(inner, ctx, b, ix, first); dump('}', b, ix); } } // namespace yaml // glaze_object_t and reflectable (structs) template requires((glaze_object_t || reflectable) && !custom_write) struct to { template static void op(auto&& value, is_context auto&& ctx, B&& b, auto& ix) { if constexpr (yaml::check_flow_style(Opts) || yaml::check_flow_context(Opts)) { yaml::write_flow_mapping(value, ctx, b, ix); } else { int32_t indent_level = 0; if constexpr (requires { ctx.indent_level; }) { indent_level = ctx.indent_level; } yaml::write_block_mapping(value, ctx, b, ix, indent_level); } } }; // writable_map_t (std::map, std::unordered_map, etc.) template struct to { template static void op(auto&& value, is_context auto&& ctx, B&& b, auto& ix) { if constexpr (yaml::check_flow_style(Opts) || yaml::check_flow_context(Opts)) { // Flow style if (!ensure_space(ctx, b, ix + 8)) [[unlikely]] { return; } dump('{', b, ix); bool first = true; for (auto&& [k, v] : value) { if (bool(ctx.error)) [[unlikely]] return; if (!first) { dump(", ", b, ix); } first = false; using key_t = std::remove_cvref_t; if constexpr (str_t) { yaml::write_yaml_string(sv{k}, ctx, b, ix); } else { serialize::op()>(k, ctx, b, ix); } dump(": ", b, ix); serialize::op()>(v, ctx, b, ix); } dump('}', b, ix); } else { // Block style int32_t indent_level = 0; if constexpr (requires { ctx.indent_level; }) { indent_level = ctx.indent_level; } yaml::write_block_mapping_nested(value, ctx, b, ix, indent_level); } } }; // Variant support template struct to { template static void op(auto&& value, is_context auto&& ctx, B&& b, auto& ix) { using V = std::remove_cvref_t; // Tagged variants emit a discriminator entry (meta::tag) when holding an object, so the // output names which alternative it is and round-trips through the reader. if constexpr (check_write_type_info(Opts) && not tag_v.empty()) { const size_t index = value.index(); std::visit( [&](auto&& v) { using Vt = std::remove_cvref_t; if constexpr (glaze_object_t || reflectable) { if constexpr (yaml::check_flow_style(Opts) || yaml::check_flow_context(Opts)) { yaml::write_tagged_flow_object(v, index, ctx, b, ix); } else { int32_t indent_level = 0; if constexpr (requires { ctx.indent_level; }) { indent_level = ctx.indent_level; } yaml::write_tagged_block_object(v, index, ctx, b, ix, indent_level, false); } } else { // Non-object alternatives (scalars, null) carry no tag, matching JSON. serialize::op(v, ctx, b, ix); } }, value); } else { std::visit([&](auto&& v) { serialize::op(v, ctx, b, ix); }, value); } } }; // Convenience functions template [[nodiscard]] error_ctx write_yaml(T&& value, Buffer& buffer) noexcept { return write()>(std::forward(value), buffer); } template [[nodiscard]] expected write_yaml(T&& value) noexcept { return write()>(std::forward(value)); } template [[nodiscard]] error_ctx write_file_yaml(T&& value, const sv file_path) noexcept { std::string buffer; auto ec = write()>(std::forward(value), buffer); if (bool(ec)) { return ec; } const auto file_ec = buffer_to_file(buffer, file_path); if (bool(file_ec)) { return {0, file_ec}; } return {}; } } // namespace glz