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

How to create a json variable? #990

Closed
miketing629 opened this issue Mar 1, 2018 · 11 comments
Closed

How to create a json variable? #990

miketing629 opened this issue Mar 1, 2018 · 11 comments
Labels
kind: question solution: proposed fix a fix for the issue has been proposed and waits for confirmation

Comments

@miketing629
Copy link

miketing629 commented Mar 1, 2018

Bug Report

  • What is the issue you have?

This is more of a question than an issue. The sample code illustrates what I'm trying to do, and the included output shows it's not working the way I hope/expect. I've tried other variations than what is in the sample - can't get anything to work.

  • Please describe the steps to reproduce the issue. Can you provide a small but working code example?

Build and run this code:

void jsonTest()
{
    try
    {
        nlohmann::json v1 = nlohmann::json::parse("{ \"a\":{\"b\":{\"c\":{\"value\":900}}} }");
        auto value1 = v1["a"]["b"]["c"].value("value", 0);

        std::cout << "value1: " << value1 << std::endl;
    }
    catch (...)
    {
        printf("value1: exception");
        std::cout << "value1: exception" << std::endl;
    }

    try
    {
        nlohmann::json v2 =
        {
            "a",
            {
                "b",
                {
                    "c",
                    {
                        "value", 900
                    }
                }
            } 
        };
        auto value2 = v2["a"]["b"]["c"].value("value", 0);

        std::cout << "value1: " << value2 << std::endl;
    }
    catch (...)
    {
        std::cout << "value2: exception" << std::endl;
    }

    try
    {
        nlohmann::json v3 =
        {
            {
                "a",
                {
                    "b",
                    {
                        "c",
                        {
                            "value", 900
                        }
                    }
                }
            }
        };
        auto value3 = v3["a"]["b"]["c"].value("value", 0);

        std::cout << "value3: " << value3 << std::endl;
    }
    catch (...)
    {
        std::cout << "value3: exception" << std::endl;
    }
}
  • What is the expected behavior?

All cases print the value 900.

  • And what is the actual behavior instead?

This is the output of the code:

value1: 900
value2 : exception
value3 : exception

Visual Studio 2017, Windows 10

  • Did you use a released version of the library or the version from the develop branch?

Released version 3.1.1.

Feature Request

  • Describe the feature in as much detail as possible.

  • Include sample usage where appropriate.

@nlohmann
Copy link
Owner

nlohmann commented Mar 1, 2018

You need to have a look at the values you are creating:

  • v2 = ["a",["b",["c",["value",900]]]]
  • v3 = {"a":["b",["c",["value",900]]]}

To create an object with the initializer list syntax, you need to write:

json v = { {"a", { {"b", { {"c", { {"value", 900} }} }} }} };

But it is much easier to write:

json v;
v["a"]["b"]["c"]["value"] = 900;

@nlohmann nlohmann added kind: question solution: proposed fix a fix for the issue has been proposed and waits for confirmation labels Mar 1, 2018
@miketing629
Copy link
Author

Thanks Niels. Your code works. I agree, easier to write the way you showed for this simple example, but I want a compile-time initializer list for a much larger json (and still trying to get my own real case to work, but at least your example shows the way).

A few followups:

  1. Your answer implies, I think, that there is a way to create arrays vs. objects in an initializer list, correct? And, if so, could you give some guidance on how to do that (e.g. in terms of nesting level or whatever other criteria there is)?
  2. For accessing objects, can you still use [] operator? Or, better, is there a way to use the -> operator? If so, do any of the examples show how to do this?
  3. Same question for values (my code is currently using the .value() method).
  4. Questions 2 and 3, if not already supported, become a feature request :)
  5. .value("name", default): It would be nice to be able to determine if the default value was used or not, perhaps through an optional 3rd output bool parameter to indicate this.
  6. Not sure this would be useful or not, but feel free to add it if you think it is: dump() method could have an option to dump the object in the form of the initializer list needed to create the object at compile time. Well, certainly it would be useful for resolving my initial question, but not sure it would make sense as a general-use feature.

And, btw, thanks for creating this tool and providing great support!

@nlohmann
Copy link
Owner

nlohmann commented Mar 6, 2018

