Program crashes with ordered_json, but works fine with json
Description
The same program (code in case 1) produces the expected result using regular nlohmann::json, but crashes using nlohmann::ordered_json. A simple variant of this program (code in case 2) produces the expected result using json, but a different result with ordered_json.
Reproduction steps
Consider this input json file ("test.json"):
{
"number": 14,
"structure": {
"field1": 1,
"field2": 2
}
}
The 2 small programs listed into "Minimal code example" will work fine and produce the expected results when using my_json = nlohmann::json. However, if we change the type to nlohmann::ordered_json, the first program will crash and the second program will not add new_field as expected.
Expected vs. actual results
Expected result for case 1:
{
"number": 14,
"my_structures": {
"structure": {
"field1": 1
}
}
}
Expected result for case 2:
{
"number": 14,
"my_structures": {
"structure": {
"field1": 1,
"field2": 2,
"new_field": "new"
}
}
}
Minimal code example
// CASE 1: removing an old field
using my_json = nlohmann::json;
int main()
{
std::ifstream InputStream( "test.json" );
my_json Root = my_json::parse( InputStream );
// create new level at root
my_json& MyStructures = Root[ "my_structures" ];
// move structure into that new level
MyStructures[ "structure" ] = Root[ "structure" ];
Root.erase( "structure" );
// add new structure field
MyStructures[ "structure" ].erase( "field2" );
std::ofstream OutputStream( "converted.json" );
OutputStream << std::setw( 2 ) << Root << std::endl;
}
// CASE 2: adding a new field
using my_json = nlohmann::json;
int main()
{
std::ifstream InputStream( "test.json" );
my_json Root = my_json::parse( InputStream );
// create new level at root
my_json& MyStructures = Root[ "my_structures" ];
// move structure into that new level
MyStructures[ "structure" ] = Root[ "structure" ];
Root.erase( "structure" );
// add new structure field
MyStructures[ "structure" ][ "new_field" ] = "new";
std::ofstream OutputStream( "converted.json" );
OutputStream << std::setw( 2 ) << Root << std::endl;
}
Error messages
I am using the library as a single include, and for the crashing case an exception is being thrown at this point (line 21878):
private:
template < typename KeyType, detail::enable_if_t <
detail::has_erase_with_key_type<basic_json_t, KeyType>::value, int > = 0 >
size_type erase_internal(KeyType && key)
{
// this erase only works for objects
if (JSON_HEDLEY_UNLIKELY(!is_object()))
{
-----> JSON_THROW(type_error::create(307, detail::concat("cannot use erase() with ", type_name()), this));
}
return m_data.m_value.object->erase(std::forward<KeyType>(key));
}
The exception message is: [json.exception.type_error.307] cannot use erase() with null
Compiler and operating system
Windows 10, Microsoft Visual Studio Professional 2019 Version 16.11.24
Library version
Github at commit 7efe875495a3ed7d805ddbb01af0c7725f50c88b
Validation
- [ ] The bug also occurs if the latest version from the
developbranch is used. - [ ] I can successfully compile and run the unit tests.
Update: I have verified that the crash also happens when I use json.hpp from the latest release (version 3.11.3).
// create new level at root
my_json& MyStructures = Root[ "my_structures" ];
// move structure into that new level
MyStructures[ "structure" ] = Root[ "structure" ];
Root.erase( "structure" );
// add new structure field
MyStructures[ "structure" ].erase( "field2" );
You can't keep references into the ordered_json across modifications to the fields of the object, as it uses a vector internally. Try this:
// create new level at root and
// move structure into that new level
Root[ "my_structures" ][ "structure" ] = Root[ "structure" ];
Root.erase( "structure" );
// add new structure field
Root[ "my_structures" ][ "structure" ].erase( "field2" );
Thank you gregmarr, it does work this way. I didn't know of this limitation. Does the documentation mention this? If so, please excuse me.
I don't think there is a section in iterator/pointer/reference invalidation. It is mentioned in the code at https://github.com/nlohmann/json/blob/develop/include/nlohmann/json.hpp#L768 and was the cause of https://github.com/nlohmann/json/issues/2962 fixed by https://github.com/nlohmann/json/pull/2963
There is a "todo" section here: https://github.com/nlohmann/json/blob/0457de21cffb298c22b629e538036bfeb96130b7/docs/mkdocs/docs/api/basic_json/index.md?plain=1#L43
Thanks @gregmarr. I marked this as a documentation issue.