nlohmann / json

JSON for Modern C++
https://json.nlohmann.me
MIT License
43.29k stars 6.75k forks source link

object.dump gives quoted string, want to use .dump() to generate javascripts. #826

Closed coolhandle01 closed 6 years ago

coolhandle01 commented 7 years ago

Feature Request

First up, amazing library. Love it. Definitely going to play well with V8 and V8PP, so well done.

static auto matic= boost::format("var fn = function() { return %s };");

auto j = R"({ "some_object": {"happy": true, "pi": 3.141 } })"_json;
auto dumped_j = j.at("some_object").dump();
auto formatted = boost::str(matic % dumped_j ); 
// formatted == "var fn = function() { return \"<some_object_here>\"; }".
// ^^^^ formatted[0] and formatted[len-1] are '\"'
nlohmann commented 7 years ago

I am not sure whether producing non-compliant JSON text is in the scope of this library. However, I do not fully understand your use case. Could you please provide the exact desired output for the JSON value

{
    "some_object": {
        "happy": true,
        "pi": 3.141
    }
}
coolhandle01 commented 7 years ago

As JSON, I'd like it as it is - in C++ land, I'd like a string representation of the object without the surrounding quotes, so that I can inject it into the last %s here: boost::format("var %s = %s.%s(%s)")

I am building a JavaScript based on JSON data. json.h -> C++ string representing an object -> string inserted into another string(via format/str) -> write to file.

nlohmann commented 7 years ago

So how would the JSON above look like?

coolhandle01 commented 7 years ago

Sorry for inflicting confusion here!

The JSON is something like this:

{
    "Calls":
    [
        {
            "var": "returned_value",
            "fn": "named_method",
            "args":
            {
                "some_object": {
                    "happy": true,
                    "pi": 3.141
                 }
            }
        },
        ...
    ]
}

And I'd like to generate the following string as painlessly as possible, before I write it to some_script.js:

var returned_value = KnownThing.named_method({ "some_object": {"happy": true, "pi": 3.141 }});

nlohmann commented 7 years ago

So you want

{
    "some_object": {
        "happy": true,
        "pi": 3.141
    }
}

to become

{ "some_object": {"happy": true, "pi": 3.141 }}

?

coolhandle01 commented 7 years ago

Nono, Indentation is not an issue or concern here.

When I call dump, I'd like { "some_object": {"happy": true, "pi": 3.141 }} but I get "{ "some_object": {"happy": true, "pi": 3.141 }}" in fact in the debugger it looks like this: \"{ "some_object": {"happy": true, "pi": 3.141 }}\" (last I checked, might be going insane on this project) and so I have to dequote the string result of .dump() with some hacky method, before writing the said string to some .js. Like: auto js = boost::str(boost::format("var %s = %s.%s(%s)"), varname, objname, methodname, args); where auto args = j.dump();

I'm really struggling to make this clearer!

nlohmann commented 7 years ago

How did you parse the JSON text? Did you use parse to did you write something like json j = "..."?

gregmarr commented 7 years ago

It is impossible for the code you provided to produce the string "{ "some_object": {"happy": true, "pi": 3.141 }}" because your code dumps "some_object". Can you verify that the code you provided is the same as what you're using?

coolhandle01 commented 7 years ago

I've worked around it now, because I obviously can't have random symbols in the JSON (variables), so I'm representing var references as strings, and doing a search, so I'm no longer using j.dump() with boost::format. Let me prepare a standalone example to emulate what I was doing. I will return soon with code.

nlohmann commented 7 years ago

Any news on this?

neel commented 5 years ago

I have a similar requirement. Keep the keys unquoted optionally. This can be done by passing a flag to the dump() function. Also I need a verbatim type value which will have string content but dump will not put quotes around it.

nlohmann commented 5 years ago

Without quotes, dump would produce invalid JSON text. If you want such a function, you can traverse the values yourself recursively and produce the desired result. I do not see this as part of the API.

coolhandle01 commented 5 years ago

Hi Niels, I've been meaning to get back to you, apologies for not doing so sooner.

In the end I traversed the values as suggested, and probably ended up with better code for what I was trying to achieve - essentially writing a script where blocks from the JSON are args to the methods (but didn't want to parse the JSON str on the other side of the JS to make the object).

I moved the goalposts by changing the API I was targeting to methods accepting individual args instead of objects, to make things easier.

I do think it's a bit wierd that the dump() method gives you escaped quotes though, as I've never seen a json file where the first character was a " - always { or [, but its fairly likely you've seen more exotic json that I.

Quote weirdness I can deal with, and that aside, this library is brilliant, and I am grateful for it :)

