Skip to content

Commit eae38e9

Browse files
committed
Add modifying access via json_pointer through operator[]
1 parent 38abdbf commit eae38e9

File tree

3 files changed

+126
-53
lines changed

3 files changed

+126
-53
lines changed

include/tao/json/json_pointer.hh

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,13 +21,13 @@ namespace tao
2121
return;
2222
}
2323
if ( v[ 0 ] != '/' ) {
24-
throw std::domain_error( "invalid json_pointer value, must be empty or begin with '/'" );
24+
throw std::invalid_argument( "invalid json_pointer value, must be empty or begin with '/'" );
2525
}
2626
// TODO: Should we also check UTF-8 encoding and code-point range?
2727
for ( auto it = v.begin(); it != v.end(); ++it ) {
2828
if ( *it == '~' ) {
2929
if ( ++it == v.end() || !( *it == '0' || *it == '1' ) ) {
30-
throw std::domain_error( "invalid json_pointer escape sequence, '~' must be followed by '0' or '1'" );
30+
throw std::invalid_argument( "invalid json_pointer escape sequence, '~' must be followed by '0' or '1'" );
3131
}
3232
}
3333
}

include/tao/json/value.hh

Lines changed: 75 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -300,7 +300,7 @@ namespace tao
300300
template< typename T >
301301
tao::optional< T > optional() const
302302
{
303-
if( this->is_null() ) {
303+
if ( this->is_null() ) {
304304
return tao::nullopt;
305305
}
306306
else {
@@ -408,11 +408,6 @@ namespace tao
408408
return m_union.o.at( key );
409409
}
410410

411-
// basic_value & at( const json_pointer & k )
412-
// {
413-
// // TODO: Implement me!
414-
// }
415-
416411
const basic_value & at( const json_pointer & k ) const
417412
{
418413
const basic_value * v = this;
@@ -422,9 +417,12 @@ namespace tao
422417
switch ( v->m_type ) {
423418
case json::type::ARRAY:
424419
{
420+
const auto o = p;
425421
const auto t = internal::next_json_pointer_token( ++p, e );
426422
if ( ( t.find_first_not_of( "0123456789" ) != std::string::npos ) || ( t.size() > 1 && t[ 0 ] == '0' ) ) {
427-
throw std::out_of_range( "unable to resolve json_pointer, invalid token for const array access '" + t + '\'' );
423+
throw std::invalid_argument( "unable to resolve json_pointer '" + k.value() + "', "
424+
"invalid token for const array access '" + t + "' "
425+
"at '" + std::string( k.value().c_str(), o ) + '\'' );
428426
}
429427
v = &v->at( std::stoull( t ) );
430428
}
@@ -436,7 +434,69 @@ namespace tao
436434
v = v->unsafe_get_pointer();
437435
break;
438436
default:
439-
throw std::out_of_range( "unable to resolve json_pointer" );
437+
throw std::runtime_error( "unable to resolve json_pointer '" + k.value() + "', "
438+
"value at '" + std::string( k.value().c_str(), p ) + "' "
439+
"is neither 'object', 'array' nor 'pointer', "
440+
"but '" + to_string( v->m_type ) + "'" );
441+
}
442+
}
443+
return *v;
444+
}
445+
446+
// Unlike the above pure getter, this adds null elements when the leaf is a missing object key or '-' for an array.
447+
// Note: Can not transcend a type::POINTER value as we would modify another value simultaneously.
448+
449+
basic_value & operator[] ( const json_pointer & k )
450+
{
451+
basic_value * v = this;
452+
const char * p = k.value().c_str();
453+
const char * e = p + k.value().size();
454+
while ( p != e ) {
455+
switch ( v->m_type ) {
456+
case json::type::ARRAY:
457+
{
458+
const auto o = p;
459+
const auto t = internal::next_json_pointer_token( ++p, e );
460+
if ( t == "-" ) {
461+
if ( p != e ) {
462+
throw std::runtime_error( "unable to resolve json_pointer '" + k.value() + "' "
463+
"at '" + std::string( k.value().c_str(), o ) + "', "
464+
"array access via '-'-token must occur as the last fragment" );
465+
}
466+
v->unsafe_emplace_back( null );
467+
return v->m_union.a.back();
468+
}
469+
if ( ( t.find_first_not_of( "0123456789" ) != std::string::npos ) || ( t.size() > 1 && t[ 0 ] == '0' ) ) {
470+
throw std::invalid_argument( "unable to resolve json_pointer '" + k.value() + "', "
471+
"invalid token for array access '" + t + "' "
472+
"at '" + std::string( k.value().c_str(), o ) + '\'' );
473+
}
474+
v = &v->at( std::stoull( t ) );
475+
}
476+
break;
477+
case json::type::OBJECT:
478+
{
479+
auto t = internal::next_json_pointer_token( ++p, e );
480+
const auto it = v->m_union.o.find( t );
481+
if ( it != v->m_union.o.end() ) {
482+
v = & it->second;
483+
}
484+
else {
485+
if ( p != e ) {
486+
throw std::runtime_error( "unable to resolve json_pointer '" + k.value() + "' "
487+
"at '" + std::string( k.value().c_str(), p ) + '\'' );
488+
}
489+
const auto r = v->unsafe_emplace( std::move( t ), null );
490+
assert( r.second );
491+
return r.first->second;
492+
}
493+
}
494+
break;
495+
default:
496+
throw std::runtime_error( "unable to resolve json_pointer '" + k.value() + "', "
497+
"value at '" + std::string( k.value().c_str(), p ) + "' "
498+
"is neither 'object' nor 'array', "
499+
"but '" + to_string( v->m_type ) + "'" );
440500
}
441501
}
442502
return *v;
@@ -467,7 +527,7 @@ namespace tao
467527
{
468528
TAOCPP_JSON_CHECK_TYPE_ERROR( m_type, json::type::OBJECT );
469529
const auto it = m_union.o.find( key );
470-
if( it == m_union.o.end() ) {
530+
if ( it == m_union.o.end() ) {
471531
return tao::nullopt;
472532
}
473533
else {
@@ -488,7 +548,7 @@ namespace tao
488548
void unsafe_assign( std::initializer_list< pair< Traits > > && l )
489549
{
490550
unsafe_emplace_object();
491-
for( auto & e : l ) {
551+
for ( auto & e : l ) {
492552
const auto r = unsafe_emplace( std::move( e.key ), std::move( e.value ) );
493553
if ( ! r.second ) {
494554
throw std::runtime_error( "duplicate key detected: " + r.first->first );
@@ -499,7 +559,7 @@ namespace tao
499559
void unsafe_assign( const std::initializer_list< pair< Traits > > & l )
500560
{
501561
unsafe_emplace_object();
502-
for( auto & e : l ) {
562+
for ( auto & e : l ) {
503563
const auto r = unsafe_emplace( e.key, e.value );
504564
if ( ! r.second ) {
505565
throw std::runtime_error( "duplicate key detected: " + r.first->first );
@@ -659,7 +719,7 @@ namespace tao
659719
prepare_array();
660720
auto & v = unsafe_get_array();
661721
v.reserve( v.size() + l.size() );
662-
for( auto & e : l ) {
722+
for ( auto & e : l ) {
663723
unsafe_emplace_back( std::move( e.value ) );
664724
}
665725
}
@@ -669,15 +729,15 @@ namespace tao
669729
prepare_array();
670730
auto & v = unsafe_get_array();
671731
v.reserve( v.size() + l.size() );
672-
for( const auto & e : l ) {
732+
for ( const auto & e : l ) {
673733
unsafe_emplace_back( e.value );
674734
}
675735
}
676736

677737
void insert( std::initializer_list< pair< Traits > > && l )
678738
{
679739
prepare_object();
680-
for( auto & e : l ) {
740+
for ( auto & e : l ) {
681741
const auto r = unsafe_emplace( std::move( e.key ), std::move( e.value ) );
682742
if ( ! r.second ) {
683743
throw std::runtime_error( "duplicate key detected: " + r.first->first );
@@ -688,7 +748,7 @@ namespace tao
688748
void insert( const std::initializer_list< pair< Traits > > & l )
689749
{
690750
prepare_object();
691-
for( auto & e : l ) {
751+
for ( auto & e : l ) {
692752
const auto r = unsafe_emplace( e.key, e.value );
693753
if ( ! r.second ) {
694754
throw std::runtime_error( "duplicate key detected: " + r.first->first );

src/test/json/json_pointer.cc

Lines changed: 49 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ namespace tao
2626
// create test vector
2727
const std::string ezp( "o\0p", 3 );
2828
const std::string ezq( "o\0q", 3 );
29-
const value cv {
29+
value v {
3030
{ "foo", value::array( { "bar", "baz" } ) },
3131
{ "", 0 },
3232
{ "a/b", 1 },
@@ -43,41 +43,54 @@ namespace tao
4343
};
4444

4545
// check access
46-
TEST_ASSERT( cv.at( ""_json_pointer ) == cv );
47-
48-
TEST_ASSERT( cv.at( "/foo"_json_pointer ) == value::array( { "bar", "baz" } ) );
49-
TEST_THROWS( cv.at( "/bar"_json_pointer ) );
50-
51-
TEST_ASSERT( cv.at( "/foo/0"_json_pointer ) == "bar" );
52-
TEST_ASSERT( cv.at( "/foo/1"_json_pointer ) == "baz" );
53-
TEST_THROWS( cv.at( "/foo/2"_json_pointer ) );
54-
55-
TEST_THROWS( cv.at( "/foo/00"_json_pointer ) );
56-
TEST_THROWS( cv.at( "/foo/01"_json_pointer ) );
57-
TEST_THROWS( cv.at( "/foo/0 "_json_pointer ) );
58-
TEST_THROWS( cv.at( "/foo/ 0"_json_pointer ) );
59-
TEST_THROWS( cv.at( "/foo/1 "_json_pointer ) );
60-
TEST_THROWS( cv.at( "/foo/ 1"_json_pointer ) );
61-
TEST_THROWS( cv.at( "/foo/-"_json_pointer ) );
62-
TEST_THROWS( cv.at( "/foo/bar"_json_pointer ) );
63-
64-
TEST_ASSERT( cv.at( "/"_json_pointer ) == 0 );
65-
TEST_THROWS( cv.at( "//"_json_pointer ) );
66-
67-
TEST_ASSERT( cv.at( "/a~1b"_json_pointer ) == 1 );
68-
TEST_ASSERT( cv.at( "/c%d"_json_pointer ) == 2 );
69-
TEST_ASSERT( cv.at( "/e^f"_json_pointer ) == 3 );
70-
TEST_ASSERT( cv.at( "/g|h"_json_pointer ) == 4 );
71-
TEST_ASSERT( cv.at( "/i\\j"_json_pointer ) == 5 );
72-
TEST_ASSERT( cv.at( "/k\"l"_json_pointer ) == 6 );
73-
TEST_ASSERT( cv.at( "/ "_json_pointer ) == 7 );
74-
TEST_ASSERT( cv.at( "/m~0n"_json_pointer ) == 8 );
75-
76-
TEST_ASSERT( cv.at( "/o\0p"_json_pointer ) == 9 );
77-
TEST_ASSERT( cv.at( "/o\0q"_json_pointer ) == 10 );
78-
TEST_THROWS( cv.at( "/o\0r"_json_pointer ) );
79-
80-
TEST_ASSERT( cv.at( "/-"_json_pointer ) == 11 );
46+
TEST_ASSERT( v.at( ""_json_pointer ) == v );
47+
48+
TEST_ASSERT( v.at( "/foo"_json_pointer ) == value::array( { "bar", "baz" } ) );
49+
TEST_THROWS( v.at( "/bar"_json_pointer ) );
50+
51+
TEST_ASSERT( v.at( "/foo/0"_json_pointer ) == "bar" );
52+
TEST_ASSERT( v.at( "/foo/1"_json_pointer ) == "baz" );
53+
TEST_THROWS( v.at( "/foo/2"_json_pointer ) );
54+
55+
TEST_THROWS( v.at( "/foo/00"_json_pointer ) );
56+
TEST_THROWS( v.at( "/foo/01"_json_pointer ) );
57+
TEST_THROWS( v.at( "/foo/0 "_json_pointer ) );
58+
TEST_THROWS( v.at( "/foo/ 0"_json_pointer ) );
59+
TEST_THROWS( v.at( "/foo/1 "_json_pointer ) );
60+
TEST_THROWS( v.at( "/foo/ 1"_json_pointer ) );
61+
TEST_THROWS( v.at( "/foo/-"_json_pointer ) );
62+
TEST_THROWS( v.at( "/foo/bar"_json_pointer ) );
63+
64+
TEST_ASSERT( v.at( "/"_json_pointer ) == 0 );
65+
TEST_THROWS( v.at( "//"_json_pointer ) );
66+
67+
TEST_ASSERT( v.at( "/a~1b"_json_pointer ) == 1 );
68+
TEST_ASSERT( v.at( "/c%d"_json_pointer ) == 2 );
69+
TEST_ASSERT( v.at( "/e^f"_json_pointer ) == 3 );
70+
TEST_ASSERT( v.at( "/g|h"_json_pointer ) == 4 );
71+
TEST_ASSERT( v.at( "/i\\j"_json_pointer ) == 5 );
72+
TEST_ASSERT( v.at( "/k\"l"_json_pointer ) == 6 );
73+
TEST_ASSERT( v.at( "/ "_json_pointer ) == 7 );
74+
TEST_ASSERT( v.at( "/m~0n"_json_pointer ) == 8 );
75+
76+
TEST_ASSERT( v.at( "/o\0p"_json_pointer ) == 9 );
77+
TEST_ASSERT( v.at( "/o\0q"_json_pointer ) == 10 );
78+
TEST_THROWS( v.at( "/o\0r"_json_pointer ) );
79+
80+
TEST_ASSERT( v.at( "/-"_json_pointer ) == 11 );
81+
82+
// check modifications
83+
v[ "/foo/-"_json_pointer ] = "bat";
84+
TEST_ASSERT( v.at( "/foo"_json_pointer ) != value::array( { "bar", "baz" } ) );
85+
TEST_ASSERT( v.at( "/foo"_json_pointer ) == value::array( { "bar", "baz", "bat" } ) );
86+
87+
v[ "/foo/-"_json_pointer ]; // no assignment, but the null is appended anyways... TODO: change that to require an assignment?
88+
TEST_ASSERT( v.at( "/foo"_json_pointer ) != value::array( { "bar", "baz", "bat" } ) );
89+
TEST_ASSERT( v.at( "/foo"_json_pointer ) == value::array( { "bar", "baz", "bat", null } ) );
90+
91+
TEST_THROWS( v.at( "bar" ) );
92+
v[ "/bar"_json_pointer ] = 42;
93+
TEST_ASSERT( v.at( "bar" ) == 42 );
8194
}
8295

8396
} // json

0 commit comments

Comments
 (0)