6

I'm using nlohmann json (JSON for modern C++) with an embedded project. The OS is Mongoose OS. Mongoose has a nice config system where config data is handled and laid out in the mos.yml file. This file, at build time, is converted to structures and accessor functions. So, I can get the config data as a structure, which contains other, nested, structures. I need to convert this to JSON.

My understanding is that nlohmann::json has the ability to convert JSON to and from my own types, all I have to do is supply the to_json() and from_json() methods as explained here:

nlohmann json docs (type conversion example)

This example code is pretty straightforward:

struct person {
    std::string name;
    std::string address;
    int age;
};

void to_json(json& j, const person& p) {
    j = json{{"name", p.name}, {"address", p.address}, {"age", p.age}};
}

but the example presented is very simple. My type is more complex and I have not been able to figure out the syntax for a more complex structure, like this one (excerpted for brevity):

struct mgos_config_mytype {
  struct mgos_config_mytype_input input;
  struct mgos_config_mytype_speed speed;

  /* many others omitted */

};

struct mgos_config_mytype_input {
  struct mgos_config_mytype_input_wired_buttons wired_buttons;
};

struct mgos_config_mytype_input_wired_buttons {
  const char * btn1;
  const char * btn2;
  const char * btn3;
};

If someone could show me how it's done or point in the right direction it would be appreciated, thank you.

Azeem
  • 11,148
  • 4
  • 27
  • 40
Jim Archer
  • 1,337
  • 5
  • 30
  • 43

1 Answers1

12

Here's an example with nested types with to_json defined for consolidated type Person (live):

#include <iostream>
#include <string>

#include <nlohmann/json.hpp>
using nlohmann::json;

struct Name
{
    std::string first;
    std::string last;
};

struct Address
{
    std::string houseNo;
    std::string street;
    std::string city;
    std::string postalCode;
    std::string country;
};

struct Person
{
    Name    name;
    Address address;
    int     age;
};

void to_json(json& j, const Person& p)
{
    j = json{
        { "name", {
            { "first", p.name.first },
            { "last", p.name.last }
            } 
        },
        { "address", {
            { "house", p.address.houseNo },
            { "street", p.address.street },
            { "city", p.address.city },
            { "postal_code", p.address.postalCode },
            { "country", p.address.country }
            }
        },
        { "age", p.age}
    };
}

int main()
{
    const Person p { 
        { "firstname", "lastname" }, 
        { "123", "St. ABC", "XYZ", "123456", "country" }, 
        18
    };

    json j { p };
    std::cout << j.dump(4) << '\n';

    return 0;
}

Output:

[
    {
        "address": {
            "city": "XYZ",
            "country": "country",
            "house": "123",
            "postal_code": "123456",
            "street": "St. ABC"
        },
        "age": 18,
        "name": {
            "first": "firstname",
            "last": "lastname"
        }
    }
]

Depending on the complexity and reusability of your nested types, you can define to_json and from_json for all the types.

Here's Person-to-JSON and JSON-to-Person example (live):

#include <iostream>
#include <string>

#include <nlohmann/json.hpp>
using nlohmann::json;

struct Name
{
    std::string first;
    std::string last;
};

struct Address
{
    std::string houseNo;
    std::string street;
    std::string city;
    std::string postalCode;
    std::string country;
};

struct Person
{
    Name    name;
    Address address;
    int     age;
};

void to_json(json& j, const Name& name)
{
    j = json{
        { "first", name.first },
        { "last", name.last }
    };
}

void from_json(const json& j, Name& name)
{
    j.at("first").get_to(name.first);
    j.at("last").get_to(name.last);
}

void to_json(json& j, const Address& address)
{
    j = json{
        { "house", address.houseNo },
        { "street", address.street },
        { "city", address.city },
        { "postalCode", address.postalCode },
        { "country", address.country }
    };
}

void from_json(const json& j, Address& address)
{
    j.at("house").get_to(address.houseNo);
    j.at("street").get_to(address.street);
    j.at("street").get_to(address.street);
    j.at("city").get_to(address.city);
    j.at("postalCode").get_to(address.postalCode);
    j.at("country").get_to(address.country);
}

void to_json(json& j, const Person& p)
{
    j = json{
        { "name", p.name },
        { "address", p.address },
        { "age", p.age }
    };
}

