Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Issue with optional "any" type in C++ #1961

Open
flounderpinto opened this issue Jun 17, 2022 · 1 comment
Open

Issue with optional "any" type in C++ #1961

flounderpinto opened this issue Jun 17, 2022 · 1 comment

Comments

@flounderpinto
Copy link

flounderpinto commented Jun 17, 2022

Given the following JSON schema:

{
  "$schema": "http://json-schema.org/draft-04/schema#",
  "definitions": {
    "example": {
      "type": "object",
        "required": [
          "foo",
          "bar"
        ],
        "additionalProperties": false,
        "properties": {
          "foo": {
            "type": "boolean"
          },
          "bar": {
            "type": "string"
          },
          "alpha": {
          },
          "beta": {
            "type": "integer"
          }
        }
    }
  },
  "$ref": "#/definitions/example"
}

The following C++ is generated (with the --hide-null-optional and --no-boost options):

//  To parse this JSON data, first install
//
//      json.hpp  https://github.com/nlohmann/json
//
//  Then include this file, and then do
//
//     Test data = nlohmann::json::parse(jsonString);

#pragma once

#include "json.hpp"

#include <optional>
#include <stdexcept>
#include <regex>

#ifndef NLOHMANN_OPT_HELPER
#define NLOHMANN_OPT_HELPER
namespace nlohmann {
    template <typename T>
    struct adl_serializer<std::shared_ptr<T>> {
        static void to_json(json & j, const std::shared_ptr<T> & opt) {
            if (!opt) j = nullptr; else j = *opt;
        }

        static std::shared_ptr<T> from_json(const json & j) {
            if (j.is_null()) return std::unique_ptr<T>(); else return std::unique_ptr<T>(new T(j.get<T>()));
        }
    };
}
#endif

namespace quicktype {
    using nlohmann::json;

    inline json get_untyped(const json & j, const char * property) {
        if (j.find(property) != j.end()) {
            return j.at(property).get<json>();
        }
        return json();
    }

    inline json get_untyped(const json & j, std::string property) {
        return get_untyped(j, property.data());
    }

    template <typename T>
    inline std::shared_ptr<T> get_optional(const json & j, const char * property) {
        if (j.find(property) != j.end()) {
            return j.at(property).get<std::shared_ptr<T>>();
        }
        return std::shared_ptr<T>();
    }

    template <typename T>
    inline std::shared_ptr<T> get_optional(const json & j, std::string property) {
        return get_optional<T>(j, property.data());
    }

    class Test {
        public:
        Test() = default;
        virtual ~Test() = default;

        private:
        nlohmann::json alpha;
        std::string bar;
        std::shared_ptr<int64_t> beta;
        bool foo;

        public:
        const nlohmann::json & get_alpha() const { return alpha; }
        nlohmann::json & get_mutable_alpha() { return alpha; }
        void set_alpha(const nlohmann::json & value) { this->alpha = value; }

        const std::string & get_bar() const { return bar; }
        std::string & get_mutable_bar() { return bar; }
        void set_bar(const std::string & value) { this->bar = value; }

        std::shared_ptr<int64_t> get_beta() const { return beta; }
        void set_beta(std::shared_ptr<int64_t> value) { this->beta = value; }

        const bool & get_foo() const { return foo; }
        bool & get_mutable_foo() { return foo; }
        void set_foo(const bool & value) { this->foo = value; }
    };
}

namespace nlohmann {
    void from_json(const json & j, quicktype::Test & x);
    void to_json(json & j, const quicktype::Test & x);

    inline void from_json(const json & j, quicktype::Test& x) {
        x.set_alpha(quicktype::get_untyped(j, "alpha"));
        x.set_bar(j.at("bar").get<std::string>());
        x.set_beta(quicktype::get_optional<int64_t>(j, "beta"));
        x.set_foo(j.at("foo").get<bool>());
    }

    inline void to_json(json & j, const quicktype::Test & x) {
        j = json::object();
        if (x.get_alpha()) {
            j["alpha"] = x.get_alpha();
        }
        j["bar"] = x.get_bar();
        if (x.get_beta()) {
            j["beta"] = x.get_beta();
        }
        j["foo"] = x.get_foo();
    }
}

Notice that for all types, if they're optional, they're std::shared_ptr<>. Then in the to_json() methods, there's an (if x.pointer()) ... method, to check for null before adding to the json object. The "any" type is a nlohmann::json object, and keeping with the same paradigm, an optional "any" type should be a std::shared_ptr<nlohmann::json>, but is generated as a nlohmann::json object instead. This almost works since an empty json object does have the concept of "null". Unfortunately, nlohmann/json does not have a bool operator (nlohmann/json#951), so the if (x.get_alpha()) line above is invalid.

I think this all boils down to one bad line: https://github.com/quicktype/quicktype/blob/master/src/quicktype-core/language/CPlusPlus.ts#L851
Simply removing that line seems to fix the problem.

@wcpfeffer
Copy link

I also ran into this, another solution is to not use the --hide-null-optional flag.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants