Skip to content

Commit cfdc478

Browse files
committed
Refactor json_pointer accessors
1 parent 680bfc6 commit cfdc478

File tree

2 files changed

+118
-113
lines changed

2 files changed

+118
-113
lines changed

include/tao/json/json_pointer.hh

Lines changed: 75 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@
88
#include <stdexcept>
99
#include <utility>
1010

11+
#include "type.hh"
12+
1113
namespace tao
1214
{
1315
namespace json
@@ -33,34 +35,6 @@ namespace tao
3335
}
3436
}
3537

36-
inline std::string next_json_pointer_token( const char * & p, const char * e )
37-
{
38-
std::string result;
39-
// TODO: Find next '/' first, call result.reserve( x )?
40-
while ( p != e ) {
41-
switch ( *p ) {
42-
case '/':
43-
return result;
44-
case '~':
45-
switch ( *++p ) {
46-
case '0':
47-
result += '~';
48-
break;
49-
case '1':
50-
result += '/';
51-
break;
52-
default:
53-
assert( !"code should be unreachable" ); // LCOV_EXCL_LINE
54-
}
55-
++p;
56-
break;
57-
default:
58-
result += *p++;
59-
}
60-
}
61-
return result;
62-
}
63-
6438
} // internal
6539

6640
// RFC 6901
@@ -99,12 +73,85 @@ namespace tao
9973
return *this;
10074
}
10175

76+
explicit operator bool() const noexcept
77+
{
78+
return ! m_value.empty();
79+
}
80+
10281
const std::string& value() const noexcept
10382
{
10483
return m_value;
10584
}
85+
86+
std::pair< json_pointer, std::string > split() const
87+
{
88+
const auto p = m_value.rfind( '/' );
89+
return { json_pointer( m_value.substr( 0, p ) ), m_value.substr( p + 1 ) };
90+
}
10691
};
10792

93+
namespace internal
94+
{
95+
inline std::string json_pointer_next_token( const char * & p, const char * e )
96+
{
97+
std::string result;
98+
// TODO: Find next '/' first, call result.reserve( x )?
99+
while ( p != e ) {
100+
switch ( *p ) {
101+
case '/':
102+
return result;
103+
case '~':
104+
switch ( *++p ) {
105+
case '0':
106+
result += '~';
107+
break;
108+
case '1':
109+
result += '/';
110+
break;
111+
default:
112+
assert( !"code should be unreachable" ); // LCOV_EXCL_LINE
113+
}
114+
++p;
115+
break;
116+
default:
117+
result += *p++;
118+
}
119+
}
120+
return result;
121+
}
122+
123+
inline unsigned long long json_pointer_token_to_index( const std::string & t )
124+
{
125+
if ( ( t.find_first_not_of( "0123456789" ) != std::string::npos ) || ( t.size() > 1 && t[ 0 ] == '0' ) ) {
126+
throw std::invalid_argument( "unable to resolve json_pointer, invalid token for array access '" + t + '\'' );
127+
}
128+
return std::stoull( t );
129+
}
130+
131+
template< typename T >
132+
T & json_pointer_at( T * v, const json_pointer & k )
133+
{
134+
const char * p = k.value().c_str();
135+
const char * e = p + k.value().size();
136+
while ( p != e ) {
137+
switch ( v->type() ) {
138+
case type::ARRAY:
139+
v = &v->at( json_pointer_token_to_index( json_pointer_next_token( ++p, e ) ) );
140+
break;
141+
case type::OBJECT:
142+
v = &v->at( json_pointer_next_token( ++p, e ) );
143+
break;
144+
default:
145+
throw std::runtime_error( "unable to resolve json_pointer, "
146+
"value at '" + std::string( k.value().c_str(), p ) + "' "
147+
"is neither 'object' nor 'array', "
148+
"but '" + to_string( v->type() ) + "'" );
149+
}
150+
}
151+
return * v;
152+
}
153+
}
154+
108155
inline namespace literals
109156
{
110157
inline json_pointer operator"" _json_pointer( const char * data, const std::size_t size )

include/tao/json/value.hh

Lines changed: 43 additions & 85 deletions
Original file line numberDiff line numberDiff line change
@@ -410,94 +410,12 @@ namespace tao
410410

411411
const basic_value & at( const json_pointer & k ) const
412412
{
413-
const basic_value * v = this;
414-
const char * p = k.value().c_str();
415-
const char * e = p + k.value().size();
416-
while ( p != e ) {
417-
switch ( v->m_type ) {
418-
case json::type::ARRAY:
419-
{
420-
const auto t = internal::next_json_pointer_token( ++p, e );
421-
if ( ( t.find_first_not_of( "0123456789" ) != std::string::npos ) || ( t.size() > 1 && t[ 0 ] == '0' ) ) {
422-
throw std::invalid_argument( "unable to resolve json_pointer '" + k.value() + "', "
423-
"invalid token for const array access '" + t + "' "
424-
"at '" + std::string( k.value().c_str(), p - t.size() - 1 ) + '\'' );
425-
}
426-
v = &v->at( std::stoull( t ) );
427-
}
428-
break;
429-
case json::type::OBJECT:
430-
v = &v->at( internal::next_json_pointer_token( ++p, e ) );
431-
break;
432-
case json::type::POINTER:
433-
v = v->unsafe_get_pointer();
434-
break;
435-
default:
436-
throw std::runtime_error( "unable to resolve json_pointer '" + k.value() + "', "
437-
"value at '" + std::string( k.value().c_str(), p ) + "' "
438-
"is neither 'object', 'array' nor 'pointer', "
439-
"but '" + to_string( v->m_type ) + "'" );
440-
}
441-
}
442-
return *v;
413+
return internal::json_pointer_at( this, k );
443414
}
444415

445-
// Unlike the above pure getter, this adds null elements when the leaf is a missing object key or '-' for an array.
446-
// Note: Can not transcend a type::POINTER value as we would modify another value simultaneously.
447-
448-
basic_value & operator[] ( const json_pointer & k )
416+
basic_value & at( const json_pointer & k )
449417
{
450-
basic_value * v = this;
451-
const char * p = k.value().c_str();
452-
const char * e = p + k.value().size();
453-
while ( p != e ) {
454-
switch ( v->m_type ) {
455-
case json::type::ARRAY:
456-
{
457-
const auto t = internal::next_json_pointer_token( ++p, e );
458-
if ( t == "-" ) {
459-
if ( p != e ) {
460-
throw std::runtime_error( "unable to resolve json_pointer '" + k.value() + "' "
461-
"at '" + std::string( k.value().c_str(), p - t.size() - 1 ) + "', "
462-
"array access via '-'-token must occur as the last fragment" );
463-
}
464-
v->unsafe_emplace_back( null );
465-
return v->m_union.a.back();
466-
}
467-
if ( ( t.find_first_not_of( "0123456789" ) != std::string::npos ) || ( t.size() > 1 && t[ 0 ] == '0' ) ) {
468-
throw std::invalid_argument( "unable to resolve json_pointer '" + k.value() + "', "
469-
"invalid token for array access '" + t + "' "
470-
"at '" + std::string( k.value().c_str(), p - t.size() - 1 ) + '\'' );
471-
}
472-
v = &v->at( std::stoull( t ) );
473-
}
474-
break;
475-
case json::type::OBJECT:
476-
{
477-
auto t = internal::next_json_pointer_token( ++p, e );
478-
const auto it = v->m_union.o.find( t );
479-
if ( it != v->m_union.o.end() ) {
480-
v = & it->second;
481-
}
482-
else {
483-
if ( p != e ) {
484-
throw std::runtime_error( "unable to resolve json_pointer '" + k.value() + "' "
485-
"at '" + std::string( k.value().c_str(), p ) + '\'' );
486-
}
487-
const auto r = v->unsafe_emplace( std::move( t ), null );
488-
assert( r.second );
489-
return r.first->second;
490-
}
491-
}
492-
break;
493-
default:
494-
throw std::runtime_error( "unable to resolve json_pointer '" + k.value() + "', "
495-
"value at '" + std::string( k.value().c_str(), p ) + "' "
496-
"is neither 'object' nor 'array', "
497-
"but '" + to_string( v->m_type ) + "'" );
498-
}
499-
}
500-
return *v;
418+
return internal::json_pointer_at( this, k );
501419
}
502420

503421
basic_value & unsafe_at( const std::size_t index )
@@ -520,6 +438,46 @@ namespace tao
520438
return m_union.o.find( key )->second;
521439
}
522440

441+
// Unlike the above pure getter, this adds null elements when the leaf is a missing object key or '-' for an array.
442+
443+
basic_value & operator[] ( const json_pointer & k )
444+
{
445+
if ( ! k ) {
446+
return * this;
447+
}
448+
const auto sp = k.split();
449+
basic_value & v = internal::json_pointer_at( this, sp.first );
450+
switch ( v.m_type ) {
451+
case json::type::ARRAY:
452+
{
453+
const auto & t = sp.second;
454+
if ( t == "-" ) {
455+
v.unsafe_emplace_back( null );
456+
return v.m_union.a.back();
457+
}
458+
return v.at( internal::json_pointer_token_to_index( t ) );
459+
}
460+
break;
461+
case json::type::OBJECT:
462+
{
463+
auto & t = sp.second;
464+
const auto it = v.m_union.o.find( t );
465+
if ( it == v.m_union.o.end() ) {
466+
const auto r = v.unsafe_emplace( std::move( t ), null );
467+
assert( r.second );
468+
return r.first->second;
469+
}
470+
return it->second;
471+
}
472+
break;
473+
default:
474+
throw std::runtime_error( "unable to resolve json_pointer '" + k.value() + "', "
475+
"value at '" + sp.first.value() + "' "
476+
"is neither 'object' nor 'array', "
477+
"but '" + to_string( v.m_type ) + "'" );
478+
}
479+
}
480+
523481
template< typename T >
524482
tao::optional< T > optional( const std::string & key ) const
525483
{

0 commit comments

Comments
 (0)