Add REST API stitching
This commit is contained in:
161
jsn/filter.go
Normal file
161
jsn/filter.go
Normal file
@ -0,0 +1,161 @@
|
||||
package jsn
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
|
||||
"github.com/cespare/xxhash/v2"
|
||||
)
|
||||
|
||||
func Filter(w *bytes.Buffer, b []byte, keys []string) error {
|
||||
var err error
|
||||
|
||||
kmap := make(map[uint64]struct{}, len(keys))
|
||||
|
||||
for i := range keys {
|
||||
kmap[xxhash.Sum64String(keys[i])] = struct{}{}
|
||||
}
|
||||
|
||||
// is an list
|
||||
isList := false
|
||||
|
||||
// list item
|
||||
item := 0
|
||||
|
||||
// field in an object
|
||||
field := 0
|
||||
|
||||
s, e, d := 0, 0, 0
|
||||
|
||||
var k []byte
|
||||
state := expectKey
|
||||
|
||||
for i := 0; i < len(b); i++ {
|
||||
if state == expectObjClose || state == expectListClose {
|
||||
switch b[i] {
|
||||
case '{', '[':
|
||||
d++
|
||||
case '}', ']':
|
||||
d--
|
||||
}
|
||||
}
|
||||
|
||||
switch {
|
||||
case state == expectKey:
|
||||
switch b[i] {
|
||||
case '[':
|
||||
if !isList {
|
||||
err = w.WriteByte('[')
|
||||
}
|
||||
isList = true
|
||||
case '{':
|
||||
if item == 0 {
|
||||
err = w.WriteByte('{')
|
||||
} else {
|
||||
_, err = w.Write([]byte("},{"))
|
||||
}
|
||||
item++
|
||||
field = 0
|
||||
case '"':
|
||||
state = expectKeyClose
|
||||
s = i
|
||||
i++
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
case state == expectKeyClose && b[i] == '"':
|
||||
state = expectColon
|
||||
k = b[(s + 1):i]
|
||||
|
||||
case state == expectColon && b[i] == ':':
|
||||
state = expectValue
|
||||
|
||||
case state == expectValue && b[i] == '"':
|
||||
state = expectString
|
||||
|
||||
case state == expectString && b[i] == '"':
|
||||
e = i
|
||||
|
||||
case state == expectValue && b[i] == '[':
|
||||
state = expectListClose
|
||||
d++
|
||||
|
||||
case state == expectListClose && d == 0 && b[i] == ']':
|
||||
e = i
|
||||
|
||||
case state == expectValue && b[i] == '{':
|
||||
state = expectObjClose
|
||||
d++
|
||||
|
||||
case state == expectObjClose && d == 0 && b[i] == '}':
|
||||
e = i
|
||||
|
||||
case state == expectValue && (b[i] >= '0' && b[i] <= '9'):
|
||||
state = expectNumClose
|
||||
|
||||
case state == expectNumClose &&
|
||||
((b[i] < '0' || b[i] > '9') &&
|
||||
(b[i] != '.' && b[i] != 'e' && b[i] != 'E' && b[i] != '+' && b[i] != '-')):
|
||||
i--
|
||||
e = i
|
||||
|
||||
case state == expectValue &&
|
||||
(b[i] == 'f' || b[i] == 'F' || b[i] == 't' || b[i] == 'T'):
|
||||
state = expectBoolClose
|
||||
|
||||
case state == expectBoolClose && (b[i] == 'e' || b[i] == 'E'):
|
||||
e = i
|
||||
}
|
||||
|
||||
if e != 0 {
|
||||
state = expectKey
|
||||
cb := b[s:(e + 1)]
|
||||
e = 0
|
||||
|
||||
if _, ok := kmap[xxhash.Sum64(k)]; !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
if field != 0 {
|
||||
if err := w.WriteByte(','); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
sk := 0
|
||||
for i := 0; i < len(cb); i++ {
|
||||
if cb[i] == '\n' || cb[i] == '\t' {
|
||||
if _, err := w.Write(cb[sk:i]); err != nil {
|
||||
return err
|
||||
}
|
||||
sk = i + 1
|
||||
}
|
||||
}
|
||||
|
||||
if sk > 0 && sk < len(cb) {
|
||||
_, err = w.Write(cb[sk:len(cb)])
|
||||
} else {
|
||||
_, err = w.Write(cb)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
field++
|
||||
}
|
||||
}
|
||||
|
||||
if item != 0 {
|
||||
if err := w.WriteByte('}'); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if isList {
|
||||
if err := w.WriteByte(']'); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
133
jsn/get.go
Normal file
133
jsn/get.go
Normal file
@ -0,0 +1,133 @@
|
||||
package jsn
|
||||
|
||||
import (
|
||||
"github.com/cespare/xxhash/v2"
|
||||
)
|
||||
|
||||
const (
|
||||
expectKey int = iota
|
||||
expectKeyClose
|
||||
expectColon
|
||||
expectValue
|
||||
expectString
|
||||
expectListClose
|
||||
expectObjClose
|
||||
expectBoolClose
|
||||
expectNumClose
|
||||
)
|
||||
|
||||
type Field struct {
|
||||
Key []byte
|
||||
Value []byte
|
||||
}
|
||||
|
||||
func Value(b []byte) []byte {
|
||||
e := (len(b) - 1)
|
||||
switch {
|
||||
case b[0] == '"' && b[e] == '"':
|
||||
return b[1:(len(b) - 1)]
|
||||
case b[0] == '[' && b[e] == ']':
|
||||
return nil
|
||||
case b[0] == '{' && b[e] == '}':
|
||||
return nil
|
||||
default:
|
||||
return b
|
||||
}
|
||||
}
|
||||
|
||||
func Get(b []byte, keys [][]byte) []Field {
|
||||
kmap := make(map[uint64]struct{}, len(keys))
|
||||
|
||||
for i := range keys {
|
||||
kmap[xxhash.Sum64(keys[i])] = struct{}{}
|
||||
}
|
||||
|
||||
res := make([]Field, 20)
|
||||
|
||||
s, e, d := 0, 0, 0
|
||||
|
||||
var k []byte
|
||||
state := expectKey
|
||||
|
||||
n := 0
|
||||
for i := 0; i < len(b); i++ {
|
||||
if state == expectObjClose || state == expectListClose {
|
||||
switch b[i] {
|
||||
case '{', '[':
|
||||
d++
|
||||
case '}', ']':
|
||||
d--
|
||||
}
|
||||
}
|
||||
|
||||
switch {
|
||||
case state == expectKey && b[i] == '"':
|
||||
state = expectKeyClose
|
||||
s = i
|
||||
|
||||
case state == expectKeyClose && b[i] == '"':
|
||||
state = expectColon
|
||||
k = b[(s + 1):i]
|
||||
|
||||
case state == expectColon && b[i] == ':':
|
||||
state = expectValue
|
||||
|
||||
case state == expectValue && b[i] == '"':
|
||||
state = expectString
|
||||
s = i
|
||||
|
||||
case state == expectString && b[i] == '"':
|
||||
e = i
|
||||
|
||||
case state == expectValue && b[i] == '[':
|
||||
state = expectListClose
|
||||
s = i
|
||||
d++
|
||||
|
||||
case state == expectListClose && d == 0 && b[i] == ']':
|
||||
e = i
|
||||
i = s
|
||||
|
||||
case state == expectValue && b[i] == '{':
|
||||
state = expectObjClose
|
||||
s = i
|
||||
d++
|
||||
|
||||
case state == expectObjClose && d == 0 && b[i] == '}':
|
||||
e = i
|
||||
i = s
|
||||
|
||||
case state == expectValue && (b[i] >= '0' && b[i] <= '9'):
|
||||
state = expectNumClose
|
||||
s = i
|
||||
|
||||
case state == expectNumClose &&
|
||||
((b[i] < '0' || b[i] > '9') &&
|
||||
(b[i] != '.' && b[i] != 'e' && b[i] != 'E' && b[i] != '+' && b[i] != '-')):
|
||||
i--
|
||||
e = i
|
||||
|
||||
case state == expectValue &&
|
||||
(b[i] == 'f' || b[i] == 'F' || b[i] == 't' || b[i] == 'T'):
|
||||
state = expectBoolClose
|
||||
s = i
|
||||
|
||||
case state == expectBoolClose && (b[i] == 'e' || b[i] == 'E'):
|
||||
e = i
|
||||
}
|
||||
|
||||
if e != 0 {
|
||||
_, ok := kmap[xxhash.Sum64(k)]
|
||||
|
||||
if ok {
|
||||
res[n] = Field{k, b[s:(e + 1)]}
|
||||
n++
|
||||
}
|
||||
|
||||
state = expectKey
|
||||
e = 0
|
||||
}
|
||||
}
|
||||
|
||||
return res[:n]
|
||||
}
|
365
jsn/json_test.go
Normal file
365
jsn/json_test.go
Normal file
@ -0,0 +1,365 @@
|
||||
package jsn
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"testing"
|
||||
)
|
||||
|
||||
var (
|
||||
input1 = `
|
||||
{
|
||||
"data": {
|
||||
"test": { "__twitter_id": "ABCD" },
|
||||
"users": [
|
||||
{
|
||||
"id": 1,
|
||||
"full_name": "Sidney Stroman",
|
||||
"email": "user0@demo.com",
|
||||
"__twitter_id": "2048666903444506956",
|
||||
"embed": {
|
||||
"id": 8,
|
||||
"full_name": "Caroll Orn Sr.",
|
||||
"email": "joannarau@hegmann.io",
|
||||
"__twitter_id": "ABC123"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 2,
|
||||
"full_name": "Jerry Dickinson",
|
||||
"email": "user1@demo.com",
|
||||
"__twitter_id": [{ "name": "hello" }, { "name": "world"}]
|
||||
},
|
||||
{
|
||||
"id": 3,
|
||||
"full_name": "Kenna Cassin",
|
||||
"email": "user2@demo.com",
|
||||
"__twitter_id": { "name": "hello", "address": { "work": "1 infinity loop" } }
|
||||
},
|
||||
{
|
||||
"id": 4,
|
||||
"full_name": "Mr. Pat Parisian",
|
||||
"email": "__twitter_id",
|
||||
"__twitter_id": 1234567890
|
||||
},
|
||||
{
|
||||
"id": 5,
|
||||
"full_name": "Bette Ebert",
|
||||
"email": "janeenrath@goyette.com",
|
||||
"__twitter_id": 1.23E
|
||||
},
|
||||
{
|
||||
"id": 6,
|
||||
"full_name": "Everett Kiehn",
|
||||
"email": "michael@bartoletti.com",
|
||||
"__twitter_id": true
|
||||
},
|
||||
{
|
||||
"id": 7,
|
||||
"full_name": "Katrina Cronin",
|
||||
"email": "loretaklocko@framivolkman.org",
|
||||
"__twitter_id": false
|
||||
},
|
||||
{
|
||||
"id": 8,
|
||||
"full_name": "Caroll Orn Sr.",
|
||||
"email": "joannarau@hegmann.io",
|
||||
"__twitter_id": "2048666903444506956"
|
||||
},
|
||||
{
|
||||
"id": 9,
|
||||
"full_name": "Gwendolyn Ziemann",
|
||||
"email": "renaytoy@rutherford.co",
|
||||
"__twitter_id": ["hello", "world"]
|
||||
},
|
||||
{
|
||||
"id": 10,
|
||||
"full_name": "Mrs. Rosann Fritsch",
|
||||
"email": "holliemosciski@thiel.org",
|
||||
"__twitter_id": "2048666903444506956"
|
||||
},
|
||||
{
|
||||
"id": 11,
|
||||
"full_name": "Arden Koss",
|
||||
"email": "cristobalankunding@howewelch.org",
|
||||
"__twitter_id": "2048666903444506956"
|
||||
},
|
||||
{
|
||||
"id": 12,
|
||||
"full_name": "Brenton Bauch PhD",
|
||||
"email": "renee@miller.co",
|
||||
"__twitter_id": 1
|
||||
},
|
||||
{
|
||||
"id": 13,
|
||||
"full_name": "Daine Gleichner",
|
||||
"email": "andrea@gmail.com",
|
||||
"__twitter_id": "",
|
||||
"id__twitter_id": "NOOO",
|
||||
"work_email": "andrea@nienow.co"
|
||||
}
|
||||
]}
|
||||
}`
|
||||
|
||||
input2 = `
|
||||
[{
|
||||
"id": 1,
|
||||
"full_name": "Sidney Stroman",
|
||||
"email": "user0@demo.com",
|
||||
"__twitter_id": "2048666903444506956",
|
||||
"embed": {
|
||||
"id": 8,
|
||||
"full_name": "Caroll Orn Sr.",
|
||||
"email": "joannarau@hegmann.io",
|
||||
"__twitter_id": "ABC123"
|
||||
}
|
||||
},
|
||||
{
|
||||
"m": 1,
|
||||
"id": 2,
|
||||
"full_name": "Jerry Dickinson",
|
||||
"email": "user1@demo.com",
|
||||
"__twitter_id": [{ "name": "hello" }, { "name": "world"}]
|
||||
}]`
|
||||
|
||||
input3 = `
|
||||
{
|
||||
"data": {
|
||||
"test": { "__twitter_id": "ABCD" },
|
||||
"users": [{"id":1,"embed":{"id":8}},{"id":2},{"id":3},{"id":4},{"id":5},{"id":6},{"id":7},{"id":8},{"id":9},{"id":10},{"id":11},{"id":12},{"id":13}]
|
||||
}
|
||||
}`
|
||||
|
||||
input4 = `
|
||||
{ "users" : [{
|
||||
"id": 1,
|
||||
"full_name": "Sidney Stroman",
|
||||
"email": "user0@demo.com",
|
||||
"__twitter_id": "2048666903444506956",
|
||||
"embed": {
|
||||
"id": 8,
|
||||
"full_name": "Caroll Orn Sr.",
|
||||
"email": "joannarau@hegmann.io",
|
||||
"__twitter_id": "ABC123"
|
||||
}
|
||||
},
|
||||
{
|
||||
"m": 1,
|
||||
"id": 2,
|
||||
"full_name": "Jerry Dickinson",
|
||||
"email": "user1@demo.com",
|
||||
"__twitter_id": [{ "name": "hello" }, { "name": "world"}]
|
||||
}] }`
|
||||
)
|
||||
|
||||
func TestGet(t *testing.T) {
|
||||
values := Get([]byte(input1), [][]byte{
|
||||
[]byte("__twitter_id"),
|
||||
[]byte("work_email"),
|
||||
})
|
||||
|
||||
expected := []Field{
|
||||
{[]byte("__twitter_id"), []byte(`"ABCD"`)},
|
||||
{[]byte("__twitter_id"), []byte(`"2048666903444506956"`)},
|
||||
{[]byte("__twitter_id"), []byte(`"ABC123"`)},
|
||||
{[]byte("__twitter_id"),
|
||||
[]byte(`[{ "name": "hello" }, { "name": "world"}]`)},
|
||||
{[]byte("__twitter_id"),
|
||||
[]byte(`{ "name": "hello", "address": { "work": "1 infinity loop" } }`),
|
||||
},
|
||||
{[]byte("__twitter_id"), []byte(`1234567890`)},
|
||||
{[]byte("__twitter_id"), []byte(`1.23E`)},
|
||||
{[]byte("__twitter_id"), []byte(`true`)},
|
||||
{[]byte("__twitter_id"), []byte(`false`)},
|
||||
{[]byte("__twitter_id"), []byte(`"2048666903444506956"`)},
|
||||
{[]byte("__twitter_id"), []byte(`["hello", "world"]`)},
|
||||
{[]byte("__twitter_id"), []byte(`"2048666903444506956"`)},
|
||||
{[]byte("__twitter_id"), []byte(`"2048666903444506956"`)},
|
||||
{[]byte("__twitter_id"), []byte(`1`)},
|
||||
{[]byte("__twitter_id"), []byte(`""`)},
|
||||
{[]byte("work_email"), []byte(`"andrea@nienow.co"`)},
|
||||
}
|
||||
|
||||
if len(values) != len(expected) {
|
||||
t.Fatal("len(values) != len(expected)")
|
||||
}
|
||||
|
||||
for i := range expected {
|
||||
if bytes.Equal(values[i].Key, expected[i].Key) == false {
|
||||
t.Error(string(values[i].Key), " != ", string(expected[i].Key))
|
||||
}
|
||||
|
||||
if bytes.Equal(values[i].Value, expected[i].Value) == false {
|
||||
t.Error(string(values[i].Value), " != ", string(expected[i].Value))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestValue(t *testing.T) {
|
||||
v1 := []byte("12345")
|
||||
if !bytes.Equal(Value(v1), v1) {
|
||||
t.Fatal("Number value invalid")
|
||||
}
|
||||
|
||||
v2 := []byte(`"12345"`)
|
||||
if !bytes.Equal(Value(v2), []byte(`12345`)) {
|
||||
t.Fatal("String value invalid")
|
||||
}
|
||||
|
||||
v3 := []byte(`{ "hello": "world" }`)
|
||||
if Value(v3) != nil {
|
||||
t.Fatal("Object value is not nil", Value(v3))
|
||||
}
|
||||
|
||||
v4 := []byte(`[ "hello", "world" ]`)
|
||||
if Value(v4) != nil {
|
||||
t.Fatal("List value is not nil")
|
||||
}
|
||||
}
|
||||
|
||||
func TestFilter(t *testing.T) {
|
||||
var b bytes.Buffer
|
||||
Filter(&b, []byte(input2), []string{"id", "full_name", "embed"})
|
||||
|
||||
expected := `[{"id": 1,"full_name": "Sidney Stroman","embed": {"id": 8,"full_name": "Caroll Orn Sr.","email": "joannarau@hegmann.io","__twitter_id": "ABC123"}},{"id": 2,"full_name": "Jerry Dickinson"}]`
|
||||
|
||||
if b.String() != expected {
|
||||
t.Error("Does not match expected json")
|
||||
}
|
||||
}
|
||||
func TestStrip(t *testing.T) {
|
||||
path1 := [][]byte{[]byte("data"), []byte("users")}
|
||||
value1 := Strip([]byte(input3), path1)
|
||||
|
||||
expected := []byte(`[{"id":1,"embed":{"id":8}},{"id":2},{"id":3},{"id":4},{"id":5},{"id":6},{"id":7},{"id":8},{"id":9},{"id":10},{"id":11},{"id":12},{"id":13}]`)
|
||||
|
||||
if bytes.Equal(value1, expected) == false {
|
||||
t.Log(value1)
|
||||
t.Error("[Valid path] Does not match expected json")
|
||||
}
|
||||
|
||||
path2 := [][]byte{[]byte("boo"), []byte("hoo")}
|
||||
value2 := Strip([]byte(input3), path2)
|
||||
|
||||
if bytes.Equal(value2, []byte(input3)) == false {
|
||||
t.Log(value2)
|
||||
t.Error("[Invalid path] Does not match expected json")
|
||||
}
|
||||
}
|
||||
|
||||
func TestReplace(t *testing.T) {
|
||||
var buf bytes.Buffer
|
||||
|
||||
from := []Field{
|
||||
{[]byte("__twitter_id"), []byte(`[{ "name": "hello" }, { "name": "world"}]`)},
|
||||
{[]byte("__twitter_id"), []byte(`"ABC123"`)},
|
||||
}
|
||||
|
||||
to := []Field{
|
||||
{[]byte("__twitter_id"), []byte(`"1234567890"`)},
|
||||
{[]byte("some_list"), []byte(`[{"id":1,"embed":{"id":8}},{"id":2},{"id":3},{"id":4},{"id":5},{"id":6},{"id":7},{"id":8},{"id":9},{"id":10},{"id":11},{"id":12},{"id":13}]`)},
|
||||
}
|
||||
|
||||
expected := `{ "users" : [{
|
||||
"id": 1,
|
||||
"full_name": "Sidney Stroman",
|
||||
"email": "user0@demo.com",
|
||||
"__twitter_id": "2048666903444506956",
|
||||
"embed": {
|
||||
"id": 8,
|
||||
"full_name": "Caroll Orn Sr.",
|
||||
"email": "joannarau@hegmann.io",
|
||||
"some_list":[{"id":1,"embed":{"id":8}},{"id":2},{"id":3},{"id":4},{"id":5},{"id":6},{"id":7},{"id":8},{"id":9},{"id":10},{"id":11},{"id":12},{"id":13}]
|
||||
}
|
||||
},
|
||||
{
|
||||
"m": 1,
|
||||
"id": 2,
|
||||
"full_name": "Jerry Dickinson",
|
||||
"email": "user1@demo.com",
|
||||
"__twitter_id":"1234567890"
|
||||
}] }`
|
||||
|
||||
err := Replace(&buf, []byte(input4), from, to)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if buf.String() != expected {
|
||||
t.Log(buf.String())
|
||||
t.Error("Does not match expected json")
|
||||
}
|
||||
}
|
||||
|
||||
func TestReplaceEmpty(t *testing.T) {
|
||||
var buf bytes.Buffer
|
||||
|
||||
json := `{ "users" : [{"id":1,"full_name":"Sidney Stroman","email":"user0@demo.com","__users_twitter_id":"2048666903444506956"}, {"id":2,"full_name":"Jerry Dickinson","email":"user1@demo.com","__users_twitter_id":"2048666903444506956"}, {"id":3,"full_name":"Kenna Cassin","email":"user2@demo.com","__users_twitter_id":"2048666903444506956"}, {"id":4,"full_name":"Mr. Pat Parisian","email":"rodney@kautzer.biz","__users_twitter_id":"2048666903444506956"}, {"id":5,"full_name":"Bette Ebert","email":"janeenrath@goyette.com","__users_twitter_id":"2048666903444506956"}, {"id":6,"full_name":"Everett Kiehn","email":"michael@bartoletti.com","__users_twitter_id":"2048666903444506956"}, {"id":7,"full_name":"Katrina Cronin","email":"loretaklocko@framivolkman.org","__users_twitter_id":"2048666903444506956"}, {"id":8,"full_name":"Caroll Orn Sr.","email":"joannarau@hegmann.io","__users_twitter_id":"2048666903444506956"}, {"id":9,"full_name":"Gwendolyn Ziemann","email":"renaytoy@rutherford.co","__users_twitter_id":"2048666903444506956"}, {"id":10,"full_name":"Mrs. Rosann Fritsch","email":"holliemosciski@thiel.org","__users_twitter_id":"2048666903444506956"}, {"id":11,"full_name":"Arden Koss","email":"cristobalankunding@howewelch.org","__users_twitter_id":"2048666903444506956"}, {"id":12,"full_name":"Brenton Bauch PhD","email":"renee@miller.co","__users_twitter_id":"2048666903444506956"}, {"id":13,"full_name":"Daine Gleichner","email":"andrea@nienow.co","__users_twitter_id":"2048666903444506956"}] }`
|
||||
|
||||
err := Replace(&buf, []byte(json), []Field{}, []Field{})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if buf.String() != json {
|
||||
t.Log(buf.String())
|
||||
t.Error("Does not match expected json")
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkGet(b *testing.B) {
|
||||
b.ReportAllocs()
|
||||
|
||||
for n := 0; n < b.N; n++ {
|
||||
Get([]byte(input1), [][]byte{[]byte("__twitter_id")})
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkFilter(b *testing.B) {
|
||||
var buf bytes.Buffer
|
||||
|
||||
keys := []string{"id", "full_name", "embed", "email", "__twitter_id"}
|
||||
b.ResetTimer()
|
||||
b.ReportAllocs()
|
||||
|
||||
for n := 0; n < b.N; n++ {
|
||||
err := Filter(&buf, []byte(input2), keys)
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
buf.Reset()
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkStrip(b *testing.B) {
|
||||
path := [][]byte{[]byte("data"), []byte("users")}
|
||||
b.ReportAllocs()
|
||||
|
||||
for n := 0; n < b.N; n++ {
|
||||
Strip([]byte(input3), path)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkReplace(b *testing.B) {
|
||||
var buf bytes.Buffer
|
||||
|
||||
from := []Field{
|
||||
{[]byte("__twitter_id"), []byte(`[{ "name": "hello" }, { "name": "world"}]`)},
|
||||
{[]byte("__twitter_id"), []byte(`"ABC123"`)},
|
||||
}
|
||||
|
||||
to := []Field{
|
||||
{[]byte("__twitter_id"), []byte(`"1234567890"`)},
|
||||
{[]byte("some_list"), []byte(`[{"id":1,"embed":{"id":8}},{"id":2},{"id":3},{"id":4},{"id":5},{"id":6},{"id":7},{"id":8},{"id":9},{"id":10},{"id":11},{"id":12},{"id":13}]`)},
|
||||
}
|
||||
|
||||
b.ResetTimer()
|
||||
b.ReportAllocs()
|
||||
|
||||
for n := 0; n < b.N; n++ {
|
||||
err := Replace(&buf, []byte(input4), from, to)
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
buf.Reset()
|
||||
}
|
||||
}
|
158
jsn/replace.go
Normal file
158
jsn/replace.go
Normal file
@ -0,0 +1,158 @@
|
||||
package jsn
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
|
||||
"github.com/cespare/xxhash/v2"
|
||||
)
|
||||
|
||||
func Replace(w *bytes.Buffer, b []byte, from, to []Field) error {
|
||||
if len(from) != len(to) {
|
||||
return errors.New("'from' and 'to' must be of the same length")
|
||||
}
|
||||
|
||||
h := xxhash.New()
|
||||
tmap := make(map[uint64]int, len(from))
|
||||
|
||||
for i, f := range from {
|
||||
h.Write(f.Key)
|
||||
h.Write(f.Value)
|
||||
|
||||
tmap[h.Sum64()] = i
|
||||
h.Reset()
|
||||
}
|
||||
|
||||
s, e, d := 0, 0, 0
|
||||
|
||||
state := expectKey
|
||||
ws, we := -1, len(b)
|
||||
|
||||
for i := 0; i < len(b); i++ {
|
||||
// skip any left padding whitespace
|
||||
if ws == -1 && (b[i] == '{' || b[i] == '[') {
|
||||
ws = i
|
||||
}
|
||||
|
||||
if state == expectObjClose || state == expectListClose {
|
||||
switch b[i] {
|
||||
case '{', '[':
|
||||
d++
|
||||
case '}', ']':
|
||||
d--
|
||||
}
|
||||
}
|
||||
|
||||
switch {
|
||||
case state == expectKey && b[i] == '"':
|
||||
state = expectKeyClose
|
||||
s = i
|
||||
|
||||
case state == expectKeyClose && b[i] == '"':
|
||||
state = expectColon
|
||||
h.Write(b[(s + 1):i])
|
||||
we = s
|
||||
|
||||
case state == expectColon && b[i] == ':':
|
||||
state = expectValue
|
||||
|
||||
case state == expectValue && b[i] == '"':
|
||||
state = expectString
|
||||
s = i
|
||||
|
||||
case state == expectString && b[i] == '"':
|
||||
e = i
|
||||
|
||||
case state == expectValue && b[i] == '[':
|
||||
state = expectListClose
|
||||
s = i
|
||||
d++
|
||||
|
||||
case state == expectListClose && d == 0 && b[i] == ']':
|
||||
e = i
|
||||
|
||||
case state == expectValue && b[i] == '{':
|
||||
state = expectObjClose
|
||||
s = i
|
||||
d++
|
||||
|
||||
case state == expectObjClose && d == 0 && b[i] == '}':
|
||||
e = i
|
||||
|
||||
case state == expectValue && (b[i] >= '0' && b[i] <= '9'):
|
||||
state = expectNumClose
|
||||
s = i
|
||||
|
||||
case state == expectNumClose &&
|
||||
((b[i] < '0' || b[i] > '9') &&
|
||||
(b[i] != '.' && b[i] != 'e' && b[i] != 'E' && b[i] != '+' && b[i] != '-')):
|
||||
i--
|
||||
e = i
|
||||
|
||||
case state == expectValue &&
|
||||
(b[i] == 'f' || b[i] == 'F' || b[i] == 't' || b[i] == 'T'):
|
||||
state = expectBoolClose
|
||||
s = i
|
||||
|
||||
case state == expectBoolClose && (b[i] == 'e' || b[i] == 'E'):
|
||||
e = i
|
||||
}
|
||||
|
||||
if e != 0 {
|
||||
e++
|
||||
|
||||
h.Write(b[s:e])
|
||||
n, ok := tmap[h.Sum64()]
|
||||
h.Reset()
|
||||
|
||||
if ok {
|
||||
if _, err := w.Write(b[ws:(we + 1)]); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if len(to[n].Key) != 0 {
|
||||
var err error
|
||||
|
||||
if _, err := w.Write(to[n].Key); err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err := w.WriteString(`":`); err != nil {
|
||||
return err
|
||||
}
|
||||
if len(to[n].Value) != 0 {
|
||||
_, err = w.Write(to[n].Value)
|
||||
} else {
|
||||
_, err = w.WriteString("null")
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ws = e
|
||||
} else if b[e] == ',' {
|
||||
ws = e + 1
|
||||
} else {
|
||||
ws = e
|
||||
}
|
||||
}
|
||||
|
||||
if !ok && (b[s] == '[' || b[s] == '{') {
|
||||
// the i++ in the for loop will add 1 so we account for that (s - 1)
|
||||
i = s - 1
|
||||
}
|
||||
|
||||
state = expectKey
|
||||
we = len(b)
|
||||
e = 0
|
||||
d = 0
|
||||
}
|
||||
}
|
||||
|
||||
if ws == -1 || (ws == 0 && we == len(b)) {
|
||||
w.Write(b)
|
||||
} else {
|
||||
w.Write(b[ws:we])
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
101
jsn/strip.go
Normal file
101
jsn/strip.go
Normal file
@ -0,0 +1,101 @@
|
||||
package jsn
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
)
|
||||
|
||||
func Strip(b []byte, path [][]byte) []byte {
|
||||
s, e, d := 0, 0, 0
|
||||
|
||||
ob := b
|
||||
pi := 0
|
||||
pm := false
|
||||
state := expectKey
|
||||
|
||||
for i := 0; i < len(b); i++ {
|
||||
if state == expectObjClose || state == expectListClose {
|
||||
switch b[i] {
|
||||
case '{', '[':
|
||||
d++
|
||||
case '}', ']':
|
||||
d--
|
||||
}
|
||||
}
|
||||
|
||||
switch {
|
||||
case state == expectKey && b[i] == '"':
|
||||
state = expectKeyClose
|
||||
s = i
|
||||
|
||||
case state == expectKeyClose && b[i] == '"':
|
||||
state = expectColon
|
||||
if pi == len(path) {
|
||||
pi = 0
|
||||
}
|
||||
pm = bytes.Equal(b[(s+1):i], path[pi])
|
||||
if pm {
|
||||
pi++
|
||||
}
|
||||
|
||||
case state == expectColon && b[i] == ':':
|
||||
state = expectValue
|
||||
|
||||
case state == expectValue && b[i] == '"':
|
||||
state = expectString
|
||||
s = i
|
||||
|
||||
case state == expectString && b[i] == '"':
|
||||
e = i
|
||||
|
||||
case state == expectValue && b[i] == '[':
|
||||
state = expectListClose
|
||||
s = i
|
||||
d++
|
||||
|
||||
case state == expectListClose && d == 0 && b[i] == ']':
|
||||
e = i
|
||||
|
||||
case state == expectValue && b[i] == '{':
|
||||
state = expectObjClose
|
||||
s = i
|
||||
d++
|
||||
|
||||
case state == expectObjClose && d == 0 && b[i] == '}':
|
||||
e = i
|
||||
|
||||
case state == expectValue && (b[i] >= '0' && b[i] <= '9'):
|
||||
state = expectNumClose
|
||||
s = i
|
||||
|
||||
case state == expectNumClose &&
|
||||
((b[i] < '0' || b[i] > '9') &&
|
||||
(b[i] != '.' && b[i] != 'e' && b[i] != 'E' && b[i] != '+' && b[i] != '-')):
|
||||
i--
|
||||
e = i
|
||||
|
||||
case state == expectValue &&
|
||||
(b[i] == 'f' || b[i] == 'F' || b[i] == 't' || b[i] == 'T'):
|
||||
state = expectBoolClose
|
||||
s = i
|
||||
|
||||
case state == expectBoolClose && (b[i] == 'e' || b[i] == 'E'):
|
||||
e = i
|
||||
}
|
||||
|
||||
if e != 0 {
|
||||
if pm && (b[s] == '[' || b[s] == '{') {
|
||||
b = b[s:(e + 1)]
|
||||
i = 0
|
||||
|
||||
if pi == len(path) {
|
||||
return b
|
||||
}
|
||||
}
|
||||
|
||||
state = expectKey
|
||||
e = 0
|
||||
}
|
||||
}
|
||||
|
||||
return ob
|
||||
}
|
Reference in New Issue
Block a user