nlohmann commented 5 years ago

I do think it's a bit wierd that the dump() method gives you escaped quotes though, as I've never seen a json file where the first character was a " - always { or [, but its fairly likely you've seen more exotic json that I.

We only escape quotes inside strings, and this is required by the JSON specification, see

This is not weird, it is the only correct way to produce a valid JSON text.

mlutken commented 4 years ago

Perhaps you can use something along these lines. Perhaps Niels can tell if this is the way to go:

/** Converts an json object to a string representation.
Similar to json.dump() but without the quotes.
Assumes that the object is not structured (ie. primitive).
For structured object a empty string is returned.

Special values like null, numbers etc. are converted to
string representation.
*/
std::string to_string(const nlohmann::json& object)
{
    if (object.is_structured()) return "";
    if (object.is_null()) return "null";
    else if (object.is_string()) return object.get<std::string>();
    else if (object.is_boolean()) return object.get<bool>() ? "true" : "false";
    else if (object.is_number_float()) return std::to_string(object.get<double>());
    else if (object.is_number_integer()) return std::to_string(object.get<long>());
    else if (object.is_number_unsigned()) return std::to_string(object.get<unsigned long>()); // Seems unused
    else if (object.is_number()) return std::to_string(object.get<double>()); // Seems unused
    else {
        // Fallback in case we forgot an if above!
        std::string s = object.dump();
        return s.substr(1, s.length() -2) + " FIXMENM";
    }
}
nlohmann commented 4 years ago

You could simplify this to code like

std::string to_string(const nlohmann::json& j)
{
    switch (j.type())
    {
        // avoid escaping string value
        case value_t::string:
            return j.get<std::string>();

        case value_t::array:
        case value_t::object:
            return "";

        // use dump() for all other value types
        default:
            return j.dump();
    }
}
JDdeVilliers commented 4 years ago

I've been having similar issues. I see this question being asked a few times, so didn't wish to open another ticket. This seemed like the appropriate place to ask (if there's still life here). Full example code

pos posarr[3];
char posname[16];
int i;

posarr[0].x = 10;
posarr[0].y = 11;
sprintf(posname, "pos1");
memcpy(posarr[0].name, posname, sizeof(posarr[0].name));

posarr[1].x = 20;
posarr[1].y = 21;
sprintf(posname, "pos2");
memcpy(posarr[1].name, posname, sizeof(posarr[1].name));

posarr[2].x = 30;
posarr[2].y = 31;
sprintf(posname, "pos3");
memcpy(posarr[2].name, posname, sizeof(posarr[2].name));

nlohmann::json positem, posxlist, posylist, posnamelist;
for (i = 0; i < 3; ++i)
{
    positem = posarr[i].x;
    posxlist.push_back(positem);
    positem = posarr[i].y;
    posylist.push_back(positem);
    positem = posarr[i].name;
    posnamelist.push_back(positem);
}

nlohmann::json allpos = {
    {"x", posxlist.dump()},
    {"y", posylist.dump()},
    {"name", posnamelist.dump()},
    {"items", i}
};

dlog_print(DLOG_DEBUG, scServiceLogTag, "EXAMPLE OUTPUT |%s|", allpos.dump().c_str());

outputs

EXAMPLE OUTPUT |{"items":3,"name":"[\"pos1\",\"pos2\",\"pos3\"]","x":"[10,20,30]","y":"[11,21,31]"}|

The string variables have \ escape characters (is this a separate issue?), and the arrays are wrapped as strings. What I'm looking for / expecting is

EXAMPLE OUTPUT |{"items":3,"name":["pos1","pos2","pos3"],"x":[10,20,30],"y":[11,21,31]}|

I've tried to replace .dump() when building allpos with get<std::string>() as suggested in some tickets, but the app crashes at runtime, possibly because it's not actually a string, but a JSON array? (Tizen 4.0 Wearable - if that's relevant)

nlohmann commented 4 years ago

Just don't call dump, so the value remains of type json. It is then properly escaped.

JDdeVilliers commented 4 years ago

@nlohmann , oh my gosh. Perfect. Thank you so much. And also, I'm really impressed by both the project and your amazing support.