Skip to content

Discriminant cannot appear after enum-by-name fields? #433

@pkerichang

Description

@pkerichang

OK this is a bizarre issue, following is a minimal example:

#include <fstream>
#include <iostream>
#include <sstream>
#include <string>
#include <string_view>
#include <variant>
#include <vector>

#include <magic_enum.hpp>

#include <daw/daw_tuple_forward.h>
#include <daw/json/daw_json_link.h>

enum class test_enum : int {
    val_0 = 0,
    val_1 = 1,
};

enum class data_type : int { FOO = 0, BAR = 1 };

struct foo_type {
    int field_1;
};

struct bar_type {
    test_enum field_1;
    // std::string field_1;
};

using variant_type = std::variant<foo_type, bar_type>;

namespace daw::json {
template <typename E> struct json_data_contract<E, std::enable_if_t<std::is_enum_v<E>>> {

    template <typename Enum> struct enum_name_converter {
        static_assert(std::is_enum_v<Enum>, "Only enum types are supported");
        constexpr std::string_view operator()(Enum e) const { return magic_enum::enum_name(e); }

        constexpr Enum operator()(std::string_view sv) const {
            auto test = magic_enum::enum_cast<Enum>(sv);
            if (!test) {
                std::string msg = "Bad enum value ";
                msg += sv;
                msg += " for enum type ";
                msg += magic_enum::enum_type_name<Enum>();
                throw std::runtime_error(msg);
            }
            return test.value();
        }
    };

    using type =
        json_type_alias<json_custom_no_name<E, enum_name_converter<E>, enum_name_converter<E>>>;
};

template <> struct json_data_contract<foo_type> {
    static constexpr char const dtype[] = "dtype";
    static constexpr char const field_1[] = "field_1";

    struct builder {
        template <class... Args> constexpr auto operator()(data_type dtype, Args &&...args) const {
            return foo_type{std::forward<Args>(args)...};
        }
    };

    using constructor_t = builder;
    using type = json_member_list<json_class<dtype, data_type>, json_number<field_1, int>>;
    static inline auto to_json_data(const foo_type &val) {
        return daw::forward_nonrvalue_as_tuple(data_type::FOO, val.field_1);
    }
};
template <> struct json_data_contract<bar_type> {
    static constexpr char const dtype[] = "dtype";
    static constexpr char const field_1[] = "field_1";

    struct builder {
        template <class... Args> constexpr auto operator()(data_type dtype, Args &&...args) const {
            return bar_type{std::forward<Args>(args)...};
        }
    };

    using constructor_t = builder;
    using type = json_member_list<json_class<dtype, data_type>, json_class<field_1, test_enum>>;
    // using type = json_member_list<json_class<dtype, data_type>, json_string<field_1>>;
    static inline auto to_json_data(const bar_type &val) {
        return daw::forward_nonrvalue_as_tuple(data_type::BAR, val.field_1);
    }
};

template <> struct json_data_contract<variant_type> {
    struct switcher {
        // Convert JSON tag member to type index
        constexpr size_t operator()(data_type type) const { return static_cast<size_t>(type); }
        // Get value for Tag from class value
        data_type operator()(const variant_type &val) const {
            return static_cast<data_type>(val.index());
        }
    };

    static constexpr char const dtype[] = "dtype";

    using type =
        json_submember_tagged_variant<json_class<dtype, data_type>, switcher, foo_type, bar_type>;
};

} // namespace daw::json

int main(int argc, char **argv) {
    std::ifstream t("test.json");
    std::stringstream buffer;
    buffer << t.rdbuf();

    auto data_source = buffer.str();
    std::string_view data_view(data_source.data(), data_source.size());

    try {
        auto obj = daw::json::from_json<std::vector<variant_type>>(data_view);
        auto obj_json = daw::json::to_json(
            obj, daw::json::options::output_flags<daw::json::options::SerializationFormat::Pretty>);
        std::cout << obj_json.c_str() << std::endl;

    } catch (const daw::json::json_exception &err) {
        std::cout << daw::json::to_formatted_string(err, data_source.data()) << std::endl;
        throw err;
    }
}

After compiling , if you run this with the following test.json content:

[
  {
    "dtype": "BAR",
    "field_1": "val_0"
  },
  {
    "field_1": 3,
    "dtype": "FOO"
  }
]

It works as expected. However, if you use swap the order of dtype and field_1 in the first element, like so:

[
  {
    "field_1": "val_0",
    "dtype": "BAR"
  },
  {
    "field_1": 3,
    "dtype": "FOO"
  }
]

then you get the following error:

reason: Invalid or corrupt string 
location: near line: 3 col: 18
"[  {    "field_1": "v"

terminate called after throwing an instance of 'daw::json::v3_24_0d::json_exception'
  what():  Invalid or corrupt string
Aborted

Curiously, if you change bar_type::field_1 to a string type instead of an enum type, then it now parses correctly again.

This seems to imply that the discriminator tag cannot appear after a serialize-by-name enum field? Any idea what's the bug?

Metadata

Metadata

Assignees

Labels

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions