diff --git a/.github/workflows/check_amalgamation.yml b/.github/workflows/check_amalgamation.yml index 8104a20af..910eeef0f 100644 --- a/.github/workflows/check_amalgamation.yml +++ b/.github/workflows/check_amalgamation.yml @@ -163,9 +163,9 @@ jobs: python3 $GENERATOR --list-tags TAG_COUNT=$(python3 $GENERATOR --list-tags | wc -l) - python3 $GENERATOR --abi-tags diag,_dp,_vns --abi-tags _diag --output $RUNNER_TEMP/abi_tags.natvis - grep -q '' $RUNNER_TEMP/abi_tags.natvis - grep -q '' $RUNNER_TEMP/abi_tags.natvis + python3 $GENERATOR --abi-tags diag,_dp,_ns --abi-tags _diag --output $RUNNER_TEMP/abi_tags.natvis + grep -q '' $RUNNER_TEMP/abi_tags.natvis + grep -q '' $RUNNER_TEMP/abi_tags.natvis test $(grep -c ' - - null - {*(m_data.m_value.object)} - {*(m_data.m_value.array)} - {*(m_data.m_value.string)} - {m_data.m_value.boolean} - {m_data.m_value.number_integer} - {m_data.m_value.number_unsigned} - {m_data.m_value.number_float} - discarded - - - *(m_data.m_value.object),view(simple) - - - *(m_data.m_value.array),view(simple) - - - - - - - {second} - - second - - - - - - null - {*(m_data.m_value.object)} - {*(m_data.m_value.array)} - {*(m_data.m_value.string)} - {m_data.m_value.boolean} - {m_data.m_value.number_integer} - {m_data.m_value.number_unsigned} - {m_data.m_value.number_float} - discarded - - - *(m_data.m_value.object),view(simple) - - - *(m_data.m_value.array),view(simple) - - - - - - null - {*(m_data.m_value.object)} - {*(m_data.m_value.array)} - {*(m_data.m_value.string)} - {m_data.m_value.boolean} - {m_data.m_value.number_integer} - {m_data.m_value.number_unsigned} - {m_data.m_value.number_float} - discarded - - - *(m_data.m_value.object),view(simple) - - - *(m_data.m_value.array),view(simple) - - - - - - - null - {*(m_data.m_value.object)} - {*(m_data.m_value.array)} - {*(m_data.m_value.string)} - {m_data.m_value.boolean} - {m_data.m_value.number_integer} - {m_data.m_value.number_unsigned} - {m_data.m_value.number_float} - discarded - - - *(m_data.m_value.object),view(simple) - - - *(m_data.m_value.array),view(simple) - - - - - - - {second} - - second - - - - - - null - {*(m_data.m_value.object)} - {*(m_data.m_value.array)} - {*(m_data.m_value.string)} - {m_data.m_value.boolean} - {m_data.m_value.number_integer} - {m_data.m_value.number_unsigned} - {m_data.m_value.number_float} - discarded - - - *(m_data.m_value.object),view(simple) - - - *(m_data.m_value.array),view(simple) - - - - - - null - {*(m_data.m_value.object)} - {*(m_data.m_value.array)} - {*(m_data.m_value.string)} - {m_data.m_value.boolean} - {m_data.m_value.number_integer} - {m_data.m_value.number_unsigned} - {m_data.m_value.number_float} - discarded - - - *(m_data.m_value.object),view(simple) - - - *(m_data.m_value.array),view(simple) - - - - - - - null - {*(m_data.m_value.object)} - {*(m_data.m_value.array)} - {*(m_data.m_value.string)} - {m_data.m_value.boolean} - {m_data.m_value.number_integer} - {m_data.m_value.number_unsigned} - {m_data.m_value.number_float} - discarded - - - *(m_data.m_value.object),view(simple) - - - *(m_data.m_value.array),view(simple) - - - - - - - {second} - - second - - - - - - null - {*(m_data.m_value.object)} - {*(m_data.m_value.array)} - {*(m_data.m_value.string)} - {m_data.m_value.boolean} - {m_data.m_value.number_integer} - {m_data.m_value.number_unsigned} - {m_data.m_value.number_float} - discarded - - - *(m_data.m_value.object),view(simple) - - - *(m_data.m_value.array),view(simple) - - - - - - null - {*(m_data.m_value.object)} - {*(m_data.m_value.array)} - {*(m_data.m_value.string)} - {m_data.m_value.boolean} - {m_data.m_value.number_integer} - {m_data.m_value.number_unsigned} - {m_data.m_value.number_float} - discarded - - - *(m_data.m_value.object),view(simple) - - - *(m_data.m_value.array),view(simple) - - - - - - - null - {*(m_data.m_value.object)} - {*(m_data.m_value.array)} - {*(m_data.m_value.string)} - {m_data.m_value.boolean} - {m_data.m_value.number_integer} - {m_data.m_value.number_unsigned} - {m_data.m_value.number_float} - discarded - - - *(m_data.m_value.object),view(simple) - - - *(m_data.m_value.array),view(simple) - - - - - - - {second} - - second - - - - - - null - {*(m_data.m_value.object)} - {*(m_data.m_value.array)} - {*(m_data.m_value.string)} - {m_data.m_value.boolean} - {m_data.m_value.number_integer} - {m_data.m_value.number_unsigned} - {m_data.m_value.number_float} - discarded - - - *(m_data.m_value.object),view(simple) - - - *(m_data.m_value.array),view(simple) - - - - - - null - {*(m_data.m_value.object)} - {*(m_data.m_value.array)} - {*(m_data.m_value.string)} - {m_data.m_value.boolean} - {m_data.m_value.number_integer} - {m_data.m_value.number_unsigned} - {m_data.m_value.number_float} - discarded - - - *(m_data.m_value.object),view(simple) - - - *(m_data.m_value.array),view(simple) - - - - null @@ -3631,302 +3335,6 @@ - - - null - {*(m_data.m_value.object)} - {*(m_data.m_value.array)} - {*(m_data.m_value.string)} - {m_data.m_value.boolean} - {m_data.m_value.number_integer} - {m_data.m_value.number_unsigned} - {m_data.m_value.number_float} - discarded - - - *(m_data.m_value.object),view(simple) - - - *(m_data.m_value.array),view(simple) - - - - - - - {second} - - second - - - - - - null - {*(m_data.m_value.object)} - {*(m_data.m_value.array)} - {*(m_data.m_value.string)} - {m_data.m_value.boolean} - {m_data.m_value.number_integer} - {m_data.m_value.number_unsigned} - {m_data.m_value.number_float} - discarded - - - *(m_data.m_value.object),view(simple) - - - *(m_data.m_value.array),view(simple) - - - - - - null - {*(m_data.m_value.object)} - {*(m_data.m_value.array)} - {*(m_data.m_value.string)} - {m_data.m_value.boolean} - {m_data.m_value.number_integer} - {m_data.m_value.number_unsigned} - {m_data.m_value.number_float} - discarded - - - *(m_data.m_value.object),view(simple) - - - *(m_data.m_value.array),view(simple) - - - - - - - null - {*(m_data.m_value.object)} - {*(m_data.m_value.array)} - {*(m_data.m_value.string)} - {m_data.m_value.boolean} - {m_data.m_value.number_integer} - {m_data.m_value.number_unsigned} - {m_data.m_value.number_float} - discarded - - - *(m_data.m_value.object),view(simple) - - - *(m_data.m_value.array),view(simple) - - - - - - - {second} - - second - - - - - - null - {*(m_data.m_value.object)} - {*(m_data.m_value.array)} - {*(m_data.m_value.string)} - {m_data.m_value.boolean} - {m_data.m_value.number_integer} - {m_data.m_value.number_unsigned} - {m_data.m_value.number_float} - discarded - - - *(m_data.m_value.object),view(simple) - - - *(m_data.m_value.array),view(simple) - - - - - - null - {*(m_data.m_value.object)} - {*(m_data.m_value.array)} - {*(m_data.m_value.string)} - {m_data.m_value.boolean} - {m_data.m_value.number_integer} - {m_data.m_value.number_unsigned} - {m_data.m_value.number_float} - discarded - - - *(m_data.m_value.object),view(simple) - - - *(m_data.m_value.array),view(simple) - - - - - - - null - {*(m_data.m_value.object)} - {*(m_data.m_value.array)} - {*(m_data.m_value.string)} - {m_data.m_value.boolean} - {m_data.m_value.number_integer} - {m_data.m_value.number_unsigned} - {m_data.m_value.number_float} - discarded - - - *(m_data.m_value.object),view(simple) - - - *(m_data.m_value.array),view(simple) - - - - - - - {second} - - second - - - - - - null - {*(m_data.m_value.object)} - {*(m_data.m_value.array)} - {*(m_data.m_value.string)} - {m_data.m_value.boolean} - {m_data.m_value.number_integer} - {m_data.m_value.number_unsigned} - {m_data.m_value.number_float} - discarded - - - *(m_data.m_value.object),view(simple) - - - *(m_data.m_value.array),view(simple) - - - - - - null - {*(m_data.m_value.object)} - {*(m_data.m_value.array)} - {*(m_data.m_value.string)} - {m_data.m_value.boolean} - {m_data.m_value.number_integer} - {m_data.m_value.number_unsigned} - {m_data.m_value.number_float} - discarded - - - *(m_data.m_value.object),view(simple) - - - *(m_data.m_value.array),view(simple) - - - - - - - null - {*(m_data.m_value.object)} - {*(m_data.m_value.array)} - {*(m_data.m_value.string)} - {m_data.m_value.boolean} - {m_data.m_value.number_integer} - {m_data.m_value.number_unsigned} - {m_data.m_value.number_float} - discarded - - - *(m_data.m_value.object),view(simple) - - - *(m_data.m_value.array),view(simple) - - - - - - - {second} - - second - - - - - - null - {*(m_data.m_value.object)} - {*(m_data.m_value.array)} - {*(m_data.m_value.string)} - {m_data.m_value.boolean} - {m_data.m_value.number_integer} - {m_data.m_value.number_unsigned} - {m_data.m_value.number_float} - discarded - - - *(m_data.m_value.object),view(simple) - - - *(m_data.m_value.array),view(simple) - - - - - - null - {*(m_data.m_value.object)} - {*(m_data.m_value.array)} - {*(m_data.m_value.string)} - {m_data.m_value.boolean} - {m_data.m_value.number_integer} - {m_data.m_value.number_unsigned} - {m_data.m_value.number_float} - discarded - - - *(m_data.m_value.object),view(simple) - - - *(m_data.m_value.array),view(simple) - - - - null @@ -5259,302 +4667,6 @@ - - - null - {*(m_data.m_value.object)} - {*(m_data.m_value.array)} - {*(m_data.m_value.string)} - {m_data.m_value.boolean} - {m_data.m_value.number_integer} - {m_data.m_value.number_unsigned} - {m_data.m_value.number_float} - discarded - - - *(m_data.m_value.object),view(simple) - - - *(m_data.m_value.array),view(simple) - - - - - - - {second} - - second - - - - - - null - {*(m_data.m_value.object)} - {*(m_data.m_value.array)} - {*(m_data.m_value.string)} - {m_data.m_value.boolean} - {m_data.m_value.number_integer} - {m_data.m_value.number_unsigned} - {m_data.m_value.number_float} - discarded - - - *(m_data.m_value.object),view(simple) - - - *(m_data.m_value.array),view(simple) - - - - - - null - {*(m_data.m_value.object)} - {*(m_data.m_value.array)} - {*(m_data.m_value.string)} - {m_data.m_value.boolean} - {m_data.m_value.number_integer} - {m_data.m_value.number_unsigned} - {m_data.m_value.number_float} - discarded - - - *(m_data.m_value.object),view(simple) - - - *(m_data.m_value.array),view(simple) - - - - - - - null - {*(m_data.m_value.object)} - {*(m_data.m_value.array)} - {*(m_data.m_value.string)} - {m_data.m_value.boolean} - {m_data.m_value.number_integer} - {m_data.m_value.number_unsigned} - {m_data.m_value.number_float} - discarded - - - *(m_data.m_value.object),view(simple) - - - *(m_data.m_value.array),view(simple) - - - - - - - {second} - - second - - - - - - null - {*(m_data.m_value.object)} - {*(m_data.m_value.array)} - {*(m_data.m_value.string)} - {m_data.m_value.boolean} - {m_data.m_value.number_integer} - {m_data.m_value.number_unsigned} - {m_data.m_value.number_float} - discarded - - - *(m_data.m_value.object),view(simple) - - - *(m_data.m_value.array),view(simple) - - - - - - null - {*(m_data.m_value.object)} - {*(m_data.m_value.array)} - {*(m_data.m_value.string)} - {m_data.m_value.boolean} - {m_data.m_value.number_integer} - {m_data.m_value.number_unsigned} - {m_data.m_value.number_float} - discarded - - - *(m_data.m_value.object),view(simple) - - - *(m_data.m_value.array),view(simple) - - - - - - - null - {*(m_data.m_value.object)} - {*(m_data.m_value.array)} - {*(m_data.m_value.string)} - {m_data.m_value.boolean} - {m_data.m_value.number_integer} - {m_data.m_value.number_unsigned} - {m_data.m_value.number_float} - discarded - - - *(m_data.m_value.object),view(simple) - - - *(m_data.m_value.array),view(simple) - - - - - - - {second} - - second - - - - - - null - {*(m_data.m_value.object)} - {*(m_data.m_value.array)} - {*(m_data.m_value.string)} - {m_data.m_value.boolean} - {m_data.m_value.number_integer} - {m_data.m_value.number_unsigned} - {m_data.m_value.number_float} - discarded - - - *(m_data.m_value.object),view(simple) - - - *(m_data.m_value.array),view(simple) - - - - - - null - {*(m_data.m_value.object)} - {*(m_data.m_value.array)} - {*(m_data.m_value.string)} - {m_data.m_value.boolean} - {m_data.m_value.number_integer} - {m_data.m_value.number_unsigned} - {m_data.m_value.number_float} - discarded - - - *(m_data.m_value.object),view(simple) - - - *(m_data.m_value.array),view(simple) - - - - - - - null - {*(m_data.m_value.object)} - {*(m_data.m_value.array)} - {*(m_data.m_value.string)} - {m_data.m_value.boolean} - {m_data.m_value.number_integer} - {m_data.m_value.number_unsigned} - {m_data.m_value.number_float} - discarded - - - *(m_data.m_value.object),view(simple) - - - *(m_data.m_value.array),view(simple) - - - - - - - {second} - - second - - - - - - null - {*(m_data.m_value.object)} - {*(m_data.m_value.array)} - {*(m_data.m_value.string)} - {m_data.m_value.boolean} - {m_data.m_value.number_integer} - {m_data.m_value.number_unsigned} - {m_data.m_value.number_float} - discarded - - - *(m_data.m_value.object),view(simple) - - - *(m_data.m_value.array),view(simple) - - - - - - null - {*(m_data.m_value.object)} - {*(m_data.m_value.array)} - {*(m_data.m_value.string)} - {m_data.m_value.boolean} - {m_data.m_value.number_integer} - {m_data.m_value.number_unsigned} - {m_data.m_value.number_float} - discarded - - - *(m_data.m_value.object),view(simple) - - - *(m_data.m_value.array),view(simple) - - - - null @@ -6739,302 +5851,6 @@ - - - null - {*(m_data.m_value.object)} - {*(m_data.m_value.array)} - {*(m_data.m_value.string)} - {m_data.m_value.boolean} - {m_data.m_value.number_integer} - {m_data.m_value.number_unsigned} - {m_data.m_value.number_float} - discarded - - - *(m_data.m_value.object),view(simple) - - - *(m_data.m_value.array),view(simple) - - - - - - - {second} - - second - - - - - - null - {*(m_data.m_value.object)} - {*(m_data.m_value.array)} - {*(m_data.m_value.string)} - {m_data.m_value.boolean} - {m_data.m_value.number_integer} - {m_data.m_value.number_unsigned} - {m_data.m_value.number_float} - discarded - - - *(m_data.m_value.object),view(simple) - - - *(m_data.m_value.array),view(simple) - - - - - - null - {*(m_data.m_value.object)} - {*(m_data.m_value.array)} - {*(m_data.m_value.string)} - {m_data.m_value.boolean} - {m_data.m_value.number_integer} - {m_data.m_value.number_unsigned} - {m_data.m_value.number_float} - discarded - - - *(m_data.m_value.object),view(simple) - - - *(m_data.m_value.array),view(simple) - - - - - - - null - {*(m_data.m_value.object)} - {*(m_data.m_value.array)} - {*(m_data.m_value.string)} - {m_data.m_value.boolean} - {m_data.m_value.number_integer} - {m_data.m_value.number_unsigned} - {m_data.m_value.number_float} - discarded - - - *(m_data.m_value.object),view(simple) - - - *(m_data.m_value.array),view(simple) - - - - - - - {second} - - second - - - - - - null - {*(m_data.m_value.object)} - {*(m_data.m_value.array)} - {*(m_data.m_value.string)} - {m_data.m_value.boolean} - {m_data.m_value.number_integer} - {m_data.m_value.number_unsigned} - {m_data.m_value.number_float} - discarded - - - *(m_data.m_value.object),view(simple) - - - *(m_data.m_value.array),view(simple) - - - - - - null - {*(m_data.m_value.object)} - {*(m_data.m_value.array)} - {*(m_data.m_value.string)} - {m_data.m_value.boolean} - {m_data.m_value.number_integer} - {m_data.m_value.number_unsigned} - {m_data.m_value.number_float} - discarded - - - *(m_data.m_value.object),view(simple) - - - *(m_data.m_value.array),view(simple) - - - - - - - null - {*(m_data.m_value.object)} - {*(m_data.m_value.array)} - {*(m_data.m_value.string)} - {m_data.m_value.boolean} - {m_data.m_value.number_integer} - {m_data.m_value.number_unsigned} - {m_data.m_value.number_float} - discarded - - - *(m_data.m_value.object),view(simple) - - - *(m_data.m_value.array),view(simple) - - - - - - - {second} - - second - - - - - - null - {*(m_data.m_value.object)} - {*(m_data.m_value.array)} - {*(m_data.m_value.string)} - {m_data.m_value.boolean} - {m_data.m_value.number_integer} - {m_data.m_value.number_unsigned} - {m_data.m_value.number_float} - discarded - - - *(m_data.m_value.object),view(simple) - - - *(m_data.m_value.array),view(simple) - - - - - - null - {*(m_data.m_value.object)} - {*(m_data.m_value.array)} - {*(m_data.m_value.string)} - {m_data.m_value.boolean} - {m_data.m_value.number_integer} - {m_data.m_value.number_unsigned} - {m_data.m_value.number_float} - discarded - - - *(m_data.m_value.object),view(simple) - - - *(m_data.m_value.array),view(simple) - - - - - - - null - {*(m_data.m_value.object)} - {*(m_data.m_value.array)} - {*(m_data.m_value.string)} - {m_data.m_value.boolean} - {m_data.m_value.number_integer} - {m_data.m_value.number_unsigned} - {m_data.m_value.number_float} - discarded - - - *(m_data.m_value.object),view(simple) - - - *(m_data.m_value.array),view(simple) - - - - - - - {second} - - second - - - - - - null - {*(m_data.m_value.object)} - {*(m_data.m_value.array)} - {*(m_data.m_value.string)} - {m_data.m_value.boolean} - {m_data.m_value.number_integer} - {m_data.m_value.number_unsigned} - {m_data.m_value.number_float} - discarded - - - *(m_data.m_value.object),view(simple) - - - *(m_data.m_value.array),view(simple) - - - - - - null - {*(m_data.m_value.object)} - {*(m_data.m_value.array)} - {*(m_data.m_value.string)} - {m_data.m_value.boolean} - {m_data.m_value.number_integer} - {m_data.m_value.number_unsigned} - {m_data.m_value.number_float} - discarded - - - *(m_data.m_value.object),view(simple) - - - *(m_data.m_value.array),view(simple) - - - - null @@ -8071,302 +6887,6 @@ - - - null - {*(m_data.m_value.object)} - {*(m_data.m_value.array)} - {*(m_data.m_value.string)} - {m_data.m_value.boolean} - {m_data.m_value.number_integer} - {m_data.m_value.number_unsigned} - {m_data.m_value.number_float} - discarded - - - *(m_data.m_value.object),view(simple) - - - *(m_data.m_value.array),view(simple) - - - - - - - {second} - - second - - - - - - null - {*(m_data.m_value.object)} - {*(m_data.m_value.array)} - {*(m_data.m_value.string)} - {m_data.m_value.boolean} - {m_data.m_value.number_integer} - {m_data.m_value.number_unsigned} - {m_data.m_value.number_float} - discarded - - - *(m_data.m_value.object),view(simple) - - - *(m_data.m_value.array),view(simple) - - - - - - null - {*(m_data.m_value.object)} - {*(m_data.m_value.array)} - {*(m_data.m_value.string)} - {m_data.m_value.boolean} - {m_data.m_value.number_integer} - {m_data.m_value.number_unsigned} - {m_data.m_value.number_float} - discarded - - - *(m_data.m_value.object),view(simple) - - - *(m_data.m_value.array),view(simple) - - - - - - - null - {*(m_data.m_value.object)} - {*(m_data.m_value.array)} - {*(m_data.m_value.string)} - {m_data.m_value.boolean} - {m_data.m_value.number_integer} - {m_data.m_value.number_unsigned} - {m_data.m_value.number_float} - discarded - - - *(m_data.m_value.object),view(simple) - - - *(m_data.m_value.array),view(simple) - - - - - - - {second} - - second - - - - - - null - {*(m_data.m_value.object)} - {*(m_data.m_value.array)} - {*(m_data.m_value.string)} - {m_data.m_value.boolean} - {m_data.m_value.number_integer} - {m_data.m_value.number_unsigned} - {m_data.m_value.number_float} - discarded - - - *(m_data.m_value.object),view(simple) - - - *(m_data.m_value.array),view(simple) - - - - - - null - {*(m_data.m_value.object)} - {*(m_data.m_value.array)} - {*(m_data.m_value.string)} - {m_data.m_value.boolean} - {m_data.m_value.number_integer} - {m_data.m_value.number_unsigned} - {m_data.m_value.number_float} - discarded - - - *(m_data.m_value.object),view(simple) - - - *(m_data.m_value.array),view(simple) - - - - - - - null - {*(m_data.m_value.object)} - {*(m_data.m_value.array)} - {*(m_data.m_value.string)} - {m_data.m_value.boolean} - {m_data.m_value.number_integer} - {m_data.m_value.number_unsigned} - {m_data.m_value.number_float} - discarded - - - *(m_data.m_value.object),view(simple) - - - *(m_data.m_value.array),view(simple) - - - - - - - {second} - - second - - - - - - null - {*(m_data.m_value.object)} - {*(m_data.m_value.array)} - {*(m_data.m_value.string)} - {m_data.m_value.boolean} - {m_data.m_value.number_integer} - {m_data.m_value.number_unsigned} - {m_data.m_value.number_float} - discarded - - - *(m_data.m_value.object),view(simple) - - - *(m_data.m_value.array),view(simple) - - - - - - null - {*(m_data.m_value.object)} - {*(m_data.m_value.array)} - {*(m_data.m_value.string)} - {m_data.m_value.boolean} - {m_data.m_value.number_integer} - {m_data.m_value.number_unsigned} - {m_data.m_value.number_float} - discarded - - - *(m_data.m_value.object),view(simple) - - - *(m_data.m_value.array),view(simple) - - - - - - - null - {*(m_data.m_value.object)} - {*(m_data.m_value.array)} - {*(m_data.m_value.string)} - {m_data.m_value.boolean} - {m_data.m_value.number_integer} - {m_data.m_value.number_unsigned} - {m_data.m_value.number_float} - discarded - - - *(m_data.m_value.object),view(simple) - - - *(m_data.m_value.array),view(simple) - - - - - - - {second} - - second - - - - - - null - {*(m_data.m_value.object)} - {*(m_data.m_value.array)} - {*(m_data.m_value.string)} - {m_data.m_value.boolean} - {m_data.m_value.number_integer} - {m_data.m_value.number_unsigned} - {m_data.m_value.number_float} - discarded - - - *(m_data.m_value.object),view(simple) - - - *(m_data.m_value.array),view(simple) - - - - - - null - {*(m_data.m_value.object)} - {*(m_data.m_value.array)} - {*(m_data.m_value.string)} - {m_data.m_value.boolean} - {m_data.m_value.number_integer} - {m_data.m_value.number_unsigned} - {m_data.m_value.number_float} - discarded - - - *(m_data.m_value.object),view(simple) - - - *(m_data.m_value.array),view(simple) - - - - null @@ -9255,302 +7775,6 @@ - - - null - {*(m_data.m_value.object)} - {*(m_data.m_value.array)} - {*(m_data.m_value.string)} - {m_data.m_value.boolean} - {m_data.m_value.number_integer} - {m_data.m_value.number_unsigned} - {m_data.m_value.number_float} - discarded - - - *(m_data.m_value.object),view(simple) - - - *(m_data.m_value.array),view(simple) - - - - - - - {second} - - second - - - - - - null - {*(m_data.m_value.object)} - {*(m_data.m_value.array)} - {*(m_data.m_value.string)} - {m_data.m_value.boolean} - {m_data.m_value.number_integer} - {m_data.m_value.number_unsigned} - {m_data.m_value.number_float} - discarded - - - *(m_data.m_value.object),view(simple) - - - *(m_data.m_value.array),view(simple) - - - - - - null - {*(m_data.m_value.object)} - {*(m_data.m_value.array)} - {*(m_data.m_value.string)} - {m_data.m_value.boolean} - {m_data.m_value.number_integer} - {m_data.m_value.number_unsigned} - {m_data.m_value.number_float} - discarded - - - *(m_data.m_value.object),view(simple) - - - *(m_data.m_value.array),view(simple) - - - - - - - null - {*(m_data.m_value.object)} - {*(m_data.m_value.array)} - {*(m_data.m_value.string)} - {m_data.m_value.boolean} - {m_data.m_value.number_integer} - {m_data.m_value.number_unsigned} - {m_data.m_value.number_float} - discarded - - - *(m_data.m_value.object),view(simple) - - - *(m_data.m_value.array),view(simple) - - - - - - - {second} - - second - - - - - - null - {*(m_data.m_value.object)} - {*(m_data.m_value.array)} - {*(m_data.m_value.string)} - {m_data.m_value.boolean} - {m_data.m_value.number_integer} - {m_data.m_value.number_unsigned} - {m_data.m_value.number_float} - discarded - - - *(m_data.m_value.object),view(simple) - - - *(m_data.m_value.array),view(simple) - - - - - - null - {*(m_data.m_value.object)} - {*(m_data.m_value.array)} - {*(m_data.m_value.string)} - {m_data.m_value.boolean} - {m_data.m_value.number_integer} - {m_data.m_value.number_unsigned} - {m_data.m_value.number_float} - discarded - - - *(m_data.m_value.object),view(simple) - - - *(m_data.m_value.array),view(simple) - - - - - - - null - {*(m_data.m_value.object)} - {*(m_data.m_value.array)} - {*(m_data.m_value.string)} - {m_data.m_value.boolean} - {m_data.m_value.number_integer} - {m_data.m_value.number_unsigned} - {m_data.m_value.number_float} - discarded - - - *(m_data.m_value.object),view(simple) - - - *(m_data.m_value.array),view(simple) - - - - - - - {second} - - second - - - - - - null - {*(m_data.m_value.object)} - {*(m_data.m_value.array)} - {*(m_data.m_value.string)} - {m_data.m_value.boolean} - {m_data.m_value.number_integer} - {m_data.m_value.number_unsigned} - {m_data.m_value.number_float} - discarded - - - *(m_data.m_value.object),view(simple) - - - *(m_data.m_value.array),view(simple) - - - - - - null - {*(m_data.m_value.object)} - {*(m_data.m_value.array)} - {*(m_data.m_value.string)} - {m_data.m_value.boolean} - {m_data.m_value.number_integer} - {m_data.m_value.number_unsigned} - {m_data.m_value.number_float} - discarded - - - *(m_data.m_value.object),view(simple) - - - *(m_data.m_value.array),view(simple) - - - - - - - null - {*(m_data.m_value.object)} - {*(m_data.m_value.array)} - {*(m_data.m_value.string)} - {m_data.m_value.boolean} - {m_data.m_value.number_integer} - {m_data.m_value.number_unsigned} - {m_data.m_value.number_float} - discarded - - - *(m_data.m_value.object),view(simple) - - - *(m_data.m_value.array),view(simple) - - - - - - - {second} - - second - - - - - - null - {*(m_data.m_value.object)} - {*(m_data.m_value.array)} - {*(m_data.m_value.string)} - {m_data.m_value.boolean} - {m_data.m_value.number_integer} - {m_data.m_value.number_unsigned} - {m_data.m_value.number_float} - discarded - - - *(m_data.m_value.object),view(simple) - - - *(m_data.m_value.array),view(simple) - - - - - - null - {*(m_data.m_value.object)} - {*(m_data.m_value.array)} - {*(m_data.m_value.string)} - {m_data.m_value.boolean} - {m_data.m_value.number_integer} - {m_data.m_value.number_unsigned} - {m_data.m_value.number_float} - discarded - - - *(m_data.m_value.object),view(simple) - - - *(m_data.m_value.array),view(simple) - - - - null @@ -10291,302 +8515,6 @@ - - - null - {*(m_data.m_value.object)} - {*(m_data.m_value.array)} - {*(m_data.m_value.string)} - {m_data.m_value.boolean} - {m_data.m_value.number_integer} - {m_data.m_value.number_unsigned} - {m_data.m_value.number_float} - discarded - - - *(m_data.m_value.object),view(simple) - - - *(m_data.m_value.array),view(simple) - - - - - - - {second} - - second - - - - - - null - {*(m_data.m_value.object)} - {*(m_data.m_value.array)} - {*(m_data.m_value.string)} - {m_data.m_value.boolean} - {m_data.m_value.number_integer} - {m_data.m_value.number_unsigned} - {m_data.m_value.number_float} - discarded - - - *(m_data.m_value.object),view(simple) - - - *(m_data.m_value.array),view(simple) - - - - - - null - {*(m_data.m_value.object)} - {*(m_data.m_value.array)} - {*(m_data.m_value.string)} - {m_data.m_value.boolean} - {m_data.m_value.number_integer} - {m_data.m_value.number_unsigned} - {m_data.m_value.number_float} - discarded - - - *(m_data.m_value.object),view(simple) - - - *(m_data.m_value.array),view(simple) - - - - - - - null - {*(m_data.m_value.object)} - {*(m_data.m_value.array)} - {*(m_data.m_value.string)} - {m_data.m_value.boolean} - {m_data.m_value.number_integer} - {m_data.m_value.number_unsigned} - {m_data.m_value.number_float} - discarded - - - *(m_data.m_value.object),view(simple) - - - *(m_data.m_value.array),view(simple) - - - - - - - {second} - - second - - - - - - null - {*(m_data.m_value.object)} - {*(m_data.m_value.array)} - {*(m_data.m_value.string)} - {m_data.m_value.boolean} - {m_data.m_value.number_integer} - {m_data.m_value.number_unsigned} - {m_data.m_value.number_float} - discarded - - - *(m_data.m_value.object),view(simple) - - - *(m_data.m_value.array),view(simple) - - - - - - null - {*(m_data.m_value.object)} - {*(m_data.m_value.array)} - {*(m_data.m_value.string)} - {m_data.m_value.boolean} - {m_data.m_value.number_integer} - {m_data.m_value.number_unsigned} - {m_data.m_value.number_float} - discarded - - - *(m_data.m_value.object),view(simple) - - - *(m_data.m_value.array),view(simple) - - - - - - - null - {*(m_data.m_value.object)} - {*(m_data.m_value.array)} - {*(m_data.m_value.string)} - {m_data.m_value.boolean} - {m_data.m_value.number_integer} - {m_data.m_value.number_unsigned} - {m_data.m_value.number_float} - discarded - - - *(m_data.m_value.object),view(simple) - - - *(m_data.m_value.array),view(simple) - - - - - - - {second} - - second - - - - - - null - {*(m_data.m_value.object)} - {*(m_data.m_value.array)} - {*(m_data.m_value.string)} - {m_data.m_value.boolean} - {m_data.m_value.number_integer} - {m_data.m_value.number_unsigned} - {m_data.m_value.number_float} - discarded - - - *(m_data.m_value.object),view(simple) - - - *(m_data.m_value.array),view(simple) - - - - - - null - {*(m_data.m_value.object)} - {*(m_data.m_value.array)} - {*(m_data.m_value.string)} - {m_data.m_value.boolean} - {m_data.m_value.number_integer} - {m_data.m_value.number_unsigned} - {m_data.m_value.number_float} - discarded - - - *(m_data.m_value.object),view(simple) - - - *(m_data.m_value.array),view(simple) - - - - - - - null - {*(m_data.m_value.object)} - {*(m_data.m_value.array)} - {*(m_data.m_value.string)} - {m_data.m_value.boolean} - {m_data.m_value.number_integer} - {m_data.m_value.number_unsigned} - {m_data.m_value.number_float} - discarded - - - *(m_data.m_value.object),view(simple) - - - *(m_data.m_value.array),view(simple) - - - - - - - {second} - - second - - - - - - null - {*(m_data.m_value.object)} - {*(m_data.m_value.array)} - {*(m_data.m_value.string)} - {m_data.m_value.boolean} - {m_data.m_value.number_integer} - {m_data.m_value.number_unsigned} - {m_data.m_value.number_float} - discarded - - - *(m_data.m_value.object),view(simple) - - - *(m_data.m_value.array),view(simple) - - - - - - null - {*(m_data.m_value.object)} - {*(m_data.m_value.array)} - {*(m_data.m_value.string)} - {m_data.m_value.boolean} - {m_data.m_value.number_integer} - {m_data.m_value.number_unsigned} - {m_data.m_value.number_float} - discarded - - - *(m_data.m_value.object),view(simple) - - - *(m_data.m_value.array),view(simple) - - - - null @@ -11179,302 +9107,6 @@ - - - null - {*(m_data.m_value.object)} - {*(m_data.m_value.array)} - {*(m_data.m_value.string)} - {m_data.m_value.boolean} - {m_data.m_value.number_integer} - {m_data.m_value.number_unsigned} - {m_data.m_value.number_float} - discarded - - - *(m_data.m_value.object),view(simple) - - - *(m_data.m_value.array),view(simple) - - - - - - - {second} - - second - - - - - - null - {*(m_data.m_value.object)} - {*(m_data.m_value.array)} - {*(m_data.m_value.string)} - {m_data.m_value.boolean} - {m_data.m_value.number_integer} - {m_data.m_value.number_unsigned} - {m_data.m_value.number_float} - discarded - - - *(m_data.m_value.object),view(simple) - - - *(m_data.m_value.array),view(simple) - - - - - - null - {*(m_data.m_value.object)} - {*(m_data.m_value.array)} - {*(m_data.m_value.string)} - {m_data.m_value.boolean} - {m_data.m_value.number_integer} - {m_data.m_value.number_unsigned} - {m_data.m_value.number_float} - discarded - - - *(m_data.m_value.object),view(simple) - - - *(m_data.m_value.array),view(simple) - - - - - - - null - {*(m_data.m_value.object)} - {*(m_data.m_value.array)} - {*(m_data.m_value.string)} - {m_data.m_value.boolean} - {m_data.m_value.number_integer} - {m_data.m_value.number_unsigned} - {m_data.m_value.number_float} - discarded - - - *(m_data.m_value.object),view(simple) - - - *(m_data.m_value.array),view(simple) - - - - - - - {second} - - second - - - - - - null - {*(m_data.m_value.object)} - {*(m_data.m_value.array)} - {*(m_data.m_value.string)} - {m_data.m_value.boolean} - {m_data.m_value.number_integer} - {m_data.m_value.number_unsigned} - {m_data.m_value.number_float} - discarded - - - *(m_data.m_value.object),view(simple) - - - *(m_data.m_value.array),view(simple) - - - - - - null - {*(m_data.m_value.object)} - {*(m_data.m_value.array)} - {*(m_data.m_value.string)} - {m_data.m_value.boolean} - {m_data.m_value.number_integer} - {m_data.m_value.number_unsigned} - {m_data.m_value.number_float} - discarded - - - *(m_data.m_value.object),view(simple) - - - *(m_data.m_value.array),view(simple) - - - - - - - null - {*(m_data.m_value.object)} - {*(m_data.m_value.array)} - {*(m_data.m_value.string)} - {m_data.m_value.boolean} - {m_data.m_value.number_integer} - {m_data.m_value.number_unsigned} - {m_data.m_value.number_float} - discarded - - - *(m_data.m_value.object),view(simple) - - - *(m_data.m_value.array),view(simple) - - - - - - - {second} - - second - - - - - - null - {*(m_data.m_value.object)} - {*(m_data.m_value.array)} - {*(m_data.m_value.string)} - {m_data.m_value.boolean} - {m_data.m_value.number_integer} - {m_data.m_value.number_unsigned} - {m_data.m_value.number_float} - discarded - - - *(m_data.m_value.object),view(simple) - - - *(m_data.m_value.array),view(simple) - - - - - - null - {*(m_data.m_value.object)} - {*(m_data.m_value.array)} - {*(m_data.m_value.string)} - {m_data.m_value.boolean} - {m_data.m_value.number_integer} - {m_data.m_value.number_unsigned} - {m_data.m_value.number_float} - discarded - - - *(m_data.m_value.object),view(simple) - - - *(m_data.m_value.array),view(simple) - - - - - - - null - {*(m_data.m_value.object)} - {*(m_data.m_value.array)} - {*(m_data.m_value.string)} - {m_data.m_value.boolean} - {m_data.m_value.number_integer} - {m_data.m_value.number_unsigned} - {m_data.m_value.number_float} - discarded - - - *(m_data.m_value.object),view(simple) - - - *(m_data.m_value.array),view(simple) - - - - - - - {second} - - second - - - - - - null - {*(m_data.m_value.object)} - {*(m_data.m_value.array)} - {*(m_data.m_value.string)} - {m_data.m_value.boolean} - {m_data.m_value.number_integer} - {m_data.m_value.number_unsigned} - {m_data.m_value.number_float} - discarded - - - *(m_data.m_value.object),view(simple) - - - *(m_data.m_value.array),view(simple) - - - - - - null - {*(m_data.m_value.object)} - {*(m_data.m_value.array)} - {*(m_data.m_value.string)} - {m_data.m_value.boolean} - {m_data.m_value.number_integer} - {m_data.m_value.number_unsigned} - {m_data.m_value.number_float} - discarded - - - *(m_data.m_value.object),view(simple) - - - *(m_data.m_value.array),view(simple) - - - - null @@ -11919,302 +9551,6 @@ - - - null - {*(m_data.m_value.object)} - {*(m_data.m_value.array)} - {*(m_data.m_value.string)} - {m_data.m_value.boolean} - {m_data.m_value.number_integer} - {m_data.m_value.number_unsigned} - {m_data.m_value.number_float} - discarded - - - *(m_data.m_value.object),view(simple) - - - *(m_data.m_value.array),view(simple) - - - - - - - {second} - - second - - - - - - null - {*(m_data.m_value.object)} - {*(m_data.m_value.array)} - {*(m_data.m_value.string)} - {m_data.m_value.boolean} - {m_data.m_value.number_integer} - {m_data.m_value.number_unsigned} - {m_data.m_value.number_float} - discarded - - - *(m_data.m_value.object),view(simple) - - - *(m_data.m_value.array),view(simple) - - - - - - null - {*(m_data.m_value.object)} - {*(m_data.m_value.array)} - {*(m_data.m_value.string)} - {m_data.m_value.boolean} - {m_data.m_value.number_integer} - {m_data.m_value.number_unsigned} - {m_data.m_value.number_float} - discarded - - - *(m_data.m_value.object),view(simple) - - - *(m_data.m_value.array),view(simple) - - - - - - - null - {*(m_data.m_value.object)} - {*(m_data.m_value.array)} - {*(m_data.m_value.string)} - {m_data.m_value.boolean} - {m_data.m_value.number_integer} - {m_data.m_value.number_unsigned} - {m_data.m_value.number_float} - discarded - - - *(m_data.m_value.object),view(simple) - - - *(m_data.m_value.array),view(simple) - - - - - - - {second} - - second - - - - - - null - {*(m_data.m_value.object)} - {*(m_data.m_value.array)} - {*(m_data.m_value.string)} - {m_data.m_value.boolean} - {m_data.m_value.number_integer} - {m_data.m_value.number_unsigned} - {m_data.m_value.number_float} - discarded - - - *(m_data.m_value.object),view(simple) - - - *(m_data.m_value.array),view(simple) - - - - - - null - {*(m_data.m_value.object)} - {*(m_data.m_value.array)} - {*(m_data.m_value.string)} - {m_data.m_value.boolean} - {m_data.m_value.number_integer} - {m_data.m_value.number_unsigned} - {m_data.m_value.number_float} - discarded - - - *(m_data.m_value.object),view(simple) - - - *(m_data.m_value.array),view(simple) - - - - - - - null - {*(m_data.m_value.object)} - {*(m_data.m_value.array)} - {*(m_data.m_value.string)} - {m_data.m_value.boolean} - {m_data.m_value.number_integer} - {m_data.m_value.number_unsigned} - {m_data.m_value.number_float} - discarded - - - *(m_data.m_value.object),view(simple) - - - *(m_data.m_value.array),view(simple) - - - - - - - {second} - - second - - - - - - null - {*(m_data.m_value.object)} - {*(m_data.m_value.array)} - {*(m_data.m_value.string)} - {m_data.m_value.boolean} - {m_data.m_value.number_integer} - {m_data.m_value.number_unsigned} - {m_data.m_value.number_float} - discarded - - - *(m_data.m_value.object),view(simple) - - - *(m_data.m_value.array),view(simple) - - - - - - null - {*(m_data.m_value.object)} - {*(m_data.m_value.array)} - {*(m_data.m_value.string)} - {m_data.m_value.boolean} - {m_data.m_value.number_integer} - {m_data.m_value.number_unsigned} - {m_data.m_value.number_float} - discarded - - - *(m_data.m_value.object),view(simple) - - - *(m_data.m_value.array),view(simple) - - - - - - - null - {*(m_data.m_value.object)} - {*(m_data.m_value.array)} - {*(m_data.m_value.string)} - {m_data.m_value.boolean} - {m_data.m_value.number_integer} - {m_data.m_value.number_unsigned} - {m_data.m_value.number_float} - discarded - - - *(m_data.m_value.object),view(simple) - - - *(m_data.m_value.array),view(simple) - - - - - - - {second} - - second - - - - - - null - {*(m_data.m_value.object)} - {*(m_data.m_value.array)} - {*(m_data.m_value.string)} - {m_data.m_value.boolean} - {m_data.m_value.number_integer} - {m_data.m_value.number_unsigned} - {m_data.m_value.number_float} - discarded - - - *(m_data.m_value.object),view(simple) - - - *(m_data.m_value.array),view(simple) - - - - - - null - {*(m_data.m_value.object)} - {*(m_data.m_value.array)} - {*(m_data.m_value.string)} - {m_data.m_value.boolean} - {m_data.m_value.number_integer} - {m_data.m_value.number_unsigned} - {m_data.m_value.number_float} - discarded - - - *(m_data.m_value.object),view(simple) - - - *(m_data.m_value.array),view(simple) - - - - null @@ -12511,302 +9847,6 @@ - - - null - {*(m_data.m_value.object)} - {*(m_data.m_value.array)} - {*(m_data.m_value.string)} - {m_data.m_value.boolean} - {m_data.m_value.number_integer} - {m_data.m_value.number_unsigned} - {m_data.m_value.number_float} - discarded - - - *(m_data.m_value.object),view(simple) - - - *(m_data.m_value.array),view(simple) - - - - - - - {second} - - second - - - - - - null - {*(m_data.m_value.object)} - {*(m_data.m_value.array)} - {*(m_data.m_value.string)} - {m_data.m_value.boolean} - {m_data.m_value.number_integer} - {m_data.m_value.number_unsigned} - {m_data.m_value.number_float} - discarded - - - *(m_data.m_value.object),view(simple) - - - *(m_data.m_value.array),view(simple) - - - - - - null - {*(m_data.m_value.object)} - {*(m_data.m_value.array)} - {*(m_data.m_value.string)} - {m_data.m_value.boolean} - {m_data.m_value.number_integer} - {m_data.m_value.number_unsigned} - {m_data.m_value.number_float} - discarded - - - *(m_data.m_value.object),view(simple) - - - *(m_data.m_value.array),view(simple) - - - - - - - null - {*(m_data.m_value.object)} - {*(m_data.m_value.array)} - {*(m_data.m_value.string)} - {m_data.m_value.boolean} - {m_data.m_value.number_integer} - {m_data.m_value.number_unsigned} - {m_data.m_value.number_float} - discarded - - - *(m_data.m_value.object),view(simple) - - - *(m_data.m_value.array),view(simple) - - - - - - - {second} - - second - - - - - - null - {*(m_data.m_value.object)} - {*(m_data.m_value.array)} - {*(m_data.m_value.string)} - {m_data.m_value.boolean} - {m_data.m_value.number_integer} - {m_data.m_value.number_unsigned} - {m_data.m_value.number_float} - discarded - - - *(m_data.m_value.object),view(simple) - - - *(m_data.m_value.array),view(simple) - - - - - - null - {*(m_data.m_value.object)} - {*(m_data.m_value.array)} - {*(m_data.m_value.string)} - {m_data.m_value.boolean} - {m_data.m_value.number_integer} - {m_data.m_value.number_unsigned} - {m_data.m_value.number_float} - discarded - - - *(m_data.m_value.object),view(simple) - - - *(m_data.m_value.array),view(simple) - - - - - - - null - {*(m_data.m_value.object)} - {*(m_data.m_value.array)} - {*(m_data.m_value.string)} - {m_data.m_value.boolean} - {m_data.m_value.number_integer} - {m_data.m_value.number_unsigned} - {m_data.m_value.number_float} - discarded - - - *(m_data.m_value.object),view(simple) - - - *(m_data.m_value.array),view(simple) - - - - - - - {second} - - second - - - - - - null - {*(m_data.m_value.object)} - {*(m_data.m_value.array)} - {*(m_data.m_value.string)} - {m_data.m_value.boolean} - {m_data.m_value.number_integer} - {m_data.m_value.number_unsigned} - {m_data.m_value.number_float} - discarded - - - *(m_data.m_value.object),view(simple) - - - *(m_data.m_value.array),view(simple) - - - - - - null - {*(m_data.m_value.object)} - {*(m_data.m_value.array)} - {*(m_data.m_value.string)} - {m_data.m_value.boolean} - {m_data.m_value.number_integer} - {m_data.m_value.number_unsigned} - {m_data.m_value.number_float} - discarded - - - *(m_data.m_value.object),view(simple) - - - *(m_data.m_value.array),view(simple) - - - - - - - null - {*(m_data.m_value.object)} - {*(m_data.m_value.array)} - {*(m_data.m_value.string)} - {m_data.m_value.boolean} - {m_data.m_value.number_integer} - {m_data.m_value.number_unsigned} - {m_data.m_value.number_float} - discarded - - - *(m_data.m_value.object),view(simple) - - - *(m_data.m_value.array),view(simple) - - - - - - - {second} - - second - - - - - - null - {*(m_data.m_value.object)} - {*(m_data.m_value.array)} - {*(m_data.m_value.string)} - {m_data.m_value.boolean} - {m_data.m_value.number_integer} - {m_data.m_value.number_unsigned} - {m_data.m_value.number_float} - discarded - - - *(m_data.m_value.object),view(simple) - - - *(m_data.m_value.array),view(simple) - - - - - - null - {*(m_data.m_value.object)} - {*(m_data.m_value.array)} - {*(m_data.m_value.string)} - {m_data.m_value.boolean} - {m_data.m_value.number_integer} - {m_data.m_value.number_unsigned} - {m_data.m_value.number_float} - discarded - - - *(m_data.m_value.object),view(simple) - - - *(m_data.m_value.array),view(simple) - - - - null @@ -12955,744 +9995,4 @@ - - - null - {*(m_data.m_value.object)} - {*(m_data.m_value.array)} - {*(m_data.m_value.string)} - {m_data.m_value.boolean} - {m_data.m_value.number_integer} - {m_data.m_value.number_unsigned} - {m_data.m_value.number_float} - discarded - - - *(m_data.m_value.object),view(simple) - - - *(m_data.m_value.array),view(simple) - - - - - - - {second} - - second - - - - - - null - {*(m_data.m_value.object)} - {*(m_data.m_value.array)} - {*(m_data.m_value.string)} - {m_data.m_value.boolean} - {m_data.m_value.number_integer} - {m_data.m_value.number_unsigned} - {m_data.m_value.number_float} - discarded - - - *(m_data.m_value.object),view(simple) - - - *(m_data.m_value.array),view(simple) - - - - - - null - {*(m_data.m_value.object)} - {*(m_data.m_value.array)} - {*(m_data.m_value.string)} - {m_data.m_value.boolean} - {m_data.m_value.number_integer} - {m_data.m_value.number_unsigned} - {m_data.m_value.number_float} - discarded - - - *(m_data.m_value.object),view(simple) - - - *(m_data.m_value.array),view(simple) - - - - - - - null - {*(m_data.m_value.object)} - {*(m_data.m_value.array)} - {*(m_data.m_value.string)} - {m_data.m_value.boolean} - {m_data.m_value.number_integer} - {m_data.m_value.number_unsigned} - {m_data.m_value.number_float} - discarded - - - *(m_data.m_value.object),view(simple) - - - *(m_data.m_value.array),view(simple) - - - - - - - {second} - - second - - - - - - null - {*(m_data.m_value.object)} - {*(m_data.m_value.array)} - {*(m_data.m_value.string)} - {m_data.m_value.boolean} - {m_data.m_value.number_integer} - {m_data.m_value.number_unsigned} - {m_data.m_value.number_float} - discarded - - - *(m_data.m_value.object),view(simple) - - - *(m_data.m_value.array),view(simple) - - - - - - null - {*(m_data.m_value.object)} - {*(m_data.m_value.array)} - {*(m_data.m_value.string)} - {m_data.m_value.boolean} - {m_data.m_value.number_integer} - {m_data.m_value.number_unsigned} - {m_data.m_value.number_float} - discarded - - - *(m_data.m_value.object),view(simple) - - - *(m_data.m_value.array),view(simple) - - - - - - - null - {*(m_data.m_value.object)} - {*(m_data.m_value.array)} - {*(m_data.m_value.string)} - {m_data.m_value.boolean} - {m_data.m_value.number_integer} - {m_data.m_value.number_unsigned} - {m_data.m_value.number_float} - discarded - - - *(m_data.m_value.object),view(simple) - - - *(m_data.m_value.array),view(simple) - - - - - - - {second} - - second - - - - - - null - {*(m_data.m_value.object)} - {*(m_data.m_value.array)} - {*(m_data.m_value.string)} - {m_data.m_value.boolean} - {m_data.m_value.number_integer} - {m_data.m_value.number_unsigned} - {m_data.m_value.number_float} - discarded - - - *(m_data.m_value.object),view(simple) - - - *(m_data.m_value.array),view(simple) - - - - - - null - {*(m_data.m_value.object)} - {*(m_data.m_value.array)} - {*(m_data.m_value.string)} - {m_data.m_value.boolean} - {m_data.m_value.number_integer} - {m_data.m_value.number_unsigned} - {m_data.m_value.number_float} - discarded - - - *(m_data.m_value.object),view(simple) - - - *(m_data.m_value.array),view(simple) - - - - - - - null - {*(m_data.m_value.object)} - {*(m_data.m_value.array)} - {*(m_data.m_value.string)} - {m_data.m_value.boolean} - {m_data.m_value.number_integer} - {m_data.m_value.number_unsigned} - {m_data.m_value.number_float} - discarded - - - *(m_data.m_value.object),view(simple) - - - *(m_data.m_value.array),view(simple) - - - - - - - {second} - - second - - - - - - null - {*(m_data.m_value.object)} - {*(m_data.m_value.array)} - {*(m_data.m_value.string)} - {m_data.m_value.boolean} - {m_data.m_value.number_integer} - {m_data.m_value.number_unsigned} - {m_data.m_value.number_float} - discarded - - - *(m_data.m_value.object),view(simple) - - - *(m_data.m_value.array),view(simple) - - - - - - null - {*(m_data.m_value.object)} - {*(m_data.m_value.array)} - {*(m_data.m_value.string)} - {m_data.m_value.boolean} - {m_data.m_value.number_integer} - {m_data.m_value.number_unsigned} - {m_data.m_value.number_float} - discarded - - - *(m_data.m_value.object),view(simple) - - - *(m_data.m_value.array),view(simple) - - - - - - - null - {*(m_data.m_value.object)} - {*(m_data.m_value.array)} - {*(m_data.m_value.string)} - {m_data.m_value.boolean} - {m_data.m_value.number_integer} - {m_data.m_value.number_unsigned} - {m_data.m_value.number_float} - discarded - - - *(m_data.m_value.object),view(simple) - - - *(m_data.m_value.array),view(simple) - - - - - - - {second} - - second - - - - - - null - {*(m_data.m_value.object)} - {*(m_data.m_value.array)} - {*(m_data.m_value.string)} - {m_data.m_value.boolean} - {m_data.m_value.number_integer} - {m_data.m_value.number_unsigned} - {m_data.m_value.number_float} - discarded - - - *(m_data.m_value.object),view(simple) - - - *(m_data.m_value.array),view(simple) - - - - - - null - {*(m_data.m_value.object)} - {*(m_data.m_value.array)} - {*(m_data.m_value.string)} - {m_data.m_value.boolean} - {m_data.m_value.number_integer} - {m_data.m_value.number_unsigned} - {m_data.m_value.number_float} - discarded - - - *(m_data.m_value.object),view(simple) - - - *(m_data.m_value.array),view(simple) - - - - - - - null - {*(m_data.m_value.object)} - {*(m_data.m_value.array)} - {*(m_data.m_value.string)} - {m_data.m_value.boolean} - {m_data.m_value.number_integer} - {m_data.m_value.number_unsigned} - {m_data.m_value.number_float} - discarded - - - *(m_data.m_value.object),view(simple) - - - *(m_data.m_value.array),view(simple) - - - - - - - {second} - - second - - - - - - null - {*(m_data.m_value.object)} - {*(m_data.m_value.array)} - {*(m_data.m_value.string)} - {m_data.m_value.boolean} - {m_data.m_value.number_integer} - {m_data.m_value.number_unsigned} - {m_data.m_value.number_float} - discarded - - - *(m_data.m_value.object),view(simple) - - - *(m_data.m_value.array),view(simple) - - - - - - null - {*(m_data.m_value.object)} - {*(m_data.m_value.array)} - {*(m_data.m_value.string)} - {m_data.m_value.boolean} - {m_data.m_value.number_integer} - {m_data.m_value.number_unsigned} - {m_data.m_value.number_float} - discarded - - - *(m_data.m_value.object),view(simple) - - - *(m_data.m_value.array),view(simple) - - - - - - - null - {*(m_data.m_value.object)} - {*(m_data.m_value.array)} - {*(m_data.m_value.string)} - {m_data.m_value.boolean} - {m_data.m_value.number_integer} - {m_data.m_value.number_unsigned} - {m_data.m_value.number_float} - discarded - - - *(m_data.m_value.object),view(simple) - - - *(m_data.m_value.array),view(simple) - - - - - - - {second} - - second - - - - - - null - {*(m_data.m_value.object)} - {*(m_data.m_value.array)} - {*(m_data.m_value.string)} - {m_data.m_value.boolean} - {m_data.m_value.number_integer} - {m_data.m_value.number_unsigned} - {m_data.m_value.number_float} - discarded - - - *(m_data.m_value.object),view(simple) - - - *(m_data.m_value.array),view(simple) - - - - - - null - {*(m_data.m_value.object)} - {*(m_data.m_value.array)} - {*(m_data.m_value.string)} - {m_data.m_value.boolean} - {m_data.m_value.number_integer} - {m_data.m_value.number_unsigned} - {m_data.m_value.number_float} - discarded - - - *(m_data.m_value.object),view(simple) - - - *(m_data.m_value.array),view(simple) - - - - - - - null - {*(m_data.m_value.object)} - {*(m_data.m_value.array)} - {*(m_data.m_value.string)} - {m_data.m_value.boolean} - {m_data.m_value.number_integer} - {m_data.m_value.number_unsigned} - {m_data.m_value.number_float} - discarded - - - *(m_data.m_value.object),view(simple) - - - *(m_data.m_value.array),view(simple) - - - - - - - {second} - - second - - - - - - null - {*(m_data.m_value.object)} - {*(m_data.m_value.array)} - {*(m_data.m_value.string)} - {m_data.m_value.boolean} - {m_data.m_value.number_integer} - {m_data.m_value.number_unsigned} - {m_data.m_value.number_float} - discarded - - - *(m_data.m_value.object),view(simple) - - - *(m_data.m_value.array),view(simple) - - - - - - null - {*(m_data.m_value.object)} - {*(m_data.m_value.array)} - {*(m_data.m_value.string)} - {m_data.m_value.boolean} - {m_data.m_value.number_integer} - {m_data.m_value.number_unsigned} - {m_data.m_value.number_float} - discarded - - - *(m_data.m_value.object),view(simple) - - - *(m_data.m_value.array),view(simple) - - - - - - - null - {*(m_data.m_value.object)} - {*(m_data.m_value.array)} - {*(m_data.m_value.string)} - {m_data.m_value.boolean} - {m_data.m_value.number_integer} - {m_data.m_value.number_unsigned} - {m_data.m_value.number_float} - discarded - - - *(m_data.m_value.object),view(simple) - - - *(m_data.m_value.array),view(simple) - - - - - - - {second} - - second - - - - - - null - {*(m_data.m_value.object)} - {*(m_data.m_value.array)} - {*(m_data.m_value.string)} - {m_data.m_value.boolean} - {m_data.m_value.number_integer} - {m_data.m_value.number_unsigned} - {m_data.m_value.number_float} - discarded - - - *(m_data.m_value.object),view(simple) - - - *(m_data.m_value.array),view(simple) - - - - - - null - {*(m_data.m_value.object)} - {*(m_data.m_value.array)} - {*(m_data.m_value.string)} - {m_data.m_value.boolean} - {m_data.m_value.number_integer} - {m_data.m_value.number_unsigned} - {m_data.m_value.number_float} - discarded - - - *(m_data.m_value.object),view(simple) - - - *(m_data.m_value.array),view(simple) - - - - - - - null - {*(m_data.m_value.object)} - {*(m_data.m_value.array)} - {*(m_data.m_value.string)} - {m_data.m_value.boolean} - {m_data.m_value.number_integer} - {m_data.m_value.number_unsigned} - {m_data.m_value.number_float} - discarded - - - *(m_data.m_value.object),view(simple) - - - *(m_data.m_value.array),view(simple) - - - - - - - {second} - - second - - - - - - null - {*(m_data.m_value.object)} - {*(m_data.m_value.array)} - {*(m_data.m_value.string)} - {m_data.m_value.boolean} - {m_data.m_value.number_integer} - {m_data.m_value.number_unsigned} - {m_data.m_value.number_float} - discarded - - - *(m_data.m_value.object),view(simple) - - - *(m_data.m_value.array),view(simple) - - - - - - null - {*(m_data.m_value.object)} - {*(m_data.m_value.array)} - {*(m_data.m_value.string)} - {m_data.m_value.boolean} - {m_data.m_value.number_integer} - {m_data.m_value.number_unsigned} - {m_data.m_value.number_float} - discarded - - - *(m_data.m_value.object),view(simple) - - - *(m_data.m_value.array),view(simple) - - - - diff --git a/single_include/nlohmann/json.hpp b/single_include/nlohmann/json.hpp index 74511d74f..f500c68ec 100644 --- a/single_include/nlohmann/json.hpp +++ b/single_include/nlohmann/json.hpp @@ -193,26 +193,14 @@ #define NLOHMANN_JSON_ABI_TAG_NO_SIMD #endif -#ifdef JSON_VIEW_NO_SIMD - #define NLOHMANN_JSON_ABI_TAG_VIEW_NO_SIMD _vns -#else - #define NLOHMANN_JSON_ABI_TAG_VIEW_NO_SIMD -#endif - -#ifdef JSON_VIEW_USE_SSSE3 - #define NLOHMANN_JSON_ABI_TAG_VIEW_USE_SSSE3 _vssse3 -#else - #define NLOHMANN_JSON_ABI_TAG_VIEW_USE_SSSE3 -#endif - #ifndef NLOHMANN_JSON_NAMESPACE_NO_VERSION #define NLOHMANN_JSON_NAMESPACE_NO_VERSION 0 #endif // Construct the namespace ABI tags component -#define NLOHMANN_JSON_ABI_TAGS_CONCAT_EX(a, b, c, d, e, f, g, h, i, j, k, l, m) json_abi ## a ## b ## c ## d ## e ## f ## g ## h ## i ## j ## k ## l ## m -#define NLOHMANN_JSON_ABI_TAGS_CONCAT(a, b, c, d, e, f, g, h, i, j, k, l, m) \ - NLOHMANN_JSON_ABI_TAGS_CONCAT_EX(a, b, c, d, e, f, g, h, i, j, k, l, m) +#define NLOHMANN_JSON_ABI_TAGS_CONCAT_EX(a, b, c, d, e, f, g, h, i, j, k) json_abi ## a ## b ## c ## d ## e ## f ## g ## h ## i ## j ## k +#define NLOHMANN_JSON_ABI_TAGS_CONCAT(a, b, c, d, e, f, g, h, i, j, k) \ + NLOHMANN_JSON_ABI_TAGS_CONCAT_EX(a, b, c, d, e, f, g, h, i, j, k) #define NLOHMANN_JSON_ABI_TAGS \ NLOHMANN_JSON_ABI_TAGS_CONCAT( \ @@ -226,9 +214,7 @@ NLOHMANN_JSON_ABI_TAG_OBJECTS_FOR_ENUM_KEYED_MAPS, \ NLOHMANN_JSON_ABI_TAG_DISABLE_STREAM_BULK_SCAN, \ NLOHMANN_JSON_ABI_TAG_DISABLE_UNLOCKED_FILE_IO, \ - NLOHMANN_JSON_ABI_TAG_NO_SIMD, \ - NLOHMANN_JSON_ABI_TAG_VIEW_NO_SIMD, \ - NLOHMANN_JSON_ABI_TAG_VIEW_USE_SSSE3) + NLOHMANN_JSON_ABI_TAG_NO_SIMD) // Construct the namespace version component #define NLOHMANN_JSON_NAMESPACE_VERSION_CONCAT_EX(major, minor, patch) \ @@ -286,6 +272,7 @@ #include // min, transform #include // array +#include // nullptr_t #include // forward_list #include // inserter, front_inserter, end #include // map @@ -2677,15 +2664,17 @@ JSON_HEDLEY_DIAGNOSTIC_POP }); \ j = ((it != std::end(m)) ? it : std::begin(m))->second; \ } \ - template \ + template::value, int> = 0> \ inline void from_json(const BasicJsonType& j, ENUM_TYPE& e) \ { \ /* NOLINTNEXTLINE(modernize-type-traits) we use C++11 */ \ static_assert(std::is_enum::value, #ENUM_TYPE " must be an enum!"); \ + /* (for a view, the table holds basic_json values, which compare with the view) */ \ + using nlohmann_json_value_t = nlohmann::detail::json_type_of_t; \ /* NOLINTNEXTLINE(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays) we don't want to depend on */ \ - static const std::pair m[] = __VA_ARGS__; \ + static const std::pair m[] = __VA_ARGS__; \ auto it = std::find_if(std::begin(m), std::end(m), \ - [&j](const std::pair& ej_pair) -> bool \ + [&j](const std::pair& ej_pair) -> bool \ { \ return ej_pair.second == j; \ }); \ @@ -2716,20 +2705,22 @@ JSON_HEDLEY_DIAGNOSTIC_POP if (it != std::end(m)) j = it->second; \ else ::nlohmann::detail::templated_json_throw(nlohmann::detail::out_of_range::create(nlohmann::detail::exception_id::value_out_of_range,"enum value out of range for " #ENUM_TYPE, nullptr)); \ } \ - template \ + template::value, int> = 0> \ inline void from_json(const BasicJsonType& j, ENUM_TYPE& e) \ { \ /* NOLINTNEXTLINE(modernize-type-traits) we use C++11 */ \ static_assert(std::is_enum::value, #ENUM_TYPE " must be an enum!"); \ + /* (for a view, the table holds basic_json values, which compare with the view) */ \ + using nlohmann_json_value_t = nlohmann::detail::json_type_of_t; \ /* NOLINTNEXTLINE(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays) we don't want to depend on */ \ - static const std::pair m[] = __VA_ARGS__; \ + static const std::pair m[] = __VA_ARGS__; \ auto it = std::find_if(std::begin(m), std::end(m), \ - [&j](const std::pair& ej_pair) -> bool \ + [&j](const std::pair& ej_pair) -> bool \ { \ return ej_pair.second == j; \ }); \ if (it != std::end(m)) e = it->first; \ - else ::nlohmann::detail::templated_json_throw(nlohmann::detail::out_of_range::create(nlohmann::detail::exception_id::value_out_of_range, nlohmann::detail::concat("enum value out of range for " #ENUM_TYPE ": ", j.dump(-1, ' ', false, nlohmann::detail::error_handler_t::replace)), &j)); \ + else ::nlohmann::detail::templated_json_throw(nlohmann::detail::out_of_range::create(nlohmann::detail::exception_id::value_out_of_range, nlohmann::detail::concat("enum value out of range for " #ENUM_TYPE ": ", nlohmann::detail::from_json_dump(j)), nlohmann::detail::exception_context(j))); \ } // Ugly macros to avoid uglier copy-paste when specializing basic_json. They @@ -2989,6 +2980,8 @@ JSON_HEDLEY_DIAGNOSTIC_POP #define NLOHMANN_JSON_FROM_WITH_DEFAULT_WITH_NAME(v1, v2) nlohmann_json_t.v2 = !nlohmann_json_j.is_null() ? nlohmann_json_j.value(v1, nlohmann_json_default_obj.v2) : nlohmann_json_default_obj.v2; #define NLOHMANN_JSON_BASIC_TYPE_TEMPLATE(...) template::value, int> = 0> __VA_ARGS__ +// from_json() also reads from the views of +#define NLOHMANN_JSON_FROM_TYPE_TEMPLATE(...) template::value, int> = 0> __VA_ARGS__ // Helpers used to dispatch the NLOHMANN_DEFINE_TYPE_*/NLOHMANN_DEFINE_DERIVED_TYPE_* // macros below between a zero-member and a one-or-more-member implementation @@ -3041,18 +3034,18 @@ JSON_HEDLEY_DIAGNOSTIC_POP */ #define NLOHMANN_JSON_DEFINE_TYPE_INTRUSIVE_MEMBERS(Type, ...) \ NLOHMANN_JSON_BASIC_TYPE_TEMPLATE(friend void to_json(BasicJsonType& nlohmann_json_j, const Type& nlohmann_json_t) { NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_TO, __VA_ARGS__)) }) \ - NLOHMANN_JSON_BASIC_TYPE_TEMPLATE(friend void from_json(const BasicJsonType& nlohmann_json_j, Type& nlohmann_json_t) { NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_FROM, __VA_ARGS__)) }) + NLOHMANN_JSON_FROM_TYPE_TEMPLATE(friend void from_json(const BasicJsonType& nlohmann_json_j, Type& nlohmann_json_t) { NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_FROM, __VA_ARGS__)) }) #define NLOHMANN_JSON_DEFINE_TYPE_INTRUSIVE_EMPTY(Type) \ NLOHMANN_JSON_BASIC_TYPE_TEMPLATE(friend void to_json(BasicJsonType& nlohmann_json_j, const Type&) { nlohmann_json_j = BasicJsonType::object(); }) \ /* NOLINTNEXTLINE(bugprone-macro-parentheses) Type is used as a declarator type, not in an expression */ \ - NLOHMANN_JSON_BASIC_TYPE_TEMPLATE(friend void from_json(const BasicJsonType&, Type&) noexcept { }) + NLOHMANN_JSON_FROM_TYPE_TEMPLATE(friend void from_json(const BasicJsonType&, Type&) noexcept { }) #define NLOHMANN_DEFINE_TYPE_INTRUSIVE(...) NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_TYPE_BODY(NLOHMANN_JSON_DEFINE_TYPE_INTRUSIVE_, __VA_ARGS__)(__VA_ARGS__)) #define NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_NAMES(Type, ...) \ NLOHMANN_JSON_BASIC_TYPE_TEMPLATE(friend void to_json(BasicJsonType& nlohmann_json_j, const Type& nlohmann_json_t) { NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_DOUBLE_PASTE(NLOHMANN_JSON_TO_WITH_NAME, __VA_ARGS__)) }) \ - NLOHMANN_JSON_BASIC_TYPE_TEMPLATE(friend void from_json(const BasicJsonType& nlohmann_json_j, Type& nlohmann_json_t) { NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_DOUBLE_PASTE(NLOHMANN_JSON_FROM_WITH_NAME, __VA_ARGS__)) }) + NLOHMANN_JSON_FROM_TYPE_TEMPLATE(friend void from_json(const BasicJsonType& nlohmann_json_j, Type& nlohmann_json_t) { NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_DOUBLE_PASTE(NLOHMANN_JSON_FROM_WITH_NAME, __VA_ARGS__)) }) /*! @brief macro @@ -3062,7 +3055,7 @@ JSON_HEDLEY_DIAGNOSTIC_POP */ #define NLOHMANN_JSON_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT_MEMBERS(Type, ...) \ NLOHMANN_JSON_BASIC_TYPE_TEMPLATE(friend void to_json(BasicJsonType& nlohmann_json_j, const Type& nlohmann_json_t) { NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_TO, __VA_ARGS__)) }) \ - NLOHMANN_JSON_BASIC_TYPE_TEMPLATE(friend void from_json(const BasicJsonType& nlohmann_json_j, Type& nlohmann_json_t) { const Type nlohmann_json_default_obj{}; NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_FROM_WITH_DEFAULT, __VA_ARGS__)) }) + NLOHMANN_JSON_FROM_TYPE_TEMPLATE(friend void from_json(const BasicJsonType& nlohmann_json_j, Type& nlohmann_json_t) { const Type nlohmann_json_default_obj{}; NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_FROM_WITH_DEFAULT, __VA_ARGS__)) }) // identical to NLOHMANN_JSON_DEFINE_TYPE_INTRUSIVE_EMPTY: with no members there is nothing to default #define NLOHMANN_JSON_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT_EMPTY(Type) NLOHMANN_JSON_DEFINE_TYPE_INTRUSIVE_EMPTY(Type) @@ -3071,7 +3064,7 @@ JSON_HEDLEY_DIAGNOSTIC_POP #define NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT_WITH_NAMES(Type, ...) \ NLOHMANN_JSON_BASIC_TYPE_TEMPLATE(friend void to_json(BasicJsonType& nlohmann_json_j, const Type& nlohmann_json_t) { NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_DOUBLE_PASTE(NLOHMANN_JSON_TO_WITH_NAME, __VA_ARGS__)) }) \ - NLOHMANN_JSON_BASIC_TYPE_TEMPLATE(friend void from_json(const BasicJsonType& nlohmann_json_j, Type& nlohmann_json_t) { const Type nlohmann_json_default_obj{}; NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_DOUBLE_PASTE(NLOHMANN_JSON_FROM_WITH_DEFAULT_WITH_NAME, __VA_ARGS__)) }) + NLOHMANN_JSON_FROM_TYPE_TEMPLATE(friend void from_json(const BasicJsonType& nlohmann_json_j, Type& nlohmann_json_t) { const Type nlohmann_json_default_obj{}; NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_DOUBLE_PASTE(NLOHMANN_JSON_FROM_WITH_DEFAULT_WITH_NAME, __VA_ARGS__)) }) /*! @brief macro @@ -3098,18 +3091,18 @@ JSON_HEDLEY_DIAGNOSTIC_POP */ #define NLOHMANN_JSON_DEFINE_TYPE_NON_INTRUSIVE_MEMBERS(Type, ...) \ NLOHMANN_JSON_BASIC_TYPE_TEMPLATE(void to_json(BasicJsonType& nlohmann_json_j, const Type& nlohmann_json_t) { NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_TO, __VA_ARGS__)) }) \ - NLOHMANN_JSON_BASIC_TYPE_TEMPLATE(void from_json(const BasicJsonType& nlohmann_json_j, Type& nlohmann_json_t) { NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_FROM, __VA_ARGS__)) }) + NLOHMANN_JSON_FROM_TYPE_TEMPLATE(void from_json(const BasicJsonType& nlohmann_json_j, Type& nlohmann_json_t) { NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_FROM, __VA_ARGS__)) }) #define NLOHMANN_JSON_DEFINE_TYPE_NON_INTRUSIVE_EMPTY(Type) \ NLOHMANN_JSON_BASIC_TYPE_TEMPLATE(void to_json(BasicJsonType& nlohmann_json_j, const Type&) { nlohmann_json_j = BasicJsonType::object(); }) \ /* NOLINTNEXTLINE(bugprone-macro-parentheses) Type is used as a declarator type, not in an expression */ \ - NLOHMANN_JSON_BASIC_TYPE_TEMPLATE(void from_json(const BasicJsonType&, Type&) noexcept { }) + NLOHMANN_JSON_FROM_TYPE_TEMPLATE(void from_json(const BasicJsonType&, Type&) noexcept { }) #define NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(...) NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_TYPE_BODY(NLOHMANN_JSON_DEFINE_TYPE_NON_INTRUSIVE_, __VA_ARGS__)(__VA_ARGS__)) #define NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_WITH_NAMES(Type, ...) \ NLOHMANN_JSON_BASIC_TYPE_TEMPLATE(void to_json(BasicJsonType& nlohmann_json_j, const Type& nlohmann_json_t) { NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_DOUBLE_PASTE(NLOHMANN_JSON_TO_WITH_NAME, __VA_ARGS__)) }) \ - NLOHMANN_JSON_BASIC_TYPE_TEMPLATE(void from_json(const BasicJsonType& nlohmann_json_j, Type& nlohmann_json_t) { NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_DOUBLE_PASTE(NLOHMANN_JSON_FROM_WITH_NAME, __VA_ARGS__)) }) + NLOHMANN_JSON_FROM_TYPE_TEMPLATE(void from_json(const BasicJsonType& nlohmann_json_j, Type& nlohmann_json_t) { NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_DOUBLE_PASTE(NLOHMANN_JSON_FROM_WITH_NAME, __VA_ARGS__)) }) /*! @brief macro @@ -3119,7 +3112,7 @@ JSON_HEDLEY_DIAGNOSTIC_POP */ #define NLOHMANN_JSON_DEFINE_TYPE_NON_INTRUSIVE_WITH_DEFAULT_MEMBERS(Type, ...) \ NLOHMANN_JSON_BASIC_TYPE_TEMPLATE(void to_json(BasicJsonType& nlohmann_json_j, const Type& nlohmann_json_t) { NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_TO, __VA_ARGS__)) }) \ - NLOHMANN_JSON_BASIC_TYPE_TEMPLATE(void from_json(const BasicJsonType& nlohmann_json_j, Type& nlohmann_json_t) { const Type nlohmann_json_default_obj{}; NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_FROM_WITH_DEFAULT, __VA_ARGS__)) }) + NLOHMANN_JSON_FROM_TYPE_TEMPLATE(void from_json(const BasicJsonType& nlohmann_json_j, Type& nlohmann_json_t) { const Type nlohmann_json_default_obj{}; NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_FROM_WITH_DEFAULT, __VA_ARGS__)) }) // identical to NLOHMANN_JSON_DEFINE_TYPE_NON_INTRUSIVE_EMPTY: with no members there is nothing to default #define NLOHMANN_JSON_DEFINE_TYPE_NON_INTRUSIVE_WITH_DEFAULT_EMPTY(Type) NLOHMANN_JSON_DEFINE_TYPE_NON_INTRUSIVE_EMPTY(Type) @@ -3128,7 +3121,7 @@ JSON_HEDLEY_DIAGNOSTIC_POP #define NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_WITH_DEFAULT_WITH_NAMES(Type, ...) \ NLOHMANN_JSON_BASIC_TYPE_TEMPLATE(void to_json(BasicJsonType& nlohmann_json_j, const Type& nlohmann_json_t) { NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_DOUBLE_PASTE(NLOHMANN_JSON_TO_WITH_NAME, __VA_ARGS__)) }) \ - NLOHMANN_JSON_BASIC_TYPE_TEMPLATE(void from_json(const BasicJsonType& nlohmann_json_j, Type& nlohmann_json_t) { const Type nlohmann_json_default_obj{}; NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_DOUBLE_PASTE(NLOHMANN_JSON_FROM_WITH_DEFAULT_WITH_NAME, __VA_ARGS__)) }) + NLOHMANN_JSON_FROM_TYPE_TEMPLATE(void from_json(const BasicJsonType& nlohmann_json_j, Type& nlohmann_json_t) { const Type nlohmann_json_default_obj{}; NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_DOUBLE_PASTE(NLOHMANN_JSON_FROM_WITH_DEFAULT_WITH_NAME, __VA_ARGS__)) }) /*! @brief macro @@ -3155,18 +3148,18 @@ JSON_HEDLEY_DIAGNOSTIC_POP */ #define NLOHMANN_JSON_DEFINE_DERIVED_TYPE_INTRUSIVE_MEMBERS(Type, BaseType, ...) \ NLOHMANN_JSON_BASIC_TYPE_TEMPLATE(friend void to_json(BasicJsonType& nlohmann_json_j, const Type& nlohmann_json_t) { nlohmann::to_json(nlohmann_json_j, static_cast(nlohmann_json_t)); NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_TO, __VA_ARGS__)) }) \ - NLOHMANN_JSON_BASIC_TYPE_TEMPLATE(friend void from_json(const BasicJsonType& nlohmann_json_j, Type& nlohmann_json_t) { nlohmann::from_json(nlohmann_json_j, static_cast(nlohmann_json_t)); NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_FROM, __VA_ARGS__)) }) + NLOHMANN_JSON_FROM_TYPE_TEMPLATE(friend void from_json(const BasicJsonType& nlohmann_json_j, Type& nlohmann_json_t) { ::nlohmann::detail::from_json_base(nlohmann_json_j, static_cast(nlohmann_json_t)); NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_FROM, __VA_ARGS__)) }) #define NLOHMANN_JSON_DEFINE_DERIVED_TYPE_INTRUSIVE_EMPTY(Type, BaseType) \ NLOHMANN_JSON_BASIC_TYPE_TEMPLATE(friend void to_json(BasicJsonType& nlohmann_json_j, const Type& nlohmann_json_t) { nlohmann::to_json(nlohmann_json_j, static_cast(nlohmann_json_t)); }) \ /* NOLINTNEXTLINE(bugprone-macro-parentheses) Type/BaseType are used as declarator types, not in expressions */ \ - NLOHMANN_JSON_BASIC_TYPE_TEMPLATE(friend void from_json(const BasicJsonType& nlohmann_json_j, Type& nlohmann_json_t) { nlohmann::from_json(nlohmann_json_j, static_cast(nlohmann_json_t)); }) + NLOHMANN_JSON_FROM_TYPE_TEMPLATE(friend void from_json(const BasicJsonType& nlohmann_json_j, Type& nlohmann_json_t) { ::nlohmann::detail::from_json_base(nlohmann_json_j, static_cast(nlohmann_json_t)); }) #define NLOHMANN_DEFINE_DERIVED_TYPE_INTRUSIVE(...) NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_DERIVED_TYPE_BODY(NLOHMANN_JSON_DEFINE_DERIVED_TYPE_INTRUSIVE_, __VA_ARGS__)(__VA_ARGS__)) #define NLOHMANN_DEFINE_DERIVED_TYPE_INTRUSIVE_WITH_NAMES(Type, BaseType, ...) \ NLOHMANN_JSON_BASIC_TYPE_TEMPLATE(friend void to_json(BasicJsonType& nlohmann_json_j, const Type& nlohmann_json_t) { nlohmann::to_json(nlohmann_json_j, static_cast(nlohmann_json_t)); NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_DOUBLE_PASTE(NLOHMANN_JSON_TO_WITH_NAME, __VA_ARGS__)) }) \ - NLOHMANN_JSON_BASIC_TYPE_TEMPLATE(friend void from_json(const BasicJsonType& nlohmann_json_j, Type& nlohmann_json_t) { nlohmann::from_json(nlohmann_json_j, static_cast(nlohmann_json_t)); NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_DOUBLE_PASTE(NLOHMANN_JSON_FROM_WITH_NAME, __VA_ARGS__)) }) + NLOHMANN_JSON_FROM_TYPE_TEMPLATE(friend void from_json(const BasicJsonType& nlohmann_json_j, Type& nlohmann_json_t) { ::nlohmann::detail::from_json_base(nlohmann_json_j, static_cast(nlohmann_json_t)); NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_DOUBLE_PASTE(NLOHMANN_JSON_FROM_WITH_NAME, __VA_ARGS__)) }) /*! @brief macro @@ -3176,7 +3169,7 @@ JSON_HEDLEY_DIAGNOSTIC_POP */ #define NLOHMANN_JSON_DEFINE_DERIVED_TYPE_INTRUSIVE_WITH_DEFAULT_MEMBERS(Type, BaseType, ...) \ NLOHMANN_JSON_BASIC_TYPE_TEMPLATE(friend void to_json(BasicJsonType& nlohmann_json_j, const Type& nlohmann_json_t) { nlohmann::to_json(nlohmann_json_j, static_cast(nlohmann_json_t)); NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_TO, __VA_ARGS__)) }) \ - NLOHMANN_JSON_BASIC_TYPE_TEMPLATE(friend void from_json(const BasicJsonType& nlohmann_json_j, Type& nlohmann_json_t) { nlohmann::from_json(nlohmann_json_j, static_cast(nlohmann_json_t)); const Type nlohmann_json_default_obj{}; NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_FROM_WITH_DEFAULT, __VA_ARGS__)) }) + NLOHMANN_JSON_FROM_TYPE_TEMPLATE(friend void from_json(const BasicJsonType& nlohmann_json_j, Type& nlohmann_json_t) { ::nlohmann::detail::from_json_base(nlohmann_json_j, static_cast(nlohmann_json_t)); const Type nlohmann_json_default_obj{}; NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_FROM_WITH_DEFAULT, __VA_ARGS__)) }) // identical to NLOHMANN_JSON_DEFINE_DERIVED_TYPE_INTRUSIVE_EMPTY: with no members there is nothing to default #define NLOHMANN_JSON_DEFINE_DERIVED_TYPE_INTRUSIVE_WITH_DEFAULT_EMPTY(Type, BaseType) NLOHMANN_JSON_DEFINE_DERIVED_TYPE_INTRUSIVE_EMPTY(Type, BaseType) @@ -3185,7 +3178,7 @@ JSON_HEDLEY_DIAGNOSTIC_POP #define NLOHMANN_DEFINE_DERIVED_TYPE_INTRUSIVE_WITH_DEFAULT_WITH_NAMES(Type, BaseType, ...) \ NLOHMANN_JSON_BASIC_TYPE_TEMPLATE(friend void to_json(BasicJsonType& nlohmann_json_j, const Type& nlohmann_json_t) { nlohmann::to_json(nlohmann_json_j, static_cast(nlohmann_json_t)); NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_DOUBLE_PASTE(NLOHMANN_JSON_TO_WITH_NAME, __VA_ARGS__)) }) \ - NLOHMANN_JSON_BASIC_TYPE_TEMPLATE(friend void from_json(const BasicJsonType& nlohmann_json_j, Type& nlohmann_json_t) { nlohmann::from_json(nlohmann_json_j, static_cast(nlohmann_json_t)); const Type nlohmann_json_default_obj{}; NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_DOUBLE_PASTE(NLOHMANN_JSON_FROM_WITH_DEFAULT_WITH_NAME, __VA_ARGS__)) }) + NLOHMANN_JSON_FROM_TYPE_TEMPLATE(friend void from_json(const BasicJsonType& nlohmann_json_j, Type& nlohmann_json_t) { ::nlohmann::detail::from_json_base(nlohmann_json_j, static_cast(nlohmann_json_t)); const Type nlohmann_json_default_obj{}; NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_DOUBLE_PASTE(NLOHMANN_JSON_FROM_WITH_DEFAULT_WITH_NAME, __VA_ARGS__)) }) /*! @brief macro @@ -3213,18 +3206,18 @@ JSON_HEDLEY_DIAGNOSTIC_POP */ #define NLOHMANN_JSON_DEFINE_DERIVED_TYPE_NON_INTRUSIVE_MEMBERS(Type, BaseType, ...) \ NLOHMANN_JSON_BASIC_TYPE_TEMPLATE(void to_json(BasicJsonType& nlohmann_json_j, const Type& nlohmann_json_t) { nlohmann::to_json(nlohmann_json_j, static_cast(nlohmann_json_t)); NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_TO, __VA_ARGS__)) }) \ - NLOHMANN_JSON_BASIC_TYPE_TEMPLATE(void from_json(const BasicJsonType& nlohmann_json_j, Type& nlohmann_json_t) { nlohmann::from_json(nlohmann_json_j, static_cast(nlohmann_json_t)); NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_FROM, __VA_ARGS__)) }) + NLOHMANN_JSON_FROM_TYPE_TEMPLATE(void from_json(const BasicJsonType& nlohmann_json_j, Type& nlohmann_json_t) { ::nlohmann::detail::from_json_base(nlohmann_json_j, static_cast(nlohmann_json_t)); NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_FROM, __VA_ARGS__)) }) #define NLOHMANN_JSON_DEFINE_DERIVED_TYPE_NON_INTRUSIVE_EMPTY(Type, BaseType) \ NLOHMANN_JSON_BASIC_TYPE_TEMPLATE(void to_json(BasicJsonType& nlohmann_json_j, const Type& nlohmann_json_t) { nlohmann::to_json(nlohmann_json_j, static_cast(nlohmann_json_t)); }) \ /* NOLINTNEXTLINE(bugprone-macro-parentheses) Type/BaseType are used as declarator types, not in expressions */ \ - NLOHMANN_JSON_BASIC_TYPE_TEMPLATE(void from_json(const BasicJsonType& nlohmann_json_j, Type& nlohmann_json_t) { nlohmann::from_json(nlohmann_json_j, static_cast(nlohmann_json_t)); }) + NLOHMANN_JSON_FROM_TYPE_TEMPLATE(void from_json(const BasicJsonType& nlohmann_json_j, Type& nlohmann_json_t) { ::nlohmann::detail::from_json_base(nlohmann_json_j, static_cast(nlohmann_json_t)); }) #define NLOHMANN_DEFINE_DERIVED_TYPE_NON_INTRUSIVE(...) NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_DERIVED_TYPE_BODY(NLOHMANN_JSON_DEFINE_DERIVED_TYPE_NON_INTRUSIVE_, __VA_ARGS__)(__VA_ARGS__)) #define NLOHMANN_DEFINE_DERIVED_TYPE_NON_INTRUSIVE_WITH_NAMES(Type, BaseType, ...) \ NLOHMANN_JSON_BASIC_TYPE_TEMPLATE(void to_json(BasicJsonType& nlohmann_json_j, const Type& nlohmann_json_t) { nlohmann::to_json(nlohmann_json_j, static_cast(nlohmann_json_t)); NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_DOUBLE_PASTE(NLOHMANN_JSON_TO_WITH_NAME, __VA_ARGS__)) }) \ - NLOHMANN_JSON_BASIC_TYPE_TEMPLATE(void from_json(const BasicJsonType& nlohmann_json_j, Type& nlohmann_json_t) { nlohmann::from_json(nlohmann_json_j, static_cast(nlohmann_json_t)); NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_DOUBLE_PASTE(NLOHMANN_JSON_FROM_WITH_NAME, __VA_ARGS__)) }) + NLOHMANN_JSON_FROM_TYPE_TEMPLATE(void from_json(const BasicJsonType& nlohmann_json_j, Type& nlohmann_json_t) { ::nlohmann::detail::from_json_base(nlohmann_json_j, static_cast(nlohmann_json_t)); NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_DOUBLE_PASTE(NLOHMANN_JSON_FROM_WITH_NAME, __VA_ARGS__)) }) /*! @brief macro @@ -3234,7 +3227,7 @@ JSON_HEDLEY_DIAGNOSTIC_POP */ #define NLOHMANN_JSON_DEFINE_DERIVED_TYPE_NON_INTRUSIVE_WITH_DEFAULT_MEMBERS(Type, BaseType, ...) \ NLOHMANN_JSON_BASIC_TYPE_TEMPLATE(void to_json(BasicJsonType& nlohmann_json_j, const Type& nlohmann_json_t) { nlohmann::to_json(nlohmann_json_j, static_cast(nlohmann_json_t)); NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_TO, __VA_ARGS__)) }) \ - NLOHMANN_JSON_BASIC_TYPE_TEMPLATE(void from_json(const BasicJsonType& nlohmann_json_j, Type& nlohmann_json_t) { nlohmann::from_json(nlohmann_json_j, static_cast(nlohmann_json_t)); const Type nlohmann_json_default_obj{}; NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_FROM_WITH_DEFAULT, __VA_ARGS__)) }) + NLOHMANN_JSON_FROM_TYPE_TEMPLATE(void from_json(const BasicJsonType& nlohmann_json_j, Type& nlohmann_json_t) { ::nlohmann::detail::from_json_base(nlohmann_json_j, static_cast(nlohmann_json_t)); const Type nlohmann_json_default_obj{}; NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_FROM_WITH_DEFAULT, __VA_ARGS__)) }) // identical to NLOHMANN_JSON_DEFINE_DERIVED_TYPE_NON_INTRUSIVE_EMPTY: with no members there is nothing to default #define NLOHMANN_JSON_DEFINE_DERIVED_TYPE_NON_INTRUSIVE_WITH_DEFAULT_EMPTY(Type, BaseType) NLOHMANN_JSON_DEFINE_DERIVED_TYPE_NON_INTRUSIVE_EMPTY(Type, BaseType) @@ -3243,7 +3236,7 @@ JSON_HEDLEY_DIAGNOSTIC_POP #define NLOHMANN_DEFINE_DERIVED_TYPE_NON_INTRUSIVE_WITH_DEFAULT_WITH_NAMES(Type, BaseType, ...) \ NLOHMANN_JSON_BASIC_TYPE_TEMPLATE(void to_json(BasicJsonType& nlohmann_json_j, const Type& nlohmann_json_t) { nlohmann::to_json(nlohmann_json_j, static_cast(nlohmann_json_t)); NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_DOUBLE_PASTE(NLOHMANN_JSON_TO_WITH_NAME, __VA_ARGS__)) }) \ - NLOHMANN_JSON_BASIC_TYPE_TEMPLATE(void from_json(const BasicJsonType& nlohmann_json_j, Type& nlohmann_json_t) { nlohmann::from_json(nlohmann_json_j, static_cast(nlohmann_json_t)); const Type nlohmann_json_default_obj{}; NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_DOUBLE_PASTE(NLOHMANN_JSON_FROM_WITH_DEFAULT_WITH_NAME, __VA_ARGS__)) }) + NLOHMANN_JSON_FROM_TYPE_TEMPLATE(void from_json(const BasicJsonType& nlohmann_json_j, Type& nlohmann_json_t) { ::nlohmann::detail::from_json_base(nlohmann_json_j, static_cast(nlohmann_json_t)); const Type nlohmann_json_default_obj{}; NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_DOUBLE_PASTE(NLOHMANN_JSON_FROM_WITH_DEFAULT_WITH_NAME, __VA_ARGS__)) }) /*! @brief macro @@ -4530,6 +4523,47 @@ template struct negation : std::integral_constant < bool, !B::value > { #endif +/////////////////////// +// json_view helpers // +/////////////////////// + +template +using basic_json_type_t = typename T::basic_json_type; + +template +using materialize_function_t = decltype(std::declval().materialize()); + +// a basic_json_view of (detected by its member type +// basic_json_type, a basic_json, and materialize() returning that type, so that +// json.hpp needs no declaration of the view) +template +struct is_json_view : std::false_type {}; + +template +struct is_json_view>::value>> + : std::is_same, basic_json_type_t> {}; + +// the types from_json() functions of the NLOHMANN_DEFINE_TYPE_* and +// NLOHMANN_JSON_SERIALIZE_ENUM macros read from +template +struct is_basic_json_or_view : disjunction, is_json_view> {}; + +// the basic_json type of a basic_json value or of a view +template +struct json_type_of +{ + using type = T; +}; + +template +struct json_type_of::value>> +{ + using type = typename T::basic_json_type; +}; + +template +using json_type_of_t = typename json_type_of::type; + // Reimplementation of is_constructible and is_default_constructible, due to them being broken for // std::pair and std::tuple until LWG 2367 fix (see https://cplusplus.github.io/LWG/lwg-defects.html#2367). // This causes compile errors in e.g., Clang 3.5 or GCC 4.9. @@ -5565,7 +5599,7 @@ enum class exception_id : int type_not_serializable = 317, ///< a value cannot be serialized to the requested binary format enum_key_duplicate = 318, ///< an enum-keyed map has two keys that serialize to the same string document_binary_value = 319, ///< a binary value is stored in a json_document - document_image_unsupported = 320, ///< a json_document image cannot be saved or loaded (discarded document, big-endian target) + document_image_unsupported = 320, ///< a discarded json_document cannot be saved as an image discarded_value_used = 321, ///< a discarded value is used // out_of_range array_index_out_of_range = 401, ///< an array index is out of range @@ -5880,6 +5914,20 @@ void templated_json_throw(ExceptionType exception) (void)exception; } +// the context of an exception about j: j itself for a basic_json value (whose +// path JSON_DIAGNOSTICS shows), none for a view of +template::value, int> = 0> +const BasicJsonType * exception_context(const BasicJsonType& j) noexcept +{ + return &j; +} + +template::value, int> = 0> +std::nullptr_t exception_context(const View& /*unused*/) noexcept +{ + return nullptr; +} + /*! @brief throws because @a j does not have the type a conversion expects @param[in] expected the expected type(s), e.g. "array" or "binary or array" @@ -5891,7 +5939,7 @@ JSON_HEDLEY_NO_RETURN JSON_HEDLEY_NEVER_INLINE void throw_type_must_be(const cha { static_cast(expected); // unused when JSON_NOEXCEPTION is defined static_cast(j); - JSON_THROW(type_error::create(exception_id::type_mismatch, concat("type must be ", expected, ", but is ", j.type_name()), &j)); + JSON_THROW(type_error::create(exception_id::type_mismatch, concat("type must be ", expected, ", but is ", j.type_name()), exception_context(j))); } /*! @@ -6049,6 +6097,59 @@ NLOHMANN_JSON_NAMESPACE_END // #include +// #include +// __ _____ _____ _____ +// __| | __| | | | JSON for Modern C++ +// | | |__ | | | | | | version 3.12.0 +// |_____|_____|_____|_|___| https://github.com/nlohmann/json +// +// SPDX-FileCopyrightText: 2013-2026 Niels Lohmann +// SPDX-License-Identifier: MIT + + + +// #include + + +NLOHMANN_JSON_NAMESPACE_BEGIN +namespace detail +{ + +/// how to treat decoding errors +/// +/// @ref basic_json::dump uses this to decide what to do with ill-formed +/// UTF-8 while escaping a string, and the binary writers (@ref +/// basic_json::to_cbor, @ref basic_json::to_ubjson, @ref +/// basic_json::to_bjdata, @ref basic_json::to_bson) use it the same way for +/// string values and object keys. The binary readers (@ref +/// basic_json::from_cbor, @ref basic_json::from_msgpack, @ref +/// basic_json::from_ubjson, @ref basic_json::from_bjdata, @ref +/// basic_json::from_bson) use it to decide whether to check text strings +/// and object keys for well-formed UTF-8 at all, since none of those +/// formats requires a decoder to do so. +enum class error_handler_t +{ + strict, ///< throw a type_error/parse_error exception in case of invalid UTF-8 + replace, ///< replace invalid UTF-8 sequences with U+FFFD + ignore, ///< ignore invalid UTF-8 sequences + keep ///< keep invalid UTF-8 sequences unchanged +}; + +/// the default error handler of the CBOR, UBJSON, BJData, and BSON writers: +/// error_handler_t::strict if JSON_STRICT_BINARY_UTF8 is enabled, otherwise +/// error_handler_t::keep (the behavior before version 3.13.0) +constexpr error_handler_t binary_writer_default_error_handler() noexcept +{ +#if JSON_STRICT_BINARY_UTF8 + return error_handler_t::strict; +#else + return error_handler_t::keep; +#endif +} + +} // namespace detail +NLOHMANN_JSON_NAMESPACE_END + // #include // #include @@ -6067,6 +6168,70 @@ NLOHMANN_JSON_NAMESPACE_BEGIN namespace detail { +// The from_json() overloads below read basic_json values and the views of +// (BasicJsonType is then a view, and +// json_type_of_t its basic_json type). The helpers below are +// the accesses that differ between the two. + +// the enable_if parameter of the overloads that read basic_json values (written +// with negation<> because astyle spaces out a template argument list with '!') +template +using enable_if_not_view_t = enable_if_t>::value, int>; + +// the boolean of j, a boolean +template = 0> +typename BasicJsonType::boolean_t boolean_value(const BasicJsonType& j) +{ + return *j.template get_ptr(); +} + +template::value, int> = 0> +bool boolean_value(const View& v) +{ + return v.template get(); +} + +// the string of j, a string +template = 0> +const typename BasicJsonType::string_t& string_value(const BasicJsonType& j) +{ + return *j.template get_ptr(); +} + +template::value, int> = 0> +typename View::string_t string_value(const View& v) +{ + const auto s = v.get_string(); + return typename View::string_t(s.data(), s.size()); +} + +// the binary value of j, a binary value (never a view: a view of a JSON text +// holds no binary values) +template = 0> +const typename BasicJsonType::binary_t& binary_value(const BasicJsonType& j) +{ + return *j.template get_ptr(); +} + +template::value, int> = 0> +typename View::basic_json_type::binary_t binary_value(const View& /*unused*/) +{ + return {}; // LCOV_EXCL_LINE +} + +// the elements of j, an array, for a range-based for loop +template = 0> +const typename BasicJsonType::array_t& array_elements(const BasicJsonType& j) +{ + return *j.template get_ptr(); +} + +template::value, int> = 0> +View array_elements(const View& v) noexcept +{ + return v; // (a view is two pointers) +} + template inline void from_json(const BasicJsonType& j, typename std::nullptr_t& n) { @@ -6095,7 +6260,7 @@ void from_json(const BasicJsonType& j, std::optional& opt) // read a number value of any of the three number types into val; // returns false (leaving val untouched) if j is not a number -template +template = 0> bool get_number_value(const BasicJsonType& j, ArithmeticType& val) { switch (static_cast(j)) @@ -6132,10 +6297,22 @@ bool get_number_value(const BasicJsonType& j, ArithmeticType& val) } } +// a view converts numbers itself (as basic_json does) +template::value, int> = 0> +bool get_number_value(const View& v, ArithmeticType& val) +{ + if (!v.is_number()) + { + return false; + } + val = v.template get(); + return true; +} + // overloads for basic_json template parameters template < typename BasicJsonType, typename ArithmeticType, enable_if_t < std::is_arithmetic::value&& - !std::is_same::value, + !std::is_same::boolean_t>::value, int > = 0 > void get_arithmetic_value(const BasicJsonType& j, ArithmeticType& val) { @@ -6146,31 +6323,31 @@ void get_arithmetic_value(const BasicJsonType& j, ArithmeticType& val) } template -inline void from_json(const BasicJsonType& j, typename BasicJsonType::boolean_t& b) +inline void from_json(const BasicJsonType& j, typename json_type_of_t::boolean_t& b) { if (JSON_HEDLEY_UNLIKELY(!j.is_boolean())) { throw_type_must_be("boolean", j); } - b = *j.template get_ptr(); + b = boolean_value(j); } template -inline void from_json(const BasicJsonType& j, typename BasicJsonType::string_t& s) +inline void from_json(const BasicJsonType& j, typename json_type_of_t::string_t& s) { if (JSON_HEDLEY_UNLIKELY(!j.is_string())) { throw_type_must_be("string", j); } - s = *j.template get_ptr(); + s = string_value(j); } template < typename BasicJsonType, typename StringType, enable_if_t < - std::is_assignable::value - && is_detected_exact::value - && !std::is_same::value + std::is_assignable::string_t>::value + && is_detected_exact::string_t::value_type, value_type_t, StringType>::value + && !std::is_same::string_t, StringType>::value && !is_json_ref::value, int > = 0 > inline void from_json(const BasicJsonType& j, StringType& s) { @@ -6179,23 +6356,23 @@ inline void from_json(const BasicJsonType& j, StringType& s) throw_type_must_be("string", j); } - s = *j.template get_ptr(); + s = string_value(j); } template -inline void from_json(const BasicJsonType& j, typename BasicJsonType::number_float_t& val) +inline void from_json(const BasicJsonType& j, typename json_type_of_t::number_float_t& val) { get_arithmetic_value(j, val); } template -inline void from_json(const BasicJsonType& j, number_unsigned_overload_t& val) +inline void from_json(const BasicJsonType& j, number_unsigned_overload_t>& val) { get_arithmetic_value(j, val); } template -inline void from_json(const BasicJsonType& j, typename BasicJsonType::number_integer_t& val) +inline void from_json(const BasicJsonType& j, typename json_type_of_t::number_integer_t& val) { get_arithmetic_value(j, val); } @@ -6207,8 +6384,8 @@ inline void from_json(const BasicJsonType& j, EnumType& e) { using underlying_type = typename std::underlying_type::type; // get_arithmetic_value() does not accept boolean_t; read the number that to_json() wrote instead - using value_type = typename std::conditional::value, - typename BasicJsonType::number_unsigned_t, underlying_type>::type; + using value_type = typename std::conditional::boolean_t>::value, + typename json_type_of_t::number_unsigned_t, underlying_type>::type; value_type val; get_arithmetic_value(j, val); e = static_cast(bool_aware_static_cast(val)); @@ -6217,7 +6394,7 @@ inline void from_json(const BasicJsonType& j, EnumType& e) // forward_list doesn't have an insert method template::value, int> = 0> + enable_if_t, T>::value, int> = 0> inline void from_json(const BasicJsonType& j, std::forward_list& l) { if (JSON_HEDLEY_UNLIKELY(!j.is_array())) @@ -6225,16 +6402,16 @@ inline void from_json(const BasicJsonType& j, std::forward_list& l throw_type_must_be("array", j); } l.clear(); - std::transform(j.rbegin(), j.rend(), - std::front_inserter(l), [](const BasicJsonType & i) + auto pos = l.before_begin(); + for (const auto& elem : array_elements(j)) { - return i.template get(); - }); + pos = l.insert_after(pos, elem.template get()); + } } // valarray doesn't have an insert method template::value, int> = 0> + enable_if_t, T>::value, int> = 0> inline void from_json(const BasicJsonType& j, std::valarray& l) { if (JSON_HEDLEY_UNLIKELY(!j.is_array())) @@ -6242,11 +6419,11 @@ inline void from_json(const BasicJsonType& j, std::valarray& l) throw_type_must_be("array", j); } l.resize(j.size()); - std::transform(j.begin(), j.end(), std::begin(l), - [](const BasicJsonType & elem) + std::size_t i = 0; + for (const auto& elem : array_elements(j)) { - return elem.template get(); - }); + l[i++] = elem.template get(); + } } // element is not itself a C array: read it directly @@ -6274,7 +6451,8 @@ auto from_json(const BasicJsonType& j, T (&arr)[N]) // NOLINT(cppcoreguidelines from_json_c_array_element(j, arr); } -template +// (for a view, array_t is read element by element below) +template = 0> inline void from_json_array_impl(const BasicJsonType& j, typename BasicJsonType::array_t& arr, priority_tag<3> /*unused*/) { arr = *j.template get_ptr(); @@ -6283,7 +6461,7 @@ inline void from_json_array_impl(const BasicJsonType& j, typename BasicJsonType: // fill a std::array from the JSON array j: the size is checked once and the // elements are read in order, so a short array converts the elements it has and // then throws the out_of_range error at() would throw for the first missing one -template +template = 0> void from_json_std_array(const BasicJsonType& j, std::array& arr) { const auto& inner_array = *j.template get_ptr(); @@ -6298,6 +6476,21 @@ void from_json_std_array(const BasicJsonType& j, std::array& arr) } } +// (a view has no random access to the elements: they are read in order) +template::value, int> = 0> +void from_json_std_array(const View& v, std::array& arr) +{ + std::size_t size = 0; + for (auto it = v.begin(); size < N && it != v.end(); ++it) + { + arr[size++] = (*it).template get(); + } + if (JSON_HEDLEY_UNLIKELY(size < N)) + { + static_cast(v.at(size)); + } +} + template auto from_json_array_impl(const BasicJsonType& j, std::array& arr, priority_tag<2> /*unused*/) @@ -6346,9 +6539,8 @@ auto from_json_array_impl(const BasicJsonType& j, ConstructibleArrayType& arr, p void()) { ConstructibleArrayType ret; - const auto* inner_array = j.template get_ptr(); - reserve_if_possible(ret, inner_array->size(), priority_tag<1> {}); - for (const auto& elem : *inner_array) + reserve_if_possible(ret, j.size(), priority_tag<1> {}); + for (const auto& elem : array_elements(j)) { // get() returns *this, this won't call a from_json // method when value_type is BasicJsonType @@ -6359,11 +6551,11 @@ auto from_json_array_impl(const BasicJsonType& j, ConstructibleArrayType& arr, p template < typename BasicJsonType, typename ConstructibleArrayType, enable_if_t < - is_constructible_array_type::value&& - !is_constructible_object_type::value&& - !is_constructible_string_type::value&& - !std::is_same::value&& - !is_compatible_binary_type::value&& + is_constructible_array_type, ConstructibleArrayType>::value&& + !is_constructible_object_type, ConstructibleArrayType>::value&& + !is_constructible_string_type, ConstructibleArrayType>::value&& + !std::is_same::binary_t>::value&& + !is_compatible_binary_type, ConstructibleArrayType>::value&& !is_basic_json::value, int > = 0 > auto from_json(const BasicJsonType& j, ConstructibleArrayType& arr) @@ -6430,24 +6622,24 @@ auto from_json(const BasicJsonType& j, identity_tag> tag) } template -inline void from_json(const BasicJsonType& j, typename BasicJsonType::binary_t& bin) +inline void from_json(const BasicJsonType& j, typename json_type_of_t::binary_t& bin) { if (JSON_HEDLEY_UNLIKELY(!j.is_binary())) { throw_type_must_be("binary", j); } - bin = *j.template get_ptr(); + bin = binary_value(j); } template < typename BasicJsonType, typename CompatibleArrayType, - enable_if_t < is_compatible_binary_type::value, + enable_if_t < is_compatible_binary_type, CompatibleArrayType>::value, int > = 0 > inline void from_json(const BasicJsonType& j, CompatibleArrayType& bin) { if (j.is_binary()) { - bin = static_cast(*j.template get_ptr()); + bin = static_cast(binary_value(j)); } else if (j.is_array()) { @@ -6476,8 +6668,47 @@ void from_json_object_emplace(ConstructibleObjectType& obj, Key&& key, Value&& v obj.emplace(std::forward(key), std::forward(value)); } +// a member of a view's object: a later member with the same key replaces an +// earlier one, as parse() keeps the last value of a repeated key (containers +// without operator[] keep the first) +template +auto from_json_object_assign(ConstructibleObjectType& obj, Key&& key, Value&& value, priority_tag<1> /*unused*/) +-> decltype(obj[std::forward(key)] = std::forward(value), void()) +{ + obj[std::forward(key)] = std::forward(value); +} + +template +void from_json_object_assign(ConstructibleObjectType& obj, Key&& key, Value&& value, priority_tag<0> /*unused*/) +{ + obj.emplace(std::forward(key), std::forward(value)); +} + +template = 0> +void from_json_object_impl(const BasicJsonType& j, ConstructibleObjectType& ret) +{ + const auto* inner_object = j.template get_ptr(); + reserve_if_possible(ret, inner_object->size(), priority_tag<1> {}); + for (const auto& p : *inner_object) + { + from_json_object_emplace(ret, p.first, p.second.template get(), priority_tag<1> {}); + } +} + +template::value, int> = 0> +void from_json_object_impl(const View& v, ConstructibleObjectType& ret) +{ + using string_t = typename View::basic_json_type::object_t::key_type; + reserve_if_possible(ret, v.size(), priority_tag<1> {}); + for (const auto item : v.items()) + { + const auto key = item.key(); + from_json_object_assign(ret, string_t(key.data(), key.size()), item.value().template get(), priority_tag<1> {}); + } +} + template::value, int> = 0> + enable_if_t, ConstructibleObjectType>::value, int> = 0> inline void from_json(const BasicJsonType& j, ConstructibleObjectType& obj) { if (JSON_HEDLEY_UNLIKELY(!j.is_object())) @@ -6486,12 +6717,7 @@ inline void from_json(const BasicJsonType& j, ConstructibleObjectType& obj) } ConstructibleObjectType ret; - const auto* inner_object = j.template get_ptr(); - reserve_if_possible(ret, inner_object->size(), priority_tag<1> {}); - for (const auto& p : *inner_object) - { - from_json_object_emplace(ret, p.first, p.second.template get(), priority_tag<1> {}); - } + from_json_object_impl(j, ret); obj = std::move(ret); } @@ -6500,10 +6726,10 @@ inline void from_json(const BasicJsonType& j, ConstructibleObjectType& obj) template < typename BasicJsonType, typename ArithmeticType, enable_if_t < std::is_arithmetic::value&& - !std::is_same::value&& - !std::is_same::value&& - !std::is_same::value&& - !std::is_same::value, + !std::is_same::number_unsigned_t>::value&& + !std::is_same::number_integer_t>::value&& + !std::is_same::number_float_t>::value&& + !std::is_same::boolean_t>::value, int > = 0 > inline void from_json(const BasicJsonType& j, ArithmeticType& val) { @@ -6517,7 +6743,7 @@ inline void from_json(const BasicJsonType& j, ArithmeticType& val) { throw_type_must_be("number", j); } - val = static_cast(*j.template get_ptr()); + val = static_cast(boolean_value(j)); } template @@ -6527,7 +6753,7 @@ detail::uncvref_t from_json_tuple_get_impl(BasicJsonType&& j, detail::iden } template::value, int> = 0> + detail::enable_if_t>, is_compatible_reference_type>::value, int> = 0> Type from_json_tuple_get_impl(BasicJsonType && j, detail::identity_tag /*unused*/, detail::priority_tag<1> /*unused*/) { return std::forward(j).template get_ref(); @@ -6571,7 +6797,7 @@ inline void from_json_tuple_impl(const BasicJsonType& j, std::pair& p, p template std::tuple from_json_tuple_impl(const BasicJsonType& j, identity_tag> /*unused*/, priority_tag<2> /*unused*/) { - static_assert(conjunction>, is_compatible_reference_type>...>::value, + static_assert(conjunction>, conjunction, is_compatible_reference_type>>...>::value, "Can not return a tuple containing references to types not contained in a Json, try Json::get_to()"); return from_json_tuple_impl_base<1, Args...>(j, index_sequence_for {}); } @@ -6617,7 +6843,7 @@ void from_json_pair_array_to_map(const BasicJsonType& j, MapType& m) // read a map with enum keys from an object, using the enum's own from_json for // the keys (e.g., from NLOHMANN_JSON_SERIALIZE_ENUM); this is the form written // with JSON_USE_OBJECTS_FOR_ENUM_KEYED_MAPS -template +template = 0> inline bool from_json_enum_keyed_object(const BasicJsonType& j, Map& m, std::true_type /*key is enum*/) { if (!j.is_object()) @@ -6632,6 +6858,24 @@ inline bool from_json_enum_keyed_object(const BasicJsonType& j, Map& m, std::tru return true; } +template::value, int> = 0> +inline bool from_json_enum_keyed_object(const View& v, Map& m, std::true_type /*key is enum*/) +{ + using json_t = typename View::basic_json_type; + if (!v.is_object()) + { + return false; + } + m.clear(); + for (const auto item : v.items()) + { + const auto key = item.key(); + from_json_object_assign(m, json_t(typename json_t::string_t(key.data(), key.size())).template get(), + item.value().template get(), priority_tag<1> {}); + } + return true; +} + template inline bool from_json_enum_keyed_object(const BasicJsonType& /*j*/, Map& /*m*/, std::false_type /*key is enum*/) { @@ -6640,7 +6884,7 @@ inline bool from_json_enum_keyed_object(const BasicJsonType& /*j*/, Map& /*m*/, template < typename BasicJsonType, typename Key, typename Value, typename Compare, typename Allocator, typename = enable_if_t < !std::is_constructible < - typename BasicJsonType::string_t, Key >::value >> + typename json_type_of_t::string_t, Key >::value >> void from_json(const BasicJsonType& j, std::map& m) { // NOLINTNEXTLINE(modernize-type-traits) we use C++11 @@ -6653,7 +6897,7 @@ void from_json(const BasicJsonType& j, std::map& template < typename BasicJsonType, typename Key, typename Value, typename Hash, typename KeyEqual, typename Allocator, typename = enable_if_t < !std::is_constructible < - typename BasicJsonType::string_t, Key >::value >> + typename json_type_of_t::string_t, Key >::value >> void from_json(const BasicJsonType& j, std::unordered_map& m) { // NOLINTNEXTLINE(modernize-type-traits) we use C++11 @@ -6677,7 +6921,7 @@ inline void from_json(const BasicJsonType& j, std_fs::path& p) { throw_type_must_be("string", j); } - const auto& s = *j.template get_ptr(); + const auto& s = string_value(j); // Checking for C++20 standard or later can be insufficient in case the // library support for char8_t is either incomplete or was disabled // altogether. Use the __cpp_lib_char8_t feature test instead. @@ -6689,16 +6933,99 @@ inline void from_json(const BasicJsonType& j, std_fs::path& p) } #endif -struct from_json_fn +namespace from_json_adl { - template - auto operator()(const BasicJsonType& j, T&& val) const +// hides the library's from_json() overloads above (and the from_json object): +// a call from this namespace finds only the from_json() functions of the +// arguments' own namespaces and classes (ADL); the declaration is never viable +// and never defined (not "= delete", which GCC before 6 reports as a use) +struct hidden; +void from_json(const hidden&); + +// a type no from_json() written for JSON values accepts: a from_json() that +// accepts it is not constrained to them (see has_view_from_json; it lives +// here, so that ADL finds no other from_json() for it) +struct probe +{}; + +struct fn +{ + template + auto operator()(const SourceType& j, T&& val) const noexcept(noexcept(from_json(j, std::forward(val)))) -> decltype(from_json(j, std::forward(val))) { return from_json(j, std::forward(val)); } }; +} // namespace from_json_adl + +// the sources whose from_json() is only looked up by ADL: the probe +template +struct is_adl_only_from_json_source : std::is_same {}; + +struct from_json_fn +{ + template>::value, int> = 0> + auto operator()(const BasicJsonType& j, T && val) const + noexcept(noexcept(from_json(j, std::forward(val)))) + -> decltype(from_json(j, std::forward(val))) + { + return from_json(j, std::forward(val)); + } + + template::value, int> = 0> + auto operator()(const SourceType& j, T && val) const + noexcept(noexcept(from_json_adl::fn {}(j, std::forward(val)))) + -> decltype(from_json_adl::fn {}(j, std::forward(val))) + { + return from_json_adl::fn {}(j, std::forward(val)); + } +}; + +// Whether JSONSerializer::from_json(view, T&) reads T from a view (of +// ) directly: it must accept the view but not the +// probe. An unconstrained from_json() template (written for basic_json, but +// accepting anything) is thus not called with a view, nor are the library's +// own conversions; the view converts such types through materialize(). +template +using serializer_from_json_t = decltype(Serializer::from_json(std::declval(), std::declval())); + +template +struct has_view_from_json + : conjunction < is_detected, + negation>> {}; + +// the base part of a type of the NLOHMANN_DEFINE_DERIVED_TYPE_* macros: from a +// view through its get_to(), which reads the base from the view where the +// base's from_json() accepts views, and from materialize() otherwise +template::value, int> = 0> +void from_json_base(const BasicJsonType& j, T& base) +{ + from_json_fn {}(j, base); +} + +template::value, int> = 0> +void from_json_base(const View& v, T& base) +{ + v.get_to(base); +} + +// a value as an exception message shows it (invalid UTF-8 replaced; the +// strings of a view are always valid UTF-8) +template::value, int> = 0> +typename BasicJsonType::string_t from_json_dump(const BasicJsonType& j) +{ + return j.dump(-1, ' ', false, error_handler_t::replace); +} + +template::value, int> = 0> +typename View::string_t from_json_dump(const View& v) +{ + return v.dump(); +} } // namespace detail @@ -6793,57 +7120,6 @@ NLOHMANN_JSON_NAMESPACE_END // #include // #include -// __ _____ _____ _____ -// __| | __| | | | JSON for Modern C++ -// | | |__ | | | | | | version 3.12.0 -// |_____|_____|_____|_|___| https://github.com/nlohmann/json -// -// SPDX-FileCopyrightText: 2013-2026 Niels Lohmann -// SPDX-License-Identifier: MIT - - - -// #include - - -NLOHMANN_JSON_NAMESPACE_BEGIN -namespace detail -{ - -/// how to treat decoding errors -/// -/// @ref basic_json::dump uses this to decide what to do with ill-formed -/// UTF-8 while escaping a string, and the binary writers (@ref -/// basic_json::to_cbor, @ref basic_json::to_ubjson, @ref -/// basic_json::to_bjdata, @ref basic_json::to_bson) use it the same way for -/// string values and object keys. The binary readers (@ref -/// basic_json::from_cbor, @ref basic_json::from_msgpack, @ref -/// basic_json::from_ubjson, @ref basic_json::from_bjdata, @ref -/// basic_json::from_bson) use it to decide whether to check text strings -/// and object keys for well-formed UTF-8 at all, since none of those -/// formats requires a decoder to do so. -enum class error_handler_t -{ - strict, ///< throw a type_error/parse_error exception in case of invalid UTF-8 - replace, ///< replace invalid UTF-8 sequences with U+FFFD - ignore, ///< ignore invalid UTF-8 sequences - keep ///< keep invalid UTF-8 sequences unchanged -}; - -/// the default error handler of the CBOR, UBJSON, BJData, and BSON writers: -/// error_handler_t::strict if JSON_STRICT_BINARY_UTF8 is enabled, otherwise -/// error_handler_t::keep (the behavior before version 3.13.0) -constexpr error_handler_t binary_writer_default_error_handler() noexcept -{ -#if JSON_STRICT_BINARY_UTF8 - return error_handler_t::strict; -#else - return error_handler_t::keep; -#endif -} - -} // namespace detail -NLOHMANN_JSON_NAMESPACE_END NLOHMANN_JSON_NAMESPACE_BEGIN @@ -17702,6 +17978,9 @@ class binary_reader /// UBJSON/BJData: the type marker of an optimized container, so that /// its elements are read without one of their own; 0 otherwise char_int_type type_marker; + /// BSON: the position right after this document, according to the + /// size it declares + std::size_t end_position = 0; /// BSON: the size this document declares, in bytes std::int32_t declared_size = 0; /// whether to close this container with end_object() or end_array() @@ -17838,6 +18117,23 @@ class binary_reader return false; } + // the smallest document is the size prefix and the terminator + if (JSON_HEDLEY_UNLIKELY(document_size < 5)) + { + return bson_document_size_error("document size ", document_size, " is less than 5"); + } + + // the declared size bounds where the document ends: an embedded + // document must end before the terminator of the document that contains it + const auto unsigned_size = static_cast(document_size); + const std::size_t document_end = unsigned_size > (std::numeric_limits::max)() - document_start + ? (std::numeric_limits::max)() + : document_start + unsigned_size; + if (JSON_HEDLEY_UNLIKELY(!container_stack.empty() && document_end >= container_stack.back().end_position)) + { + return bson_document_size_error("document size ", document_size, " exceeds the size of the enclosing document"); + } + if (JSON_HEDLEY_UNLIKELY(!enter_container(is_object, detail::unknown_size()))) { return false; @@ -17845,6 +18141,7 @@ class binary_reader container_frame& frame = container_stack.back(); frame.start_position = document_start; + frame.end_position = document_end; frame.declared_size = document_size; return true; } @@ -17903,6 +18200,13 @@ class binary_reader return false; } + // an element needs at least its name's terminator, and the + // document its own terminator, within the declared size + if (JSON_HEDLEY_UNLIKELY(chars_read + 2 > container_stack.back().end_position)) + { + return bson_document_size_error("document size ", container_stack.back().declared_size, " is too small for its content"); + } + const std::size_t element_type_parse_position = chars_read; key.clear(); if (JSON_HEDLEY_UNLIKELY(!get_bson_cstr(key))) @@ -18051,9 +18355,46 @@ class binary_reader } result.set_subtype(subtype); + // the "old binary" subtype 0x02 starts with an int32 that repeats the + // size of the payload following it; the payload does not include it + if (subtype == 0x02) + { + return get_bson_old_binary(len, result); + } + return get_binary(len, result); } + /*! + @brief Parses the payload of a BSON binary value with subtype 0x02. + @param[in] len The length of the binary value, including the inner int32. + @param[in,out] result A reference to the binary variable where the payload + (without the inner int32) is to be stored. + @pre len >= 0 + @return `true` if the inner length was present and matches @a len + */ + template + bool get_bson_old_binary(const NumberType len, binary_t& result) + { + if (JSON_HEDLEY_UNLIKELY(len < 4)) + { + return bson_length_error("length of binary subtype 0x02 must be at least 4, is ", len, "binary"); + } + + std::int32_t inner_length{}; + if (JSON_HEDLEY_UNLIKELY((!get_number(inner_length)))) + { + return false; + } + + if (JSON_HEDLEY_UNLIKELY(static_cast(inner_length) != static_cast(len) - 4)) + { + return bson_length_error("inner length of binary subtype 0x02 does not match the payload size; inner length is ", inner_length, "binary"); + } + + return get_binary(inner_length, result); + } + /*! @brief Read a BSON document element of the given @a element_type. @param[in] element_type The BSON element type, c.f. http://bsonspec.org/spec.html @@ -18685,6 +19026,10 @@ class binary_reader // level as required by RFC 8949, Section 3.2.3 bool indefinite = false; + // the chunk currently being validated; only used for the chunks of an + // indefinite-length string when the UTF-8 check can do something + string_t chunk; + while (true) { if (JSON_HEDLEY_UNLIKELY(!unexpect_eof("string"))) @@ -18704,26 +19049,82 @@ class binary_reader } // a break marker closes the indefinite-length string; outside - // of one it falls through to the error below + // of one it falls through to the error below. Every chunk has + // been checked on its own, so there is nothing left to check. if (indefinite && current == 0xFF) { - return check_string_utf8(result, context); - } - - if (JSON_HEDLEY_UNLIKELY(!get_cbor_string_chunk(result, indefinite))) - { - return false; + return true; } if (!indefinite) { - return check_string_utf8(result, context); + return get_cbor_string_chunk(result, false) && check_string_utf8(result, context); + } + + if (error_handler == error_handler_t::keep) + { + // nothing to check: read the chunk directly into the result + if (JSON_HEDLEY_UNLIKELY(!get_cbor_string_chunk(result, true))) + { + return false; + } + } + else + { + // RFC 8949, Section 3.2.3: each chunk is a text string of its + // own and must be valid UTF-8 by itself, i.e., a code point + // cannot be spread over two chunks + chunk.clear(); + if (JSON_HEDLEY_UNLIKELY(!get_cbor_string_chunk(chunk, true) || !check_string_utf8(chunk, context))) + { + return false; + } + result.append(chunk.data(), chunk.size()); } get(); } } + /*! + @brief skips the tags in front of a CBOR object key + + A tag may wrap any data item, so it may also wrap a key. Like in front of a + value, the tags are skipped for the handlers ignore and store; with the + handler error they are left to @ref get_cbor_object_key, which rejects the + key. The tag loop is iterative, so the number of tags is not limited by the + recursion depth. + + @param[in] tag_handler how CBOR tags should be treated + + @return whether the tags could be skipped (false on EOF in a tag argument) + + @pre @a current is the first byte of the key + */ + bool skip_cbor_key_tags(const cbor_tag_handler_t tag_handler) + { + if (tag_handler == cbor_tag_handler_t::error) + { + return true; + } + + // 0xC0..0xDB: tag with the tag number in the head (0xC0..0xD7) or in + // the 1, 2, 4, or 8 bytes that follow (0xD8..0xDB) + while (current >= 0xC0 && current <= 0xDB) + { + if (current >= 0xD8) + { + std::uint64_t tag_to_ignore{}; + if (JSON_HEDLEY_UNLIKELY(!get_cbor_argument(tag_to_ignore))) + { + return false; + } + } + get(); + } + return true; + } + /*! @brief reads a CBOR object key @@ -19090,6 +19491,10 @@ class binary_reader if (top.is_object) { + if (JSON_HEDLEY_UNLIKELY(!skip_cbor_key_tags(tag_handler))) + { + return false; + } if (JSON_HEDLEY_UNLIKELY(!read_object_key<&binary_reader::get_cbor_object_key>(key))) { return false; @@ -20403,6 +20808,14 @@ class binary_reader return false; } + // validate the type marker up front: otherwise it would only be + // checked when an element is read, and a container with count 0 + // would accept any marker + if (JSON_HEDLEY_UNLIKELY(!is_valid_optimized_type(result.second))) + { + return optimized_type_error(); + } + get_ignore_noop(); if (JSON_HEDLEY_UNLIKELY(current != '#')) { @@ -20607,6 +21020,8 @@ class binary_reader // get_ubjson_size_value() (the type marker is known before the dimension vector // that determines size_and_type.first is read, so it is emitted first there to // match the documented _ArrayType_, _ArraySize_, _ArrayData_ key order) + // 'C' elements are read as unsigned bytes, but BJData limits a char to 0..127 + const bool nd_is_char = size_and_type.second == 'C'; if (size_and_type.second == 'C' || size_and_type.second == 'B') { size_and_type.second = 'U'; @@ -20620,6 +21035,24 @@ class binary_reader for (std::size_t i = 0; i < size_and_type.first; ++i) { + if (nd_is_char) + { + get(); + if (JSON_HEDLEY_UNLIKELY(!unexpect_eof("char"))) + { + return false; + } + if (JSON_HEDLEY_UNLIKELY(current > 127)) + { + return last_byte_error(exception_id::invalid_string_or_size, "byte after 'C' must be in range 0x00..0x7F", "char", ""); + } + if (JSON_HEDLEY_UNLIKELY(!sax->number_unsigned(static_cast(current)))) + { + return false; + } + continue; + } + if (JSON_HEDLEY_UNLIKELY(!get_ubjson_value(size_and_type.second))) { return false; @@ -21597,9 +22030,9 @@ class binary_reader // sequence), UBJSON, BJData, or BSON requires a decoder to reject // ill-formed UTF-8. Checking (and, with @ref error_handler_t::strict, // rejecting, or with `replace`/`ignore`, sanitizing) is opt-in via - // @ref error_handler, applied once the whole string (all chunks of - // an indefinite-length CBOR string included) has been assembled, by - // @ref check_string_utf8 at the call site. + // @ref error_handler, applied by @ref check_string_utf8 at the call + // site to the whole string or, for an indefinite-length CBOR string, + // to each chunk on its own (RFC 8949, Section 3.2.3). return get_bytes(len, "string", result); } @@ -21794,6 +22227,12 @@ class binary_reader return last_byte_error(exception_id::unexpected_byte, concat("document size ", std::to_string(document_size), " does not match the number of bytes read (", std::to_string(bytes_read), ")"), "document"); } + JSON_HEDLEY_NEVER_INLINE + bool bson_document_size_error(const char* prefix, const std::int32_t document_size, const char* suffix) const + { + return last_byte_error(exception_id::unexpected_byte, concat(prefix, std::to_string(document_size), suffix), "document"); + } + JSON_HEDLEY_NEVER_INLINE bool bson_unsupported_type_error(const std::size_t position, const char_int_type element_type) const { @@ -21899,6 +22338,44 @@ class binary_reader std::size_t bon8_pushback_size = 0; JSON_PRIVATE_UNLESS_TESTED: + /*! + @brief check whether @a marker is a type marker that may follow '$' + UBJSON: Z N T F i U I l L d D H C S [ {; BJData: the numeric markers and C + (BJData's excluded markers are rejected separately). + */ + bool is_valid_optimized_type(const char_int_type marker) const noexcept + { + switch (marker) + { + case 'i': + case 'U': + case 'I': + case 'l': + case 'L': + case 'd': + case 'D': + case 'C': + return true; + case 'Z': + case 'N': + case 'T': + case 'F': + case 'H': + case 'S': + case '[': + case '{': + return input_format != input_format_t::bjdata; + case 'B': + case 'u': + case 'm': + case 'M': + case 'h': + return input_format == input_format_t::bjdata; + default: + return false; + } + } + /*! @brief whether @a marker is excluded from BJData's optimized ND-array types @return whether @a marker is one of 'F', 'H', 'N', 'S', 'T', 'Z', '[', '{' @@ -21941,6 +22418,8 @@ class binary_reader return "uint8"; case 'd': return "single"; + case 'h': + return "half"; case 'i': return "int8"; case 'l': @@ -24576,15 +25055,19 @@ class json_pointer // convert null values to arrays or objects before continuing if (ptr->is_null()) { - // check if the reference token is a number - const bool nums = - std::all_of(reference_token.begin(), reference_token.end(), - [](const unsigned char x) + // check if the reference token is a valid array index, that is + // a nonempty sequence of digits without a leading '0' + // (cf. RFC 6901, Sect. 4); tokens that could never be a valid + // array index (such as "01" or "") are treated as object keys + const bool nums = !reference_token.empty() + && (reference_token.size() == 1 || reference_token[0] != '0') + && std::all_of(reference_token.begin(), reference_token.end(), + [](const unsigned char x) { return std::isdigit(x); }); - // change value to an array for numbers or "-" or to object otherwise + // change value to an array for array indices or "-" or to object otherwise *ptr = (nums || reference_token == "-") ? detail::value_t::array : detail::value_t::object; @@ -27938,7 +28421,15 @@ class binary_writer throw_subtype_out_of_range(value.subtype(), "the BSON binary subtype", j); } - return sizeof(std::int32_t) + value.size() + 1ul; + // the "old binary" subtype 0x02 repeats the payload size in an inner + // int32 in front of the payload + return sizeof(std::int32_t) + (is_bson_old_binary(value) ? sizeof(std::int32_t) : 0ul) + value.size() + 1ul; + } + + /// @return whether @a value has the BSON subtype 0x02 ("binary (old)") + static bool is_bson_old_binary(const binary_t& value) noexcept + { + return value.has_subtype() && value.subtype() == 0x02; } /*! @@ -27951,10 +28442,18 @@ class binary_writer { write_bson_entry_header(name, 0x05); - write_number(to_bson_length(value.size()), true); + const bool old_binary = is_bson_old_binary(value); + + // the length field counts the inner int32 of the old binary subtype + write_number(to_bson_length(value.size() + (old_binary ? sizeof(std::int32_t) : 0ul)), true); write_number(value.has_subtype() ? static_cast(value.subtype()) : static_cast(0x00)); + if (old_binary) + { + write_number(to_bson_length(value.size()), true); + } + write_bytes(value.data(), value.size()); } @@ -28114,7 +28613,8 @@ class binary_writer (or the header of a string or binary value) in one reservation @return whether the entry was written; false for a discarded value, a string or binary value whose length does not fit the length - field, in which case nothing was written + field, or a binary value with subtype 0x02, in which case nothing + was written @pre @a name is at most bson_scalar_max_name_size characters long and is not changed by the error handler */ @@ -28175,7 +28675,9 @@ class binary_writer if (j.is_binary()) { const binary_t& value = *j.m_data.m_value.binary; - if (JSON_HEDLEY_UNLIKELY(!value_in_range_of(value.size()))) + // the old binary subtype 0x02 has an inner length that the single + // reservation has no room for; write_bson_binary writes it + if (JSON_HEDLEY_UNLIKELY(!value_in_range_of(value.size()) || is_bson_old_binary(value))) { return false; } @@ -30083,7 +30585,7 @@ class binary_writer A C++11 `constexpr` function cannot contain a `switch`, so this is a plain comparison chain instead; it is only reached once per ND-array candidate object. Keep in sync with binary_reader's `bjd_type_name()`, which maps - the other way. + the other way (it also reads "half", which is never written). */ static CharType bjdata_ndarray_type_marker(const string_t& name) { @@ -30291,10 +30793,13 @@ class binary_writer switch (dtype) { case 'U': - case 'C': case 'B': ok = write_bjdata_ndarray_element(el, dry_run); break; + case 'C': + // BJData limits a char to 0..127 (the intersection of the int8 and uint8 ranges) + ok = bjdata_ndarray_value_in_range(el) && bjdata_ndarray_value_in_range(el); + break; case 'i': ok = write_bjdata_ndarray_element(el, dry_run); break; @@ -42809,7 +43314,6 @@ public: switch (parent.m_data.m_type) { - case value_t::null: case value_t::object: { // use operator[] to add value @@ -42841,6 +43345,7 @@ public: // the parent of an "add" target must be an object or array // (see #4292) + case value_t::null: case value_t::string: case value_t::boolean: case value_t::number_integer: diff --git a/single_include/nlohmann/json_fwd.hpp b/single_include/nlohmann/json_fwd.hpp index cd64b76a6..ce51d81bd 100644 --- a/single_include/nlohmann/json_fwd.hpp +++ b/single_include/nlohmann/json_fwd.hpp @@ -145,26 +145,14 @@ #define NLOHMANN_JSON_ABI_TAG_NO_SIMD #endif -#ifdef JSON_VIEW_NO_SIMD - #define NLOHMANN_JSON_ABI_TAG_VIEW_NO_SIMD _vns -#else - #define NLOHMANN_JSON_ABI_TAG_VIEW_NO_SIMD -#endif - -#ifdef JSON_VIEW_USE_SSSE3 - #define NLOHMANN_JSON_ABI_TAG_VIEW_USE_SSSE3 _vssse3 -#else - #define NLOHMANN_JSON_ABI_TAG_VIEW_USE_SSSE3 -#endif - #ifndef NLOHMANN_JSON_NAMESPACE_NO_VERSION #define NLOHMANN_JSON_NAMESPACE_NO_VERSION 0 #endif // Construct the namespace ABI tags component -#define NLOHMANN_JSON_ABI_TAGS_CONCAT_EX(a, b, c, d, e, f, g, h, i, j, k, l, m) json_abi ## a ## b ## c ## d ## e ## f ## g ## h ## i ## j ## k ## l ## m -#define NLOHMANN_JSON_ABI_TAGS_CONCAT(a, b, c, d, e, f, g, h, i, j, k, l, m) \ - NLOHMANN_JSON_ABI_TAGS_CONCAT_EX(a, b, c, d, e, f, g, h, i, j, k, l, m) +#define NLOHMANN_JSON_ABI_TAGS_CONCAT_EX(a, b, c, d, e, f, g, h, i, j, k) json_abi ## a ## b ## c ## d ## e ## f ## g ## h ## i ## j ## k +#define NLOHMANN_JSON_ABI_TAGS_CONCAT(a, b, c, d, e, f, g, h, i, j, k) \ + NLOHMANN_JSON_ABI_TAGS_CONCAT_EX(a, b, c, d, e, f, g, h, i, j, k) #define NLOHMANN_JSON_ABI_TAGS \ NLOHMANN_JSON_ABI_TAGS_CONCAT( \ @@ -178,9 +166,7 @@ NLOHMANN_JSON_ABI_TAG_OBJECTS_FOR_ENUM_KEYED_MAPS, \ NLOHMANN_JSON_ABI_TAG_DISABLE_STREAM_BULK_SCAN, \ NLOHMANN_JSON_ABI_TAG_DISABLE_UNLOCKED_FILE_IO, \ - NLOHMANN_JSON_ABI_TAG_NO_SIMD, \ - NLOHMANN_JSON_ABI_TAG_VIEW_NO_SIMD, \ - NLOHMANN_JSON_ABI_TAG_VIEW_USE_SSSE3) + NLOHMANN_JSON_ABI_TAG_NO_SIMD) // Construct the namespace version component #define NLOHMANN_JSON_NAMESPACE_VERSION_CONCAT_EX(major, minor, patch) \ diff --git a/single_include/nlohmann/json_view.hpp b/single_include/nlohmann/json_view.hpp index 338b55492..e611ff540 100644 --- a/single_include/nlohmann/json_view.hpp +++ b/single_include/nlohmann/json_view.hpp @@ -239,9 +239,22 @@ struct node_flags static constexpr std::uint8_t linked = 32; ///< an entry of a moved sequence links to this value (editable documents): its extent in the parsed layout no longer matters }; -/// kind of an entry of an edited sequence that stands for a value stored -/// elsewhere (the address of the value's node is kept in the len/next bytes) -constexpr std::uint8_t kind_link = 10; +/// the kind of a node: the value_t of its value, or link +enum class node_kind : std::uint8_t +{ + null = static_cast(value_t::null), + object = static_cast(value_t::object), + array = static_cast(value_t::array), + string = static_cast(value_t::string), // NOLINT(build/include_what_you_use): an enumerator, not std::string + boolean = static_cast(value_t::boolean), + number_integer = static_cast(value_t::number_integer), + number_unsigned = static_cast(value_t::number_unsigned), + number_float = static_cast(value_t::number_float), + /// an entry of an edited sequence that stands for a value stored elsewhere + /// (the address of the value's node is kept in the len/next bytes); placed + /// after value_t::discarded, which no node ever has + link = static_cast(value_t::discarded) + 1, +}; /// One entry of the flat index, in document order. An object's members are /// stored as key node followed by the value's subtree. Integers keep their @@ -249,7 +262,7 @@ constexpr std::uint8_t kind_link = 10; /// always the next one, and the token length follows from `extra`). struct node { - std::uint8_t kind; ///< value_t, or kind_link + node_kind kind; ///< the kind of the value, or link std::uint8_t flags; ///< node_flags std::uint16_t extra; ///< numbers: integer digits (low byte) and fraction digits (high byte), 255 = "many"; objects: number of the object's key hash index (1-based, 0 = none); otherwise 0 std::uint32_t off; ///< source offset (string content, number token, literal, bracket); arena offset if escaped/edited; number of the element sequence if moved @@ -258,13 +271,13 @@ struct node }; static_assert(sizeof(node) == 16, "node must stay 16 bytes"); -// the node kinds are value_t values; the test below (object and array are 1 -// and 2) and those of the number kinds depend on this numbering -static_assert(static_cast(value_t::null) == 0 && static_cast(value_t::object) == 1 - && static_cast(value_t::array) == 2 && static_cast(value_t::string) == 3 - && static_cast(value_t::boolean) == 4 && static_cast(value_t::number_integer) == 5 - && static_cast(value_t::number_unsigned) == 6 && static_cast(value_t::number_float) == 7, - "the node format depends on the numbering of value_t"); +// images store the node kinds as bytes, and the test below (object and array +// are 1 and 2) and those of the number kinds depend on this numbering +static_assert(static_cast(node_kind::null) == 0 && static_cast(node_kind::object) == 1 + && static_cast(node_kind::array) == 2 && static_cast(node_kind::string) == 3 + && static_cast(node_kind::boolean) == 4 && static_cast(node_kind::number_integer) == 5 + && static_cast(node_kind::number_unsigned) == 6 && static_cast(node_kind::number_float) == 7, + "the node format depends on the numbering of node_kind"); NLOHMANN_VIEW_ALWAYS_INLINE bool is_container(const node& n) noexcept { @@ -284,7 +297,7 @@ inline void make_link(node& n, node* target) noexcept { target->flags = static_cast(target->flags | node_flags::linked); n = node{}; - n.kind = kind_link; + n.kind = node_kind::link; std::memcpy(reinterpret_cast(&n) + 8, static_cast(&target), sizeof(const node*)); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast) } @@ -314,8 +327,8 @@ NLOHMANN_VIEW_ALWAYS_INLINE void set_integer_bits(node& n, std::uint64_t v) noex /// token length of a number node NLOHMANN_VIEW_ALWAYS_INLINE std::uint32_t number_length(const node& n) noexcept { - return n.kind == static_cast(value_t::number_float) ? n.len - : (n.extra & 0xFFu) + (n.kind == static_cast(value_t::number_integer) ? 1u : 0u); + return n.kind == node_kind::number_float ? n.len + : (n.extra & 0xFFu) + (n.kind == node_kind::number_integer ? 1u : 0u); } /// estimated number of nodes for an input of `size` bytes (one node per ~12 @@ -547,7 +560,7 @@ struct document_data /// following loads. static NLOHMANN_VIEW_ALWAYS_INLINE const node* deref(const node* n) noexcept { - return NLOHMANN_VIEW_LIKELY(n->kind != kind_link) ? n : follow_link(n); + return NLOHMANN_VIEW_LIKELY(n->kind != node_kind::link) ? n : follow_link(n); } static NLOHMANN_VIEW_NOINLINE const node* follow_link(const node* n) noexcept @@ -665,25 +678,25 @@ NLOHMANN_JSON_NAMESPACE_END // with different flags would have different definitions of the same inline // functions): the check is compiled for SSSE3 with a function attribute and // used where the CPU has SSSE3 (all x86-64 CPUs since about 2011), else the -// portable check. JSON_VIEW_USE_SSSE3 skips the CPU check (for code compiled -// for SSSE3 anyway); JSON_VIEW_NO_SIMD selects the portable code. +// portable check. Code compiled for SSSE3 anyway (-mssse3 or more) skips the +// CPU check. JSON_NO_SIMD selects the portable code, as in the lexer. // NLOHMANN_VIEW_NO_TARGET_ATTRIBUTE (set by the GCC module interface, where GCC // ignores the target attribute and then rejects the SSSE3 intrinsics) keeps the // check portable without disabling the SSE2 code. -#if !defined(JSON_VIEW_NO_SIMD) && defined(__aarch64__) && (defined(__GNUC__) || defined(__clang__)) && NLOHMANN_VIEW_LITTLE_ENDIAN +#if !defined(JSON_NO_SIMD) && defined(__aarch64__) && (defined(__GNUC__) || defined(__clang__)) && NLOHMANN_VIEW_LITTLE_ENDIAN #include #define NLOHMANN_VIEW_NEON 1 #else #define NLOHMANN_VIEW_NEON 0 #endif // (x86 only: other targets can define __SSE2__ as well, e.g., WebAssembly with -msse2, but have no ) -#if !defined(JSON_VIEW_NO_SIMD) && !NLOHMANN_VIEW_NEON && (defined(__x86_64__) || defined(__i386__) || defined(_M_X64) || defined(_M_IX86)) && (defined(__SSE2__) || defined(_M_X64) || (defined(_M_IX86_FP) && _M_IX86_FP >= 2)) +#if !defined(JSON_NO_SIMD) && !NLOHMANN_VIEW_NEON && (defined(__x86_64__) || defined(__i386__) || defined(_M_X64) || defined(_M_IX86)) && (defined(__SSE2__) || defined(_M_X64) || (defined(_M_IX86_FP) && _M_IX86_FP >= 2)) #include #define NLOHMANN_VIEW_SSE2 1 #else #define NLOHMANN_VIEW_SSE2 0 #endif -#if NLOHMANN_VIEW_SSE2 && defined(JSON_VIEW_USE_SSSE3) +#if NLOHMANN_VIEW_SSE2 && defined(__SSSE3__) #include #define NLOHMANN_VIEW_SSSE3 1 // NOLINT(cppcoreguidelines-macro-to-enum,modernize-macro-to-enum) #else @@ -1830,11 +1843,11 @@ run_found: switch (cur()) { case '{': - open(value_t::object); + open(node_kind::object); ++p; goto obj_first; case '[': - open(value_t::array); + open(node_kind::array); ++p; goto arr_first; default: @@ -1856,11 +1869,11 @@ run_found: if (NLOHMANN_VIEW_UNLIKELY(!string())) { return false; } \ break; \ case '{': \ - open(value_t::object); \ + open(node_kind::object); \ ++p; \ goto obj_first; \ case '[': \ - open(value_t::array); \ + open(node_kind::array); \ ++p; \ goto arr_first; \ case '-': \ @@ -2077,17 +2090,17 @@ root_done: NLOHMANN_VIEW_ALWAYS_INLINE bool literal_true() { - return literal("true", value_t::boolean, node_flags::is_true); + return literal("true", node_kind::boolean, node_flags::is_true); } NLOHMANN_VIEW_ALWAYS_INLINE bool literal_false() { - return literal("false", value_t::boolean, 0); + return literal("false", node_kind::boolean, 0); } NLOHMANN_VIEW_ALWAYS_INLINE bool literal_null() { - return literal("null", value_t::null, 0); + return literal("null", node_kind::null, 0); } /// skip whitespace (and comments); false on a malformed comment @@ -2153,7 +2166,7 @@ indent_done: /// append a node: (kind, flags, extra, off) and the second word (len, or /// an integer's value); two stores on little-endian targets - NLOHMANN_VIEW_ALWAYS_INLINE node* push_node(value_t k, std::uint8_t flags, std::uint16_t extra, std::size_t off, std::uint64_t second) + NLOHMANN_VIEW_ALWAYS_INLINE node* push_node(node_kind k, std::uint8_t flags, std::uint16_t extra, std::size_t off, std::uint64_t second) { if (NLOHMANN_VIEW_UNLIKELY(out == cap)) { @@ -2169,7 +2182,7 @@ indent_done: std::memcpy(reinterpret_cast(n), &first, 8); std::memcpy(reinterpret_cast(n) + 8, &second, 8); #else - n->kind = static_cast(k); + n->kind = k; n->flags = flags; n->extra = extra; n->off = static_cast(off); @@ -2181,7 +2194,7 @@ indent_done: return n; } - NLOHMANN_VIEW_ALWAYS_INLINE void open(value_t k) + NLOHMANN_VIEW_ALWAYS_INLINE void open(node_kind k) { // (base is read after push_node(), which moves the node array when it // grows; in one expression the order of the two is unspecified) @@ -2208,7 +2221,7 @@ indent_done: max_depth = (std::max)(max_depth, depth); cur_idx = idx; cur_count = 0; - cur_is_object = k == value_t::object; + cur_is_object = k == node_kind::object; } NLOHMANN_VIEW_ALWAYS_INLINE void close() @@ -2238,7 +2251,7 @@ indent_done: /// the literal `text` at p (N counts the terminating NUL) template - NLOHMANN_VIEW_ALWAYS_INLINE bool literal(const char (&text)[N], value_t k, std::uint8_t flags) // NOLINT(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays): a string literal + NLOHMANN_VIEW_ALWAYS_INLINE bool literal(const char (&text)[N], node_kind k, std::uint8_t flags) // NOLINT(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays): a string literal { const std::size_t n = N - 1; if (NLOHMANN_VIEW_UNLIKELY(e - p < static_cast(n) || std::memcmp(p, text, n) != 0)) @@ -2317,23 +2330,23 @@ indent_done: is_float = true; } - value_t kind = value_t::number_float; + node_kind kind = node_kind::number_float; if (!is_float) { // integers that do not fit become floats, as in parse() if (negative) { - kind = digits_exceed(int_start, int_digits, "9223372036854775808") ? value_t::number_float : value_t::number_integer; + kind = digits_exceed(int_start, int_digits, "9223372036854775808") ? node_kind::number_float : node_kind::number_integer; } else { - kind = digits_exceed(int_start, int_digits, "18446744073709551615") ? value_t::number_float : value_t::number_unsigned; + kind = digits_exceed(int_start, int_digits, "18446744073709551615") ? node_kind::number_float : node_kind::number_unsigned; } } // parse() rejects floats that overflow; only numbers whose magnitude // could reach the largest FloatType (1e308 for double, 1e38 for // float) need the conversion - if (NLOHMANN_VIEW_UNLIKELY(static_cast(int_digits) + exponent > std::numeric_limits::max_exponent10 - 8 && kind == value_t::number_float)) + if (NLOHMANN_VIEW_UNLIKELY(static_cast(int_digits) + exponent > std::numeric_limits::max_exponent10 - 8 && kind == node_kind::number_float)) { if (builder::float_overflows(s, p)) { @@ -2343,7 +2356,7 @@ indent_done: } const auto layout = static_cast((int_digits < 255 ? int_digits : 255) | ((frac_digits < 255 ? frac_digits : 255) << 8)); auto second = static_cast(p - s); - if (kind != value_t::number_float) + if (kind != node_kind::number_float) { // integers are converted now, while their digits are in cache const std::uint64_t m = int_digits <= 19 ? parse_upto19(int_start, static_cast(int_digits), e) @@ -2366,7 +2379,7 @@ indent_done: p = scan_string_run(p, e); if (NLOHMANN_VIEW_LIKELY(cur() == '"')) { - push_node(value_t::string, 0, 0, static_cast(s - b), static_cast(p - s)); + push_node(node_kind::string, 0, 0, static_cast(s - b), static_cast(p - s)); ++p; return true; } @@ -2376,7 +2389,7 @@ indent_done: return false; } p = r.p; - push_node(value_t::string, node_flags::escaped, 0, r.start, r.len); + push_node(node_kind::string, node_flags::escaped, 0, r.start, r.len); return true; } }; @@ -3001,7 +3014,7 @@ NLOHMANN_JSON_NAMESPACE_END // live in chunks that never move; strings and number tokens written by edits // live in the edit arena. An array/object whose elements change gets // node_flags::moved: its elements then live in a separate sequence (a header -// node, then the entries), whose entries link to the values (kind_link). +// node, then the entries), whose entries link to the values (node_kind::link). NLOHMANN_JSON_NAMESPACE_BEGIN namespace detail @@ -3159,7 +3172,7 @@ inline node* block_of(document_data& d, node* n, std::size_t extra) return nh; } reserve_moved(d); // (so that set_moved() below cannot throw: the links are marked before) - const bool object = n->kind == static_cast(value_t::object); + const bool object = n->kind == node_kind::object; const std::size_t used = 1 + (static_cast(n->len) * (object ? 2 : 1)); const std::size_t cap = used + extra; node* const h = alloc_nodes(d, cap); @@ -3225,7 +3238,7 @@ inline node* find_parent(const document_data& d, const node* target) { return nullptr; // LCOV_EXCL_LINE (the value is inside c) } - const bool object = c->kind == static_cast(value_t::object); + const bool object = c->kind == node_kind::object; const node* down = nullptr; for (const node* p = d.first_child_edited(c), *e = d.child_end_edited(c); p != e;) { @@ -3692,7 +3705,7 @@ const node* element_at(const document_data& d, const node* array, std::size_t id template const node* last_child(const document_data& d, const node* container) noexcept { - const std::size_t value_offset = container->kind == static_cast(value_t::object) ? 1 : 0; + const std::size_t value_offset = container->kind == node_kind::object ? 1 : 0; const node* const end = navigation::end(d, container); const node* last = navigation::first(d, container); if (value_offset == 0 && container->next == container->len + 1u && (!navigation::editable() || (container->flags & node_flags::moved) == 0)) @@ -3714,10 +3727,32 @@ NLOHMANN_JSON_NAMESPACE_END // #include +// #include +// __ _____ _____ _____ +// __| | __| | | | JSON for Modern C++ +// | | |__ | | | | | | version 3.12.0 +// |_____|_____|_____|_|___| https://github.com/nlohmann/json +// +// SPDX-FileCopyrightText: 2013-2026 Niels Lohmann +// SPDX-License-Identifier: MIT + + + +#include // uint8_t +#include // false_type, true_type + +// #include NLOHMANN_JSON_NAMESPACE_BEGIN -template +/// whether a basic_json_document and its views can be edited +enum class view_mode : std::uint8_t +{ + read_only, ///< parsed once, then only read (the default) + editable, ///< also has set(), push_back(), insert(), and erase() +}; + +template class basic_json_view; namespace detail @@ -3725,6 +3760,35 @@ namespace detail namespace view { +/// whether two view modes differ +template +struct is_other_mode : std::true_type {}; + +template +struct is_other_mode : std::false_type {}; + +/// whether a view of mode From converts implicitly to one of mode To: a +/// read-only document has no edits, so a view of it is read correctly as a +/// view of an editable document that has not been edited yet +template +struct is_widening_mode : std::false_type {}; + +template<> +struct is_widening_mode : std::true_type {}; + +} // namespace view +} // namespace detail + +NLOHMANN_JSON_NAMESPACE_END + + +NLOHMANN_JSON_NAMESPACE_BEGIN + +namespace detail +{ +namespace view +{ + /// the index of the first true condition (the number of conditions if none is) template struct first_true : std::integral_constant {}; @@ -3808,15 +3872,15 @@ class editor View set(const View& object, string_view_t key, V&& value) { node* const o = own(object); - if (o->kind != static_cast(value_t::object) && o->kind != static_cast(value_t::null)) + if (o->kind != node_kind::object && o->kind != node_kind::null) { throw_type_error(exception_id::subscript_wrong_type, "cannot use operator[] with a string argument with ", object.type_name()); } check_utf8(key.data(), key.size()); const encoded e = encode(std::forward(value)); - if (o->kind == static_cast(value_t::null)) + if (o->kind == node_kind::null) { - become_empty(o, value_t::object); + become_empty(o, node_kind::object); } // an existing member: assign it (and drop later duplicates, so that // lookups, iteration, and materialize() agree) @@ -3859,7 +3923,7 @@ class editor View set(const View& array, std::size_t idx, V&& value) { node* const a = own(array); - if (a->kind != static_cast(value_t::array)) + if (a->kind != node_kind::array) { throw_type_error(exception_id::subscript_wrong_type, "cannot use operator[] with a numeric argument with ", array.type_name()); } @@ -3876,14 +3940,14 @@ class editor View push_back(const View& array, V&& value) { node* const a = own(array); - if (a->kind != static_cast(value_t::array) && a->kind != static_cast(value_t::null)) + if (a->kind != node_kind::array && a->kind != node_kind::null) { throw_type_error(exception_id::push_back_wrong_type, "cannot use push_back() with ", array.type_name()); } const encoded e = encode(std::forward(value)); - if (a->kind == static_cast(value_t::null)) + if (a->kind == node_kind::null) { - become_empty(a, value_t::array); + become_empty(a, node_kind::array); } node* const slot = new_slot(e); node* const h = block_of(m_doc, a, 1); @@ -3900,7 +3964,7 @@ class editor View insert(const View& array, std::size_t idx, V&& value) { node* const a = own(array); - if (a->kind != static_cast(value_t::array)) + if (a->kind != node_kind::array) { throw_type_error(exception_id::insert_wrong_type, "cannot use insert() with ", array.type_name()); } @@ -3920,7 +3984,7 @@ class editor std::size_t erase(const View& object, string_view_t key) { node* const o = own(object); - if (o->kind != static_cast(value_t::object)) + if (o->kind != node_kind::object) { throw_type_error(exception_id::erase_wrong_type, "cannot use erase() with ", object.type_name()); } @@ -3938,7 +4002,7 @@ class editor void erase(const View& array, std::size_t idx) { node* const a = own(array); - if (a->kind != static_cast(value_t::array)) + if (a->kind != node_kind::array) { throw_type_error(exception_id::erase_wrong_type, "cannot use erase() with ", array.type_name()); } @@ -4012,11 +4076,11 @@ class editor } /// turn a null into an empty array/object in place - static void become_empty(node* n, value_t k) noexcept + static void become_empty(node* n, node_kind k) noexcept { const std::uint8_t linked = n->flags & node_flags::linked; *n = node{}; - n->kind = static_cast(k); + n->kind = k; n->flags = static_cast(node_flags::is_new | linked); n->next = 1; } @@ -4093,8 +4157,8 @@ class editor template struct is_view : std::false_type {}; - template - struct is_view> : std::true_type {}; + template + struct is_view> : std::true_type {}; template encoded encode(V&& v) @@ -4109,8 +4173,8 @@ class editor } /// a view of any document (copied; nothing is shared with it) - template - encoded encode_impl(const basic_json_view& v, encode_tag<0> /*view*/) + template + encoded encode_impl(const basic_json_view& v, encode_tag<0> /*view*/) { if (NLOHMANN_VIEW_UNLIKELY(v.m_node == nullptr)) { @@ -4122,8 +4186,8 @@ class editor r.scalar = copy_scalar(*v.m_doc, *v.m_node); return r; } - r.region = alloc_nodes(m_doc, count_nodes(*v.m_doc, v.m_node)); - fill_nodes(*v.m_doc, v.m_node, r.region); + r.region = alloc_nodes(m_doc, count_nodes(*v.m_doc, v.m_node)); + fill_nodes(*v.m_doc, v.m_node, r.region); edit_state_of(m_doc).regions.emplace(r.region, nullptr); return r; } @@ -4145,14 +4209,14 @@ class editor encoded encode_impl(std::nullptr_t /*unused*/, encode_tag<2> /*null*/) { encoded r; - r.scalar = plain_node(value_t::null); + r.scalar = plain_node(node_kind::null); return r; } encoded encode_impl(bool b, encode_tag<3> /*boolean*/) { encoded r; - r.scalar = plain_node(value_t::boolean); + r.scalar = plain_node(node_kind::boolean); r.scalar.flags = static_cast(r.scalar.flags | (b ? node_flags::is_true : 0)); return r; } @@ -4181,10 +4245,10 @@ class editor return encode_impl(BasicJsonType(std::forward(x)), encode_tag<1> {}); } - static node plain_node(value_t k) noexcept + static node plain_node(node_kind k) noexcept { node n{}; - n.kind = static_cast(k); + n.kind = k; n.flags = node_flags::is_new; return n; } @@ -4198,19 +4262,19 @@ class editor template node number_node(T x, std::integral_constant /*signed*/) { - return integer_node(static_cast(static_cast(x)), value_t::number_integer); + return integer_node(static_cast(static_cast(x)), node_kind::number_integer); } template node number_node(T x, std::integral_constant /*unsigned*/) { - return integer_node(static_cast(x), value_t::number_unsigned); + return integer_node(static_cast(x), node_kind::number_unsigned); } /// an integer with its canonical token in the edit arena - node integer_node(std::uint64_t bits, value_t k) + node integer_node(std::uint64_t bits, node_kind k) { - const bool negative = k == value_t::number_integer && static_cast(bits) < 0; + const bool negative = k == node_kind::number_integer && static_cast(bits) < 0; std::uint64_t magnitude = negative ? 0 - bits : bits; std::array buf{}; char* p = buf.data() + buf.size(); // NOLINT(misc-const-correctness): digits are written through p @@ -4229,7 +4293,7 @@ class editor n.flags = static_cast(n.flags | node_flags::edited); n.off = append_text(m_doc, p, len); // number_length() adds one for the sign of number_integer nodes - n.extra = static_cast(k == value_t::number_integer ? len - 1 : len); + n.extra = static_cast(k == node_kind::number_integer ? len - 1 : len); set_integer_bits(n, bits); return n; } @@ -4251,7 +4315,7 @@ class editor { text = BasicJsonType(x).dump(); } - node n = plain_node(value_t::number_float); + node n = plain_node(node_kind::number_float); n.flags = static_cast(n.flags | node_flags::edited); n.extra = 0xFFFFu; // (the digit layout is not recorded) n.off = append_text(m_doc, text.data(), text.size()); @@ -4266,7 +4330,7 @@ class editor { throw_out_of_range(exception_id::document_too_large, "strings of 4 GiB or more are not supported by json_document"); // LCOV_EXCL_LINE } - node n = plain_node(value_t::string); + node n = plain_node(node_kind::string); n.flags = static_cast(n.flags | node_flags::edited); n.off = append_text(m_doc, s, len); n.len = static_cast(len); @@ -4288,10 +4352,10 @@ class editor return string_node(from.str(n), n.len); case value_t::number_integer: case value_t::number_unsigned: - return integer_node(integer_bits(n), static_cast(n.kind)); + return integer_node(integer_bits(n), n.kind); case value_t::number_float: { - node r = plain_node(value_t::number_float); + node r = plain_node(node_kind::number_float); r.flags = static_cast(r.flags | node_flags::edited); r.off = append_text(m_doc, from.str(n), n.len); r.len = n.len; @@ -4300,7 +4364,7 @@ class editor } case value_t::boolean: { - node r = plain_node(value_t::boolean); + node r = plain_node(node_kind::boolean); r.flags = static_cast(r.flags | (n.flags & node_flags::is_true)); return r; } @@ -4310,7 +4374,7 @@ class editor case value_t::binary: case value_t::discarded: default: - return plain_node(value_t::null); + return plain_node(node_kind::null); } } @@ -4319,17 +4383,17 @@ class editor switch (j.type()) { case value_t::null: - return plain_node(value_t::null); + return plain_node(node_kind::null); case value_t::boolean: { - node r = plain_node(value_t::boolean); + node r = plain_node(node_kind::boolean); r.flags = static_cast(r.flags | (j.template get() ? node_flags::is_true : 0)); return r; } case value_t::number_integer: - return integer_node(static_cast(static_cast(j.template get())), value_t::number_integer); + return integer_node(static_cast(static_cast(j.template get())), node_kind::number_integer); case value_t::number_unsigned: - return integer_node(static_cast(j.template get()), value_t::number_unsigned); + return integer_node(static_cast(j.template get()), node_kind::number_unsigned); case value_t::number_float: return float_node(j.template get()); case value_t::string: @@ -4369,7 +4433,7 @@ class editor ++r; if (is_container(*n)) { - open.push_back(frame{walk::first(d, n), walk::end(d, n), n->kind == static_cast(value_t::object)}); + open.push_back(frame{walk::first(d, n), walk::end(d, n), n->kind == node_kind::object}); } // the next value: close finished containers, then step over the key for (;;) @@ -4416,9 +4480,9 @@ class editor if (is_container(*n)) { node* const self = out++; - *self = plain_node(static_cast(n->kind)); + *self = plain_node(n->kind); self->len = n->len; - open.push_back(frame{walk::first(d, n), walk::end(d, n), n->kind == static_cast(value_t::object), self}); + open.push_back(frame{walk::first(d, n), walk::end(d, n), n->kind == node_kind::object, self}); } else { @@ -4507,7 +4571,7 @@ class editor if (n->is_structured()) { node* const self = out++; - *self = plain_node(n->type()); + *self = plain_node(static_cast(n->type())); self->len = static_cast(n->size()); open.push_back(frame{n->cbegin(), n->cend(), n->is_object(), self}); } @@ -4903,7 +4967,7 @@ inline void compact_nodes(const document_data& d, std::size_t arena_size, std::v r.off = (v->flags & (node_flags::moved | node_flags::is_new)) != 0 ? 0 : v->off; r.len = 0; // counted below r.next = 0; // set when the container is complete - stack.push_back(frame{d.first_child_edited(v), d.child_end_edited(v), out.size(), 0, v->kind == static_cast(value_t::object)}); + stack.push_back(frame{d.first_child_edited(v), d.child_end_edited(v), out.size(), 0, v->kind == node_kind::object}); break; case value_t::string: r = string_node(*v); @@ -4924,7 +4988,7 @@ inline void compact_nodes(const document_data& d, std::size_t arena_size, std::v if (t[0] == 'n' || t[0] == 'i' || (v->len > 1 && t[1] == 'i')) { r = node{}; // nan and infinity: null, as dump() writes them - r.kind = static_cast(value_t::null); + r.kind = node_kind::null; break; } r.off = static_cast(d.size + text_tail.size()); @@ -4968,12 +5032,10 @@ inline void compact_nodes(const document_data& d, std::size_t arena_size, std::v } } +#if NLOHMANN_VIEW_LITTLE_ENDIAN // (the image format is little-endian: no save() elsewhere) /// the document as an image inline std::vector save_image(const document_data& d) { -#if !NLOHMANN_VIEW_LITTLE_ENDIAN - throw_type_error(exception_id::document_image_unsupported, "json_document images need a little-endian target"); // LCOV_EXCL_LINE -#endif const std::size_t arena_size = d.arena_size; std::vector compacted; std::string text_tail; @@ -5007,7 +5069,7 @@ inline std::vector save_image(const document_data& d) // extra of an object, and the same document saves to the same bytes for (std::size_t i = 0; i < count; ++i) { - if (nodes[i].kind == static_cast(value_t::object) && nodes[i].extra != 0) + if (nodes[i].kind == node_kind::object && nodes[i].extra != 0) { std::memset(o + (i * sizeof(node)) + offsetof(node, extra), 0, sizeof(nodes[i].extra)); } @@ -5029,6 +5091,7 @@ inline std::vector save_image(const document_data& d) *o = 0; return image; } +#endif /// the parts of a number token that the checks need struct number_token @@ -5123,7 +5186,7 @@ inline bool float_token_finite(const unsigned char* s, std::size_t len, const nu if (static_cast(t.int_digits) + t.exponent > 300) { node n{}; - n.kind = static_cast(value_t::number_float); + n.kind = node_kind::number_float; n.len = static_cast(len); const auto v = float_value(reinterpret_cast(s), n); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast) return v <= (std::numeric_limits::max)() && v >= -(std::numeric_limits::max)(); @@ -5144,7 +5207,7 @@ inline bool check_integer(const node& n, const unsigned char* text) } // the token's value is the stored one; number_integer nodes of edits can // be non-negative (as basic_json keeps the type of a value) - const bool integer = n.kind == static_cast(value_t::number_integer); + const bool integer = n.kind == node_kind::number_integer; if (t.is_float || t.int_digits > 20 || (t.negative && !integer)) { return false; @@ -5326,7 +5389,7 @@ inline bool check_image(const node* nodes, std::size_t count, const unsigned cha { return false; } - if (n.kind != static_cast(value_t::number_float)) + if (n.kind != node_kind::number_float) { return (n.extra >> 8u) == 0; } @@ -5367,7 +5430,7 @@ inline bool check_image(const node* nodes, std::size_t count, const unsigned cha const node& n = nodes[i]; if (!stack.empty() && stack.back().object && stack.back().expect_key) { - if (n.kind != static_cast(value_t::string) || !check_string(n)) + if (n.kind != node_kind::string || !check_string(n)) { return false; } @@ -5408,7 +5471,7 @@ inline bool check_image(const node* nodes, std::size_t count, const unsigned cha { // (the contents of float tokens are checked later; the token of an // integer has at most 256 characters) - if (n.kind == static_cast(value_t::number_float)) + if (n.kind == node_kind::number_float) { floats.push_back(float_range{n.off, n.len, n.extra}); } @@ -5426,7 +5489,7 @@ inline bool check_image(const node* nodes, std::size_t count, const unsigned cha { return false; } - stack.push_back(frame{i + n.next, n.len, 0, n.kind == static_cast(value_t::object), true}); + stack.push_back(frame{i + n.next, n.len, 0, n.kind == node_kind::object, true}); complete = false; break; } @@ -5449,13 +5512,11 @@ inline bool check_image(const node* nodes, std::size_t count, const unsigned cha throw_parse_error(exception_id::invalid_document_image, concat("invalid json_document image: ", what)); } +#if NLOHMANN_VIEW_LITTLE_ENDIAN // (the image format is little-endian: no load() elsewhere) /// Read an image into d. The text and the decoded strings stay in the image; /// the nodes are copied (so that they are aligned, and edits can change them). inline void load_image(document_data& d, const std::uint8_t* image, std::size_t size, image_check check) { -#if !NLOHMANN_VIEW_LITTLE_ENDIAN - throw_type_error(exception_id::document_image_unsupported, "json_document images need a little-endian target"); // LCOV_EXCL_LINE -#endif if (image == nullptr || size < sizeof(image_header)) { throw_invalid_image("too short"); @@ -5515,7 +5576,7 @@ inline void load_image(document_data& d, const std::uint8_t* image, std::size_t for (std::size_t i = 0; i < count; ++i) { const node& n = d.nodes[i]; - if (n.kind == static_cast(value_t::object) && n.len >= document_data::index_min_members) + if (n.kind == node_kind::object && n.len >= document_data::index_min_members) { d.large_objects.push_back(static_cast(i)); } @@ -5524,6 +5585,7 @@ inline void load_image(document_data& d, const std::uint8_t* image, std::size_t std::vector().swap(d.large_objects); // (only needed while building) d.discarded = false; } +#endif } // namespace view } // namespace detail @@ -6108,7 +6170,7 @@ BasicJsonType materialize(const document_data& d, const node* n) case value_t::object: case value_t::array: { - const bool object = n->kind == static_cast(value_t::object); + const bool object = n->kind == node_kind::object; if (object) { sax.start_object(n->len); @@ -7222,7 +7284,7 @@ class view_serializer case value_t::object: case value_t::array: { - const bool object = n->kind == static_cast(value_t::object); + const bool object = n->kind == node_kind::object; room(8); if (n->len == 0) { @@ -7760,19 +7822,23 @@ struct is_direct_value : std::integral_constant < bool, template struct is_direct_value, BasicJsonType, View> : std::true_type {}; +// (vectors of these, which are faster than through from_json(), and containers +// of views; other containers are read by the library's from_json()) template -struct is_direct_value, BasicJsonType, View> : std::true_type {}; +struct is_direct_value, BasicJsonType, View> : is_direct_value {}; -template -struct is_direct_value, BasicJsonType, View> : is_string_key {}; +template +struct is_direct_value, BasicJsonType, View> : is_string_key {}; -template -struct is_direct_value, BasicJsonType, View> : is_string_key {}; +template +struct is_direct_value, BasicJsonType, View> : is_string_key {}; } // namespace view } // namespace detail NLOHMANN_JSON_NAMESPACE_END +// #include + NLOHMANN_JSON_NAMESPACE_BEGIN @@ -7782,15 +7848,20 @@ NLOHMANN_JSON_NAMESPACE_BEGIN Trivially copyable (two pointers). Valid as long as the document is alive and has not been re-parsed, and as long as a borrowed source text is alive. */ -template +template class basic_json_view { + /// whether this is a view of an editable document + static constexpr bool is_editable = Mode == view_mode::editable; + using node = detail::view::node; using document_data = detail::view::document_data; /// how the index is walked (with edits only for editable documents) - using navigation = detail::view::navigation; + using navigation = detail::view::navigation; public: + /// the basic_json type of the document (it also marks views for is_json_view) + using basic_json_type = BasicJsonType; using value_t = detail::value_t; using string_t = typename BasicJsonType::string_t; using number_integer_t = typename BasicJsonType::number_integer_t; @@ -7809,6 +7880,15 @@ class basic_json_view /// an invalid view (type() == value_t::discarded) basic_json_view() noexcept = default; + /// a view of a read-only document as a view of an editable one, so that a + /// function taking an editable view accepts both (a read-only document has + /// no edits; the view reads it as an editable document before its first + /// edit, at the cost of one check per access) + template::value, int> = 0> + basic_json_view(const basic_json_view& other) noexcept // NOLINT(google-explicit-constructor,hicpp-explicit-conversions) + : m_doc(other.m_doc), m_node(other.m_node) + {} + ////////// // type // ////////// @@ -8135,7 +8215,7 @@ class basic_json_view if (is_structured() && m_node->len != 0) { return unedited() ? basic_json_view(m_doc, detail::view::last_child(*m_doc, m_node) + (is_object() ? 1 : 0)) - : basic_json_view(m_doc, navigation::value(detail::view::last_child(*m_doc, m_node) + (is_object() ? 1 : 0))); + : basic_json_view(m_doc, navigation::value(detail::view::last_child(*m_doc, m_node) + (is_object() ? 1 : 0))); } return front(); } @@ -8193,7 +8273,7 @@ class basic_json_view /// more than once); 0 if this is not an object size_type count(string_view_t key) const { - return is_object() ? detail::view::count_members(*m_doc, m_node, key.data(), key.size()) : 0; + return is_object() ? detail::view::count_members(*m_doc, m_node, key.data(), key.size()) : 0; } size_type count(const char* key) const @@ -8252,8 +8332,10 @@ class basic_json_view /// the value converted to T, as BasicJsonType::get(): arithmetic types, /// strings (string_view_t without a copy), std::nullptr_t, std::vector, - /// maps with string keys, and views are converted directly; other types - /// through materialize().get() + /// maps with string keys, and views are converted directly; types whose + /// from_json() accepts views (those of the NLOHMANN_DEFINE_TYPE_* and + /// NLOHMANN_JSON_SERIALIZE_ENUM macros, say) read the view; other types + /// go through materialize().get() template NLOHMANN_VIEW_ALWAYS_INLINE T get() const { @@ -8261,11 +8343,12 @@ class basic_json_view } /// as BasicJsonType::get_to(): the types get() converts directly are - /// assigned, all others are passed to from_json with v as it is + /// assigned, all others are passed to from_json with v as it is (with + /// the view if from_json() accepts views, else with materialize()) template T& get_to(T& v) const { - get_to_impl(v, detail::view::is_direct_value {}); + get_to_impl(v, get_to_kind {}); return v; } @@ -8335,7 +8418,7 @@ class basic_json_view } else { - detail::view::view_serializer(*m_doc, out, estimate, style).dump(m_node); + detail::view::view_serializer(*m_doc, out, estimate, style).dump(m_node); } return out; } @@ -8371,14 +8454,14 @@ class basic_json_view } /// a view of an editable document compares with one of a read-only document - template < bool E, typename std::enable_if < E != Editable, int >::type = 0 > - friend bool operator==(const basic_json_view& a, const basic_json_view& b) + template::value, int> = 0> + friend bool operator==(const basic_json_view& a, const basic_json_view& b) { - return detail::view::equal(side(a), detail::view::view_side>(b)); + return detail::view::equal(side(a), detail::view::view_side>(b)); } - template < bool E, typename std::enable_if < E != Editable, int >::type = 0 > - friend bool operator!=(const basic_json_view& a, const basic_json_view& b) + template::value, int> = 0> + friend bool operator!=(const basic_json_view& a, const basic_json_view& b) { return !(a == b); } @@ -8416,7 +8499,7 @@ class basic_json_view { return BasicJsonType(value_t::discarded); } - return unedited() ? detail::view::materialize(*m_doc, m_node) : detail::view::materialize(*m_doc, m_node); + return unedited() ? detail::view::materialize(*m_doc, m_node) : detail::view::materialize(*m_doc, m_node); } /// byte offset of this value in the source text (for strings: of the @@ -8429,8 +8512,8 @@ class basic_json_view } private: - template friend class basic_json_document; - template friend class basic_json_view; + template friend class basic_json_document; + template friend class basic_json_view; template friend class detail::view::editor; friend iterator; @@ -8446,11 +8529,11 @@ class basic_json_view } /// the template argument as a runtime condition: testing it directly in - /// `Editable && ...` makes the condition constant, which MSVC reports as + /// `is_editable && ...` makes the condition constant, which MSVC reports as /// C4127 static bool editable() noexcept { - return Editable; + return is_editable; } /// the number of source bytes of this value (estimated for values with @@ -8499,7 +8582,7 @@ class basic_json_view const node* const k = detail::view::find_member(*m_doc, m_node, key.data(), key.size()); return k != nullptr ? basic_json_view(m_doc, k + 1) : basic_json_view(); } - const node* const k = detail::view::find_member(*m_doc, m_node, key.data(), key.size()); + const node* const k = detail::view::find_member(*m_doc, m_node, key.data(), key.size()); return k != nullptr ? basic_json_view(m_doc, navigation::value(k + 1)) : basic_json_view(); } @@ -8513,7 +8596,7 @@ class basic_json_view /// the member of this object with @a key (the last one if the key occurs more than once), or nullptr NLOHMANN_VIEW_ALWAYS_INLINE const node* find_key(string_view_t key) const { - return find_key(key, std::integral_constant {}); + return find_key(key, std::integral_constant {}); } NLOHMANN_VIEW_ALWAYS_INLINE const node* find_key(string_view_t key, std::true_type /*editable*/) const @@ -8535,7 +8618,7 @@ class basic_json_view { return basic_json_view(m_doc, detail::view::element_at(*m_doc, m_node, idx)); } - return basic_json_view(m_doc, navigation::value(detail::view::element_at(*m_doc, m_node, idx))); + return basic_json_view(m_doc, navigation::value(detail::view::element_at(*m_doc, m_node, idx))); } // --- get() dispatch --- @@ -8590,22 +8673,45 @@ class basic_json_view return *this; } - template + /// vectors of the types converted directly (faster than the library's + /// from_json()) and containers of views (no from_json() reads them: a view + /// is no value of BasicJsonType); other containers are read by from_json() + template::value, int> = 0> std::vector get_impl(detail::view::value_tag> /*unused*/, detail::priority_tag<2> /*unused*/) const { return detail::view::vector_value(*this); } - template::value, int>::type = 0> - std::map get_impl(detail::view::value_tag> /*unused*/, detail::priority_tag<2> /*unused*/) const + template::value, int>::type = 0> + std::map get_impl(detail::view::value_tag> /*unused*/, detail::priority_tag<2> /*unused*/) const { - return detail::view::map_value>(*this); + return detail::view::map_value>(*this); } - template::value, int>::type = 0> - std::unordered_map get_impl(detail::view::value_tag> /*unused*/, detail::priority_tag<2> /*unused*/) const + template::value, int>::type = 0> + std::unordered_map get_impl(detail::view::value_tag> /*unused*/, detail::priority_tag<2> /*unused*/) const { - return detail::view::map_value>(*this); + return detail::view::map_value>(*this); + } + + /// whether the from_json() of T's serializer reads views (see + /// detail::has_view_from_json) + template + using reads_view = detail::has_view_from_json, basic_json_view, T>; + + /// how get_to() converts: 2 directly, 1 with a from_json() that reads + /// views, 0 through materialize() + template + using get_to_kind = std::integral_constant < int, detail::view::is_direct_value::value + ? 2 : static_cast(reads_view::value) >; + + /// types whose from_json() reads views, without materialize() + template, std::is_default_constructible>::value, int> = 0> + T get_impl(detail::view::value_tag /*unused*/, detail::priority_tag<1> /*unused*/) const + { + auto v = T(); + BasicJsonType::template json_serializer::from_json(*this, v); + return v; } /// everything else through the BasicJsonType value (from_json included) @@ -8618,14 +8724,20 @@ class basic_json_view // --- get_to() dispatch --- template - void get_to_impl(T& v, std::true_type /*direct*/) const + void get_to_impl(T& v, std::integral_constant /*direct*/) const { v = get(); } /// from_json may keep parts of v (members a JSON object lacks, say) template - void get_to_impl(T& v, std::false_type /*direct*/) const + void get_to_impl(T& v, std::integral_constant /*from_json reads views*/) const + { + BasicJsonType::template json_serializer::from_json(*this, v); + } + + template + void get_to_impl(T& v, std::integral_constant /*through materialize()*/) const { materialize().get_to(v); } @@ -8641,16 +8753,19 @@ Borrowed parses keep a pointer to the caller's text, which must outlive the document. Owned parses (parse_copy, rvalue std::string, streams, and inputs that are not contiguous byte ranges) keep their own copy. */ -template +template class basic_json_document { + /// whether the document can be edited + static constexpr bool is_editable = Mode == view_mode::editable; + using document_data = detail::view::document_data; static_assert(sizeof(typename BasicJsonType::number_integer_t) == 8 && sizeof(typename BasicJsonType::number_unsigned_t) == 8, "json_view supports 64-bit integer types only"); public: - using view_type = basic_json_view; + using view_type = basic_json_view; using value_t = detail::value_t; /// an empty (discarded) document @@ -8864,6 +8979,10 @@ class basic_json_document /// how load() checks an image (full, bounds, or none) using image_check = detail::view::image_check; +#if NLOHMANN_VIEW_LITTLE_ENDIAN + // (the image format is little-endian: big-endian targets have no save() + // and load(), so that a call fails to compile instead of always throwing) + /// The document as an image that load() reads without parsing: the node /// index, the text, and the decoded strings. An edited document is /// written in its current state (floats that are not finite become null, @@ -8905,6 +9024,7 @@ class basic_json_document detail::view::load_image(*d.m_data, d.m_data->owned_image.data(), d.m_data->owned_image.size(), check); return d; } +#endif /////////// // edits // @@ -9046,7 +9166,7 @@ class basic_json_document detail::view::editor editor() { - static_assert(Editable, "only an editable document can be edited: use basic_json_document (json_editable_document)"); + static_assert(is_editable, "only an editable document can be edited: use basic_json_document (json_editable_document)"); if (NLOHMANN_VIEW_UNLIKELY(!m_data || m_data->discarded)) { detail::view::throw_invalid_iterator(detail::exception_id::iterator_from_other_value, "view does not belong to this document"); @@ -9322,22 +9442,31 @@ class basic_json_document std::unique_ptr m_data{}; // NOLINT(readability-redundant-member-init) }; +/*! +@brief whether T is a basic_json_view (of any BasicJsonType and view_mode) + +For constraining templates that accept views, e.g. a from_json() written for +both basic_json values and views. +*/ +template +struct is_json_view : detail::is_json_view {}; + /// a parsed JSON text for json -using json_document = basic_json_document; +using json_document = basic_json_document; /// a value of a json_document -using json_view = basic_json_view; +using json_view = basic_json_view; /// a parsed JSON text for ordered_json -using ordered_json_document = basic_json_document; +using ordered_json_document = basic_json_document; /// a value of an ordered_json_document -using ordered_json_view = basic_json_view; +using ordered_json_view = basic_json_view; /// an editable parsed JSON text for json -using json_editable_document = basic_json_document; +using json_editable_document = basic_json_document; /// a value of a json_editable_document -using json_editable_view = basic_json_view; +using json_editable_view = basic_json_view; /// an editable parsed JSON text for ordered_json -using ordered_json_editable_document = basic_json_document; +using ordered_json_editable_document = basic_json_document; /// a value of an ordered_json_editable_document -using ordered_json_editable_view = basic_json_view; +using ordered_json_editable_view = basic_json_view; NLOHMANN_JSON_NAMESPACE_END diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index ee7e07f08..f4e574fc1 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -420,9 +420,9 @@ json_test_add_test_for(src/unit-file_input.cpp ) # the json_view parser again with the portable string scanning instead of -# NEON/SSE2, and on x86-64 with the SSSE3 UTF-8 check (JSON_VIEW_USE_SSSE3) +# NEON/SSE2 (JSON_NO_SIMD), and on x86-64 compiled for SSSE3 (no CPU check) json_test_set_test_options(test-json_view_builder_portable - COMPILE_DEFINITIONS JSON_VIEW_NO_SIMD + COMPILE_DEFINITIONS JSON_NO_SIMD ) json_test_add_test_for(src/unit-json_view_builder.cpp NAME test-json_view_builder_portable @@ -430,7 +430,6 @@ json_test_add_test_for(src/unit-json_view_builder.cpp ) if(CMAKE_SYSTEM_PROCESSOR MATCHES "^(x86_64|AMD64|amd64)$" AND NOT MSVC) json_test_set_test_options(test-json_view_builder_ssse3 - COMPILE_DEFINITIONS JSON_VIEW_USE_SSSE3 COMPILE_OPTIONS -mssse3 ) json_test_add_test_for(src/unit-json_view_builder.cpp diff --git a/tests/abi/config/CMakeLists.txt b/tests/abi/config/CMakeLists.txt index 930afeecc..f3f27c2ee 100644 --- a/tests/abi/config/CMakeLists.txt +++ b/tests/abi/config/CMakeLists.txt @@ -24,9 +24,7 @@ foreach(test default noversion) JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON=1 JSON_DISABLE_STREAM_BULK_SCAN=1 JSON_DISABLE_UNLOCKED_FILE_IO=1 - JSON_NO_SIMD - JSON_VIEW_NO_SIMD - JSON_VIEW_USE_SSSE3) + JSON_NO_SIMD) target_link_libraries(abi_config_${test}_all_tags PRIVATE abi_compat_main) add_test( NAME test-abi_config_${test}_all_tags diff --git a/tests/abi/config/default.cpp b/tests/abi/config/default.cpp index a9fa472d3..084edf8c9 100644 --- a/tests/abi/config/default.cpp +++ b/tests/abi/config/default.cpp @@ -64,14 +64,6 @@ TEST_CASE("default namespace") expected += "_ns"; #endif -#ifdef JSON_VIEW_NO_SIMD - expected += "_vns"; -#endif - -#ifdef JSON_VIEW_USE_SSSE3 - expected += "_vssse3"; -#endif - expected += "_v" STRINGIZE(NLOHMANN_JSON_VERSION_MAJOR); expected += "_" STRINGIZE(NLOHMANN_JSON_VERSION_MINOR); expected += "_" STRINGIZE(NLOHMANN_JSON_VERSION_PATCH) "::basic_json"; diff --git a/tests/abi/config/noversion.cpp b/tests/abi/config/noversion.cpp index a090d1d02..66164ff88 100644 --- a/tests/abi/config/noversion.cpp +++ b/tests/abi/config/noversion.cpp @@ -65,14 +65,6 @@ TEST_CASE("default namespace without version component") expected += "_ns"; #endif -#ifdef JSON_VIEW_NO_SIMD - expected += "_vns"; -#endif - -#ifdef JSON_VIEW_USE_SSSE3 - expected += "_vssse3"; -#endif - expected += "::basic_json"; // fallback for Clang diff --git a/tests/benchmarks/json_view/README.md b/tests/benchmarks/json_view/README.md index c4a05d35a..a0bd2afbc 100644 --- a/tests/benchmarks/json_view/README.md +++ b/tests/benchmarks/json_view/README.md @@ -59,6 +59,12 @@ An editable `json_document` edits in place; yyjson copies its immutable document (`yyjson_doc_mut_copy`); Boost.JSON and `json::parse` build mutable DOMs; simdjson cannot edit a document. All outputs are checked to describe the same value. +`bench_decode.cpp` measures decoding into structs whose `from_json()` comes from the `NLOHMANN_DEFINE_TYPE_*` macros +(twitter statuses, citm_catalog performances, and 20,000 synthetic records): `get()` on a view, which reads the +view directly, against `materialize().get()`, `json::parse` plus `get()`, and `get()` on an already parsed +`json` value. Only nlohmann/json takes part, as the other libraries have no comparable mapping; all engines are +checked to decode the same values. + Before anything is timed, all engines must accept each document and agree on the traversal: the number of values, the bytes of all strings and keys, and the sum of all numbers. All engines run interleaved in every round, and the best round is reported, as time and as a factor of the `json_view` time (below 1 means faster than `json_view`). Each timed diff --git a/tests/benchmarks/json_view/bench_decode.cpp b/tests/benchmarks/json_view/bench_decode.cpp new file mode 100644 index 000000000..29e3d7f81 --- /dev/null +++ b/tests/benchmarks/json_view/bench_decode.cpp @@ -0,0 +1,312 @@ +// __ _____ _____ _____ +// __| | __| | | | JSON for Modern C++ (supporting code) +// | | |__ | | | | | | version 3.12.0 +// |_____|_____|_____|_|___| https://github.com/nlohmann/json +// +// SPDX-FileCopyrightText: 2013-2026 Niels Lohmann +// SPDX-License-Identifier: MIT + +// Decoding into structs: the same types, with from_json() generated by the +// NLOHMANN_DEFINE_TYPE_* macros, read three ways. Only nlohmann/json takes +// part; the other libraries have no comparable mapping. +// +// view json_document::parse, then get() on the view (from_json reads the view) +// materialize json_document::parse, then materialize().get() (a json value first) +// json::parse json::parse, then get() +// json::get get() on an already parsed json value (the decoding alone) +// +// Documents: twitter (the statuses with their users and hashtags), citm_catalog +// (the performances with their seat categories), and a synthetic document of +// 20,000 nested records. +// +// Build: see README.md (same flags as bench_view.cpp). +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using nlohmann::json; +using nlohmann::json_document; + +static volatile std::size_t g_sink; + +namespace twitter +{ +struct hashtag +{ + std::string text; + std::vector indices; +}; +NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(hashtag, text, indices) + +struct entities +{ + std::vector hashtags; +}; +NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(entities, hashtags) + +struct user +{ + std::uint64_t id = 0; + std::string screen_name; + std::string description; + std::int64_t followers_count = 0; +}; +NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(user, id, screen_name, description, followers_count) + +struct status +{ + std::uint64_t id = 0; + std::string text; + user user; + entities entities; + std::int64_t retweet_count = 0; + std::int64_t favorite_count = 0; +}; +NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(status, id, text, user, entities, retweet_count, favorite_count) + +struct document +{ + std::vector statuses; +}; +NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(document, statuses) + +std::size_t digest(const document& d) +{ + std::size_t n = 0; + for (const auto& s : d.statuses) + { + n += s.text.size() + s.user.screen_name.size() + s.entities.hashtags.size() + static_cast(s.retweet_count); + } + return n; +} +} // namespace twitter + +namespace citm +{ +struct area +{ + std::int64_t areaId = 0; + std::vector blockIds; +}; +NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(area, areaId, blockIds) + +struct seat_category +{ + std::vector areas; + std::int64_t seatCategoryId = 0; +}; +NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(seat_category, areas, seatCategoryId) + +struct performance +{ + std::int64_t id = 0; + std::int64_t eventId = 0; + std::vector seatCategories; + std::int64_t start = 0; + std::string venueCode; +}; +NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(performance, id, eventId, seatCategories, start, venueCode) + +struct document +{ + std::vector performances; +}; +NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(document, performances) + +std::size_t digest(const document& d) +{ + std::size_t n = 0; + for (const auto& p : d.performances) + { + n += p.seatCategories.size() + p.venueCode.size() + static_cast(p.start % 1000); + for (const auto& c : p.seatCategories) + { + n += c.areas.size(); + } + } + return n; +} +} // namespace citm + +namespace synthetic +{ +struct position +{ + double x = 0; + double y = 0; +}; +NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(position, x, y) + +struct child +{ + std::int64_t id = 0; + std::string label; + bool active = false; +}; +NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(child, id, label, active) + +struct record +{ + std::int64_t id = 0; + std::string name; + std::vector tags; + position pos; + std::vector children; +}; +NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(record, id, name, tags, pos, children) + +struct document +{ + std::vector records; +}; +NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(document, records) + +std::string make_text(std::size_t n) +{ + std::string s = "{\"records\":["; + for (std::size_t i = 0; i < n; ++i) + { + const std::string id = std::to_string(i); + s += (i == 0 ? "" : ","); + s += "{\"id\":" + id + ",\"name\":\"record number " + id + "\",\"tags\":[\"alpha\",\"beta\",\"gamma\"],"; + s += "\"pos\":{\"x\":" + id + ".25,\"y\":-" + id + ".5},\"children\":["; + for (std::size_t k = 0; k < 3; ++k) + { + s += (k == 0 ? "" : ","); + s += "{\"id\":" + std::to_string(i * 3 + k) + ",\"label\":\"child " + std::to_string(k) + "\",\"active\":" + (k % 2 == 0 ? "true" : "false") + "}"; + } + s += "]}"; + } + s += "]}"; + return s; +} + +std::size_t digest(const document& d) +{ + std::size_t n = 0; + for (const auto& r : d.records) + { + n += r.name.size() + r.tags.size() + r.children.size() + static_cast(r.pos.x); + } + return n; +} +} // namespace synthetic + +namespace +{ +std::string read_file(const std::string& p) +{ + std::ifstream f(p, std::ios::binary); + if (!f) + { + std::fprintf(stderr, "cannot open %s\n", p.c_str()); + std::exit(1); + } + std::stringstream ss; + ss << f.rdbuf(); + return ss.str(); +} + +double time_ns(const std::function& f) +{ + const auto t0 = std::chrono::steady_clock::now(); + f(); + const auto t1 = std::chrono::steady_clock::now(); + return static_cast(std::chrono::duration_cast(t1 - t0).count()); +} + +struct engine +{ + std::string name; + std::function run; +}; + +template +std::vector engines(const std::string& text, const json& parsed) +{ + return + { + {"view", [&text] { const json_document d = json_document::parse(text); return digest(d.root().get()); }}, + {"materialize", [&text] { const json_document d = json_document::parse(text); return digest(d.root().materialize().get()); }}, + {"json::parse", [&text] { return digest(json::parse(text).get()); }}, + {"json::get", [&parsed] { return digest(parsed.get()); }}, + }; +} +} // namespace + +int main(int argc, char** argv) +{ + if (argc < 2) + { + std::fprintf(stderr, "usage: %s [rounds]\n", argv[0]); + return 1; + } + const std::string dir = argv[1]; + const int rounds = argc > 2 ? std::max(1, std::atoi(argv[2])) : 30; + + struct document_case + { + std::string name; + std::string text; + json parsed; + std::vector engines; + }; + std::vector cases; + cases.push_back({"twitter", read_file(dir + "/nativejson-benchmark/twitter.json"), json(), {}}); + cases.push_back({"citm_catalog", read_file(dir + "/nativejson-benchmark/citm_catalog.json"), json(), {}}); + cases.push_back({"synthetic", synthetic::make_text(20000), json(), {}}); + for (auto& c : cases) + { + c.parsed = json::parse(c.text); + } + cases[0].engines = engines(cases[0].text, cases[0].parsed); + cases[1].engines = engines(cases[1].text, cases[1].parsed); + cases[2].engines = engines(cases[2].text, cases[2].parsed); + + std::FILE* csv = std::fopen("bench_decode.csv", "w"); + if (csv == nullptr) + { + std::perror("bench_decode.csv"); + return 1; + } + std::fprintf(csv, "doc,bytes,engine,ns\n"); + for (const auto& c : cases) + { + // all engines must decode the same values + const std::size_t expected = c.engines[0].run(); + bool ok = true; + for (const auto& e : c.engines) + { + ok = ok && e.run() == expected; + } + std::vector best(c.engines.size(), 1e300); + for (int r = 0; r < rounds; ++r) + { + for (std::size_t k = 0; k < c.engines.size(); ++k) + { + g_sink = c.engines[k].run(); // untimed: see bench_view.cpp + best[k] = std::min(best[k], time_ns([&] { g_sink = c.engines[k].run(); })); + } + } + std::printf("%-13s %-7s %s", c.name.c_str(), "decode", ok ? "" : "[MISMATCH] "); + for (std::size_t k = 0; k < c.engines.size(); ++k) + { + const double us = best[k] / 1000.0; + std::printf(" %s %.*fus (%.2fx)", c.engines[k].name.c_str(), us < 10 ? 3 : (us < 1000 ? 1 : 0), us, best[k] / best[0]); + std::fprintf(csv, "%s,%zu,%s,%.1f\n", c.name.c_str(), c.text.size(), c.engines[k].name.c_str(), best[k]); + } + std::printf("\n"); + } + std::fclose(csv); + return 0; +} diff --git a/tests/benchmarks/json_view/compare.py b/tests/benchmarks/json_view/compare.py index 39d5048f0..6573c88af 100755 --- a/tests/benchmarks/json_view/compare.py +++ b/tests/benchmarks/json_view/compare.py @@ -9,7 +9,7 @@ """Compare json_view with yyjson, simdjson, Boost.JSON, and json::parse. -Builds bench_view.cpp, bench_corpus.cpp, and bench_edit.cpp against the include/ directory of +Builds bench_view.cpp, bench_corpus.cpp, bench_edit.cpp, and bench_decode.cpp against the include/ directory of this checkout, runs them, and writes the results with everything needed to reproduce them (date, commit, CPU, OS, compiler, library versions, flags) to results/-.md and .csv next to this script. @@ -283,7 +283,7 @@ def main(): objects.append(obj) binaries = {} - for bench in ['bench_view', 'bench_corpus', 'bench_edit']: + for bench in ['bench_view', 'bench_corpus', 'bench_edit', 'bench_decode']: exe = os.path.join(args.build_dir, bench) run(cxx + flags + include + [os.path.join(HERE, bench + '.cpp')] + objects + link + ['-o', exe]) binaries[bench] = exe @@ -297,6 +297,8 @@ def main(): capture_output=True, text=True).stdout outputs['bench_edit'] = run([binaries['bench_edit'], args.data, str(max(1, args.rounds // 2))], cwd=args.build_dir, capture_output=True, text=True).stdout + outputs['bench_decode'] = run([binaries['bench_decode'], args.data, str(max(1, args.rounds // 2))], cwd=args.build_dir, + capture_output=True, text=True).stdout for name, text in outputs.items(): print(text) @@ -330,7 +332,7 @@ def main(): f.write(f'\n## {name}\n\n```\n{text.rstrip()}\n```\n') with open(stem + '.csv', 'w', encoding='utf-8') as out: out.write(''.join(f'# {key}: {value}\n' for key, value in meta)) - for name in ['bench_view', 'bench_corpus', 'bench_edit']: + for name in ['bench_view', 'bench_corpus', 'bench_edit', 'bench_decode']: path = os.path.join(args.build_dir, name + '.csv') if os.path.isfile(path): with open(path, encoding='utf-8') as f: diff --git a/tests/src/unit-bjdata.cpp b/tests/src/unit-bjdata.cpp index 67e9e0a05..e4dffb6d0 100644 --- a/tests/src/unit-bjdata.cpp +++ b/tests/src/unit-bjdata.cpp @@ -2916,6 +2916,35 @@ TEST_CASE("BJData") CHECK(json::from_bjdata(out_single_ok) == json({{"_ArrayType_", "single"}, {"_ArraySize_", {2, 1}}, {"_ArrayData_", {1.5f, -1.5f}}})); } + SECTION("ndarray with _ArrayType_ \"char\" is limited to 0..127") + { + // both BJData drafts limit a char to 0..127; larger values must not + // be written as 'C' elements (the reader rejects them), so such an + // object stays a plain object that round-trips + for (const auto version : + { + json::bjdata_version_t::draft2, json::bjdata_version_t::draft3 + }) + { + json const j_ok = json({{"_ArrayType_", "char"}, {"_ArraySize_", {2, 2}}, {"_ArrayData_", {0, 97, 98, 127}}}); + const auto out_ok = json::to_bjdata(j_ok, true, true, version); + CHECK(out_ok == std::vector({'[', '$', 'C', '#', '[', '$', 'i', '#', 'i', 2, 2, 2, 0, 97, 98, 127})); + CHECK(json::from_bjdata(out_ok) == j_ok); + + for (const int bad : + { + 128, 200, 255, 256, -1 + }) + { + CAPTURE(bad) + json const j_bad = json({{"_ArrayType_", "char"}, {"_ArraySize_", {2, 2}}, {"_ArrayData_", {97, 98, 99, bad}}}); + const auto out_bad = json::to_bjdata(j_bad, true, true, version); + CHECK(out_bad.at(0) == '{'); + CHECK(json::from_bjdata(out_bad) == j_bad); + } + } + } + SECTION("ndarray annotation keys are read back in the documented order") { // from_bjdata() must emit the annotation object's keys in the order @@ -3134,7 +3163,7 @@ TEST_CASE("BJData") std::vector const v1 = {'[', '#', 'i', 0xF1}; std::vector const v2 = {'[', '$', 'I', '#', 'i', 0xF2}; std::vector const v3 = {'[', '$', 'I', '#', '[', 'i', 0xF4, 'i', 0x02, ']'}; - std::vector const v4 = {'[', '$', 0xF6, '#', 'i', 0xF7}; + std::vector const v4 = {'[', '$', 'l', '#', 'i', 0xF7}; std::vector const v5 = {'[', '$', 'I', '#', '[', 'i', 0xF5, 'i', 0xF1, ']'}; std::vector const v6 = {'[', '#', '[', 'i', 0xF3, 'i', 0x02, ']'}; @@ -3324,6 +3353,44 @@ TEST_CASE("BJData") } } + SECTION("ndarray of half-precision floats can be read") + { + // the 'h' element type is read as _ArrayType_ "half" + json _; + const std::vector half = {'[', '$', 'h', '#', '[', '$', 'U', '#', 'U', 2, 2, 2, 0x00, 0x3C, 0x00, 0x40, 0x00, 0x42, 0x00, 0x44}; + const json j_half = json::parse(R"({"_ArrayType_":"half","_ArraySize_":[2,2],"_ArrayData_":[1.0,2.0,3.0,4.0]})"); + CHECK(json::from_bjdata(half) == j_half); + + // the writer does not pack "half": it writes a plain object that reads back equal + CHECK(json::from_bjdata(json::to_bjdata(j_half)) == j_half); + CHECK(json::from_bjdata(json::to_bjdata(j_half, true, true, json::bjdata_version_t::draft3)) == j_half); + + // truncated half data + CHECK_THROWS_AS(_ = json::from_bjdata(std::vector(half.begin(), half.end() - 1)), json::parse_error&); + } + + SECTION("ndarray of char rejects values above 127") + { + json _; + const std::vector ok = {'[', '$', 'C', '#', '[', '$', 'U', '#', 'U', 2, 2, 2, 'a', 'b', 'c', 0x7F}; + CHECK(json::from_bjdata(ok) == json::parse(R"({"_ArrayType_":"char","_ArraySize_":[2,2],"_ArrayData_":[97,98,99,127]})")); + + std::vector bad = ok; + bad.back() = 0xC8; + CHECK_THROWS_AS(_ = json::from_bjdata(bad), json::parse_error&); + CHECK(json::from_bjdata(bad, true, false).is_discarded()); + + // the same restriction holds for 1-D arrays + CHECK_THROWS_AS(_ = json::from_bjdata(std::vector({'[', '$', 'C', '#', 'i', 1, 0xC8})), json::parse_error&); + + // truncated input + CHECK_THROWS_AS(_ = json::from_bjdata(std::vector(ok.begin(), ok.end() - 1)), json::parse_error&); + + // 'B' (byte) elements may use the full range + const std::vector byte_nd = {'[', '$', 'B', '#', '[', '$', 'U', '#', 'U', 2, 2, 2, 1, 2, 3, 0xC8}; + CHECK(json::from_bjdata(byte_nd) == json::parse(R"({"_ArrayType_":"byte","_ArraySize_":[2,2],"_ArrayData_":[1,2,3,200]})")); + } + SECTION("do not accept NTFZ markers in ndarray optimized type (with count)") { json _; @@ -3455,7 +3522,7 @@ TEST_CASE("BJData") CHECK(json::from_bjdata(v0, true, false).is_discarded()); std::vector const vi = {'[', '$', '#'}; - CHECK_THROWS_WITH_AS(_ = json::from_bjdata(vi), "[json.exception.parse_error.110] parse error at byte 4: syntax error while parsing BJData value: unexpected end of input", json::parse_error&); + CHECK_THROWS_WITH_AS(_ = json::from_bjdata(vi), "[json.exception.parse_error.112] parse error at byte 3: syntax error while parsing BJData type: marker 0x23 is not a permitted optimized array type", json::parse_error&); CHECK(json::from_bjdata(vi, true, false).is_discarded()); std::vector const vU = {'[', '$', 'U'}; @@ -4622,3 +4689,60 @@ TEST_CASE("BJData large strings and binaries (chunked reader)") } } } + +TEST_CASE("BJData optimized container type marker is validated for count 0") +{ + // the '$' type marker must be a legal marker even if no element follows + SECTION("invalid marker") + { + // 'Z', 'T', 'F', 'N', 'S', 'H', '[' and '{' are excluded in BJData + for (const char marker : + {'x', 'E', 'Z', 'T', 'F', 'N', 'S', 'H', '[', '{' + }) + { + for (const char open : + {'[', '{' + }) + { + for (const int count : + { + 0, 1 + }) + { + CAPTURE(marker) + CAPTURE(open) + CAPTURE(count) + const std::vector v = {static_cast(open), '$', static_cast(marker), '#', 'i', static_cast(count)}; + json _; + CHECK_THROWS_AS(_ = json::from_bjdata(v), json::parse_error&); + CHECK(json::from_bjdata(v, true, false).is_discarded()); + } + } + } + } + + SECTION("error message") + { + const std::vector v = {'[', '$', 'x', '#', 'i', 0}; + json _; + CHECK_THROWS_WITH_AS(_ = json::from_bjdata(v), "[json.exception.parse_error.112] parse error at byte 3: syntax error while parsing BJData type: marker 0x78 is not a permitted optimized array type", json::parse_error&); + } + + SECTION("valid marker") + { + for (const char marker : + {'i', 'U', 'I', 'u', 'l', 'm', 'L', 'M', 'h', 'd', 'D', 'C' + }) + { + CAPTURE(marker) + const std::vector va = {'[', '$', static_cast(marker), '#', 'i', 0}; + const std::vector vo = {'{', '$', static_cast(marker), '#', 'i', 0}; + CHECK(json::from_bjdata(va) == json::array()); + CHECK(json::from_bjdata(vo) == json::object()); + } + + // an optimized array of bytes is read as a binary value + const std::vector vb = {'[', '$', 'B', '#', 'i', 0}; + CHECK(json::from_bjdata(vb) == json::binary({})); + } +} diff --git a/tests/src/unit-bson.cpp b/tests/src/unit-bson.cpp index 03c901cee..b4756c9b5 100644 --- a/tests/src/unit-bson.cpp +++ b/tests/src/unit-bson.cpp @@ -802,12 +802,11 @@ TEST_CASE("BSON") CHECK(json::from_bson(result, true, false) == j); } - SECTION("binary member with subtype 0x02 (old binary) keeps its inner length prefix (lenient parsing)") + SECTION("binary member with subtype 0x02 (old binary) carries an inner length") { - // documented lenient behavior (see gh-5333): the payload for - // binary subtype 0x02 ("old binary") is returned as-is, - // including its own inner 4-byte length prefix; it is not - // stripped or reinterpreted + // the payload of binary subtype 0x02 ("old binary") starts with an + // int32 that repeats the payload size; the bytes below are what + // pymongo and the bson-corpus (binary.json) use std::vector const input = { 0x17, 0x00, 0x00, 0x00, // size (little endian) @@ -815,16 +814,87 @@ TEST_CASE("BSON") 0x06, 0x00, 0x00, 0x00, // size of binary (little endian) 0x02, // "old binary" subtype - 0x02, 0x00, 0x00, 0x00, // inner length prefix (part of the old-binary payload) + 0x02, 0x00, 0x00, 0x00, // inner length 0x68, 0x69, // payload ('h', 'i') 0x00 // end marker }; - // the inner length prefix is part of the (unmodified) payload - const std::vector expected_payload = {0x02, 0x00, 0x00, 0x00, 0x68, 0x69}; - const json expected = { { "entry", json::binary(expected_payload, 0x02) } }; + // the inner length is not part of the payload + const json expected = { { "entry", json::binary({0x68, 0x69}, 0x02) } }; CHECK(json::from_bson(input) == expected); + CHECK(json::to_bson(expected) == input); + CHECK(json::from_bson(json::to_bson(expected)) == expected); + } + + SECTION("binary member with subtype 0x02 (old binary) and empty payload") + { + std::vector const input = + { + 0x15, 0x00, 0x00, 0x00, // size (little endian) + 0x05, 'e', 'n', 't', 'r', 'y', '\x00', // entry: binary + 0x04, 0x00, 0x00, 0x00, // size of binary (little endian) + 0x02, // "old binary" subtype + 0x00, 0x00, 0x00, 0x00, // inner length + 0x00 // end marker + }; + + const json expected = { { "entry", json::binary({}, 0x02) } }; + CHECK(json::from_bson(input) == expected); + CHECK(json::to_bson(expected) == input); + } + + SECTION("binary member with subtype 0x02 (old binary) with an invalid inner length") + { + std::vector input = + { + 0x17, 0x00, 0x00, 0x00, // size (little endian) + 0x05, 'e', 'n', 't', 'r', 'y', '\x00', // entry: binary + 0x06, 0x00, 0x00, 0x00, // size of binary (little endian) + 0x02, // "old binary" subtype + 0x02, 0x00, 0x00, 0x00, // inner length + 0x68, 0x69, // payload ('h', 'i') + 0x00 // end marker + }; + + SECTION("inner length too long") + { + input[19] = 0x03; + } + + SECTION("inner length too short") + { + input[19] = 0x01; + } + + SECTION("inner length negative") + { + input[19] = 0xFF; + input[20] = 0xFF; + input[21] = 0xFF; + input[22] = 0xFF; + } + + CAPTURE(input) + json _; + CHECK_THROWS_AS(_ = json::from_bson(input), json::parse_error&); + } + + SECTION("binary member with subtype 0x02 (old binary) without an inner length") + { + // the payload is shorter than the inner length field + std::vector const input = + { + 0x14, 0x00, 0x00, 0x00, // size (little endian) + 0x05, 'e', 'n', 't', 'r', 'y', '\x00', // entry: binary + 0x03, 0x00, 0x00, 0x00, // size of binary (little endian) + 0x02, // "old binary" subtype + 0x01, 0x02, 0x03, // payload without an inner length + 0x00 // end marker + }; + + json _; + CHECK_THROWS_AS(_ = json::from_bson(input), json::parse_error&); } SECTION("Some more complex document") @@ -1178,7 +1248,7 @@ TEST_CASE("BSON document size mismatch") SECTION("top-level document with a negative size") { std::vector const input = {0xFF, 0xFF, 0xFF, 0xFF, 0x00}; - CHECK_THROWS_WITH_AS(_ = json::from_bson(input), "[json.exception.parse_error.112] parse error at byte 5: syntax error while parsing BSON document: document size -1 does not match the number of bytes read (5)", json::parse_error&); + CHECK_THROWS_WITH_AS(_ = json::from_bson(input), "[json.exception.parse_error.112] parse error at byte 4: syntax error while parsing BSON document: document size -1 is less than 5", json::parse_error&); CHECK(json::from_bson(input, true, false).is_discarded()); } @@ -1190,7 +1260,7 @@ TEST_CASE("BSON document size mismatch") // enclosing document instead of "d" std::vector const input = { - 0x00, 0x00, 0x00, 0x00, // outer size + 0x14, 0x00, 0x00, 0x00, // outer size 0x03, 'd', 0x00, // entry: embedded document "d" 0xFF, 0xFF, 0xFF, 0x7F, // embedded size 0x7FFFFFFF 0x0A, 'a', 0x00, // entry: null "a" @@ -1198,7 +1268,24 @@ TEST_CASE("BSON document size mismatch") 0x08, 'h', 0x00, 0x01, // entry: bool "h" = true 0x00 // outer end marker }; - CHECK_THROWS_WITH_AS(_ = json::from_bson(input), "[json.exception.parse_error.112] parse error at byte 15: syntax error while parsing BSON document: document size 2147483647 does not match the number of bytes read (8)", json::parse_error&); + CHECK_THROWS_WITH_AS(_ = json::from_bson(input), "[json.exception.parse_error.112] parse error at byte 11: syntax error while parsing BSON document: document size 2147483647 exceeds the size of the enclosing document", json::parse_error&); + CHECK(json::from_bson(input, true, false).is_discarded()); + } + + SECTION("embedded document whose size is smaller than its content") + { + // {"a":null} is 8 bytes, but the length prefix claims 7 + std::vector const input = + { + 0x14, 0x00, 0x00, 0x00, // outer size + 0x03, 'd', 0x00, // entry: embedded document "d" + 0x07, 0x00, 0x00, 0x00, // embedded size 7 (real is 8) + 0x0A, 'a', 0x00, // entry: null "a" + 0x00, // embedded end marker + 0x08, 'h', 0x00, 0x01, // entry: bool "h" = true + 0x00 // outer end marker + }; + CHECK_THROWS_WITH_AS(_ = json::from_bson(input), "[json.exception.parse_error.112] parse error at byte 15: syntax error while parsing BSON document: document size 7 does not match the number of bytes read (8)", json::parse_error&); CHECK(json::from_bson(input, true, false).is_discarded()); } @@ -1207,18 +1294,163 @@ TEST_CASE("BSON document size mismatch") // array [42] is 12 bytes, but the length prefix claims 13 std::vector const input = { - 0x00, 0x00, 0x00, 0x00, // outer size + 0x14, 0x00, 0x00, 0x00, // outer size 0x04, 'a', 0x00, // entry: array "a" 0x0D, 0x00, 0x00, 0x00, // array size 13 (real is 12) 0x10, '0', 0x00, 0x2A, 0x00, 0x00, 0x00, // entry: int32 "0" = 42 0x00, // array end marker 0x00 // outer end marker }; - CHECK_THROWS_WITH_AS(_ = json::from_bson(input), "[json.exception.parse_error.112] parse error at byte 19: syntax error while parsing BSON document: document size 13 does not match the number of bytes read (12)", json::parse_error&); + CHECK_THROWS_WITH_AS(_ = json::from_bson(input), "[json.exception.parse_error.112] parse error at byte 11: syntax error while parsing BSON document: document size 13 exceeds the size of the enclosing document", json::parse_error&); CHECK(json::from_bson(input, true, false).is_discarded()); } } +TEST_CASE("BSON declared document size bounds the content") +{ + // records the events that a SAX parser receives + struct recorder : nlohmann::json_sax + { + std::string events; + + bool null() override + { + events += "null "; + return true; + } + bool boolean(bool /*val*/) override + { + events += "boolean "; + return true; + } + bool number_integer(json::number_integer_t /*val*/) override + { + events += "integer "; + return true; + } + bool number_unsigned(json::number_unsigned_t /*val*/) override + { + events += "unsigned "; + return true; + } + bool number_float(json::number_float_t /*val*/, const std::string& /*s*/) override + { + events += "float "; + return true; + } + bool string(std::string& /*val*/) override + { + events += "string "; + return true; + } + bool binary(json::binary_t& /*val*/) override + { + events += "binary "; + return true; + } + bool start_object(std::size_t /*elements*/) override + { + events += "{ "; + return true; + } + bool key(std::string& /*val*/) override + { + events += "key "; + return true; + } + bool end_object() override + { + events += "} "; + return true; + } + bool start_array(std::size_t /*elements*/) override + { + events += "[ "; + return true; + } + bool end_array() override + { + events += "] "; + return true; + } + bool parse_error(std::size_t /*position*/, const std::string& /*last_token*/, const json::exception& /*ex*/) override + { + events += "error "; + return false; + } + }; + + SECTION("the content of a too small document is not reported") + { + // {"a":1} is 12 bytes; the length prefix claims 5, so the element + // does not fit + std::vector const input = + { + 0x05, 0x00, 0x00, 0x00, + 0x10, 'a', 0x00, 0x01, 0x00, 0x00, 0x00, + 0x00 + }; + recorder rec; + CHECK(!json::sax_parse(input, &rec, json::input_format_t::bson)); + CHECK(rec.events == "{ error "); + + // also without strict mode + recorder rec2; + CHECK(!json::sax_parse(input, &rec2, json::input_format_t::bson, false)); + CHECK(rec2.events == "{ error "); + } + + SECTION("an embedded document that does not fit its parent is not opened") + { + // the embedded document ends where the enclosing one does, so there + // is no room for the terminator of the enclosing document + std::vector const input = + { + 0x0C, 0x00, 0x00, 0x00, + 0x03, 'd', 0x00, + 0x05, 0x00, 0x00, 0x00, 0x00, + 0x00 + }; + recorder rec; + CHECK(!json::sax_parse(input, &rec, json::input_format_t::bson)); + CHECK(rec.events == "{ key error "); + } + + SECTION("an embedded document that fits exactly") + { + std::vector const input = + { + 0x0D, 0x00, 0x00, 0x00, + 0x03, 'd', 0x00, + 0x05, 0x00, 0x00, 0x00, 0x00, + 0x00 + }; + CHECK(json::from_bson(input) == json({{"d", json::object()}})); + } + + SECTION("a document size that is less than 5") + { + for (const std::int32_t size : + { + 0, 1, 4, -1, (std::numeric_limits::min)() + }) + { + CAPTURE(size) + std::vector input = + { + 0x00, 0x00, 0x00, 0x00, 0x00 + }; + input[0] = static_cast(size & 0xFF); + input[1] = static_cast((size >> 8) & 0xFF); + input[2] = static_cast((size >> 16) & 0xFF); + input[3] = static_cast((size >> 24) & 0xFF); + json _; + CHECK_THROWS_AS(_ = json::from_bson(input), json::parse_error&); + CHECK(json::from_bson(input, true, false).is_discarded()); + } + } +} + TEST_CASE("BSON nesting does not consume the call stack") { // An embedded document or array used to be read by calling back into the @@ -1923,7 +2155,7 @@ TEST_CASE("Invalid document size handling") { std::vector const v = {0x04, 0x00, 0x00, 0x00, 0x00}; json _; - CHECK_THROWS_WITH_AS(_ = json::from_bson(v), "[json.exception.parse_error.112] parse error at byte 5: syntax error while parsing BSON document: document size 4 does not match the number of bytes read (5)", json::parse_error&); + CHECK_THROWS_WITH_AS(_ = json::from_bson(v), "[json.exception.parse_error.112] parse error at byte 4: syntax error while parsing BSON document: document size 4 is less than 5", json::parse_error&); CHECK(json::from_bson(v, true, false).is_discarded()); } @@ -1938,7 +2170,7 @@ TEST_CASE("Invalid document size handling") 0x00 }; json _; - CHECK_THROWS_WITH_AS(_ = json::from_bson(v), "[json.exception.parse_error.112] parse error at byte 16: syntax error while parsing BSON document: document size 5 does not match the number of bytes read (16)", json::parse_error&); + CHECK_THROWS_WITH_AS(_ = json::from_bson(v), "[json.exception.parse_error.112] parse error at byte 5: syntax error while parsing BSON document: document size 5 is too small for its content", json::parse_error&); CHECK(json::from_bson(v, true, false).is_discarded()); } @@ -1969,7 +2201,7 @@ TEST_CASE("Invalid document size handling") 0x00 // object terminator }; json _; - CHECK_THROWS_WITH_AS(_ = json::from_bson(v), "[json.exception.parse_error.112] parse error at byte 19: syntax error while parsing BSON document: document size 5 does not match the number of bytes read (12)", json::parse_error&); + CHECK_THROWS_WITH_AS(_ = json::from_bson(v), "[json.exception.parse_error.112] parse error at byte 12: syntax error while parsing BSON document: document size 5 is too small for its content", json::parse_error&); CHECK(json::from_bson(v, true, false).is_discarded()); } diff --git a/tests/src/unit-cbor.cpp b/tests/src/unit-cbor.cpp index 9ee371495..44cfa7723 100644 --- a/tests/src/unit-cbor.cpp +++ b/tests/src/unit-cbor.cpp @@ -1807,6 +1807,93 @@ TEST_CASE("CBOR") CHECK(json::from_cbor(std::vector({0xa1, 0xff, 0x01}), true, false).is_discarded()); } + SECTION("tag in front of a key") + { + const auto ignore = json::cbor_tag_handler_t::ignore; + const auto store = json::cbor_tag_handler_t::store; + const json expected = {{"a", 10}}; + + // the tags are skipped in front of a text string key, like in + // front of a value + const std::vector> tagged = + { + {0xA1, 0xC0, 0x61, 0x61, 0x0A}, // tag in the head + {0xA1, 0xD8, 0x05, 0x61, 0x61, 0x0A}, // 1-byte tag number + {0xA1, 0xD9, 0x01, 0x2C, 0x61, 0x61, 0x0A}, // 2-byte tag number + {0xA1, 0xDA, 0x00, 0x01, 0x00, 0x00, 0x61, 0x61, 0x0A}, // 4-byte tag number + {0xA1, 0xDB, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x61, 0x61, 0x0A}, // 8-byte tag number + {0xA1, 0xC0, 0xD8, 0x05, 0xC1, 0x61, 0x61, 0x0A}, // chained tags + {0xBF, 0xC0, 0x61, 0x61, 0x0A, 0xFF}, // indefinite-length map + {0xBF, 0x61, 0x62, 0x01, 0xC0, 0x61, 0x61, 0x0A, 0xFF}, // second key tagged + {0xA1, 0xC0, 0x7F, 0x61, 0x61, 0xFF, 0x0A}, // indefinite-length text key + {0xA1, 0xC0, 0x61, 0x61, 0xC0, 0x0A}, // tagged key and value + }; + + for (const auto& input : tagged) + { + CAPTURE(input) + json const j_ignore = json::from_cbor(input, true, true, ignore); + json const j_store = json::from_cbor(input, true, true, store); + CHECK(j_ignore.at("a") == 10); + CHECK(j_store.at("a") == 10); + CHECK(j_ignore == j_store); + } + CHECK(json::from_cbor(tagged[0], true, true, ignore) == expected); + CHECK(json::from_cbor(tagged[1], true, true, store) == expected); + + // nested maps + CHECK(json::from_cbor(std::vector({0xA1, 0xC0, 0x61, 0x61, 0xA1, 0xD8, 0x05, 0x61, 0x62, 0x01}), true, true, ignore) == json{{"a", {{"b", 1}}}}); + + // the tag handler error still rejects every tag + for (const auto& input : tagged) + { + CAPTURE(input) + json _; + CHECK_THROWS_AS(_ = json::from_cbor(input), json::parse_error&); + } + { + json _; + CHECK_THROWS_WITH_AS(_ = json::from_cbor(std::vector({0xA1, 0xD8, 0x05, 0x61, 0x61, 0x0A})), "[json.exception.parse_error.113] parse error at byte 2: syntax error while parsing CBOR object key: only string keys are supported, but found a tag; last byte: 0xD8", json::parse_error&); + } + + // a tag does not turn a key of another type into a text string + { + json _; + CHECK_THROWS_WITH_AS(_ = json::from_cbor(std::vector({0xA1, 0xC0, 0x01, 0x01}), true, true, ignore), "[json.exception.parse_error.113] parse error at byte 3: syntax error while parsing CBOR object key: only string keys are supported, but found an unsigned integer; last byte: 0x01", json::parse_error&); + CHECK_THROWS_WITH_AS(_ = json::from_cbor(std::vector({0xA1, 0xD8, 0x05, 0x41, 0x61, 0x01}), true, true, store), "[json.exception.parse_error.113] parse error at byte 4: syntax error while parsing CBOR object key: only string keys are supported, but found a byte string; last byte: 0x41", json::parse_error&); + CHECK(json::from_cbor(std::vector({0xA1, 0xC0, 0x01, 0x01}), true, false, ignore).is_discarded()); + } + + // end of input after the tag, or inside the tag number + { + json _; + CHECK_THROWS_WITH_AS(_ = json::from_cbor(std::vector({0xA1, 0xC0}), true, true, ignore), "[json.exception.parse_error.110] parse error at byte 3: syntax error while parsing CBOR string: unexpected end of input", json::parse_error&); + CHECK_THROWS_AS(_ = json::from_cbor(std::vector({0xA1, 0xD8}), true, true, ignore), json::parse_error&); + CHECK_THROWS_AS(_ = json::from_cbor(std::vector({0xA1, 0xDB, 0x00, 0x00}), true, true, store), json::parse_error&); + CHECK_THROWS_AS(_ = json::from_cbor(std::vector({0xBF, 0xC0}), true, true, store), json::parse_error&); + } + + // SAX: the key event is reported without the tag (start_object, + // key, number_unsigned, end_object are four events) + { + const std::vector input = {0xA1, 0xC0, 0x61, 0x61, 0x0A}; + SaxCountdown four(4); + CHECK(json::sax_parse(input, &four, json::input_format_t::cbor, true, false, false, ignore)); + SaxCountdown three(3); + CHECK(!json::sax_parse(input, &three, json::input_format_t::cbor, true, false, false, ignore)); + SaxCountdown error_handler(4); + CHECK(!json::sax_parse(input, &error_handler, json::input_format_t::cbor)); + } + + // a long chain of tags needs no recursion + { + std::vector input = {0xA1}; + input.insert(input.end(), 100000, 0xC0); + input.insert(input.end(), {0x61, 0x61, 0x0A}); + CHECK(json::from_cbor(input, true, true, ignore) == expected); + } + } + SECTION("non-string key (see #2766 and #3381)") { // only text strings map to JSON object keys; any other key is @@ -1915,9 +2002,10 @@ TEST_CASE("CBOR") { json _; - // the chunks are concatenated as is, without checking that each - // chunk is valid UTF-8 on its own (RFC 8949, Section 3.2.3), so - // a code point split across two chunks yields a valid string + // with the default error handler keep, the chunks are concatenated + // as is, without checking that each chunk is valid UTF-8 on its + // own (RFC 8949, Section 3.2.3), so a code point split across two + // chunks yields a valid string CHECK_NOTHROW(_ = json::from_cbor(std::vector({0x7f, 0x61, 0xc3, 0x61, 0xa9, 0xff}))); CHECK(_ == "\xc3\xa9"); CHECK(_.dump() == "\"\xc3\xa9\""); @@ -1937,6 +2025,63 @@ TEST_CASE("CBOR") CHECK(json::from_cbor(std::vector({0x7f, 0x62, 0xc3, 0xa9, 0x62, 0xc3, 0xb6, 0xff})) == "\xc3\xa9\xc3\xb6"); } + SECTION("ill-formed UTF-8 in a chunk of an indefinite-length string (RFC 8949, Section 3.2.3)") + { + const auto strict = json::error_handler_t::strict; + const auto replace = json::error_handler_t::replace; + const auto ignore = json::error_handler_t::ignore; + const auto keep = json::error_handler_t::keep; + const auto tags = json::cbor_tag_handler_t::error; + + // a code point split over two chunks + const std::vector split = {0x7f, 0x61, 0xc3, 0x61, 0xa9, 0xff}; + // a 4-byte code point split over two chunks + const std::vector split_emoji = {0x7f, 0x62, 0xf0, 0x9f, 0x62, 0x98, 0x80, 0xff}; + // the same in an object key + const std::vector split_key = {0xa1, 0x7f, 0x61, 0xc3, 0x61, 0xa9, 0xff, 0x01}; + // a valid chunk followed by one with a truncated code point + const std::vector truncated = {0x7f, 0x62, 0x41, 0xc3, 0x61, 0xa9, 0xff}; + + // strict: the string is invalid + for (const auto& input : + { + split, split_emoji, split_key, truncated + }) + { + CAPTURE(input) + json _; + CHECK_THROWS_AS(_ = json::from_cbor(input, true, true, tags, strict), json::parse_error&); + CHECK(json::from_cbor(input, true, false, tags, strict).is_discarded()); + } + { + json _; + CHECK_THROWS_WITH_AS(_ = json::from_cbor(split, true, true, tags, strict), "[json.exception.parse_error.113] parse error at byte 3: syntax error while parsing CBOR string: invalid string: ill-formed UTF-8 byte", json::parse_error&); + CHECK_THROWS_WITH_AS(_ = json::from_cbor(split_key, true, true, tags, strict), "[json.exception.parse_error.113] parse error at byte 4: syntax error while parsing CBOR key: invalid string: ill-formed UTF-8 byte", json::parse_error&); + } + + // replace and ignore: every chunk is sanitized on its own + CHECK(json::from_cbor(split, true, true, tags, replace) == "\xEF\xBF\xBD\xEF\xBF\xBD"); + CHECK(json::from_cbor(split, true, true, tags, ignore) == ""); + CHECK(json::from_cbor(truncated, true, true, tags, replace) == "A\xEF\xBF\xBD\xEF\xBF\xBD"); + CHECK(json::from_cbor(truncated, true, true, tags, ignore) == "A"); + CHECK(json::from_cbor(split_key, true, true, tags, replace) == json{{"\xEF\xBF\xBD\xEF\xBF\xBD", 1}}); + + // keep: unchanged, the chunks are concatenated as is + CHECK(json::from_cbor(split, true, true, tags, keep) == "\xc3\xa9"); + CHECK(json::from_cbor(split_emoji, true, true, tags, keep) == "\xf0\x9f\x98\x80"); + + // conforming input is unaffected by the error handler + const std::vector valid = {0x7f, 0x62, 0xc3, 0xa9, 0x64, 0xf0, 0x9f, 0x98, 0x80, 0x60, 0xff}; + for (const auto handler : + { + strict, replace, ignore, keep + }) + { + CHECK(json::from_cbor(valid, true, true, tags, handler) == "\xc3\xa9\xf0\x9f\x98\x80"); + CHECK(json::from_cbor(std::vector({0x7f, 0xff}), true, true, tags, handler) == ""); + } + } + SECTION("many chunks in indefinite-length string") { constexpr std::size_t chunks = 100000; diff --git a/tests/src/unit-json_patch.cpp b/tests/src/unit-json_patch.cpp index 1de823952..37962f4b9 100644 --- a/tests/src/unit-json_patch.cpp +++ b/tests/src/unit-json_patch.cpp @@ -15,6 +15,7 @@ using nlohmann::json; #endif #include +#include #include #include #include "make_test_data_available.hpp" @@ -490,7 +491,9 @@ TEST_CASE("JSON patch") } // A.13. Invalid JSON Patch Document - // not applicable + // not applicable: the library cannot tell the duplicate "op" members + // apart, because the parser resolves them (the last one is kept); see + // "duplicate member names in an operation" below SECTION("A.14. Escape Ordering") { @@ -1618,6 +1621,24 @@ TEST_CASE("JSON patch - add to a primitive parent (regression #4292)") #endif } + SECTION("null parent") + { + json const doc = {{"foo", nullptr}}; + json const patch = {{{"op", "add"}, {"path", "/foo/bar"}, {"value", 1}}}; +#if JSON_DIAGNOSTICS + CHECK_THROWS_WITH_AS(doc.patch(patch), "[json.exception.out_of_range.411] (/foo) cannot add value: the JSON Patch 'add' target's parent is of type null, but must be an object or array", json::out_of_range&); +#else + CHECK_THROWS_WITH_AS(doc.patch(patch), "[json.exception.out_of_range.411] cannot add value: the JSON Patch 'add' target's parent is of type null, but must be an object or array", json::out_of_range&); +#endif + } + + SECTION("null document") + { + json const doc = nullptr; + json const patch = {{{"op", "add"}, {"path", "/-"}, {"value", 1}}}; + CHECK_THROWS_WITH_AS(doc.patch(patch), "[json.exception.out_of_range.411] cannot add value: the JSON Patch 'add' target's parent is of type null, but must be an object or array", json::out_of_range&); + } + SECTION("number parent") { json const doc = {{"foo", 1}}; @@ -2206,3 +2227,204 @@ TEST_CASE_TEMPLATE("JSON patch - diff round trip on hand-made documents", Json, roundtrip(Json(1), Json::parse("[1]")); } } + +TEST_CASE("JSON patch - behavior documented in the API reference") +{ + SECTION("duplicate member names in an operation") + { + // RFC 6902, A.13: the parser resolves the duplicates (the last one + // is kept), so this is a "remove" and not an "add" + const json patch = json::parse(R"([{"op": "add", "op": "remove", "path": "/a"}])"); + CHECK(patch == json::parse(R"([{"op": "remove", "path": "/a"}])")); + CHECK(json({{"a", 1}, {"b", 2}}).patch(patch) == json({{"b", 2}})); + } + + SECTION("add with a leading zero in an array index") + { + const json patch = R"([{"op": "add", "path": "/01", "value": 1}])"_json; + json doc = json::array(); +#if !defined(JSON_NOEXCEPTION) + CHECK_THROWS_AS(doc.patch(patch), json::parse_error&); + CHECK_THROWS_AS(doc.patch_inplace(patch), json::parse_error&); +#endif + CHECK(doc == json::array()); + } + + SECTION("test compares numbers by value") + { + const json doc = R"({"a": 1, "b": 0, "c": 1.5})"_json; + CHECK(doc.patch(R"([{"op": "test", "path": "/a", "value": 1.0}])"_json) == doc); + CHECK(doc.patch(R"([{"op": "test", "path": "/b", "value": -0.0}])"_json) == doc); + CHECK(doc.patch(R"([{"op": "test", "path": "/c", "value": 1.5}])"_json) == doc); + CHECK_THROWS_AS(doc.patch(R"([{"op": "test", "path": "/a", "value": 2}])"_json), json::other_error&); + CHECK_THROWS_AS(doc.patch(R"([{"op": "test", "path": "/a", "value": "1"}])"_json), json::other_error&); + } + + SECTION("test ignores the order of object members, but not the order of array elements") + { + const json doc = R"({"o": {"x": 1, "y": 2}, "a": [1, 2]})"_json; + CHECK(doc.patch(R"([{"op": "test", "path": "/o", "value": {"y": 2, "x": 1}}])"_json) == doc); + CHECK(doc.patch(R"([{"op": "test", "path": "/a", "value": [1, 2]}])"_json) == doc); + CHECK_THROWS_AS(doc.patch(R"([{"op": "test", "path": "/a", "value": [2, 1]}])"_json), json::other_error&); + } + + SECTION("test on a path that does not exist") + { + const json doc = R"({"a": 1})"_json; + // with a "value" member, the failed lookup is a failed test + CHECK_THROWS_AS(doc.patch(R"([{"op": "test", "path": "/b", "value": 1}])"_json), json::other_error&); + CHECK_THROWS_AS(doc.patch(R"([{"op": "test", "path": "/b", "value": null}])"_json), json::other_error&); + // without it, an exception is thrown, too; which one depends on the + // order of evaluation of the operands of ==, so it is not checked + CHECK_THROWS(doc.patch(R"([{"op": "test", "path": "/b"}])"_json)); + } + + SECTION("patch() has the strong guarantee") + { + const json doc = R"({"a": [1, 2], "o": {"k": "v"}})"_json; + const json before = doc; + + const std::vector failing = + { + // remove of a member that does not exist + R"([{"op": "add", "path": "/n", "value": 1}, {"op": "remove", "path": "/missing"}])"_json, + // replace of a member that does not exist + R"([{"op": "remove", "path": "/a/0"}, {"op": "replace", "path": "/missing", "value": 1}])"_json, + // move from a member that does not exist + R"([{"op": "add", "path": "/n", "value": 1}, {"op": "move", "from": "/missing", "path": "/m"}])"_json, + // copy from a member that does not exist + R"([{"op": "add", "path": "/n", "value": 1}, {"op": "copy", "from": "/missing", "path": "/m"}])"_json, + // out-of-range array index + R"([{"op": "add", "path": "/n", "value": 1}, {"op": "add", "path": "/a/5", "value": 1}])"_json, + // failing test after a successful operation + R"([{"op": "remove", "path": "/o"}, {"op": "test", "path": "/a", "value": []}])"_json, + }; + for (const auto& patch : failing) + { + CAPTURE(patch.dump()) + CHECK_THROWS_AS(doc.patch(patch), json::exception&); + CHECK(doc == before); + + json copy = doc; + CHECK_THROWS_AS(copy.patch(patch), json::exception&); + CHECK(copy == before); + } + } +} + +TEST_CASE("JSON patch - diff behavior documented in the API reference") +{ + SECTION("path is a verbatim prefix") + { + // not escaped and not validated + CHECK(json::diff(1, 2) == R"([{"op": "replace", "path": "", "value": 2}])"_json); + CHECK(json::diff(1, 2, "abc") == R"([{"op": "replace", "path": "abc", "value": 2}])"_json); + CHECK(json::diff(1, 2, "/root") == R"([{"op": "replace", "path": "/root", "value": 2}])"_json); + // an escaped token in a non-root prefix is kept as given + CHECK(json::diff(R"({"a": 1})"_json, R"({"a": 2})"_json, "/x~1y/~0") + == R"([{"op": "replace", "path": "/x~1y/~0/a", "value": 2}])"_json); + CHECK(json::diff(R"({"a": 1})"_json, R"({"a": 2})"_json, "/p") + == R"([{"op": "replace", "path": "/p/a", "value": 2}])"_json); + } + + SECTION("keys are escaped in the paths") + { + const json source = json::object(); + const json target = + { + {"", 0}, {"/", 1}, {"~", 2}, {"~1", 3}, {"~01", 4} + }; + const json patch = json::diff(source, target); + REQUIRE(patch.size() == 5); + + // the order of the operations is not checked + json paths = json::object(); + for (const auto& op : patch) + { + CHECK(op["op"] == "add"); + paths[op["path"].get()] = op["value"]; + } + CHECK(paths == json({{"/", 0}, {"/~1", 1}, {"/~0", 2}, {"/~01", 3}, {"/~001", 4}})); + CHECK(source.patch(patch) == target); + } + + SECTION("NaN is not equal to itself") + { + const json nan = std::numeric_limits::quiet_NaN(); + const json patch = json::diff(nan, nan); + CHECK(patch.size() == 1); + CHECK(patch[0]["op"] == "replace"); + CHECK(patch[0]["path"] == ""); + CHECK(patch[0]["value"].is_number_float()); + CHECK(nan.patch(patch) != nan); + + // a NaN in a patch is serialized as null + CHECK(json::parse(patch.dump())[0]["value"].is_null()); + + const json nested = {{"a", {1, nan}}}; + const json nested_patch = json::diff(nested, nested); + CHECK(nested_patch.size() == 1); + CHECK(nested_patch[0]["path"] == "/a/1"); + + // infinity is equal to itself + const json inf = std::numeric_limits::infinity(); + CHECK(json::diff(inf, inf).empty()); + } + + SECTION("discarded values are not equal to themselves") + { + const json discarded(json::value_t::discarded); + const json patch = json::diff(discarded, discarded); + CHECK(patch.size() == 1); + CHECK(patch[0]["op"] == "replace"); + CHECK(patch[0]["path"] == ""); + CHECK(patch[0]["value"].is_discarded()); + CHECK(discarded.patch(patch) != discarded); + } + + SECTION("numbers that compare equal produce no operation") + { + CHECK(json::diff(json(1), json(1.0)).empty()); + CHECK(json::diff(json(1.0), json(1)).empty()); + CHECK(json::diff(json(1U), json(1)).empty()); + CHECK(json::diff(json(1), json(1U)).empty()); + CHECK(json::diff(json(0.0), json(-0.0)).empty()); + + // the patched value keeps the number type of the source + const json source = 1; + const json patched = source.patch(json::diff(source, json(1.0))); + CHECK(patched.is_number_integer()); + CHECK(patched == json(1.0)); + + // different values are replaced + CHECK(json::diff(json(1), json(2.0)) == R"([{"op": "replace", "path": "", "value": 2.0}])"_json); + CHECK(json::diff(json(1), json(-1)) == R"([{"op": "replace", "path": "", "value": -1}])"_json); + CHECK(json::diff(json(1.0), json(1.5)) == R"([{"op": "replace", "path": "", "value": 1.5}])"_json); + CHECK(json::diff(json(1), json("1")) == R"([{"op": "replace", "path": "", "value": "1"}])"_json); + } + + SECTION("binary values") + { + const json a = json::binary({1, 2}); + const json b = json::binary({1, 3}); + CHECK(json::diff(a, a).empty()); + + const json patch = json::diff(a, b); + CHECK(patch.size() == 1); + CHECK(patch[0]["op"] == "replace"); + CHECK(patch[0]["path"] == ""); + CHECK(patch[0]["value"].is_binary()); + CHECK(a.patch(patch) == b); + + // serialized, the binary value is an object + const json parsed = json::parse(patch.dump()); + CHECK(parsed[0]["value"].is_object()); + CHECK(parsed[0]["value"] == R"({"bytes": [1, 3], "subtype": null})"_json); + } + + SECTION("a difference as a whole is a replace of the root") + { + CHECK(json::diff(json::array({1}), json::object()) == R"([{"op": "replace", "path": "", "value": {}}])"_json); + CHECK(json::diff(json(nullptr), json::array()) == R"([{"op": "replace", "path": "", "value": []}])"_json); + } +} diff --git a/tests/src/unit-json_pointer.cpp b/tests/src/unit-json_pointer.cpp index f874c9691..095f29ea5 100644 --- a/tests/src/unit-json_pointer.cpp +++ b/tests/src/unit-json_pointer.cpp @@ -493,6 +493,72 @@ TEST_CASE("JSON pointers") } } + SECTION("creating intermediate levels") + { + SECTION("tokens that are valid array indices create arrays") + { + json j; + j["/0"_json_pointer] = 1; + CHECK(j == json({1})); + + json j2; + j2["/2"_json_pointer] = 1; + CHECK(j2 == json({nullptr, nullptr, 1})); + + json j3; + j3["/-"_json_pointer] = 1; + CHECK(j3 == json({1})); + + json j4; + j4["/foo/0/0"_json_pointer] = 1; + CHECK(j4 == json({{"foo", {{1}}}})); + } + + SECTION("tokens that are no valid array indices create objects") + { + json j; + j["/one"_json_pointer] = 1; + CHECK(j == json({{"one", 1}})); + + // leading '0' can never be a valid array index (RFC 6901, Sect. 4) + json j2; + j2["/01"_json_pointer] = 1; + CHECK(j2 == json({{"01", 1}})); + + // the empty token is a valid object key, but no valid array index + json j3; + j3["/"_json_pointer] = 1; + CHECK(j3 == json({{"", 1}})); + } + + SECTION("creating a level yields the same result as reusing it (#5357)") + { + json j; + j["/a/b/01/d"_json_pointer] = "value"; + + json j_init = json::object(); + j_init["/a/b"_json_pointer] = json::object(); + j_init["/a/b/01/d"_json_pointer] = "value"; + + const json expected = json::parse(R"({"a":{"b":{"01":{"d":"value"}}}})"); + CHECK(j == expected); + CHECK(j_init == expected); + + // unflatten uses the same key + const json flat = {{"/a/b/01/d", "value"}}; + CHECK(flat.unflatten() == expected); + } + + SECTION("existing arrays still reject invalid indices") + { + json j = {1, 2, 3}; + CHECK_THROWS_WITH_AS(j["/01"_json_pointer], + "[json.exception.parse_error.106] parse error: array index '01' must not begin with '0'", json::parse_error&); + CHECK_THROWS_WITH_AS(j.at("/01"_json_pointer), + "[json.exception.parse_error.106] parse error: array index '01' must not begin with '0'", json::parse_error&); + } + } + SECTION("flatten") { json j = @@ -1082,6 +1148,56 @@ TEST_CASE("flatten of structured values") CHECK(flat_object.unflatten() == nlohmann::ordered_json::parse(R"({"b": {"1": 2}})")); } +#if !defined(JSON_NOEXCEPTION) + SECTION("an object with the key \"0\" next to a non-index key does not round-trip") + { + // the third documented case in which j != j.flatten().unflatten() + const json value = {{"a", {{"0", 1}, {"b", 2}}}}; + const json flat = value.flatten(); + CHECK(flat == json({{"/a/0", 1}, {"/a/b", 2}})); + CHECK_THROWS_WITH_AS(flat.unflatten(), + "[json.exception.parse_error.109] parse error: array index 'b' is not a number", json::parse_error&); + + // the same for other keys that are not valid array indices + CHECK_THROWS_WITH_AS(json({{"/a/0", 1}, {"/a/1a", 2}}).unflatten(), + "[json.exception.out_of_range.404] unresolved reference token '1a'", json::out_of_range&); + CHECK_THROWS_WITH_AS(json({{"/a/0", 1}, {"/a/-", 2}}).unflatten(), + "[json.exception.out_of_range.404] unresolved reference token '-'", json::out_of_range&); + } + + SECTION("unflatten with the empty token among array indices") + { + CHECK_THROWS_WITH_AS(json({{"/a/0", 1}, {"/a/", 2}}).unflatten(), + "[json.exception.out_of_range.404] unresolved reference token ''", json::out_of_range&); + CHECK_THROWS_WITH_AS(json({{"/a/0", 1}, {"/a/1", 2}, {"/a/", 3}}).unflatten(), + "[json.exception.out_of_range.404] unresolved reference token ''", json::out_of_range&); + + // without an index "0", the empty token is an ordinary object key + CHECK(json({{"/a/", 2}}).unflatten() == json({{"a", {{"", 2}}}})); + } + + SECTION("value() with the empty pointer on a primitive root") + { + // unlike at(), value() rejects primitive values before it looks at the pointer + const json number = 1; + CHECK(number.at(""_json_pointer) == 1); + CHECK_THROWS_WITH_AS(number.value(""_json_pointer, 7), + "[json.exception.type_error.306] cannot use value() with number", json::type_error&); + + const json string = "s"; + CHECK_THROWS_WITH_AS(string.value(""_json_pointer, 7), + "[json.exception.type_error.306] cannot use value() with string", json::type_error&); + + // contains() accepts the empty pointer on every value + CHECK(number.contains(""_json_pointer)); + CHECK_FALSE(number.contains("/a"_json_pointer)); + + // arrays and objects return the default for a missing location + CHECK(json::array().value(""_json_pointer, json(7)) == json::array()); + CHECK(json::array().value("/0"_json_pointer, 7) == 7); + } +#endif + SECTION("objects and arrays interleaved") { const json value = diff --git a/tests/src/unit-json_view_builder.cpp b/tests/src/unit-json_view_builder.cpp index 6935981fb..1074f7018 100644 --- a/tests/src/unit-json_view_builder.cpp +++ b/tests/src/unit-json_view_builder.cpp @@ -592,7 +592,7 @@ TEST_CASE("json_view node integer bits") }) { CAPTURE(v) - n.kind = 0x5A; + n.kind = static_cast(0x5A); n.flags = 0xA5; n.extra = 0x1234; n.off = 0x89ABCDEFu; @@ -601,7 +601,7 @@ TEST_CASE("json_view node integer bits") CHECK(n.len == static_cast(v)); CHECK(n.next == static_cast(v >> 32)); // the other fields are untouched - CHECK(n.kind == 0x5A); + CHECK(static_cast(n.kind) == 0x5A); CHECK(n.flags == 0xA5); CHECK(n.extra == 0x1234); CHECK(n.off == 0x89ABCDEFu); diff --git a/tests/src/unit-json_view_edit_values.cpp b/tests/src/unit-json_view_edit_values.cpp index 59aee3a9d..39db8d0ba 100644 --- a/tests/src/unit-json_view_edit_values.cpp +++ b/tests/src/unit-json_view_edit_values.cpp @@ -26,12 +26,119 @@ using nlohmann::json_editable_view; #include #include #include +#include #include #include // These tests were split off unit-json_view_edit.cpp, whose object file got too // large for the MinGW linker (see json_view_test_helpers.hpp). +namespace +{ +// the three ways a function can accept views: editable ones, read-only ones, or both +template +int accepts_editable(nlohmann::basic_json_view /*unused*/) +{ + return 1; +} + +template +int accepts_read_only(nlohmann::basic_json_view /*unused*/) +{ + return 2; +} + +template +int accepts_any(nlohmann::basic_json_view /*unused*/) +{ + return Mode == nlohmann::view_mode::editable ? 3 : 4; +} + +template +auto overload(View v) -> decltype(accepts_editable(v)) +{ + return accepts_editable(v); +} + +template +auto overload(View v) -> decltype(accepts_read_only(v)) +{ + return accepts_read_only(v); +} + +// a function (no template) that takes an editable view also accepts a read-only one +std::size_t sum_of(json_editable_view v) +{ + std::size_t sum = 0; + for (const json_editable_view x : v.at("values")) + { + sum += x.get(); + } + return sum; +} +} // namespace + +TEST_CASE("json_view modes") +{ + using nlohmann::basic_json_document; + using nlohmann::basic_json_view; + using nlohmann::ordered_json; + using nlohmann::view_mode; + + SECTION("read-only is the default, and the shortcuts name the modes") + { + static_assert(std::is_same, basic_json_view>::value, "default mode"); + static_assert(std::is_same, basic_json_document>::value, "default mode"); + static_assert(std::is_same>::value, "json_view"); + static_assert(std::is_same>::value, "json_editable_view"); + static_assert(std::is_same>::value, "json_editable_document"); + static_assert(std::is_same>::value, "ordered_json_editable_view"); + static_assert(std::is_same::value, "view_type"); + static_assert(std::is_same::value, "view_type"); + } + + SECTION("a function template selects views by their mode") + { + const json_document d = json_document::parse("[1]"); + const json_editable_document e = json_editable_document::parse("[1]"); + const nlohmann::ordered_json_editable_document o = nlohmann::ordered_json_editable_document::parse("[1]"); + CHECK(overload(e.root()) == 1); + CHECK(overload(o.root()) == 1); + CHECK(overload(d.root()) == 2); + CHECK(accepts_any(e.root()) == 3); + CHECK(accepts_any(d.root()) == 4); + } + + SECTION("a view of a read-only document converts to an editable view, not the other way") + { + static_assert(std::is_convertible::value, "read-only to editable"); + static_assert(!std::is_convertible::value, "not editable to read-only"); + static_assert(!std::is_convertible::value, "same BasicJsonType only"); + + const json_document d = json_document::parse(R"({"values": [1, 2, 3], "name": "a"})"); + json_editable_document e = json_editable_document::parse(R"({"values": [1, 2, 3], "name": "a"})"); + CHECK(sum_of(d.root()) == 6); + CHECK(sum_of(e.root()) == 6); + + const json_editable_view v = d.root(); + CHECK(v.size() == 2); + CHECK(v.at("name").get() == "a"); + CHECK(v.source_offset() == d.root().source_offset()); + CHECK((v == e.root())); + CHECK((v == d.root())); + + e.push_back(e.root().at("values"), 4); + CHECK(sum_of(e.root()) == 10); + CHECK((v != e.root())); + +#if !defined(JSON_NOEXCEPTION) + // the converted view still belongs to the read-only document + CHECK_THROWS_WITH_AS(e.push_back(v.at("values"), 5), "[json.exception.invalid_iterator.202] view does not belong to this document", json::invalid_iterator&); +#endif + CHECK(sum_of(d.root()) == 6); + } +} + TEST_CASE("json_view edits: views and values") { SECTION("a value that is no longer part of the document") @@ -100,7 +207,7 @@ TEST_CASE("json_view edits: views and values") // doubles have their own path to the output; other float types are // written as basic_json writes them, non-finite values as null using json_float = nlohmann::basic_json; - using document_float = nlohmann::basic_json_document; + using document_float = nlohmann::basic_json_document; document_float d = document_float::parse("[1.5]"); d.push_back(d.root(), std::numeric_limits::quiet_NaN()); d.push_back(d.root(), -std::numeric_limits::infinity()); diff --git a/tests/src/unit-json_view_from_json.cpp b/tests/src/unit-json_view_from_json.cpp new file mode 100644 index 000000000..ec296e5db --- /dev/null +++ b/tests/src/unit-json_view_from_json.cpp @@ -0,0 +1,449 @@ +// __ _____ _____ _____ +// __| | __| | | | JSON for Modern C++ (supporting code) +// | | |__ | | | | | | version 3.12.0 +// |_____|_____|_____|_|___| https://github.com/nlohmann/json +// +// SPDX-FileCopyrightText: 2013-2026 Niels Lohmann +// SPDX-License-Identifier: MIT + +#include "doctest_compatibility.h" + +#include +using nlohmann::json; +using nlohmann::json_document; +using nlohmann::json_view; +using nlohmann::json_editable_document; +using nlohmann::ordered_json_document; + +#include "json_view_test_helpers.hpp" +#if !defined(JSON_NOEXCEPTION) + using json_view_test::exception_of_call; +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef JSON_HAS_CPP_17 + #include +#endif + +// The from_json() functions of the NLOHMANN_DEFINE_TYPE_* and +// NLOHMANN_JSON_SERIALIZE_ENUM macros read views directly; other from_json() +// functions get the value of materialize(). + +namespace view_from_json +{ +struct point +{ + int x = 0; + int y = 0; + NLOHMANN_DEFINE_TYPE_INTRUSIVE(point, x, y) + + friend bool operator==(const point& a, const point& b) + { + return a.x == b.x && a.y == b.y; + } +}; + +struct shape +{ + std::string name; + std::vector points; + point center; + std::map named; + NLOHMANN_DEFINE_TYPE_INTRUSIVE(shape, name, points, center, named) +}; + +struct with_defaults +{ + int a = 1; + std::string b = "default"; + std::vector c = {1, 2}; +}; +NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_WITH_DEFAULT(with_defaults, a, b, c) + +struct with_names +{ + int value = 0; + std::string text; +}; +NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_WITH_NAMES(with_names, "Value", value, "Text", text) + +struct empty +{ + NLOHMANN_DEFINE_TYPE_INTRUSIVE(empty) +}; + +enum class color +{ + red, + green, + blue +}; +NLOHMANN_JSON_SERIALIZE_ENUM(color, +{ + {color::red, "red"}, + {color::green, "green"}, + {color::blue, nullptr}, +}) + +enum class strict_color +{ + red, + green +}; +NLOHMANN_JSON_SERIALIZE_ENUM_STRICT(strict_color, +{ + {strict_color::red, "red"}, + {strict_color::green, 1}, +}) + +struct base +{ + int id = 0; + NLOHMANN_DEFINE_TYPE_INTRUSIVE(base, id) +}; + +struct derived : base +{ + color c = color::red; + NLOHMANN_DEFINE_DERIVED_TYPE_INTRUSIVE(derived, base, c) +}; + +struct derived_with_defaults : base +{ + std::string label = "none"; + NLOHMANN_DEFINE_DERIVED_TYPE_INTRUSIVE_WITH_DEFAULT(derived_with_defaults, base, label) +}; + +// a base whose from_json() is written for json only: the derived type reads +// its own members from the view and the base part from materialize() +struct legacy_base +{ + int id = 0; +}; +// NOLINTNEXTLINE(misc-use-internal-linkage): found by ADL, like the from_json() functions of users +inline void from_json(const json& j, legacy_base& b) +{ + b.id = j.at("id").get(); +} + +struct derived_from_legacy : legacy_base +{ + int extra = 0; +}; +NLOHMANN_DEFINE_DERIVED_TYPE_NON_INTRUSIVE(derived_from_legacy, legacy_base, extra) + +// from_json() for json only: converted through materialize() +struct legacy +{ + int v = 0; +}; +// NOLINTNEXTLINE(misc-use-internal-linkage): found by ADL, like the from_json() functions of users +inline void from_json(const json& j, legacy& l) +{ + l.v = j.at("v").get(); +} + +// an unconstrained from_json() template, as in the documentation of +// arbitrary type conversions: converted through materialize() +struct unconstrained +{ + int v = 0; +}; +template +// NOLINTNEXTLINE(misc-use-internal-linkage): found by ADL, like the from_json() functions of users +void from_json(const BasicJsonType& j, unconstrained& u) +{ + u.v = j.at("v").template get(); +} + +// a from_json() constrained by the user to views and json: reads the view +struct constrained +{ + int v = 0; +}; +template < typename J, typename std::enable_if < nlohmann::is_json_view::value || std::is_same::value, int >::type = 0 > +// NOLINTNEXTLINE(misc-use-internal-linkage): found by ADL, like the from_json() functions of users +void from_json(const J& j, constrained& c) +{ + c.v = j.at("v").template get(); +} +} // namespace view_from_json + +namespace +{ +template +struct reads_view : nlohmann::detail::has_view_from_json, View, T> {}; + +// whether a member converts to the same T from the view and from the materialized value +template +bool same(const json_view& v, const json& j, const char* key) +{ + return v[key].get() == j[key].get(); +} +} // namespace + +TEST_CASE("json_view from_json") +{ + using namespace view_from_json; // NOLINT(google-build-using-namespace) + + SECTION("which from_json() functions read views") + { + static_assert(reads_view::value, "macro types read views"); + static_assert(reads_view::value, "macro types read views"); + static_assert(reads_view::value, "macro types read views"); + static_assert(reads_view::value, "macro types read views"); + static_assert(reads_view::value, "macro types read views"); + static_assert(reads_view::value, "macro types read views"); + static_assert(reads_view::value, "macro types read views"); + static_assert(reads_view::value, "enum macro types read views"); + static_assert(reads_view::value, "enum macro types read views"); + static_assert(reads_view::value, "constrained from_json() reads views"); + static_assert(reads_view::value, "editable views as well"); + static_assert(reads_view::value, "views of ordered_json as well"); + static_assert(!reads_view::value, "from_json() for json"); + static_assert(!reads_view::value, "from_json() for json"); + static_assert(!reads_view::value, "unconstrained from_json()"); + + static_assert(nlohmann::is_json_view::value, "is_json_view"); + static_assert(nlohmann::is_json_view::value, "is_json_view"); + static_assert(!nlohmann::is_json_view::value, "is_json_view"); + static_assert(!nlohmann::is_json_view::value, "is_json_view"); + static_assert(!nlohmann::is_json_view::value, "is_json_view"); + } + + SECTION("the library's own conversions read views") + { + static_assert(reads_view>::value, "std types"); + static_assert(reads_view>::value, "std types"); + static_assert(reads_view>::value, "std types"); + static_assert(reads_view>::value, "std types"); + static_assert(reads_view>::value, "std types"); + static_assert(reads_view>::value, "std types"); + static_assert(reads_view>::value, "std types"); + static_assert(reads_view>::value, "std types"); + static_assert(reads_view>::value, "std types"); + static_assert(reads_view>::value, "std types"); + static_assert(reads_view>::value, "std types"); + static_assert(reads_view>::value, "std types"); + static_assert(reads_view>::value, "std types"); + static_assert(reads_view>::value, "std types"); + static_assert(reads_view>::value, "std types"); + static_assert(reads_view>::value, "std types"); + static_assert(reads_view::value, "std types"); + static_assert(reads_view::value, "std types"); + static_assert(reads_view::value, "std types"); + static_assert(reads_view::value, "std types"); + static_assert(reads_view::value, "std types"); + static_assert(!reads_view::value, "get() is materialize()"); + static_assert(reads_view::value, "std types"); +#ifdef JSON_HAS_CPP_17 + static_assert(reads_view>::value, "std types"); +#endif + CHECK(true); + } + + SECTION("std types: the same values as from the materialized value") + { + const json_document d = json_document::parse(R"({"ints": [3, 1, 2, 1], "strings": ["b", "a", "b"], "pairs": [[1, "x"], [2, "y"]], + "points": [{"x": 1, "y": 2}, {"x": 3, "y": 4}], "object": {"b": 1, "a": 2}, "triple": [1, true, {"x": 5, "y": 6}], + "colors": {"green": 1, "red": 2}, "bytes": [1, 2, 255], "nested": [[1, 2], [3]], "number": 1.5, "flag": true, + "text": "hello", "nothing": null, "mixed": [1, 2.5, -3, 4], "flags": [true, false, true]})"); + const json j = d.root().materialize(); + const json_view v = d.root(); + CHECK(same>(v, j, "ints")); + CHECK(same>(v, j, "ints")); + CHECK(same>(v, j, "ints")); + CHECK(same>(v, j, "ints")); + CHECK(same>(v, j, "strings")); + CHECK(same>(v, j, "ints")); + CHECK(same>(v, j, "ints")); + CHECK(same>(v, j, "mixed")); + CHECK((v["pairs"][0].get>() == j["pairs"][0].get>())); + CHECK(same>>(v, j, "pairs")); + CHECK(same>(v, j, "triple")); + CHECK(same>(v, j, "pairs")); + CHECK(same>(v, j, "pairs")); + CHECK(same>(v, j, "object")); + CHECK(same>(v, j, "object")); + CHECK(same>(v, j, "colors")); + CHECK(same>(v, j, "points")); + CHECK(same>(v, j, "ints")); + CHECK(same>(v, j, "flags")); + CHECK(same>(v, j, "bytes")); + CHECK(same>>(v, j, "nested")); + CHECK(same>(v, j, "mixed")); + CHECK(same(v, j, "number")); + CHECK(same(v, j, "flag")); + CHECK(same(v, j, "text")); + CHECK(same(v, j, "nothing")); + CHECK(same(v, j, "object")); + const std::valarray va = v["ints"].get>(); + CHECK(va.size() == 4); + CHECK(va[0] == 3); + int c_array[2][2] = {{0, 0}, {0, 0}}; // NOLINT(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays): the conversion to C arrays + v["nested"][0].get_to(c_array[0]); + CHECK(c_array[0][1] == 2); +#ifdef JSON_HAS_CPP_17 + CHECK(v["points"][0].get>()->y == 2); + CHECK(!v["nothing"].get>().has_value()); +#endif + // containers of views keep views of the elements + const std::vector views = v["points"].get>(); + CHECK(views.size() == 2); + CHECK(views[1]["x"].get() == 3); + CHECK(v["object"].get>().at("a").get() == 2); + } + + SECTION("std types: duplicate keys keep the last value, as from the materialized value") + { + const json_document d = json_document::parse(R"({"a": 1, "b": 2, "a": 3})"); + CHECK((d.root().get>() == d.root().materialize().get>())); + CHECK(d.root().get>().at("a") == 3); + CHECK(d.root().get>().at("a") == 3); + } + +#if !defined(JSON_NOEXCEPTION) && !JSON_DIAGNOSTICS + SECTION("std types: the same errors as from the materialized value") + { + const json_document d = json_document::parse(R"([[1, "x"], {"a": 1}, "text", [1], 5, null])"); + const json j = d.root().materialize(); + for (std::size_t i = 0; i < 6; ++i) + { + CAPTURE(i) + const json_view v = d.root()[i]; + CHECK(exception_of_call([&] { static_cast(v.get>()); }) == exception_of_call([&] { static_cast(j[i].get>()); })); + CHECK(exception_of_call([&] { static_cast(v.get>()); }) == exception_of_call([&] { static_cast(j[i].get>()); })); + CHECK(exception_of_call([&] { static_cast(v.get>()); }) == exception_of_call([&] { static_cast(j[i].get>()); })); + CHECK(exception_of_call([&] { static_cast(v.get>()); }) == exception_of_call([&] { static_cast(j[i].get>()); })); + CHECK(exception_of_call([&] { static_cast(v.get>()); }) == exception_of_call([&] { static_cast(j[i].get>()); })); + CHECK(exception_of_call([&] { static_cast(v.get>()); }) == exception_of_call([&] { static_cast(j[i].get>()); })); + CHECK(exception_of_call([&] { static_cast(v.get>()); }) == exception_of_call([&] { static_cast(j[i].get>()); })); + CHECK(exception_of_call([&] { static_cast(v.get()); }) == exception_of_call([&] { static_cast(j[i].get()); })); + CHECK(exception_of_call([&] { static_cast(v.get>()); }) == exception_of_call([&] { static_cast(j[i].get>()); })); + } + } +#endif + + SECTION("macro types") + { + const json_document d = json_document::parse(R"({"name": "s", "points": [{"x": 1, "y": 2}, {"x": 3, "y": 4}], + "center": {"x": 5, "y": 6}, "named": {"a": {"x": 7, "y": 8}}})"); + const shape s = d.root().get(); + CHECK(s.name == "s"); + CHECK(s.points.size() == 2); + CHECK(s.points[1].y == 4); + CHECK(s.center.x == 5); + CHECK(s.named.at("a").y == 8); + + point p; + d.root()["center"].get_to(p); + CHECK(p.x == 5); + CHECK(p.y == 6); + + static_cast(d.root()["center"].get()); + } + + SECTION("defaults and names") + { + const json_document d = json_document::parse(R"({"a": 5, "Value": 3, "Text": "t"})"); + const with_defaults w = d.root().get(); + CHECK(w.a == 5); + CHECK(w.b == "default"); + CHECK(w.c == std::vector {1, 2}); + const json_document null_document = json_document::parse("null"); + CHECK(null_document.root().get().a == 1); + const with_names n = d.root().get(); + CHECK(n.value == 3); + CHECK(n.text == "t"); + } + + SECTION("enums") + { + const json_document d = json_document::parse(R"(["green", null, "purple", 1, "red"])"); + CHECK(d.root()[0].get() == color::green); + CHECK(d.root()[1].get() == color::blue); + CHECK(d.root()[2].get() == color::red); // unknown values map to the first entry + CHECK(d.root()[3].get() == strict_color::green); + CHECK(d.root()[4].get() == strict_color::red); +#if !defined(JSON_NOEXCEPTION) + const json j = d.root().materialize(); + CHECK(exception_of_call([&] { static_cast(d.root()[2].get()); }) == "[json.exception.out_of_range.410] enum value out of range for strict_color: \"purple\""); +#if !JSON_DIAGNOSTICS + CHECK(exception_of_call([&] { static_cast(d.root()[2].get()); }) == exception_of_call([&] { static_cast(j[2].get()); })); +#endif +#endif + } + + SECTION("derived types") + { + const json_document d = json_document::parse(R"({"id": 7, "c": "green", "extra": 3})"); + const derived x = d.root().get(); + CHECK(x.id == 7); + CHECK(x.c == color::green); + const derived_with_defaults y = d.root().get(); + CHECK(y.id == 7); + CHECK(y.label == "none"); + const derived_from_legacy z = d.root().get(); + CHECK(z.id == 7); + CHECK(z.extra == 3); + } + + SECTION("other from_json() functions get the value of materialize()") + { + const json_document d = json_document::parse(R"({"v": 4})"); + CHECK(d.root().get().v == 4); + CHECK(d.root().get().v == 4); + CHECK(d.root().get().v == 4); + legacy l; + d.root().get_to(l); + CHECK(l.v == 4); + } + + SECTION("duplicate keys: members are looked up in the view, where the first one wins") + { + const json_document d = json_document::parse(R"({"x": 1, "y": 2, "x": 3})"); + CHECK(d.root().get().x == 1); + // materialize() keeps the last value, like parse() + CHECK(d.root().materialize().get().x == 3); + } + + SECTION("editable documents and ordered_json") + { + json_editable_document e = json_editable_document::parse(R"({"x": 1, "y": 2})"); + e.set(e.root(), "x", 10); + CHECK(e.root().get().x == 10); + const ordered_json_document o = ordered_json_document::parse(R"({"name": "o", "points": [], "center": {"x": 1, "y": 2}, "named": {}})"); + CHECK(o.root().get().center.y == 2); + } + +#if !defined(JSON_NOEXCEPTION) && !JSON_DIAGNOSTICS + SECTION("errors are those of the same conversion of a json value") + { + // (with JSON_DIAGNOSTICS, those of json name the path of the value) + const json_document d = json_document::parse(R"([{"x": 1}, {"x": "1", "y": 2}, [1, 2], {"id": "7", "c": "red"}])"); + const json j = d.root().materialize(); + for (std::size_t i = 0; i < 3; ++i) + { + CAPTURE(i) + CHECK(exception_of_call([&] { static_cast(d.root()[i].get()); }) == exception_of_call([&] { static_cast(j[i].get()); })); + } + CHECK(exception_of_call([&] { static_cast(d.root()[3].get()); }) == exception_of_call([&] { static_cast(j[3].get()); })); + } +#endif +} diff --git a/tests/src/unit-json_view_image.cpp b/tests/src/unit-json_view_image.cpp index eaf27eb9f..ff1713371 100644 --- a/tests/src/unit-json_view_image.cpp +++ b/tests/src/unit-json_view_image.cpp @@ -28,11 +28,25 @@ using nlohmann::detail::view::node; #include #include #include +#include #include #include #include +namespace json_view_image_test +{ +// whether a document type has save() and load() (only on little-endian targets) +template +struct has_save : std::false_type {}; +template +struct has_save(std::declval().save()))> : std::true_type {}; +template +struct has_load : std::false_type {}; +template +struct has_load(T::load(std::declval&>())))> : std::true_type {}; +} // namespace json_view_image_test + #if !(defined(__BYTE_ORDER__) && defined(__ORDER_BIG_ENDIAN__) && __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__) namespace @@ -171,6 +185,9 @@ std::uint32_t rng() TEST_CASE("json_view images: round trips") { + static_assert(json_view_image_test::has_save::value && json_view_image_test::has_load::value, "save() and load() on a little-endian target"); + static_assert(json_view_image_test::has_save::value && json_view_image_test::has_load::value, "save() and load() on a little-endian target"); + SECTION("small documents") { for (const char* text : @@ -480,7 +497,7 @@ TEST_CASE("json_view images: edited documents") { CAPTURE(i) CHECK((node_at(image, i).flags & nlohmann::detail::view::node_flags::linked) == 0); - CHECK(node_at(image, i).kind != nlohmann::detail::view::kind_link); + CHECK((node_at(image, i).kind != nlohmann::detail::view::node_kind::link)); } for (const image_check check : { @@ -810,13 +827,13 @@ TEST_CASE("json_view images: check") { rejected(corrupted(image, 12, [&](node & n) { - n.kind = kind; + n.kind = static_cast(kind); }), true); } // a key that is not a string rejected(corrupted(image, 1, [](node & n) { - n.kind = 0; + n.kind = nlohmann::detail::view::node_kind::null; n.len = 0; n.off = 0; }), true); @@ -960,11 +977,11 @@ TEST_CASE("json_view images: check") }), true); rejected(corrupted(image, 0, [](node & n) { - n.kind = 2; // an array: the "keys" are values, and the counts do not match + n.kind = nlohmann::detail::view::node_kind::array; // an array: the "keys" are values, and the counts do not match }), true); const std::vector as_array = corrupted(image, 16, [](node & n) { - n.kind = 2; // {} as []: fine + n.kind = nlohmann::detail::view::node_kind::array; // {} as []: fine }); CHECK(load_result(as_array, image_check::full).empty()); const std::string expected_dump = R"({"s":"x\"y","i":-12,"u":7,"f":1.5e+300,"b":true,"n":null,"a":["t",[]]})"; @@ -1013,13 +1030,13 @@ TEST_CASE("json_view images: check") rejected(at_token(u6, 0, '8'), false); // 8 != 7 rejected(corrupted(image, 4, [](node & n) { - n.kind = 6; // "-12" as unsigned: a sign + n.kind = nlohmann::detail::view::node_kind::number_unsigned; // "-12" as unsigned: a sign n.extra = 3; }), false); // a non-negative number_integer (as edits write it): fine const std::vector positive = corrupted(image, 6, [](node & n) { - n.kind = 5; + n.kind = nlohmann::detail::view::node_kind::number_integer; n.extra = 0; }); CHECK(load_result(positive, image_check::full).empty()); @@ -1027,7 +1044,7 @@ TEST_CASE("json_view images: check") CHECK(pos.root()["u"].is_number_integer()); rejected(corrupted(image, 8, [](node & n) { - n.kind = 6; // a float token as integer + n.kind = nlohmann::detail::view::node_kind::number_unsigned; // a float token as integer n.extra = 7; }), false); } @@ -1197,7 +1214,7 @@ TEST_CASE("json_view images: check") { std::vector b = img; node n = node_at(b, i); - n.kind = kind; + n.kind = static_cast(kind); n.extra = extra; set_node(b, i, n); return load_result(b, image_check::full); @@ -1275,8 +1292,12 @@ TEST_CASE("json_view images: damaged images") TEST_CASE("json_view images: big-endian targets") { + // the image format is little-endian: save() and load() do not exist here + static_assert(!json_view_image_test::has_save::value, "no save() on a big-endian target"); + static_assert(!json_view_image_test::has_load::value, "no load() on a big-endian target"); + static_assert(!json_view_image_test::has_save::value, "no save() on a big-endian target"); const json_document d = json_document::parse("[1]"); - CHECK_THROWS_WITH_AS(d.save(), "[json.exception.type_error.320] json_document images need a little-endian target", json::type_error&); + CHECK(d.root().size() == 1); } #endif diff --git a/tests/src/unit-merge_patch.cpp b/tests/src/unit-merge_patch.cpp index 927eb8773..3f5d79b3c 100644 --- a/tests/src/unit-merge_patch.cpp +++ b/tests/src/unit-merge_patch.cpp @@ -423,3 +423,86 @@ TEST_CASE("merge_patch() with an argument that aliases *this (#5641)") } } } + +TEST_CASE("JSON Merge Patch on non-object, discarded, and binary values") +{ + SECTION("an empty object patch turns a non-object target into an empty object") + { + json target = 1; + target.merge_patch(json::object()); + CHECK(target == json::object()); + + json from_null; + from_null.merge_patch(json::object()); + CHECK(from_null == json::object()); + } + + SECTION("a non-object patch replaces a deeply nested target") + { + // checked by type, since == recurses + SECTION("number") + { + json target = json::parse(nested_objects(1000, 0)); + target.merge_patch(json(42)); + CHECK(target.is_number_integer()); + CHECK(target.get() == 42); + } + + SECTION("array") + { + json target = json::parse(nested_objects(1000, 0)); + target.merge_patch(json({1, nullptr})); + CHECK(target.is_array()); + CHECK(target.size() == 2); + CHECK(target[1].is_null()); + } + + SECTION("null") + { + json target = json::parse(nested_objects(1000, 0)); + target.merge_patch(json(nullptr)); + CHECK(target.is_null()); + } + } + + SECTION("discarded values") + { + const json discarded(json::value_t::discarded); + + // a discarded patch is not an object and replaces the target + json target = {{"a", 1}}; + target.merge_patch(discarded); + CHECK(target.is_discarded()); + + // a discarded target is replaced by an empty object first + json discarded_target(json::value_t::discarded); + discarded_target.merge_patch(json({{"a", 1}})); + CHECK(discarded_target == json({{"a", 1}})); + + json both(json::value_t::discarded); + both.merge_patch(discarded); + CHECK(both.is_discarded()); + } + + SECTION("binary values") + { + const json bin = json::binary({1, 2, 3}, 7); + + // a binary patch is not an object and replaces the target + json target = {{"a", 1}}; + target.merge_patch(bin); + CHECK(target.is_binary()); + CHECK(target.get_binary() == bin.get_binary()); + + // a binary target is replaced by an object + json binary_target = bin; + binary_target.merge_patch(json({{"a", 1}})); + CHECK(binary_target == json({{"a", 1}})); + + // binary values nested in the patch are kept + json nested = json::object(); + nested.merge_patch(json({{"a", bin}})); + CHECK(nested["a"].is_binary()); + CHECK(nested["a"].get_binary() == bin.get_binary()); + } +} diff --git a/tests/src/unit-ubjson.cpp b/tests/src/unit-ubjson.cpp index 4cce74dff..5c6378216 100644 --- a/tests/src/unit-ubjson.cpp +++ b/tests/src/unit-ubjson.cpp @@ -2023,7 +2023,7 @@ TEST_CASE("UBJSON") CHECK(json::from_ubjson(v0, true, false).is_discarded()); std::vector const vi = {'[', '$', '#'}; - CHECK_THROWS_WITH_AS(_ = json::from_ubjson(vi), "[json.exception.parse_error.110] parse error at byte 4: syntax error while parsing UBJSON value: unexpected end of input", json::parse_error&); + CHECK_THROWS_WITH_AS(_ = json::from_ubjson(vi), "[json.exception.parse_error.112] parse error at byte 3: syntax error while parsing UBJSON type: marker 0x23 is not a permitted optimized array type", json::parse_error&); CHECK(json::from_ubjson(vi, true, false).is_discarded()); std::vector const vT = {'[', '$', 'T'}; @@ -3348,3 +3348,55 @@ TEST_CASE("UBJSON large strings (chunked reader)") } } } + +TEST_CASE("UBJSON optimized container type marker is validated for count 0") +{ + // the '$' type marker must be a legal marker even if no element follows + SECTION("invalid marker") + { + for (const char marker : + {'x', 'E', 'B', 'u', 'm', 'M', 'h' + }) + { + for (const char open : + {'[', '{' + }) + { + for (const int count : + { + 0, 1 + }) + { + CAPTURE(marker) + CAPTURE(open) + CAPTURE(count) + const std::vector v = {static_cast(open), '$', static_cast(marker), '#', 'i', static_cast(count)}; + json _; + CHECK_THROWS_AS(_ = json::from_ubjson(v), json::parse_error&); + CHECK(json::from_ubjson(v, true, false).is_discarded()); + } + } + } + } + + SECTION("error message") + { + const std::vector v = {'[', '$', 'x', '#', 'i', 0}; + json _; + CHECK_THROWS_WITH_AS(_ = json::from_ubjson(v), "[json.exception.parse_error.112] parse error at byte 3: syntax error while parsing UBJSON type: marker 0x78 is not a permitted optimized array type", json::parse_error&); + } + + SECTION("valid marker") + { + for (const char marker : + {'Z', 'N', 'T', 'F', 'i', 'U', 'I', 'l', 'L', 'd', 'D', 'H', 'C', 'S', '[', '{' + }) + { + CAPTURE(marker) + const std::vector va = {'[', '$', static_cast(marker), '#', 'i', 0}; + const std::vector vo = {'{', '$', static_cast(marker), '#', 'i', 0}; + CHECK(json::from_ubjson(va) == json::array()); + CHECK(json::from_ubjson(vo) == json::object()); + } + } +} diff --git a/tools/generate_natvis/README.md b/tools/generate_natvis/README.md index 8f45f1877..cc4fc4a8f 100644 --- a/tools/generate_natvis/README.md +++ b/tools/generate_natvis/README.md @@ -34,11 +34,11 @@ version other than the one in `abi_macros.hpp`. | Option | Description | |-------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | `--list-tags` | Print every ABI tag (in namespace order) with the macro that enables it and the `NLOHMANN_JSON_ABI_TAG_*` identifier, then exit. | -| `--abi-tags TAGS` | Add the namespaces of one configuration, given as a comma-separated list of ABI tags (`_diag,_dp,_vns`; the leading underscore is optional, and the order does not matter). Can be repeated for several configurations. Unknown tags are an error. | +| `--abi-tags TAGS` | Add the namespaces of one configuration, given as a comma-separated list of ABI tags (`_diag,_dp,_ns`; the leading underscore is optional, and the order does not matter). Can be repeated for several configurations. Unknown tags are an error. | | `--all` | Generate the namespaces for every combination of ABI tags. The file size doubles with each tag (about 60 MB for 12 tags). Cannot be combined with `--abi-tags`. | | `--output FILE` | Write the Natvis file to `FILE` instead of `nlohmann_json.natvis` in the repository root. | -Each configuration gets both the versioned (`json_abi_diag_dp_vns_v3_12_0`) and the unversioned (`json_abi_diag_dp_vns`, +Each configuration gets both the versioned (`json_abi_diag_dp_ns_v3_12_0`) and the unversioned (`json_abi_diag_dp_ns`, used with `NLOHMANN_JSON_NAMESPACE_NO_VERSION`) namespace. The default namespaces (up to two ABI tags) are always included, so a generated file can replace the checked-in one. @@ -52,10 +52,10 @@ included, so a generated file can replace the checked-in one. ``` 3. Generate a Natvis file with them, for example for `JSON_DIAGNOSTICS`, `JSON_DIAGNOSTIC_POSITIONS`, and - `JSON_VIEW_NO_SIMD`: + `JSON_NO_SIMD`: ```shell - make natvis NATVIS_ARGS="--abi-tags _diag,_dp,_vns --output my_nlohmann_json.natvis" + make natvis NATVIS_ARGS="--abi-tags _diag,_dp,_ns --output my_nlohmann_json.natvis" ``` 4. Use `my_nlohmann_json.natvis` instead of the shipped `nlohmann_json.natvis`, for example by adding it to your Visual diff --git a/tools/generate_natvis/generate_natvis.py b/tools/generate_natvis/generate_natvis.py index d660c4b63..8452d98e0 100755 --- a/tools/generate_natvis/generate_natvis.py +++ b/tools/generate_natvis/generate_natvis.py @@ -91,7 +91,7 @@ if __name__ == '__main__': 'include/nlohmann/detail/abi_macros.hpp below "repo_root")') selection = parser.add_mutually_exclusive_group() selection.add_argument('--abi-tags', action='append', default=[], metavar='TAGS', - help='Comma-separated list of ABI tags (e.g. "_diag,_dp,_vns", leading underscores are ' + help='Comma-separated list of ABI tags (e.g. "_diag,_dp,_ns", leading underscores are ' 'optional) of one configuration to add to the default namespaces; can be repeated') selection.add_argument('--all', action='store_true', help='Generate the namespaces for every combination of ABI tags; the file size doubles '