Search code examples
c++jsonnlohmann-json

How do I deserialize a class that has an nlohmann::json object as its first constructor parameter?


I have a class that expects an nlohmann::json object as its first constructor parameter. I've implemented to_json and from_json for my class, but from_json doesn't get called (unless I call it explicitly) and the default deserialization just puts the entire object into the json parameter. Here is some example code.

#include <iostream>

#include "nlohmann/json.hpp"

using json = nlohmann::json;

class ExampleClass
{
public:
  ExampleClass(json data = json::object(), std::string some_string = "") : m_Data(data), m_String(some_string) {}
  ~ExampleClass() {}

  json m_Data;
  std::string m_String;
};

void to_json(json& j, const ExampleClass& example)
{
  j = json{ {"Data", example.m_Data}, {"String", example.m_String} };
}

void from_json(const json& j, ExampleClass& example)
{
  j.at("Data").get_to(example.m_Data);
  j.at("String").get_to(example.m_String);
}

int main()
{
  ExampleClass example(json{ {"key", "value"} }, "here's a string");
  std::cout << json(example) << std::endl; // {"Data":{"key":"value"},"String":"here's a string"}

  ExampleClass example_two = json::parse("{\"Data\":{\"key\":\"value\"},\"String\":\"here's a string\"}");
  std::cout << json(example_two) << std::endl; // {"Data":{"Data":{"key":"value"},"String":"here's a string"},"String":""}

  ExampleClass example_three;
  from_json(json::parse("{\"Data\":{\"key\":\"value\"},\"String\":\"here's a string\"}"), example_three);
  std::cout << json(example_three) << std::endl; // {"Data":{"key":"value"},"String":"here's a string"}
}

As you can see, explicitly calling the from_json method that I wrote works fine, but I've read you're not supposed to do that. I've used this pattern for dozens of classes elsewhere in my project and only came across this problem with classes that need a json object as their first (or only) constructor parameter. Putting a non-json parameter first makes everything work fine, but I don't understand what I'm doing wrong that this doesn't work for me.


Solution

  • The issue is that you don't actually want to call the constructor in the case of example_two. As such, you should make your constructor explicit to avoid accidental calls.

    class ExampleClass
    {
    public:
      explicit ExampleClass(json data = json::object(), std::string some_string = "") : m_Data(data), m_String(some_string) {}
      ~ExampleClass() {}
    
      json m_Data;
      std::string m_String;
    };
    

    Demo: https://godbolt.org/z/Tr1YxTa5j