nlohmann / json

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

Exception "type must be string, but is array" #1730

Closed DragonOsman closed 5 years ago

DragonOsman commented 5 years ago

I had to try to find a new currency API because the one I was using most recently (before the one with the list I showed just now) seems to be a deprecated service now. At first I found one that gets its data from the European Central Bank, but it doesn't support all of world countries. I found another one that's free and supports all currencies, but this one doesn't have a way to query it for a list of currencies either. I decided to try and make a JSON object representing a list of currencies on my own. This is the function I did it with:

// Function to create and return currency list as a nlohmann::json object
json get_currency_list()
{
    return { "currencies", 
    { { "AED", "United Arab Emirates Dirham" }, { "AFN", "Afghan Afghani" },
    { "ALL", "Albanian Lek" }, { "AMD", "Armenian Dram" }, { "ANG", "Netherlands Antillean Guilder" },
    { "AOA", "Angolan Kwanza" }, { "ARS", "Argentine Peso" }, { "AUD", "Australian Dollar" },
    { "AWG", "Aruban Florin" }, { "AZN", "Azerbaijani Manat" }, { "BAM", "Bosnia & Herzegovina Convertible Mark" },
    { "BBD", "Barbadian Dollar" }, { "BDT", "Bangladeshi Taka" }, { "BGN", "Bulgarian Lev" },
    { "BHD", "Bahraini Dinar" }, { "BIF", "Burundian Franc" }, { "BMD", "Bermudian Dollar" },
    { "BND", "Brunei Dollar" }, { "BOB", "Bolivian Boliviano" }, { "BRL", "Brazilian Real" },
    { "BSD", "Bahamian Dollar" }, { "BTN", "Bhutanese Ngultrum" }, { "BWP", "Botswana Pula" },
    { "BYN", "Belarus Ruble" }, { "BZD", "Belize Dollar" }, { "CAD", "Canadian Dollar" },
    { "CDF", "Congolese Franc" }, { "CHF", "Swiss Franc" }, { "CLP", "Chilean Peso" },
    { "CNY", "Chinese Yuan" }, { "COP", "Colombian Peso" }, { "CRC", "Costa Rican Colon" },
    { "CUC", "Cuban Convertible Peso" }, { "CVE", "Cape Verdean Escudo" }, { "CZK", "Czech Republic Koruna" },
    { "DJF", "Djiboutian Franc" }, { "DKK", "Danish Krone" }, { "DOP", "Dominican Peso" },
    { "DZD", "Algerian Dinar" }, { "EGP", "Egyptian Pound" }, { "ERN", "Eritrean Nakfa" },
    { "ETB", "Ethiopian Birr" }, { "EUR", "Euro" }, { "FJD", "Fiji Dollar" },
    { "GBP", "British Pound Sterling" }, { "GEL", "Georgian Lari" }, { "GHS", "Ghanaian Cedi" },
    { "GIP", "Gibraltar Pound" }, { "GMD", "Gambian Dalasi" }, { "GNF", "Guinea Franc" },
    { "GTQ", "Guatemalan Quetzal" }, { "GYD", "Guyanaese Dollar" }, { "HKD", "Hong Kong Dollar" },
    { "HNL", "Honduran Lempira" }, { "HRK", "Croatian Kuna" }, { "HTG", "Haiti Gourde" },
    { "HUF", "Hungarian Forint" }, { "IDR", "Indonesian Rupiah" }, { "ILS", "Israeli Shekel" },
    { "INR", "Indian Rupee" }, { "IQD", "Iraqi Dinar" }, { "IRR", "Iranian Rial" },
    { "ISK", "Icelandic Krona" }, { "JMD", "Jamaican Dollar" }, { "JOD", "Jordanian Dinar" },
    { "JPY", "Japanese Yen" }, { "KES", "Kenyan Shilling" }, { "KGS", "Kyrgystani Som" },
    { "KHR", "Cambodian Riel" }, { "KMF", "Comorian Franc" }, { "KPW", "North Korean Won" },
    { "KRW", "South Korean Won" }, { "KWD", "Kuwaiti Dinar" }, { "KYD", "Cayman Islands Dollar" },
    { "KZT", "Kazakhstan Tenge" }, { "LAK", "Laotian Kip" }, { "LBP", "Lebanese Pound" },
    { "LKR", "Sri Lankan Rupee" }, { "LRD", "Liberian Dollar" }, { "LSL", "Lesotho Loti" },
    { "LYD", "Libyan Dinar" }, { "MAD", "Moroccan Dirham" }, { "MDL", "Moldovan Leu" },
    { "MGA", "Malagasy Ariary" }, { "MKD", "Macedonian Denar" }, { "MMK", "Myanma Kyat" },
    { "MNT", "Mongolian Tugrik" }, { "MOP", "Macau Pataca" }, { "MRO", "Mauritanian Ouguiya" },
    { "MUR", "Mauritian Rupee" }, { "MVR", "Maldivian Rufiyaa" }, { "MWK", "Malawi Kwacha" },
    { "MXN", "Mexican Peso" }, { "MYR", "Malaysian Ringgit" }, { "MZN", "Mozambican Metical" },
    { "NAD", "Namibian Dollar" }, { "NGN", "Nigerian Naira" }, { "NIO", "Nicaragua Cordoba" },
    { "NOK", "Norwegian Krone" }, { "NPR", "Nepalese Rupee" }, { "NZD", "New Zealand Dollar" },
    { "OMR", "Omani Rial" }, { "PAB", "Panamanian Balboa" }, { "PEN", "Peruvian Nuevo Sol" },
    { "PGK", "Papua New Guinean Kina" }, { "PHP", "Philippine Peso" }, { "PKR", "Pakistani Rupee" },
    { "PLN", "Polish Zloty" }, { "PYG", "Paraguayan Guarani" }, { "QAR", "Qatari Riyal" },
    { "RON", "Romanian Leu" }, { "RSD", "Serbian Dinar" }, { "RUB", "Russian Ruble" },
    { "RWF", "Rwanda Franc" }, { "SAR", "Saudi Riyal" }, { "SBD", "Solomon Islands Dollar" },
    { "SCR", "Seychellois Rupee" }, { "SDG", "Sudanese Pound" }, { "SEK", "Swedish Krona" },
    { "SGD", "Singapore Dollar" }, { "SHP", "Saint Helena Pound" }, { "SLL", "Sierra Leonean Leone" },
    { "SOS", "Somali Shilling" }, { "SRD", "Surinamese Dollar" }, { "SSP", "South Sudanese Pound" },
    { "STD", "Sao Tome and Principe Dobra" }, { "SYP", "Syrian Pound" }, { "SZL", "Swazi Lilangeni" },
    { "THB", "Thai Baht" }, { "TJS", "Tajikistan Somoni" }, { "TMT", "Turkmenistani Manat" },
    { "TND", "Tunisian Dinar" }, { "TOP", "Tonga Paanga" }, { "TRY", "Turkish Lira" },
    { "TTD", "Trinidad and Tobago Dollar" }, { "TWD", "New Taiwan Dollar" }, { "TZS", "Tanzanian Shilling" },
    { "UAH", "Ukrainian Hryvnia" }, { "UGX", "Ugandan Shilling" }, { "USD", "United States Dollar" },
    { "UYU", "Uruguayan Peso" }, { "UZS", "Uzbekistan Som" }, { "VEF", "Venezuelan Bolivar" },
    { "VND", "Vietnamese Dong" }, { "VUV", "Vanuatu Vatu" }, { "WST", "Samoan Tala" },
    { "XAF", "Central African CFA franc" }, { "XCD", "East Caribbean Dollar" }, { "XOF", "West African CFA franc" },
    { "XPF", "CFP Franc" }, { "YER", "Yemeni Rial" }, { "ZAR", "South African Rand" },
    { "ZMW", "Zambian Kwacha" } } 
    };
}

