Merge pull request #26968 from jimmidyson/json-patch-lib-upgrade

Automatic merge from submit-queue

godep bump: github.com/evanphx/json-patch

Fixes #26890
pull/6/head
k8s-merge-robot 2016-06-12 15:36:54 -07:00 committed by GitHub
commit 59f918cae0
4 changed files with 134 additions and 25 deletions

2
Godeps/Godeps.json generated
View File

@ -754,7 +754,7 @@
}, },
{ {
"ImportPath": "github.com/evanphx/json-patch", "ImportPath": "github.com/evanphx/json-patch",
"Rev": "7dd4489c2eb6073e5a9d7746c3274c5b5f0387df" "Rev": "465937c80b3c07a7c7ad20cc934898646a91c1de"
}, },
{ {
"ImportPath": "github.com/garyburd/redigo/internal", "ImportPath": "github.com/garyburd/redigo/internal",

View File

@ -1,13 +1,13 @@
## JSON-Patch ## JSON-Patch
Provides the abiilty to modify and test a JSON according to a Provides the ability to modify and test a JSON according to a
[RFC6902 JSON patch](http://tools.ietf.org/html/rfc6902) and [RFC7386 JSON Merge Patch](https://tools.ietf.org/html/rfc7386). [RFC6902 JSON patch](http://tools.ietf.org/html/rfc6902) and [RFC7396 JSON Merge Patch](https://tools.ietf.org/html/rfc7396).
*Version*: **1.0** *Version*: **1.0**
[![GoDoc](https://godoc.org/github.com/evanphx/json-patch?status.svg)](http://godoc.org/github.com/evanphx/json-patch) [![GoDoc](https://godoc.org/github.com/evanphx/json-patch?status.svg)](http://godoc.org/github.com/evanphx/json-patch)
[![Build Status](https://travis-ci.org/evanphx/json-patch.svg?branch=RFC7386)](https://travis-ci.org/evanphx/json-patch) [![Build Status](https://travis-ci.org/evanphx/json-patch.svg?branch=master)](https://travis-ci.org/evanphx/json-patch)
### API Usage ### API Usage

View File

@ -4,6 +4,7 @@ import (
"encoding/json" "encoding/json"
"fmt" "fmt"
"reflect" "reflect"
"strings"
) )
func merge(cur, patch *lazyNode) *lazyNode { func merge(cur, patch *lazyNode) *lazyNode {
@ -27,6 +28,7 @@ func merge(cur, patch *lazyNode) *lazyNode {
func mergeDocs(doc, patch *partialDoc) { func mergeDocs(doc, patch *partialDoc) {
for k, v := range *patch { for k, v := range *patch {
k := decodePatchKey(k)
if v == nil { if v == nil {
delete(*doc, k) delete(*doc, k)
} else { } else {
@ -69,7 +71,7 @@ func pruneDocNulls(doc *partialDoc) *partialDoc {
} }
func pruneAryNulls(ary *partialArray) *partialArray { func pruneAryNulls(ary *partialArray) *partialArray {
var newAry []*lazyNode newAry := []*lazyNode{}
for _, v := range *ary { for _, v := range *ary {
if v != nil { if v != nil {
@ -218,6 +220,9 @@ func matchesValue(av, bv interface{}) bool {
} }
} }
return true return true
case []interface{}:
bt := bv.([]interface{})
return matchesArray(at, bt)
} }
return false return false
} }
@ -226,15 +231,16 @@ func matchesValue(av, bv interface{}) bool {
func getDiff(a, b map[string]interface{}) (map[string]interface{}, error) { func getDiff(a, b map[string]interface{}) (map[string]interface{}, error) {
into := map[string]interface{}{} into := map[string]interface{}{}
for key, bv := range b { for key, bv := range b {
escapedKey := encodePatchKey(key)
av, ok := a[key] av, ok := a[key]
// value was added // value was added
if !ok { if !ok {
into[key] = bv into[escapedKey] = bv
continue continue
} }
// If types have changed, replace completely // If types have changed, replace completely
if reflect.TypeOf(av) != reflect.TypeOf(bv) { if reflect.TypeOf(av) != reflect.TypeOf(bv) {
into[key] = bv into[escapedKey] = bv
continue continue
} }
// Types are the same, compare values // Types are the same, compare values
@ -247,23 +253,23 @@ func getDiff(a, b map[string]interface{}) (map[string]interface{}, error) {
return nil, err return nil, err
} }
if len(dst) > 0 { if len(dst) > 0 {
into[key] = dst into[escapedKey] = dst
} }
case string, float64, bool: case string, float64, bool:
if !matchesValue(av, bv) { if !matchesValue(av, bv) {
into[key] = bv into[escapedKey] = bv
} }
case []interface{}: case []interface{}:
bt := bv.([]interface{}) bt := bv.([]interface{})
if !matchesArray(at, bt) { if !matchesArray(at, bt) {
into[key] = bv into[escapedKey] = bv
} }
case nil: case nil:
switch bv.(type) { switch bv.(type) {
case nil: case nil:
// Both nil, fine. // Both nil, fine.
default: default:
into[key] = bv into[escapedKey] = bv
} }
default: default:
panic(fmt.Sprintf("Unknown type:%T in key %s", av, key)) panic(fmt.Sprintf("Unknown type:%T in key %s", av, key))
@ -278,3 +284,23 @@ func getDiff(a, b map[string]interface{}) (map[string]interface{}, error) {
} }
return into, nil return into, nil
} }
// From http://tools.ietf.org/html/rfc6901#section-4 :
//
// Evaluation of each reference token begins by decoding any escaped
// character sequence. This is performed by first transforming any
// occurrence of the sequence '~1' to '/', and then transforming any
// occurrence of the sequence '~0' to '~'.
var (
rfc6901Encoder = strings.NewReplacer("~", "~0", "/", "~1")
rfc6901Decoder = strings.NewReplacer("~1", "/", "~0", "~")
)
func decodePatchKey(k string) string {
return rfc6901Decoder.Replace(k)
}
func encodePatchKey(k string) string {
return rfc6901Encoder.Replace(k)
}

View File

@ -32,6 +32,7 @@ type partialArray []*lazyNode
type container interface { type container interface {
get(key string) (*lazyNode, error) get(key string) (*lazyNode, error)
set(key string, val *lazyNode) error set(key string, val *lazyNode) error
add(key string, val *lazyNode) error
remove(key string) error remove(key string) error
} }
@ -42,7 +43,7 @@ func newLazyNode(raw *json.RawMessage) *lazyNode {
func (n *lazyNode) MarshalJSON() ([]byte, error) { func (n *lazyNode) MarshalJSON() ([]byte, error) {
switch n.which { switch n.which {
case eRaw: case eRaw:
return *n.raw, nil return json.Marshal(n.raw)
case eDoc: case eDoc:
return json.Marshal(n.doc) return json.Marshal(n.doc)
case eAry: case eAry:
@ -269,7 +270,7 @@ func findObject(pd *partialDoc, path string) (container, string) {
for _, part := range parts { for _, part := range parts {
next, ok := doc.get(part) next, ok := doc.get(decodePatchKey(part))
if next == nil || ok != nil { if next == nil || ok != nil {
return nil, "" return nil, ""
@ -290,7 +291,7 @@ func findObject(pd *partialDoc, path string) (container, string) {
} }
} }
return doc, key return doc, decodePatchKey(key)
} }
func (d *partialDoc) set(key string, val *lazyNode) error { func (d *partialDoc) set(key string, val *lazyNode) error {
@ -298,11 +299,21 @@ func (d *partialDoc) set(key string, val *lazyNode) error {
return nil return nil
} }
func (d *partialDoc) add(key string, val *lazyNode) error {
(*d)[key] = val
return nil
}
func (d *partialDoc) get(key string) (*lazyNode, error) { func (d *partialDoc) get(key string) (*lazyNode, error) {
return (*d)[key], nil return (*d)[key], nil
} }
func (d *partialDoc) remove(key string) error { func (d *partialDoc) remove(key string) error {
_, ok := (*d)[key]
if !ok {
return fmt.Errorf("Unable to remove nonexistant key: %s", key)
}
delete(*d, key) delete(*d, key)
return nil return nil
} }
@ -314,7 +325,38 @@ func (d *partialArray) set(key string, val *lazyNode) error {
} }
idx, err := strconv.Atoi(key) idx, err := strconv.Atoi(key)
if err != nil {
return err
}
sz := len(*d)
if idx+1 > sz {
sz = idx + 1
}
ary := make([]*lazyNode, sz)
cur := *d
copy(ary, cur)
if idx >= len(ary) {
fmt.Printf("huh?: %#v[%d] %s, %s\n", ary, idx)
}
ary[idx] = val
*d = ary
return nil
}
func (d *partialArray) add(key string, val *lazyNode) error {
if key == "-" {
*d = append(*d, val)
return nil
}
idx, err := strconv.Atoi(key)
if err != nil { if err != nil {
return err return err
} }
@ -338,18 +380,25 @@ func (d *partialArray) get(key string) (*lazyNode, error) {
return nil, err return nil, err
} }
if idx >= len(*d) {
return nil, fmt.Errorf("Unable to access invalid index: %d", idx)
}
return (*d)[idx], nil return (*d)[idx], nil
} }
func (d *partialArray) remove(key string) error { func (d *partialArray) remove(key string) error {
idx, err := strconv.Atoi(key) idx, err := strconv.Atoi(key)
if err != nil { if err != nil {
return err return err
} }
cur := *d cur := *d
if idx >= len(cur) {
return fmt.Errorf("Unable to remove invalid index: %d", idx)
}
ary := make([]*lazyNode, len(cur)-1) ary := make([]*lazyNode, len(cur)-1)
copy(ary[0:idx], cur[0:idx]) copy(ary[0:idx], cur[0:idx])
@ -366,12 +415,10 @@ func (p Patch) add(doc *partialDoc, op operation) error {
con, key := findObject(doc, path) con, key := findObject(doc, path)
if con == nil { if con == nil {
return fmt.Errorf("Missing container: %s", path) return fmt.Errorf("jsonpatch add operation does not apply: doc is missing path: %s", path)
} }
con.set(key, op.value()) return con.add(key, op.value())
return nil
} }
func (p Patch) remove(doc *partialDoc, op operation) error { func (p Patch) remove(doc *partialDoc, op operation) error {
@ -379,6 +426,10 @@ func (p Patch) remove(doc *partialDoc, op operation) error {
con, key := findObject(doc, path) con, key := findObject(doc, path)
if con == nil {
return fmt.Errorf("jsonpatch remove operation does not apply: doc is missing path: %s", path)
}
return con.remove(key) return con.remove(key)
} }
@ -387,9 +438,11 @@ func (p Patch) replace(doc *partialDoc, op operation) error {
con, key := findObject(doc, path) con, key := findObject(doc, path)
con.set(key, op.value()) if con == nil {
return fmt.Errorf("jsonpatch replace operation does not apply: doc is missing path: %s", path)
}
return nil return con.set(key, op.value())
} }
func (p Patch) move(doc *partialDoc, op operation) error { func (p Patch) move(doc *partialDoc, op operation) error {
@ -397,21 +450,29 @@ func (p Patch) move(doc *partialDoc, op operation) error {
con, key := findObject(doc, from) con, key := findObject(doc, from)
val, err := con.get(key) if con == nil {
return fmt.Errorf("jsonpatch move operation does not apply: doc is missing from path: %s", from)
}
val, err := con.get(key)
if err != nil { if err != nil {
return err return err
} }
con.remove(key) err = con.remove(key)
if err != nil {
return err
}
path := op.path() path := op.path()
con, key = findObject(doc, path) con, key = findObject(doc, path)
con.set(key, val) if con == nil {
return fmt.Errorf("jsonpatch move operation does not apply: doc is missing destination path: %s", path)
}
return nil return con.set(key, val)
} }
func (p Patch) test(doc *partialDoc, op operation) error { func (p Patch) test(doc *partialDoc, op operation) error {
@ -419,12 +480,24 @@ func (p Patch) test(doc *partialDoc, op operation) error {
con, key := findObject(doc, path) con, key := findObject(doc, path)
if con == nil {
return fmt.Errorf("jsonpatch test operation does not apply: is missing path: %s", path)
}
val, err := con.get(key) val, err := con.get(key)
if err != nil { if err != nil {
return err return err
} }
if val == nil {
if op.value().raw == nil {
return nil
} else {
return fmt.Errorf("Testing value %s failed", path)
}
}
if val.equal(op.value()) { if val.equal(op.value()) {
return nil return nil
} }
@ -461,6 +534,12 @@ func DecodePatch(buf []byte) (Patch, error) {
// Apply mutates a JSON document according to the patch, and returns the new // Apply mutates a JSON document according to the patch, and returns the new
// document. // document.
func (p Patch) Apply(doc []byte) ([]byte, error) { func (p Patch) Apply(doc []byte) ([]byte, error) {
return p.ApplyIndent(doc, "")
}
// ApplyIndent mutates a JSON document according to the patch, and returns the new
// document indented.
func (p Patch) ApplyIndent(doc []byte, indent string) ([]byte, error) {
pd := &partialDoc{} pd := &partialDoc{}
err := json.Unmarshal(doc, pd) err := json.Unmarshal(doc, pd)
@ -492,5 +571,9 @@ func (p Patch) Apply(doc []byte) ([]byte, error) {
} }
} }
if indent != "" {
return json.MarshalIndent(pd, "", indent)
}
return json.Marshal(pd) return json.Marshal(pd)
} }