Skip to content

Commit ee0ed9f

Browse files
committed
Initial support for RFC 6902: JSON Patch
1 parent 3fe5f01 commit ee0ed9f

File tree

5 files changed

+189
-1
lines changed

5 files changed

+189
-1
lines changed

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ The Art of C++ / JSON is a zero-dependency C++11 header-only library that provid
1212

1313
* [RFC7159], [ECMA-404]: The JavaScript Object Notation (JSON) Data Interchange Format
1414
* [RFC6901]: JavaScript Object Notation (JSON) Pointer
15+
* [RFC6902]: JavaScript Object Notation (JSON) Patch
1516
* Numeric values are stored as `int64_t`, `uint64_t` or `double` internally.
1617
* Standard containers `std::string` for JSON strings, `std::vector< tao::json::value >` for JSON arrays, and `std::map< std::string, tao::json::value >` for JSON objects.
1718
* No memory allocations by the JSON value class itself (the wrapped standard containers perform their memory allocations normally).
@@ -90,3 +91,4 @@ The Art of C++ / JSON contains slightly modified portions of the [double-convers
9091
[RFC7159]: http://www.ietf.org/rfc/rfc7159.txt
9192
[ECMA-404]: http://www.ecma-international.org/publications/standards/Ecma-404.htm
9293
[RFC6901]: http://www.ietf.org/rfc/rfc6901.txt
94+
[RFC6902]: http://www.ietf.org/rfc/rfc6902.txt

include/tao/json.hh

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,4 +12,6 @@
1212
#include "json/parse_file.hh"
1313
#include "json/self_contained.hh"
1414

15+
#include "json/patch.hh"
16+
1517
#endif

include/tao/json/patch.hh

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
// Copyright (c) 2016 Dr. Colin Hirsch and Daniel Frey
2+
// Please see LICENSE for license or visit https://github.com/taocpp/json/
3+
4+
#ifndef TAOCPP_JSON_INCLUDE_PATCH_HH
5+
#define TAOCPP_JSON_INCLUDE_PATCH_HH
6+
7+
#include "value.hh"
8+
#include "json_pointer.hh"
9+
10+
namespace tao
11+
{
12+
namespace json
13+
{
14+
template< template< typename ... > class Traits >
15+
void patch_inplace( basic_value< Traits > & value, const basic_value< Traits > & patch )
16+
{
17+
for( const auto & entry : patch.get_array() ) {
18+
const auto & op = entry.at( "op" ).get_string();
19+
const json_pointer path( entry.at( "path" ).get_string() );
20+
if( op == "test" ) {
21+
if( value.at( path ) != entry.at( "value" ) ) {
22+
throw std::runtime_error( "test failed for: " + path.value() );
23+
}
24+
}
25+
else if( op == "remove" ) {
26+
value.erase( path );
27+
}
28+
else if( op == "add" ) {
29+
value.add( path ) = entry.at( "value" );
30+
}
31+
else if( op == "replace" ) {
32+
value.at( path ) = entry.at( "value" );
33+
}
34+
else if( op == "move" ) {
35+
const json_pointer from( entry.at( "from" ).get_string() );
36+
auto v = std::move( value.at( from ) );
37+
value.erase( from );
38+
value.add( path ) = std::move( v );
39+
}
40+
else if( op == "copy" ) {
41+
const json_pointer from( entry.at( "from" ).get_string() );
42+
auto v = value.at( from );
43+
value.add( path ) = std::move( v );
44+
}
45+
else {
46+
throw std::runtime_error( "unknown patch operation: '" + op + '\'' );
47+
}
48+
}
49+
}
50+
51+
template< template< typename ... > class Traits >
52+
basic_value< Traits > patch( const basic_value< Traits > & value, const basic_value< Traits > & patch )
53+
{
54+
basic_value< Traits > result = value;
55+
patch_inplace( result, patch );
56+
return result;
57+
}
58+
59+
} // json
60+
61+
} // tao
62+
63+
#endif

include/tao/json/value.hh

Lines changed: 79 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -438,7 +438,85 @@ namespace tao
438438
return m_union.o.find( key )->second;
439439
}
440440

