diff --git a/sjson.go b/sjson.go index 9cf3f93..ff34826 100644 --- a/sjson.go +++ b/sjson.go @@ -166,6 +166,10 @@ func atoui(r pathResult) (n int, ok bool) { if r.force { return 0, false } + if len(r.part) == 0 { + // An empty key is a valid object key, not the number zero. + return 0, false + } for i := 0; i < len(r.part); i++ { if r.part[i] < '0' || r.part[i] > '9' { return 0, false @@ -515,9 +519,6 @@ type sliceHeader struct { func set(jstr, path, raw string, stringify, del, optimistic, inplace bool) ([]byte, error) { - if path == "" { - return []byte(jstr), &errorType{"path cannot be empty"} - } if !del && optimistic && isOptimisticPath(path) { res := gjson.Get(jstr, path) if res.Exists() && res.Index > 0 { diff --git a/sjson_test.go b/sjson_test.go index aa3d968..f0c5373 100644 --- a/sjson_test.go +++ b/sjson_test.go @@ -351,3 +351,47 @@ func TestIssue61(t *testing.T) { t.Fail() } } + +func TestEmptyKeyIssue77(t *testing.T) { + const json = `{"name":{"first":"Janet"},"age":47,"":"foo"}` + + // An empty key is a valid object key and gjson reads it, so sjson has to + // be able to write and delete it too. + if gjson.Get(json, "").String() != "foo" { + t.Fatalf("gjson cannot read the empty key") + } + + testRaw(t, setString, `{"name":{"first":"Janet"},"age":47,"":"bar"}`, + json, "", "bar") + testRaw(t, setDelete, `{"name":{"first":"Janet"},"age":47}`, + json, "", nil) + + // setting an empty key that is not there yet adds it + testRaw(t, setString, `{"a":1,"":"bar"}`, `{"a":1}`, "", "bar") + testRaw(t, setString, `{"":"bar"}`, `{}`, "", "bar") + + // deleting an empty key that is not there is a no-op, like any other + // missing key + testRaw(t, setDelete, `{"a":1}`, `{"a":1}`, "", nil) + + // an empty key is a key, never the array index zero + testRaw(t, setInt, `{"x":1,"a":{"":2}}`, `{"x":1}`, "a.", 2) + testRaw(t, setInt, `{"a":{"b":{"":2}}}`, `{}`, "a.b.", 2) + testRaw(t, setInt, `{"a":{"":{"b":2}}}`, `{}`, "a..b", 2) + + // whatever sjson writes for an empty key, gjson has to read back + out, err := Set(`{"x":1}`, "a.", 2) + if err != nil { + t.Fatal(err) + } + if res := gjson.Get(out, "a."); !res.Exists() || res.Int() != 2 { + t.Fatalf("expected gjson to read back '2', got '%v' from '%v'", + res.Raw, out) + } + + // an empty key still cannot address an array element, and reports the + // same error as any other non-numeric key + if _, err := Set(`[1,2,3]`, "", "x"); err == nil { + t.Fatalf("expected an error for an empty key on an array") + } +}