I tried to use the return value of this function here to send the currency list to the JS code on my app's front end:

if (req.target() == "/?q=currency_list")
{
    try
    {
        json currency_list{ get_currency_list() };
        http::response<http::string_body> res{
            std::piecewise_construct,
            std::make_tuple(std::move(std::string(currency_list))),
            std::make_tuple(http::status::ok, req.version()) };
        res.set(http::field::server, BOOST_BEAST_VERSION_STRING);
        res.set(http::field::content_type, "application/json");
        res.content_length(res.body().size());
        res.keep_alive(req.keep_alive());
        return send(std::move(res));
    }
    catch (const json::exception& e)
    {
        std::cerr << "Line 503: Error: " << e.what() << std::endl;
    }
}

But this gave me an error saying:

[json.exception.type_error.302] type must be string, but is array

So how do I fix this error now? Should I turn my current object into a string representation of the same object (if there's a way for it to retain the keys and values it currently has)?

I'm using Windows 10 Single Language Version 1809 Build 17763.678. Compiler is the one for VS2017.

Library version 3.6.1. I don't know if it's a release version or a development branch.

DragonOsman commented 5 years ago

So if I do need to turn into a string representation of the current object, in a way that it'll retain all of the key-value pairs it has now, how do I do it? Will doing currency_list.get<std::string>() do what I want here?

Edit: Okay, using currency_list.get<std::string>() doesn't help.

nlohmann commented 5 years ago

The braces in

json currency_list{ get_currency_list() };

seem odd. Can you replace this with

json currency_list = get_currency_list();

? Note braces are interpreted as arrays, so:

json j1 = "foo";
json j2 { "foo" };

j1 is a string and j2 is an array containing a string.

DragonOsman commented 5 years ago

I was just trying to use uniform initialization. Or do you mean braced initialization with this library's objects is always interpreted as initialization for an array?

I also need to know how I can check for the existence of a key in a JSON object. Thanks.

stoeckley commented 4 years ago

I was hit by this bug as well. Modern C++ practices for uniform initialization do this:

json jfoo{f};

I was debugging this for quite awhile. This is supposed to be the normal, supported way to initialize variables modern C++ (and every variable in my projects of the last few years is initialized this way). This library does require that you not do this and instead use =.

nlohmann commented 4 years ago

Related: #2311 #2046

stoeckley commented 4 years ago

Indeed, it's definitely swimming against the tide to alter how the language standard works. The modern language expects these two statements to have the same effect:

json jfoo{f};

json jfoo = f;

The first of these two is encouraged in recent versions of C++ as it is uniform and safer, especially when using auto and that's why many developers use it. To have them behave differently is fragile.

nlohmann commented 4 years ago

I agree. But the library is proof that in fact you can implement both syntaxes differently...