441-
// Unlike the above pure getter, this adds null elements when the leaf is a missing object key or '-' for an array.
441+
void erase( const std::size_t index )
442+
{
443+
TAOCPP_JSON_CHECK_TYPE_ERROR( m_type, json::type::ARRAY );
444+
auto & a = m_union.a;
445+
if ( index >= a.size() ) {
446+
throw std::out_of_range( "array index too large" );
447+
}
448+
a.erase( a.begin() + index );
449+
}
450+
451+
void erase( const std::string & key )
452+
{
453+
TAOCPP_JSON_CHECK_TYPE_ERROR( m_type, json::type::OBJECT );
454+
if ( m_union.o.erase( key ) == 0 ) {
455+
throw std::out_of_range( "key not found: " + key );
456+
}
457+
}
458+
459+
void erase( const json_pointer & k )
460+
{
461+
if ( ! k ) {
462+
throw "TODO: Clarify with RFC!!!";
463+
}
464+
const auto sp = k.split();
465+
basic_value & v = internal::json_pointer_at( this, sp.first );
466+
switch ( v.m_type ) {
467+
case json::type::ARRAY:
468+
v.erase( internal::json_pointer_token_to_index( sp.second ) );
469+
break;
470+
case json::type::OBJECT:
471+
v.erase( sp.second );
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+
481+
basic_value & add( const json_pointer & k )
482+
{
483+
if ( ! k ) {
484+
return * this;
485+
}
486+
const auto sp = k.split();
487+
basic_value & v = internal::json_pointer_at( this, sp.first );
488+
switch ( v.m_type ) {
489+
case json::type::ARRAY:
490+
{
491+
const auto & t = sp.second;
492+
if ( t == "-" ) {
493+
v.unsafe_emplace_back( null );
494+
return v.m_union.a.back();
495+
}
496+
const auto i = internal::json_pointer_token_to_index( t );
497+
v.m_union.a.insert( v.m_union.a.begin() + i, null );
498+
return v.m_union.a.at( i );
499+
}
500+
break;
501+
case json::type::OBJECT:
502+
{
503+
auto & t = sp.second;
504+
const auto it = v.m_union.o.find( t );
505+
if ( it == v.m_union.o.end() ) {
506+
const auto r = v.unsafe_emplace( std::move( t ), null );
507+
assert( r.second );
508+
return r.first->second;
509+
}
510+
return it->second;
511+
}
512+
break;
513+
default:
514+
throw std::runtime_error( "unable to resolve json_pointer '" + k.value() + "', "
515+
"value at '" + sp.first.value() + "' "
516+
"is neither 'object' nor 'array', "
517+
"but '" + to_string( v.m_type ) + "'" );
518+
}
519+
}
442520

443521
basic_value & operator[] ( const json_pointer & k )
444522
{

src/test/json/json_patch.cc

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
// Copyright (c) 2016 Dr. Colin Hirsch and Daniel Frey
2+
// Please see LICENSE for license or visit https://github.com/taocpp/json/
3+
4+
#include "test.hh"
5+
6+
#include <iostream>
7+
8+
#include <tao/json.hh>
9+
10+
namespace tao
11+
{
12+
namespace json
13+
{
14+
void unit_test()
15+
{
16+
const value a = { { "a", { { "foo", 1 } } } };
17+
const value q = { { "q", { { "bar", 2 } } } };
18+
19+
TEST_ASSERT( patch( {}, value::array( {} ) ) == null );
20+
TEST_ASSERT( patch( a, value::array( {} ) ) == a );
21+
TEST_ASSERT( patch( q, value::array( {} ) ) == q );
22+
23+
TEST_ASSERT( patch( {}, value::array( { { { "op", "test" }, { "path", "" }, { "value", null } } } ) ) == null );
24+
TEST_THROWS( patch( {}, value::array( { { { "op", "test" }, { "path", "" }, { "value", 42 } } } ) ) );
25+
26+
TEST_ASSERT( patch( a, value::array( { { { "op", "test" }, { "path", "/a" }, { "value", { { "foo", 1 } } } } } ) ) == a );
27+
TEST_ASSERT( patch( a, value::array( { { { "op", "test" }, { "path", "/a/foo" }, { "value", 1 } } } ) ) == a );
28+
29+
TEST_THROWS( patch( a, value::array( { { { "op", "test" }, { "path", "" }, { "value", 42 } } } ) ) );
30+
TEST_THROWS( patch( a, value::array( { { { "op", "test" }, { "path", "/a" }, { "value", 42 } } } ) ) );
31+
32+
TEST_ASSERT( patch( a, value::array( { { { "op", "add" }, { "path", "/b" }, { "value", 42 } } } ) ) == value( { { "a", { { "foo", 1 } } }, { "b", 42 } } ) );
33+
TEST_ASSERT( patch( a, value::array( { { { "op", "add" }, { "path", "/a/b" }, { "value", 42 } } } ) ) == value( { { "a", { { "foo", 1 }, { "b", 42 } } } } ) );
34+
TEST_THROWS( patch( q, value::array( { { { "op", "add" }, { "path", "/a/b" }, { "value", 42 } } } ) ) );
35+
36+
// TODO: Way more tests...
37+
}
38+
39+
} // json
40+
41+
} // tao
42+
43+
#include "main.hh"

0 commit comments

Comments
 (0)