Add a json parser

This commit is contained in:
Adam Scarr 2017-08-06 19:15:07 +10:00
parent 2c0c5b628f
commit 9d7779e8ca
6 changed files with 203 additions and 1 deletions

5
.gitignore vendored
View File

@ -1,2 +1,5 @@
.idea
*.iml
*.iml
*.exe
cpu.out
mem.out

68
json/json.go Normal file
View File

@ -0,0 +1,68 @@
package json
import (
"errors"
. "github.com/vektah/goparsify"
)
var (
value Parser
array = Map(And(WS, "[", Kleene(&value, And(WS, ",")), "]"), func(n Node) Node {
return n.([]Node)[1].([]Node)
})
properties = Kleene(And(WS, String('"'), WS, ":", WS, &value), ",")
object = Map(And(WS, "{", WS, properties, WS, "}"), func(n Node) Node {
ret := map[string]interface{}{}
for _, prop := range n.([]Node)[1].([]Node) {
vals := prop.([]Node)
if len(vals) == 3 {
ret[vals[0].(string)] = vals[2]
} else {
ret[vals[0].(string)] = nil
}
}
return ret
})
_null = Map(And(WS, "null"), func(n Node) Node {
return nil
})
_true = Map(And(WS, "true"), func(n Node) Node {
return true
})
_false = Map(And(WS, "false"), func(n Node) Node {
return false
})
Y = Map(And(&value, WS), func(n Node) Node {
nodes := n.([]Node)
if len(nodes) > 0 {
return nodes[0]
}
return nil
})
)
func init() {
value = Any(_null, _true, _false, String('"'), array, object)
}
func Unmarshal(input string) (interface{}, error) {
result, remaining, err := ParseString(Y, input)
if err != nil {
return result, err
}
if remaining != "" {
return result, errors.New("left unparsed: " + remaining)
}
return result, err
}

70
json/json_test.go Normal file
View File

@ -0,0 +1,70 @@
package json
import (
"testing"
"github.com/stretchr/testify/require"
. "github.com/vektah/goparsify"
)
func TestUnmarshal(t *testing.T) {
t.Run("basic types", func(t *testing.T) {
result, err := Unmarshal(`true`)
require.NoError(t, err)
require.Equal(t, true, result)
result, err = Unmarshal(`false`)
require.NoError(t, err)
require.Equal(t, false, result)
result, err = Unmarshal(`null`)
require.NoError(t, err)
require.Equal(t, nil, result)
result, err = Unmarshal(`"true"`)
require.NoError(t, err)
require.Equal(t, "true", result)
})
t.Run("array", func(t *testing.T) {
result, err := Unmarshal(`[true, null, false]`)
require.NoError(t, err)
require.Equal(t, []Node{true, nil, false}, result)
})
t.Run("object", func(t *testing.T) {
result, err := Unmarshal(`{"true":true, "false":false, "null": null} `)
require.NoError(t, err)
require.Equal(t, map[string]interface{}{"true": true, "false": false, "null": nil}, result)
})
}
const benchmarkString = `{"true":true, "false":false, "null": null}`
//func BenchmarkUnmarshalParsec(b *testing.B) {
// bytes := []byte(benchmarkString)
//
// for i := 0; i < b.N; i++ {
// scanner := parsecJson.NewJSONScanner(bytes)
// _, remaining := parsecJson.Y(scanner)
//
// require.True(b, remaining.Endof())
// }
//}
func BenchmarkUnmarshalParsify(b *testing.B) {
for i := 0; i < b.N; i++ {
_, err := Unmarshal(benchmarkString)
require.NoError(b, err)
}
}
//
//func BenchmarkUnmarshalStdlib(b *testing.B) {
// bytes := []byte(benchmarkString)
// var result interface{}
// for i := 0; i < b.N; i++ {
// err := stdlibJson.Unmarshal(bytes, &result)
// require.NoError(b, err)
// }
//}

View File

@ -0,0 +1,3 @@
go build
profile.exe -cpuprofile cpu.out
go tool pprof --inuse_objects profile.exe cpu.out

55
json/profile/json.go Normal file
View File

@ -0,0 +1,55 @@
package main
import (
"flag"
"log"
"os"
"runtime"
"runtime/pprof"
"github.com/vektah/goparsify/json"
)
var cpuprofile = flag.String("cpuprofile", "", "write cpu profile to file")
var memprofile = flag.String("memprofile", "", "write memory profile to this file")
func main() {
flag.Parse()
if *cpuprofile != "" {
f, err := os.Create(*cpuprofile)
if err != nil {
log.Fatal(err)
}
pprof.StartCPUProfile(f)
defer func() {
pprof.StopCPUProfile()
err := f.Close()
if err != nil {
panic(err)
}
}()
}
if *memprofile != "" {
runtime.MemProfileRate = 1
}
for i := 0; i < 10000; i++ {
_, err := json.Unmarshal(`{"true":true, "false":false, "null": null}`)
if err != nil {
panic(err)
}
}
if *memprofile != "" {
f, err := os.Create(*memprofile)
if err != nil {
log.Fatal(err)
}
pprof.WriteHeapProfile(f)
f.Close()
return
}
}

View File

@ -0,0 +1,3 @@
go build
profile.exe -memprofile mem.out
go tool pprof --inuse_objects profile.exe mem.out