void from_json(const json& j, Person& p)
{
    j.at("name").get_to(p.name);
    j.at("address").get_to(p.address);
    j.at("age").get_to(p.age);
}

int main()
{
    const Person p1 { 
        { "firstname", "lastname" }, 
        { "123", "St. ABC", "XYZ", "123456", "country" }, 
        18
    };

    const json j1 { p1 };               // Get JSON object from Person
    const auto s1 = j1.dump(4);         // Get JSON string with indentation (4 spaces)
    std::cout << s1 << '\n';

    auto p2 = j1[0].get<Person>();      // Get Person object from JSON array
    p2.name = { "ABC", "XYZ" };         // Update first and last names

    const json j2 { p2 };               // Get JSON object from Person
    const auto s2 = j2.dump(4);         // Get JSON string with indentation (4 spaces)
    std::cout << s2 << '\n';


    return 0;
}

Output:

[
    {
        "address": {
            "city": "XYZ",
            "country": "country",
            "house": "123",
            "postalCode": "123456",
            "street": "St. ABC"
        },
        "age": 18,
        "name": {
            "first": "firstname",
            "last": "lastname"
        }
    }
]
[
    {
        "address": {
            "city": "XYZ",
            "country": "country",
            "house": "123",
            "postalCode": "123456",
            "street": "St. ABC"
        },
        "age": 18,
        "name": {
            "first": "ABC",
            "last": "XYZ"
        }
    }
]
Azeem
  • 11,148
  • 4
  • 27
  • 40
  • Thanks Azeem, I can't get your first example yo work correctly. It seems the library is making multiple arrays of since objects. Could you have a look? https://godbolt.org/z/Y8kNrm – Jim Archer May 11 '20 at 05:41
  • @JimArcher: You are welcome! What is your expected output in JSON for your snippet? – Azeem May 11 '20 at 05:42
  • Mostly the same thing, but without all the square brackets. They seem unneeded, although I guess they are harmless. If that's normal then okay, I just thought I was using the library incorrectly. – Jim Archer May 11 '20 at 05:50
  • @JimArcher: Right. Well, they'll hinder your parsing for complex objects as you have to keep in mind that those are arrays and you need to use an index. Take a look at this: https://godbolt.org/z/J6VpjV. I believe this is what you meant earlier. – Azeem May 11 '20 at 06:08
  • Ah yes, that looks right! Thanks VERY much, I'll give that a try! – Jim Archer May 11 '20 at 06:13
  • @JimArcher: Awesome! Sure. Pleasure! :) – Azeem May 11 '20 at 06:16
  • if I am doing below, the program crashes! what is wrong here? std::string str = "{\ \"address\": {\ \"city\": \"XYZ\",\ \"country\": \"country\",\ \"house\": \"123\",\ \"postalCode\": \"123456\",\ \"street\": \"St. ABC\"\ }\ }"; json j = json::parse(str); printf("[UT_JsonTest] Input json string : \n %s \n", j.dump(4).c_str()); auto a = j.get
    (); // crashes here!
    – Soumyajit Roy Mar 05 '21 at 05:53
  • @SoumyajitRoy: Hi! Kindly create a new question with all the error messages and share its link here. Make sure that the shared code is a complete example to reproduce the issue. Thanks! – Azeem Mar 05 '21 at 06:58
  • @SoumyajitRoy: Here's a live example: https://godbolt.org/z/Yas16h. Your JSON and `Address` object are not compatible because `Address` has no top-level `address` JSON object. So, it should be `const auto a = j["address"].get
    ();`. I believe you can follow the rest of the example. Good luck! For future reference, always create a new question because the questions might be similar but the exact use-case might be different.
    – Azeem Mar 05 '21 at 08:01
  • I don't understand why the output is an array? Is there some way to "dump" only the Person, e.g., not Person array? – Petri Apr 30 '23 at 07:35
  • 1
    @Petri: Here's a modified example from the first sample: https://godbolt.org/z/fs9ddzGK6. Enclosing a JSON object in curly braces `{}` creates an array. Maybe, that's what you were referring to. – Azeem Apr 30 '23 at 07:47
  • 1
    Thank you @Azeem, I thought it is an initializer and didn't noticed that at all :). – Petri Apr 30 '23 at 08:13