Serialise / deserialise std::optional with nlohmann-json

Viewed 97

I wish to serialise a std::optional<float> with nlohmann-json.

The example given here in the docs for a boost::optional seems very close to what I want, but I don't understand where my adaptation of it is going wrong.

It seems that the deserialization component is working for me, but not the to_json aspect.

Here is a minimal working example

// OptionalSeriealisationTest.cpp 

//#define JSON_USE_IMPLICIT_CONVERSIONS 0 
// Tried toggling this as per this comment 
// https://github.com/nlohmann/json/issues/1749#issuecomment-772996219 with no effect
// (no effect noticed)

#include <iostream>
#include <optional>
#include <nlohmann/json.hpp>
namespace nlohmann
{
    template <typename T>
    struct adl_serializer<std::optional<T>>
    {
        // This one is the issue
        static void to_json(json& j, const std::optional<T>& opt) {
            //if (opt == std::nullopt) {
            //  j = nullptr;
            //}
            //else {
            //  j = *opt; // this will call adl_serializer<t>::to_json which will
            //            // find the free function to_json in t's namespace!

            //}


            if (opt)
            {
                j = opt.value();
            }
            else
            {
                j = nullptr;
            }

            //NB same errors on the block above and the commented-out version

        }

        static void from_json(const json& j, std::optional<T>& opt) {
            if (j.is_null()) {
                opt = std::nullopt;
            }
            else {
                opt = j.get<T>(); // same as above, but with
                                  // adl_serializer<t>::from_json
            }
        }
    };
}

using json = nlohmann::ordered_json;

int main()
{
    {
        // Seems ok, breakpoints on the from_json are hit
        json j;
        j["x"] = 4.0f;
        std::optional<float> x;
        j.at("x").get_to<std::optional<float>>(x);
        std::cout <<  x.has_value() << std::endl;
        std::cout << x.value() << std::endl;
    }

    {
        // Seems ok, breakpoints on the from_json are hit
        json j;
        j["x"] = nullptr;
        std::optional<float> x = 4.0f;
        j.at("x").get_to<std::optional<float>>(x);
        std::cout << x.has_value() << std::endl;
        //std::cout << x.value() << std::endl;
    }

    {
        // Won't compile on MSVC
        std::optional<float> x = 4.0;
        json j;
        j["x"] = x; 
    }

    {
        // Won't compile on MSVC
        std::optional<float> x = 4.0;
        auto j = json({ "x", x });
    }

}

The the to_json aspects will not compile in MSVC with the following error

Severity    Code    Description Project File    Line    Suppression State
Error (active)  E0289

no instance of constructor

"nlohmann::basic_json<ObjectType, ArrayType, StringType,
BooleanType, NumberIntegerType, NumberUnsignedType, NumberFloatType,
AllocatorType, JSONSerializer, BinaryType>::basic_json

[with ObjectType=nlohmann::ordered_map, ArrayType=std::vector,
StringType=std::string, BooleanType=bool, NumberIntegerType=int64_t,
NumberUnsignedType=uint64_t, NumberFloatType=double,
AllocatorType=std::allocator, JSONSerializer=nlohmann::adl_serializer,
BinaryType=std::vector<uint8_t, std::allocator<uint8_t>>]"

matches the argument list

My specific asks are

  • What's the fix?
  • Why does this go wrong, when it seems so close to their example (boost::optional vs std::optional?)

I note there are some quite lengthy discussions on the project about trouble incorporating std::optionals into the lib itself although I don't fully follow it and understand what it means for me in practice wanting to use std::optional as a third-party object. Some of the comments suggest approaches similar to what I have here work, so I hope it amounts to an oversight / silly mistake.

Cheers!

1 Answers

This amounted to a dumb-one that's hard to spot because you're too worried its something esoteric. It's probabally quite specific to my mistake and wont likely be too much use to anyone finding this in the future though. Nonetheless, answer is as follows.

My MWE about is a simplification of something in a real codebase. The codebase uses orderd_json specifically, but short hands it to json, i.e.

using json = nlohmann::ordered_json;

The class using the word json in nlohmann is a different, more general class. It looks like when serialising to an ordered_json it needs to be specifically for the type ordered_json and can't be automagically coaxed in.

I.e. I should ha to_json taking type ordered_json as it's first argument.

        static void to_json(ordered_json& j, const std::optional<T>& opt) {/*...*/}

rather than

        static void to_json(json& j, const std::optional<T>& opt) {/*...*/}

It just turned out from_json worked anyway.

Related