nlohmann / json

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

Empty JSON object returns size of 1 #4027

Closed RochaStratovan closed 1 year ago

RochaStratovan commented 1 year ago

Description

An empty JSON object returns a size of 1rather than the expected size of 0.

This is problematic because:

  1. accept() indicates this is valid JSON (because it is)
  2. parse() parses this as valid JSON (because it is)
  3. size() indicates 1 so we expect there to be at least one KV pair
  4. Using the iterator results in an exception because there isn't really a value to parse.

Reproduction steps

Create an empty JSON object using the string "{}" or "{ }", and then check the size.

According to https://www.json.org/json-en.html these strings are the minimum valid JSON string.

Expected vs. actual results

I expect and empty JSON object to return a size of 0.

However it is returning a value of 1.

Minimal code example

**Code**

#include <stdio.h>
#include <iostream>
#include <nlohmann/json.hpp>

#define DEBUG_DUMP(x)    printf("%-3s : %s : %zu\n", \
                                #x, x.dump().c_str(), x.size())

int main(void)
{
    // This is valid JSON and it's empty, no objects.
    std::string emptyJson("{}");
    std::string emptyJson2("{ }");

    nlohmann::json    j1(emptyJson);
    DEBUG_DUMP(j1);

    nlohmann::json    j2(emptyJson2);
    DEBUG_DUMP(j2);

    // Parse nothing
    nlohmann::json    j3;
    DEBUG_DUMP(j3);

    // There isn't a way to see if this is an empty/invalid json object?
    if (j1 == j2) printf("j1 equals j2\n");
    if (j2 == j3) printf("j2 equals j3\n");

    // lets iterate over the objects that should be there, throw an excemption
    // and crash!
    for (nlohmann::json::iterator it = j1.begin();
         it != j1.end();
         ++it)
    {
        std::cout << "the key:'" << it.key() << "'" << std::endl;
        std::cout << "the value:'" << it.value() << "'" << std::endl;
    }

    return (0);
}

Output:

$ g++ -I ./json/single_include jrrtest.cpp
$ ./a.out 
j1  : "{}" : 1
j2  : "{ }" : 1
j3  : null : 0
terminate called after throwing an instance of 'nlohmann::json_abi_v3_11_2::detail::invalid_iterator'
  what():  [json.exception.invalid_iterator.207] cannot use key() for non-object iterators
Aborted (core dumped)


### Error messages

_No response_

### Compiler and operating system

Ubuntu 20.04 w/ g++ 9.4.0; Windows 11, Visual Studio 2017 15.9.53

### Library version

3.7.0

### Validation

- [X] The bug also occurs if the latest version from the [`develop`](https://github.com/nlohmann/json/tree/develop) branch is used.
- [ ] I can successfully [compile and run the unit tests](https://github.com/nlohmann/json#execute-unit-tests).
nlohmann commented 1 year ago

You are not calling the parse function, but pass strings to the constructor. This creates string values which have a size of 1 by design, see https://json.nlohmann.me/api/basic_json/size/#examples

RochaStratovan commented 1 year ago

Thank you for the clarification.

I've updated the code snippet, adding examples 4 and 5 to visualize/exercise what you are saying, and it confirms your statement.

Thank you.

Code:

#include <stdio.h>
#include <iostream>
#include <nlohmann/json.hpp>

#define DEBUG_DUMP(x)    printf("%-3s : %s : %zu\n", \
                                #x, x.dump().c_str(), x.size())

int main(void)
{
    // This is valid JSON and it's empty, no objects.
    std::string emptyJson("{}");
    std::string emptyJson2("{ }");

    nlohmann::json    j1(emptyJson);
    DEBUG_DUMP(j1);

    nlohmann::json    j2(emptyJson2);
    DEBUG_DUMP(j2);

    // Parse nothing
    nlohmann::json    j3;
    DEBUG_DUMP(j3);

    nlohmann::json    j4;
    j4 = nlohmann::json::parse(emptyJson);
    DEBUG_DUMP(j4);

    nlohmann::json    j5;
    j5 = nlohmann::json::parse(j4.dump().c_str());
    DEBUG_DUMP(j5);

    return (0);
}

Output:

$ ./a.out
j1  : "{}" : 1
j2  : "{ }" : 1
j3  : null : 0
j4  : {} : 0
j5  : {} : 0