nlohmann/json

Issue when dumping a vector of derived classes

myermo opened this issue · 4 comments

Description

I need to serialize a vector composed by derived classes. I have a Container class which holds two variables, one of them being a std::vector<Base>. When dumping the contents of the vector, only the members of the Base class are detected, and the members of the Derived classes are omitted.

Reproduction steps

Just execute the example provided.

https://godbolt.org/z/KbxKs688Y

Expected vs. actual results

Actual Result
{ "Container": { "a": 2, "data": [ { "b": 3 }, { "b": 5 } ] } }

Expected Result
{ "Container": { "a": 2, "data": [ { "b": 3, "d": 4 }, { "b": 5, "d": 6 } ] } }

Minimal code example

#include <iostream>
#include "nlohmann/json.hpp"

using json = nlohmann::ordered_json;

// Minimum example of the bug
struct Base
{
  int b{};

  Base(int b) : b(b) {};
};

struct Derived : public Base
{
  int d{};

  Derived(int b, int d) : Base(b), d(d) {};
};

struct Container
{
  int a{};
  std::vector<std::shared_ptr<Base>> v{};

  Container(int a, std::vector<std::shared_ptr<Base>> v) : a(a), v(v) {};
};

void to_json(json& j, const std::shared_ptr<Base>& b)
{
  j = json{{"b", b->b}};
}

void to_json(json& j, const std::shared_ptr<Derived>& d)
{
  j = json{{"b", d->b}};
  j.update("d", d->d);
}

void to_json(json& j, const std::shared_ptr<Container>& c)
{
  j = json{ { "a", c->a } , { "data", c->v } };
}

void createOutputJson()
{
  json j;

  auto d1 = std::make_shared<Derived>(3, 4);
  auto d2 = std::make_shared<Derived>(5, 6);

  std::vector<std::shared_ptr<Base>> v2 = {d1, d2};

  auto C = std::make_shared<Container>(2, v2);

  j["Container"] = C;

  std::cout << j.dump(4) << std::endl;
}

int main() {
    createOutputJson();
}


### Error messages

```Shell
N/A

Compiler and operating system

Arch Linux, GCC 14.2.1 and CLANG 19.1.0

Library version

b36f4c4

Validation

Do not use curly braces to initialize a JSON value, see the first item of the FAQ: https://json.nlohmann.me/home/faq/

I did the changes , but the issue persists. It seems like the to_json() function in the derived class is never called.

See new code: https://godbolt.org/z/b9f7eezT9

I did the changes , but the issue persists. It seems like the to_json() function in the derived class is never called.

That's correct, you have shared_ptr<Base>, so that's what's called. You have to do all the work there to handle the derived class, such as calling a virtual function in Base. That includes doing something like writing out a type identifier string or enum value so you know what type to instantiate in from_json<Base>.

I believe that there's an example of this in the docs.

Hi myermo,according to the new code you shared at https://godbolt.org/z/b9f7eezT9, I believe I understand the effect you're aiming for, you actually expecting the type of member v in Container could have polymorphism behavior: when you passed a Derived* type to the Container constructor, you thought the member v could be constructed as vector<Derived*>.

That's not gonna happen because std::vector does not support that kind of behavior. So the Actual Result is correct.

Polymorphism in C++ applies only to the invocation of virtual functions, so the type of member v in Container will always be vector<Base*> and the vector knows nothing about Derived*, that's why to_json() function in the derived class never be called.

I think maybe you can re-define the Container as a template as well as related to_json function.

template <typename T>
struct Container
{
    int a{};
    std::vector<T*> v{};

    Container(int a, std::vector<T*> v) : a(a), v(v) {};
};

template <typename T>
void to_json(json& j, const Container<T>* c)
{
    j = json({ { "a", c->a } , { "data", c->v } });
}