To your questions:

  1. This is explained in the README. See object() and array() for more information.
  2. The [] operator is overloaded to work with arrays (a passed number is the array index) and objects (a passed key identifies an object value. I do not know what you mean with -> in this sense.
  3. See the documentation of value for more information. It only works for objects.
  4. Did I miss anything?
  5. See Do not throw exception when default_value's type does not match the actual type #278 for a long discussion on this. I am open for ideas how to realize this.
  6. There is no function like this. Note that using initializer lists do not create json values at compile time. They call this constructor which in turn may call other constructors. This all happens at runtime, and no performance is gained compared to other constructors. (If I remember correctly, an initializer list may even be worse, because one may not be able to move values from there, but I may be wrong.)

@gregmarr
Copy link
Contributor

gregmarr commented Mar 6, 2018

  1. I don't see any value in this. If you want to do something different if the value exists than if it doesn't, then check for whether or not the value exists. That seems to me fundamentally opposed to "I don't want to know that the value doesn't exist, just use this default instead" which is what you get when you provide a default.

@miketing629
Copy link
Author

Sure, if there is a way to determine if the value exists (and I'll take it that there is, and hopefully a method that doesn't throw if the value doesn't exist). But, the value() method requires the default value parameter. So might as well let me know if it was used or not. Alternatively, default value could be made an optional parameter - and then perhaps I would not have made the suggestion to begin with ;). Of course, that would require a throw if the value doesn't exist.

In terms of [] and -> operators, [] seems to work fine everywhere. C++ allows it to be overridden to be anything you want, but natively it is for selecting an array element and intuitively that's how I thought about it for accessing json array elements. -> seems more natural for objects and values (as does . operator, but can't override that). I'm good since [] is working fine. You might consider overriding operator -> to allow developer preference.

I will close. I'm a GitHub newbie. Hopefully you can re-open if you want to add further comments. But I'm good, so closed from my perspective.

@gregmarr
Copy link
Contributor

gregmarr commented Mar 7, 2018

Sure, if there is a way to determine if the value exists (and I'll take it that there is, and hopefully a method that doesn't throw if the value doesn't exist).

Yes, find().

But, the value() method requires the default value parameter. So might as well let me know if it was used or not. Alternatively, default value could be made an optional parameter - and then perhaps I would not have made the suggestion to begin with ;)

value() is a very thin wrapper that does this:

        // if key is found, return value and given default value otherwise
        const auto it = find(key);
        if (it != end())
        {
            return *it;
        }
        return default_value;

You can do the same.

@miketing629 miketing629 reopened this Mar 18, 2018
@miketing629
Copy link
Author

Resurrecting / followup on the .value() and .find().

The commented line (line 6) throws an exception where I feel it should return the value 20. Because it throws, I worked around it by the code below line 6 which uses find() and is a bit messy. Try/catch perhaps better but also a little messy if there are a lot of accesses.

Wondering if this is by design or is fixable. Or if there is a better way.

json j;

j["a"]["b"]["c"] = 10;

int abc = j["a"]["b"].value("c", 20);
//int ade = j["a"]["d"].value("e", 20);

int ade;

auto a = j.find("a");

if (a != j.end()) {
    auto d = a->find("d");

    if (d != a->end()) {
        ade = d->value("e", 20);
    }
    else {
        ade = 20;
    }
}
else {
    ade = 20;
}

@nlohmann
Copy link
Owner

You try to call j["a"]["d"].value("e", 20);, but already j["a"]["d"] has undefined behavior, because there is no d key in object j["a"].

You could chain the value calls with an empty object as default value:

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

using json = nlohmann::json;

int main()
{
    json j;
    j["a"]["b"]["c"] = 10;

    int ade = j.value("a", json::object())
               .value("d", json::object())
               .value("e", 20);

    std::cout << ade << std::endl;
}

@miketing629
Copy link
Author

Sure, but what is different about j["a"]["b"]["c"] = 10? Isn't j["a"]["b"] an empty object before it's not?

In my actual scenario, I'm reading json from the network. If the sender sends me bad json, I want to either a) in this case, assign a default value if an optional value is missing or b) in other cases, be able to validate the schema adheres to a required schema.

@nlohmann
Copy link
Owner

For j["a"]["b"]["c"], all nonexisting keys are created inline with a null value. Using then operator[] on a null value triggers an implicit conversion to an object. value on the other hand cannot be called on a null value - it expects an object and (since it is a const function) cannot perform a conversion.

Your usecase may be similar to #1007 (comment). You may want to create an object of default values and call update (link to the documentation in #1007 (comment)) with the actual received values. Then you can be sure that all values you did not receive have a default value.

@miketing629
Copy link
Author

Yes, this strategy works. Thank you.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
kind: question solution: proposed fix a fix for the issue has been proposed and waits for confirmation
Projects
None yet
Development

No branches or pull requests

3 participants