├── .gitignore ├── NOTICE ├── fflib └── v1 │ ├── buffer_nopool.go │ ├── reader_scan_amd64.s │ ├── reader_scan_generic.go │ ├── jsonstring_test.go │ ├── reader_scan_amd64.go │ ├── reader_test.go │ ├── bytenum.go │ ├── buffer_pool.go │ ├── fold.go │ ├── iota.go │ ├── internal │ └── atoi.go │ ├── lexer_test.go │ ├── decimal.go │ ├── reader.go │ └── jsonstring.go ├── tests ├── bench.cmd ├── base.go ├── t.sh ├── t.cmd ├── ff_float_test.go ├── ff_obj_test.go ├── types │ ├── ff │ │ └── everything.go │ └── types_test.go ├── go.stripe │ ├── stripe_test.go │ ├── base │ │ └── customer.go │ └── ff │ │ └── customer.go ├── goser │ ├── goser_test.go │ ├── base │ │ └── goser.go │ └── ff │ │ └── goser.go ├── ff_invalid_test.go ├── ff_string_test.go └── encode_test.go ├── .travis.yml ├── ffjson ├── pool.go ├── encoder.go ├── decoder.go └── marshal.go ├── shared └── options.go ├── Makefile ├── inception ├── template.go ├── writerstack.go ├── encoder_tpl.go ├── tags.go ├── inception.go ├── decoder.go └── reflect.go ├── generator ├── generator.go ├── tags.go ├── tempfile.go ├── parser.go └── inceptionmain.go ├── ffjson.go ├── README.md └── LICENSE /.gitignore: -------------------------------------------------------------------------------- 1 | tests/go.stripe/ff/customer_ffjson.go 2 | tests/goser/ff/goser_ffjson.go 3 | tests/types/ff/everything_ffjson.go 4 | tests/ff_ffjson.go 5 | -------------------------------------------------------------------------------- /NOTICE: -------------------------------------------------------------------------------- 1 | ffjson 2 | Copyright (c) 2014, Paul Querna 3 | 4 | This product includes software developed by 5 | Paul Querna (http://paul.querna.org/). 6 | 7 | Portions of this software were developed as 8 | part of Go, Copyright (c) 2012 The Go Authors. -------------------------------------------------------------------------------- /fflib/v1/buffer_nopool.go: -------------------------------------------------------------------------------- 1 | // +build !go1.3 2 | 3 | package v1 4 | 5 | // Stub version of buffer_pool.go for Go 1.2, which doesn't have sync.Pool. 6 | 7 | func Pool(b []byte) {} 8 | 9 | func makeSlice(n int) []byte { 10 | return make([]byte, n) 11 | } 12 | -------------------------------------------------------------------------------- /tests/bench.cmd: -------------------------------------------------------------------------------- 1 | del ff_ffjson.go 2 | ffjson ff.go 3 | 4 | go test -benchmem -bench MarshalJSON 5 | 6 | REM ### Bench CPU ### 7 | rem go test -benchmem -test.run=none -bench MarshalJSONNative -cpuprofile="cpu.dat" -benchtime 10s &&go tool pprof -gif tests.test.exe cpu.dat >out.gif 8 | 9 | 10 | -------------------------------------------------------------------------------- /tests/base.go: -------------------------------------------------------------------------------- 1 | package tff 2 | 3 | type Foo struct { 4 | Blah int 5 | } 6 | 7 | type Record struct { 8 | Timestamp int64 `json:"id,omitempty"` 9 | OriginId uint32 10 | Bar Foo 11 | Method string `json:"meth"` 12 | ReqId string 13 | ServerIp string 14 | RemoteIp string 15 | BytesSent uint64 16 | } 17 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: go 2 | 3 | install: 4 | - A=${PWD#*github.com/};A=${A%/ffjson};cd ../..;mv $A pquerna;cd pquerna/ffjson 5 | - go get -d -v ./... 6 | - go get -u github.com/stretchr/testify/require 7 | - go get -u github.com/google/gofuzz 8 | 9 | script: make clean && make test && make test 10 | 11 | go: 12 | - 1.5 13 | -------------------------------------------------------------------------------- /tests/t.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | make -C .. 6 | ffjson ff.go 7 | 8 | # 9 | # https://twitter.com/jpetazzo/status/446476354930757632/photo/1 10 | # 11 | 12 | go test -benchmem -bench MarshalJSON 13 | go test -benchmem -bench MarshalJSONNative -cpuprofile="prof.dat" -benchtime 10s 14 | go tool pprof -gif tests.test prof.dat >out.gif 15 | -------------------------------------------------------------------------------- /tests/t.cmd: -------------------------------------------------------------------------------- 1 | go install github.com/pquerna/ffjson 2 | del ff_ffjson.go 3 | del goser\ff\goser_ffjson.go 4 | del go.stripe\ff\customer_ffjson.go 5 | del types\ff\everything_ffjson.go 6 | 7 | go test -v github.com/pquerna/ffjson/fflib/v1 github.com/pquerna/ffjson/generator github.com/pquerna/ffjson/inception && ffjson ff.go && go test -v 8 | ffjson goser/ff/goser.go && go test github.com/pquerna/ffjson/tests/goser 9 | ffjson go.stripe/ff/customer.go && go test github.com/pquerna/ffjson/tests/go.stripe 10 | ffjson types/ff/everything.go && go test github.com/pquerna/ffjson/tests/types 11 | 12 | 13 | -------------------------------------------------------------------------------- /fflib/v1/reader_scan_amd64.s: -------------------------------------------------------------------------------- 1 | // +build !appengine 2 | 3 | #define NOSPLIT 4 4 | 5 | // func scanStringSSE(s []byte, j int) (int, byte) 6 | TEXT scanStringSSE(SB),NOSPLIT,$0 7 | // TODO: http://www.strchr.com/strcmp_and_strlen_using_sse_4.2 8 | // Equal any, operand1 set to 9 | RET 10 | 11 | // Copyright 2011 The Go Authors. All rights reserved. 12 | // Use of this source code is governed by a BSD-style 13 | // license that can be found in the LICENSE file. 14 | // func haveSSE42() bool 15 | TEXT ·haveSSE42(SB),NOSPLIT,$0 16 | XORQ AX, AX 17 | INCL AX 18 | CPUID 19 | SHRQ $20, CX 20 | ANDQ $1, CX 21 | MOVB CX, ret+0(FP) 22 | RET 23 | -------------------------------------------------------------------------------- /fflib/v1/reader_scan_generic.go: -------------------------------------------------------------------------------- 1 | // +build !amd64 appengine 2 | 3 | /** 4 | * Copyright 2014 Paul Querna 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | * 18 | */ 19 | 20 | package v1 21 | 22 | func scanString(s []byte, j int) (int, byte) { 23 | for { 24 | if j >= len(s) { 25 | return j, 0 26 | } 27 | 28 | c := s[j] 29 | j++ 30 | if byteLookupTable[c]&sliceStringMask == 0 { 31 | continue 32 | } 33 | 34 | return j, c 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /ffjson/pool.go: -------------------------------------------------------------------------------- 1 | package ffjson 2 | 3 | /** 4 | * Copyright 2015 Paul Querna, Klaus Post 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | * 18 | */ 19 | 20 | import ( 21 | fflib "github.com/pquerna/ffjson/fflib/v1" 22 | ) 23 | 24 | // Send a buffer to the Pool to reuse for other instances. 25 | // 26 | // On servers where you have a lot of concurrent encoding going on, 27 | // you can hand back the byte buffer you get marshalling once you are done using it. 28 | // 29 | // You may no longer utilize the content of the buffer, since it may be used 30 | // by other goroutines. 31 | func Pool(b []byte) { 32 | fflib.Pool(b) 33 | } 34 | -------------------------------------------------------------------------------- /fflib/v1/jsonstring_test.go: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2014 Paul Querna 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | * 16 | */ 17 | 18 | package v1 19 | 20 | import ( 21 | "bytes" 22 | "testing" 23 | ) 24 | 25 | func TestWriteJsonString(t *testing.T) { 26 | var buf bytes.Buffer 27 | WriteJsonString(&buf, "foo") 28 | if string(buf.Bytes()) != `"foo"` { 29 | t.Fatalf("Expected: %v\nGot: %v", `"foo"`, string(buf.Bytes())) 30 | } 31 | 32 | buf.Reset() 33 | WriteJsonString(&buf, `f"oo`) 34 | if string(buf.Bytes()) != `"f\"oo"` { 35 | t.Fatalf("Expected: %v\nGot: %v", `"f\"oo"`, string(buf.Bytes())) 36 | } 37 | // TODO(pquerna): all them important tests. 38 | } 39 | -------------------------------------------------------------------------------- /fflib/v1/reader_scan_amd64.go: -------------------------------------------------------------------------------- 1 | // +build amd64 2 | // +build !appengine 3 | 4 | /** 5 | * Copyright 2014 Paul Querna 6 | * 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | * 19 | */ 20 | 21 | package v1 22 | 23 | func haveSSE42() bool 24 | func scanStringSSE(s []byte, j int) (int, byte) 25 | 26 | var sse42 = haveSSE42() 27 | 28 | func scanString(s []byte, j int) (int, byte) { 29 | // XXX The following fails to compile on Go 1.2. 30 | /* 31 | if false && sse42 { 32 | return scanStringSSE(s, j) 33 | } 34 | */ 35 | 36 | for { 37 | if j >= len(s) { 38 | return j, 0 39 | } 40 | 41 | c := s[j] 42 | j++ 43 | if byteLookupTable[c]&sliceStringMask == 0 { 44 | continue 45 | } 46 | 47 | return j, c 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /shared/options.go: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2014 Paul Querna, Klaus Post 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | * 16 | */ 17 | 18 | package shared 19 | 20 | type StructOptions struct { 21 | SkipDecoder bool 22 | SkipEncoder bool 23 | } 24 | 25 | type InceptionType struct { 26 | Obj interface{} 27 | Options StructOptions 28 | } 29 | type Feature int 30 | 31 | const ( 32 | Nothing Feature = 0 33 | MustDecoder = 1 << 1 34 | MustEncoder = 1 << 2 35 | MustEncDec = MustDecoder | MustEncoder 36 | ) 37 | 38 | func (i InceptionType) HasFeature(f Feature) bool { 39 | return i.HasFeature(f) 40 | } 41 | 42 | func (s StructOptions) HasFeature(f Feature) bool { 43 | hasNeeded := true 44 | if f&MustDecoder != 0 && s.SkipDecoder { 45 | hasNeeded = false 46 | } 47 | if f&MustEncoder != 0 && s.SkipEncoder { 48 | hasNeeded = false 49 | } 50 | return hasNeeded 51 | } 52 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | 2 | all: test install 3 | @echo "Done" 4 | 5 | install: 6 | go install github.com/pquerna/ffjson 7 | 8 | deps: 9 | 10 | fmt: 11 | go fmt github.com/pquerna/ffjson/... 12 | 13 | cov: 14 | # TODO: cleanup this make target. 15 | mkdir -p coverage 16 | rm -f coverage/*.html 17 | # gocov test github.com/pquerna/ffjson/generator | gocov-html > coverage/generator.html 18 | # gocov test github.com/pquerna/ffjson/inception | gocov-html > coverage/inception.html 19 | gocov test github.com/pquerna/ffjson/fflib/v1 | gocov-html > coverage/fflib.html 20 | @echo "coverage written" 21 | 22 | test-core: 23 | go test -v github.com/pquerna/ffjson/fflib/v1 github.com/pquerna/ffjson/generator github.com/pquerna/ffjson/inception 24 | 25 | test: ffize test-core 26 | go test -v github.com/pquerna/ffjson/tests/... 27 | 28 | ffize: install 29 | ffjson tests/ff.go 30 | ffjson tests/goser/ff/goser.go 31 | ffjson tests/go.stripe/ff/customer.go 32 | ffjson tests/types/ff/everything.go 33 | 34 | bench: ffize all 35 | go test -v -benchmem -bench MarshalJSON github.com/pquerna/ffjson/tests 36 | go test -v -benchmem -bench MarshalJSON github.com/pquerna/ffjson/tests/goser github.com/pquerna/ffjson/tests/go.stripe 37 | go test -v -benchmem -bench UnmarshalJSON github.com/pquerna/ffjson/tests/goser github.com/pquerna/ffjson/tests/go.stripe 38 | 39 | clean: 40 | go clean -i github.com/pquerna/ffjson/... 41 | rm -f tests/*/ff/*_ffjson.go tests/*_ffjson.go 42 | 43 | .PHONY: deps clean test fmt install all 44 | -------------------------------------------------------------------------------- /inception/template.go: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2014 Paul Querna 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | * 16 | */ 17 | 18 | package ffjsoninception 19 | 20 | import ( 21 | "bytes" 22 | "go/format" 23 | "text/template" 24 | ) 25 | 26 | const ffjsonTemplate = ` 27 | // DO NOT EDIT! 28 | // Code generated by ffjson 29 | // source: {{.InputPath}} 30 | // DO NOT EDIT! 31 | 32 | package {{.PackageName}} 33 | 34 | import ( 35 | {{range $k, $v := .OutputImports}}{{$k}} 36 | {{end}} 37 | ) 38 | 39 | {{range .OutputFuncs}} 40 | {{.}} 41 | {{end}} 42 | 43 | ` 44 | 45 | func RenderTemplate(ic *Inception) ([]byte, error) { 46 | t := template.Must(template.New("ffjson.go").Parse(ffjsonTemplate)) 47 | buf := new(bytes.Buffer) 48 | err := t.Execute(buf, ic) 49 | if err != nil { 50 | return nil, err 51 | } 52 | return format.Source(buf.Bytes()) 53 | } 54 | 55 | func tplStr(t *template.Template, data interface{}) string { 56 | buf := bytes.Buffer{} 57 | err := t.Execute(&buf, data) 58 | if err != nil { 59 | panic(err) 60 | } 61 | return buf.String() 62 | } 63 | -------------------------------------------------------------------------------- /generator/generator.go: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2014 Paul Querna 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | * 16 | */ 17 | 18 | package generator 19 | 20 | import ( 21 | "errors" 22 | "fmt" 23 | "os" 24 | ) 25 | 26 | func GenerateFiles(goCmd string, inputPath string, outputPath string, importName string, forceRegenerate bool) error { 27 | 28 | if _, StatErr := os.Stat(outputPath); !os.IsNotExist(StatErr) { 29 | inputFileInfo, inputFileErr := os.Stat(inputPath) 30 | outputFileInfo, outputFileErr := os.Stat(outputPath) 31 | 32 | if nil == outputFileErr && nil == inputFileErr { 33 | if !forceRegenerate && inputFileInfo.ModTime().Before(outputFileInfo.ModTime()) { 34 | fmt.Println("File " + outputPath + " already exists.") 35 | 36 | return nil 37 | } 38 | } 39 | } 40 | 41 | packageName, structs, err := ExtractStructs(inputPath) 42 | if err != nil { 43 | return err 44 | } 45 | 46 | im := NewInceptionMain(goCmd, inputPath, outputPath) 47 | 48 | err = im.Generate(packageName, structs, importName) 49 | if err != nil { 50 | return errors.New(fmt.Sprintf("error=%v path=%q", err, im.TempMainPath)) 51 | } 52 | 53 | err = im.Run() 54 | if err != nil { 55 | return err 56 | } 57 | 58 | return nil 59 | } 60 | -------------------------------------------------------------------------------- /generator/tags.go: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2014 Paul Querna 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | * 16 | */ 17 | 18 | package generator 19 | 20 | import ( 21 | "strings" 22 | ) 23 | 24 | // from: http://golang.org/src/pkg/encoding/json/tags.go 25 | 26 | // tagOptions is the string following a comma in a struct field's "json" 27 | // tag, or the empty string. It does not include the leading comma. 28 | type tagOptions string 29 | 30 | // parseTag splits a struct field's json tag into its name and 31 | // comma-separated options. 32 | func parseTag(tag string) (string, tagOptions) { 33 | if idx := strings.Index(tag, ","); idx != -1 { 34 | return tag[:idx], tagOptions(tag[idx+1:]) 35 | } 36 | return tag, tagOptions("") 37 | } 38 | 39 | // Contains reports whether a comma-separated list of options 40 | // contains a particular substr flag. substr must be surrounded by a 41 | // string boundary or commas. 42 | func (o tagOptions) Contains(optionName string) bool { 43 | if len(o) == 0 { 44 | return false 45 | } 46 | s := string(o) 47 | for s != "" { 48 | var next string 49 | i := strings.Index(s, ",") 50 | if i >= 0 { 51 | s, next = s[:i], s[i+1:] 52 | } 53 | if s == optionName { 54 | return true 55 | } 56 | s = next 57 | } 58 | return false 59 | } 60 | -------------------------------------------------------------------------------- /inception/writerstack.go: -------------------------------------------------------------------------------- 1 | package ffjsoninception 2 | 3 | import "strings" 4 | 5 | // ConditionalWrite is a stack containing a number of pending writes 6 | type ConditionalWrite struct { 7 | Queued []string 8 | } 9 | 10 | // Write will add a string to be written 11 | func (w *ConditionalWrite) Write(s string) { 12 | w.Queued = append(w.Queued, s) 13 | } 14 | 15 | // DeleteLast will delete the last added write 16 | func (w *ConditionalWrite) DeleteLast() { 17 | if len(w.Queued) == 0 { 18 | return 19 | } 20 | w.Queued = w.Queued[:len(w.Queued)-1] 21 | } 22 | 23 | // Last will return the last added write 24 | func (w *ConditionalWrite) Last() string { 25 | if len(w.Queued) == 0 { 26 | return "" 27 | } 28 | return w.Queued[len(w.Queued)-1] 29 | } 30 | 31 | // Flush will return all queued writes, and return 32 | // "" (empty string) in nothing has been queued 33 | // "buf.WriteByte('" + byte + "')" + '\n' if one bute has been queued. 34 | // "buf.WriteString(`" + string + "`)" + "\n" if more than one byte has been queued. 35 | func (w *ConditionalWrite) Flush() string { 36 | combined := strings.Join(w.Queued, "") 37 | if len(combined) == 0 { 38 | return "" 39 | } 40 | 41 | w.Queued = nil 42 | if len(combined) == 1 { 43 | return "buf.WriteByte('" + combined + "')" + "\n" 44 | } 45 | return "buf.WriteString(`" + combined + "`)" + "\n" 46 | } 47 | 48 | func (w *ConditionalWrite) FlushTo(out string) string { 49 | out += w.Flush() 50 | return out 51 | } 52 | 53 | // WriteFlush will add a string and return the Flush result for the queue 54 | func (w *ConditionalWrite) WriteFlush(s string) string { 55 | w.Write(s) 56 | return w.Flush() 57 | } 58 | 59 | // GetQueued will return the current queued content without flushing. 60 | func (w *ConditionalWrite) GetQueued() string { 61 | t := w.Queued 62 | s := w.Flush() 63 | w.Queued = t 64 | return s 65 | } 66 | -------------------------------------------------------------------------------- /inception/encoder_tpl.go: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2014 Paul Querna 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | * 16 | */ 17 | 18 | package ffjsoninception 19 | 20 | import ( 21 | "reflect" 22 | "text/template" 23 | ) 24 | 25 | var encodeTpl map[string]*template.Template 26 | 27 | func init() { 28 | encodeTpl = make(map[string]*template.Template) 29 | 30 | funcs := map[string]string{ 31 | "handleMarshaler": handleMarshalerTxt, 32 | } 33 | tplFuncs := template.FuncMap{} 34 | 35 | for k, v := range funcs { 36 | encodeTpl[k] = template.Must(template.New(k).Funcs(tplFuncs).Parse(v)) 37 | } 38 | } 39 | 40 | type handleMarshaler struct { 41 | IC *Inception 42 | Name string 43 | Typ reflect.Type 44 | Ptr reflect.Kind 45 | MarshalJSONBuf bool 46 | Marshaler bool 47 | } 48 | 49 | var handleMarshalerTxt = ` 50 | { 51 | {{if eq .Typ.Kind .Ptr}} 52 | if {{.Name}} == nil { 53 | buf.WriteString("null") 54 | return nil 55 | } 56 | {{end}} 57 | 58 | {{if eq .MarshalJSONBuf true}} 59 | err = {{.Name}}.MarshalJSONBuf(buf) 60 | if err != nil { 61 | return err 62 | } 63 | {{else if eq .Marshaler true}} 64 | obj, err = {{.Name}}.MarshalJSON() 65 | if err != nil { 66 | return err 67 | } 68 | buf.Write(obj) 69 | {{end}} 70 | } 71 | ` 72 | -------------------------------------------------------------------------------- /generator/tempfile.go: -------------------------------------------------------------------------------- 1 | // Copyright 2010 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package generator 6 | 7 | import ( 8 | "os" 9 | "path/filepath" 10 | "strconv" 11 | "sync" 12 | "time" 13 | ) 14 | 15 | // Random number state. 16 | // We generate random temporary file names so that there's a good 17 | // chance the file doesn't exist yet - keeps the number of tries in 18 | // TempFile to a minimum. 19 | var rand uint32 20 | var randmu sync.Mutex 21 | 22 | func reseed() uint32 { 23 | return uint32(time.Now().UnixNano() + int64(os.Getpid())) 24 | } 25 | 26 | func nextSuffix() string { 27 | randmu.Lock() 28 | r := rand 29 | if r == 0 { 30 | r = reseed() 31 | } 32 | r = r*1664525 + 1013904223 // constants from Numerical Recipes 33 | rand = r 34 | randmu.Unlock() 35 | return strconv.Itoa(int(1e9 + r%1e9))[1:] 36 | } 37 | 38 | // TempFile creates a new temporary file in the directory dir 39 | // with a name beginning with prefix, opens the file for reading 40 | // and writing, and returns the resulting *os.File. 41 | // If dir is the empty string, TempFile uses the default directory 42 | // for temporary files (see os.TempDir). 43 | // Multiple programs calling TempFile simultaneously 44 | // will not choose the same file. The caller can use f.Name() 45 | // to find the pathname of the file. It is the caller's responsibility 46 | // to remove the file when no longer needed. 47 | func TempFileWithPostfix(dir, prefix string, postfix string) (f *os.File, err error) { 48 | if dir == "" { 49 | dir = os.TempDir() 50 | } 51 | 52 | nconflict := 0 53 | for i := 0; i < 10000; i++ { 54 | name := filepath.Join(dir, prefix+nextSuffix()+postfix) 55 | f, err = os.OpenFile(name, os.O_RDWR|os.O_CREATE|os.O_EXCL, 0600) 56 | if os.IsExist(err) { 57 | if nconflict++; nconflict > 10 { 58 | rand = reseed() 59 | } 60 | continue 61 | } 62 | break 63 | } 64 | return 65 | } 66 | -------------------------------------------------------------------------------- /fflib/v1/reader_test.go: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2014 Paul Querna 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | * 16 | */ 17 | 18 | package v1 19 | 20 | import ( 21 | "testing" 22 | ) 23 | 24 | func tsliceString(t *testing.T, expected string, enc string) { 25 | var out Buffer 26 | ffr := newffReader([]byte(enc + `"`)) 27 | err := ffr.SliceString(&out) 28 | if err != nil { 29 | t.Fatalf("unexpect SliceString error: %v from %v", err, enc) 30 | } 31 | 32 | if out.String() != expected { 33 | t.Fatalf(`failed to decode %v into %v, got: %v`, enc, expected, out.String()) 34 | } 35 | } 36 | 37 | func TestUnicode(t *testing.T) { 38 | var testvecs = map[string]string{ 39 | "€": `\u20AC`, 40 | "𐐷": `\uD801\uDC37`, 41 | } 42 | 43 | for k, v := range testvecs { 44 | tsliceString(t, k, v) 45 | } 46 | } 47 | 48 | func TestBadUnicode(t *testing.T) { 49 | var out Buffer 50 | ffr := newffReader([]byte(`\u20--"`)) 51 | err := ffr.SliceString(&out) 52 | if err == nil { 53 | t.Fatalf("expected SliceString hex decode error") 54 | } 55 | } 56 | 57 | func TestNonUnicodeEscape(t *testing.T) { 58 | var out Buffer 59 | ffr := newffReader([]byte(`\t\n\r"`)) 60 | err := ffr.SliceString(&out) 61 | if err != nil { 62 | t.Fatalf("unexpected SliceString error: %v", err) 63 | } 64 | } 65 | 66 | func TestInvalidEscape(t *testing.T) { 67 | var out Buffer 68 | ffr := newffReader([]byte(`\x134"`)) 69 | err := ffr.SliceString(&out) 70 | if err == nil { 71 | t.Fatalf("expected SliceString escape decode error") 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /ffjson/encoder.go: -------------------------------------------------------------------------------- 1 | package ffjson 2 | 3 | /** 4 | * Copyright 2015 Paul Querna, Klaus Post 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | * 18 | */ 19 | 20 | import ( 21 | "encoding/json" 22 | "errors" 23 | fflib "github.com/pquerna/ffjson/fflib/v1" 24 | "io" 25 | "reflect" 26 | ) 27 | 28 | // This is a reusable encoder. 29 | // It allows to encode many objects to a single writer. 30 | // This should not be used by more than one goroutine at the time. 31 | type Encoder struct { 32 | buf fflib.Buffer 33 | w io.Writer 34 | enc *json.Encoder 35 | } 36 | 37 | // NewEncoder returns a reusable Encoder. 38 | // Output will be written to the supplied writer. 39 | func NewEncoder(w io.Writer) *Encoder { 40 | return &Encoder{w: w, enc: json.NewEncoder(w)} 41 | } 42 | 43 | // Encode the data in the supplied value to the stream 44 | // given on creation. 45 | // When the function returns the output has been 46 | // written to the stream. 47 | func (e *Encoder) Encode(v interface{}) error { 48 | f, ok := v.(marshalerFaster) 49 | if ok { 50 | e.buf.Reset() 51 | err := f.MarshalJSONBuf(&e.buf) 52 | if err != nil { 53 | return err 54 | } 55 | 56 | _, err = io.Copy(e.w, &e.buf) 57 | return err 58 | } 59 | 60 | return e.enc.Encode(v) 61 | } 62 | 63 | // EncodeFast will unmarshal the data if fast marshall is available. 64 | // This function can be used if you want to be sure the fast 65 | // marshal is used or in testing. 66 | // If you would like to have fallback to encoding/json you can use the 67 | // regular Encode() method. 68 | func (e *Encoder) EncodeFast(v interface{}) error { 69 | _, ok := v.(marshalerFaster) 70 | if !ok { 71 | return errors.New("ffjson marshal not available for type " + reflect.TypeOf(v).String()) 72 | } 73 | return e.Encode(v) 74 | } 75 | -------------------------------------------------------------------------------- /inception/tags.go: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2014 Paul Querna 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | * 16 | */ 17 | 18 | package ffjsoninception 19 | 20 | import ( 21 | "strings" 22 | "unicode" 23 | ) 24 | 25 | // from: http://golang.org/src/pkg/encoding/json/tags.go 26 | 27 | // tagOptions is the string following a comma in a struct field's "json" 28 | // tag, or the empty string. It does not include the leading comma. 29 | type tagOptions string 30 | 31 | // parseTag splits a struct field's json tag into its name and 32 | // comma-separated options. 33 | func parseTag(tag string) (string, tagOptions) { 34 | if idx := strings.Index(tag, ","); idx != -1 { 35 | return tag[:idx], tagOptions(tag[idx+1:]) 36 | } 37 | return tag, tagOptions("") 38 | } 39 | 40 | // Contains reports whether a comma-separated list of options 41 | // contains a particular substr flag. substr must be surrounded by a 42 | // string boundary or commas. 43 | func (o tagOptions) Contains(optionName string) bool { 44 | if len(o) == 0 { 45 | return false 46 | } 47 | s := string(o) 48 | for s != "" { 49 | var next string 50 | i := strings.Index(s, ",") 51 | if i >= 0 { 52 | s, next = s[:i], s[i+1:] 53 | } 54 | if s == optionName { 55 | return true 56 | } 57 | s = next 58 | } 59 | return false 60 | } 61 | 62 | func isValidTag(s string) bool { 63 | if s == "" { 64 | return false 65 | } 66 | for _, c := range s { 67 | switch { 68 | case strings.ContainsRune("!#$%&()*+-./:<=>?@[]^_{|}~ ", c): 69 | // Backslash and quote chars are reserved, but 70 | // otherwise any punctuation chars are allowed 71 | // in a tag name. 72 | default: 73 | if !unicode.IsLetter(c) && !unicode.IsDigit(c) { 74 | return false 75 | } 76 | } 77 | } 78 | return true 79 | } 80 | -------------------------------------------------------------------------------- /fflib/v1/bytenum.go: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2014 Paul Querna 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | * 16 | */ 17 | 18 | /* Portions of this file are on Go stdlib's strconv/iota.go */ 19 | // Copyright 2009 The Go Authors. All rights reserved. 20 | // Use of this source code is governed by a BSD-style 21 | // license that can be found in the LICENSE file. 22 | 23 | package v1 24 | 25 | import ( 26 | "github.com/pquerna/ffjson/fflib/v1/internal" 27 | ) 28 | 29 | func ParseFloat(s []byte, bitSize int) (f float64, err error) { 30 | return internal.ParseFloat(s, bitSize) 31 | } 32 | 33 | // ParseUint is like ParseInt but for unsigned numbers, and oeprating on []byte 34 | func ParseUint(s []byte, base int, bitSize int) (n uint64, err error) { 35 | if len(s) == 1 { 36 | switch s[0] { 37 | case '0': 38 | return 0, nil 39 | case '1': 40 | return 1, nil 41 | case '2': 42 | return 2, nil 43 | case '3': 44 | return 3, nil 45 | case '4': 46 | return 4, nil 47 | case '5': 48 | return 5, nil 49 | case '6': 50 | return 6, nil 51 | case '7': 52 | return 7, nil 53 | case '8': 54 | return 8, nil 55 | case '9': 56 | return 9, nil 57 | } 58 | } 59 | return internal.ParseUint(s, base, bitSize) 60 | } 61 | 62 | func ParseInt(s []byte, base int, bitSize int) (i int64, err error) { 63 | if len(s) == 1 { 64 | switch s[0] { 65 | case '0': 66 | return 0, nil 67 | case '1': 68 | return 1, nil 69 | case '2': 70 | return 2, nil 71 | case '3': 72 | return 3, nil 73 | case '4': 74 | return 4, nil 75 | case '5': 76 | return 5, nil 77 | case '6': 78 | return 6, nil 79 | case '7': 80 | return 7, nil 81 | case '8': 82 | return 8, nil 83 | case '9': 84 | return 9, nil 85 | } 86 | } 87 | return internal.ParseInt(s, base, bitSize) 88 | } 89 | -------------------------------------------------------------------------------- /tests/ff_float_test.go: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2014 Paul Querna 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | * 16 | */ 17 | 18 | package tff 19 | 20 | import ( 21 | "testing" 22 | ) 23 | 24 | // Test data from https://github.com/akheron/jansson/tree/master/test/suites/valid 25 | // jansson, Copyright (c) 2009-2014 Petri Lehtinen 26 | // (MIT Licensed) 27 | 28 | func TestFloatRealCapitalENegativeExponent(t *testing.T) { 29 | testExpectedXValBare(t, 30 | 0.01, 31 | `1E-2`, 32 | &Xfloat64{}) 33 | } 34 | 35 | func TestFloatRealCapitalEPositiveExponent(t *testing.T) { 36 | testExpectedXValBare(t, 37 | 100.0, 38 | `1E+2`, 39 | &Xfloat64{}) 40 | } 41 | 42 | func TestFloatRealCapital(t *testing.T) { 43 | testExpectedXValBare(t, 44 | 1e22, 45 | `1E22`, 46 | &Xfloat64{}) 47 | } 48 | 49 | func TestFloatRealExponent(t *testing.T) { 50 | testExpectedXValBare(t, 51 | 1.2299999999999999e47, 52 | `123e45`, 53 | &Xfloat64{}) 54 | } 55 | 56 | func TestFloatRealFractionExponent(t *testing.T) { 57 | testExpectedXValBare(t, 58 | 1.23456e80, 59 | `123.456e78`, 60 | &Xfloat64{}) 61 | } 62 | 63 | func TestFloatRealNegativeExponent(t *testing.T) { 64 | testExpectedXValBare(t, 65 | 0.01, 66 | `1e-2`, 67 | &Xfloat64{}) 68 | } 69 | 70 | func TestFloatRealPositiveExponent(t *testing.T) { 71 | testExpectedXValBare(t, 72 | 100.0, 73 | `1e2`, 74 | &Xfloat64{}) 75 | } 76 | 77 | func TestFloatRealSubnormalNumber(t *testing.T) { 78 | testExpectedXValBare(t, 79 | 1.8011670033376514e-308, 80 | `1.8011670033376514e-308`, 81 | &Xfloat64{}) 82 | } 83 | 84 | func TestFloatRealUnderflow(t *testing.T) { 85 | testExpectedXValBare(t, 86 | 0.0, 87 | `123e-10000000`, 88 | &Xfloat64{}) 89 | } 90 | 91 | func TestFloatNull(t *testing.T) { 92 | testExpectedXValBare(t, 93 | 0.0, 94 | `null`, 95 | &Xfloat64{}) 96 | } 97 | 98 | func TestFloatInt(t *testing.T) { 99 | testExpectedXValBare(t, 100 | 1.0, 101 | `1`, 102 | &Xfloat64{}) 103 | } 104 | -------------------------------------------------------------------------------- /ffjson.go: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2014 Paul Querna 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | * 16 | */ 17 | 18 | package main 19 | 20 | import ( 21 | _ "github.com/pquerna/ffjson/fflib/v1" 22 | "github.com/pquerna/ffjson/generator" 23 | _ "github.com/pquerna/ffjson/inception" 24 | 25 | "flag" 26 | "fmt" 27 | "os" 28 | "path/filepath" 29 | "regexp" 30 | ) 31 | 32 | var outputPathFlag = flag.String("w", "", "Write generate code to this path instead of ${input}_ffjson.go.") 33 | var goCmdFlag = flag.String("go-cmd", "", "Path to go command; Useful for `goapp` support.") 34 | var importNameFlag = flag.String("import-name", "", "Override import name in case it cannot be detected.") 35 | var forceRegenerateFlag = flag.Bool("force-regenerate", false, "Regenerate every input file, without cheching modification date.") 36 | 37 | func usage() { 38 | fmt.Fprintf(os.Stderr, "Usage of %s:\n\n", os.Args[0]) 39 | fmt.Fprintf(os.Stderr, "\t%s [options] [input_file]\n\n", os.Args[0]) 40 | fmt.Fprintf(os.Stderr, "%s generates Go code for optimized JSON serialization.\n\n", os.Args[0]) 41 | flag.PrintDefaults() 42 | os.Exit(1) 43 | } 44 | 45 | var extRe = regexp.MustCompile(`(.*)(\.go)$`) 46 | 47 | func main() { 48 | flag.Parse() 49 | extra := flag.Args() 50 | 51 | if len(extra) != 1 { 52 | usage() 53 | } 54 | 55 | inputPath := filepath.ToSlash(extra[0]) 56 | 57 | var outputPath string 58 | if outputPathFlag == nil || *outputPathFlag == "" { 59 | outputPath = extRe.ReplaceAllString(inputPath, "${1}_ffjson.go") 60 | } else { 61 | outputPath = *outputPathFlag 62 | } 63 | 64 | var goCmd string 65 | if goCmdFlag == nil || *goCmdFlag == "" { 66 | goCmd = "go" 67 | } else { 68 | goCmd = *goCmdFlag 69 | } 70 | 71 | var importName string 72 | if importNameFlag != nil && *importNameFlag != "" { 73 | importName = *importNameFlag 74 | } 75 | 76 | err := generator.GenerateFiles(goCmd, inputPath, outputPath, importName, *forceRegenerateFlag) 77 | 78 | if err != nil { 79 | fmt.Fprintf(os.Stderr, "Error: %s:\n\n", err) 80 | os.Exit(1) 81 | } 82 | 83 | println(outputPath) 84 | } 85 | -------------------------------------------------------------------------------- /tests/ff_obj_test.go: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2014 Paul Querna 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | * 16 | */ 17 | 18 | package tff 19 | 20 | import ( 21 | fflib "github.com/pquerna/ffjson/fflib/v1" 22 | 23 | "testing" 24 | ) 25 | 26 | // Test data from https://github.com/akheron/jansson/tree/master/test/suites 27 | // jansson, Copyright (c) 2009-2014 Petri Lehtinen 28 | // (MIT Licensed) 29 | 30 | func TestInvalidBareKey(t *testing.T) { 31 | testExpectedError(t, 32 | &fflib.LexerError{}, 33 | `{X:"foo"}`, 34 | &Xobj{}) 35 | } 36 | 37 | func TestInvalidNoValue(t *testing.T) { 38 | testExpectedError(t, 39 | &fflib.LexerError{}, 40 | `{"X":}`, 41 | &Xobj{}) 42 | } 43 | 44 | func TestInvalidTrailingComma(t *testing.T) { 45 | testExpectedError(t, 46 | &fflib.LexerError{}, 47 | `{"X":"foo",}`, 48 | &Xobj{}) 49 | } 50 | 51 | func TestInvalidRougeComma(t *testing.T) { 52 | testExpectedError(t, 53 | &fflib.LexerError{}, 54 | `{,}`, 55 | &Xobj{}) 56 | } 57 | 58 | func TestInvalidRougeColon(t *testing.T) { 59 | testExpectedError(t, 60 | &fflib.LexerError{}, 61 | `{:}`, 62 | &Xobj{}) 63 | } 64 | 65 | func TestInvalidMissingColon(t *testing.T) { 66 | testExpectedError(t, 67 | &fflib.LexerError{}, 68 | `{"X""foo"}`, 69 | &Xobj{}) 70 | testExpectedError(t, 71 | &fflib.LexerError{}, 72 | `{"X" "foo"}`, 73 | &Xobj{}) 74 | testExpectedError(t, 75 | &fflib.LexerError{}, 76 | `{"X","foo"}`, 77 | &Xobj{}) 78 | } 79 | 80 | func TestInvalidUnmatchedBrace(t *testing.T) { 81 | testExpectedError(t, 82 | &fflib.LexerError{}, 83 | `[`, 84 | &Xobj{}) 85 | } 86 | 87 | func TestInvalidUnmatchedBracket(t *testing.T) { 88 | testExpectedError(t, 89 | &fflib.LexerError{}, 90 | `{`, 91 | &Xobj{}) 92 | } 93 | 94 | func TestInvalidExpectedObjGotArray(t *testing.T) { 95 | testExpectedError(t, 96 | &fflib.LexerError{}, 97 | `[]`, 98 | &Xobj{}) 99 | } 100 | 101 | func TestInvalidUnterminatedValue(t *testing.T) { 102 | testExpectedError(t, 103 | &fflib.LexerError{}, 104 | `{"X": "foo`, 105 | &Xobj{}) 106 | } 107 | -------------------------------------------------------------------------------- /ffjson/decoder.go: -------------------------------------------------------------------------------- 1 | package ffjson 2 | 3 | /** 4 | * Copyright 2015 Paul Querna, Klaus Post 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | * 18 | */ 19 | 20 | import ( 21 | "encoding/json" 22 | "errors" 23 | fflib "github.com/pquerna/ffjson/fflib/v1" 24 | "io" 25 | "io/ioutil" 26 | "reflect" 27 | ) 28 | 29 | // This is a reusable decoder. 30 | // This should not be used by more than one goroutine at the time. 31 | type Decoder struct { 32 | fs *fflib.FFLexer 33 | } 34 | 35 | // NewDecoder returns a reusable Decoder. 36 | func NewDecoder() *Decoder { 37 | return &Decoder{} 38 | } 39 | 40 | // Decode the data in the supplied data slice. 41 | func (d *Decoder) Decode(data []byte, v interface{}) error { 42 | f, ok := v.(unmarshalFaster) 43 | if ok { 44 | if d.fs == nil { 45 | d.fs = fflib.NewFFLexer(data) 46 | } else { 47 | d.fs.Reset(data) 48 | } 49 | return f.UnmarshalJSONFFLexer(d.fs, fflib.FFParse_map_start) 50 | } 51 | 52 | um, ok := v.(json.Unmarshaler) 53 | if ok { 54 | return um.UnmarshalJSON(data) 55 | } 56 | return json.Unmarshal(data, v) 57 | } 58 | 59 | // Decode the data from the supplied reader. 60 | // You should expect that data is read into memory before it is decoded. 61 | func (d *Decoder) DecodeReader(r io.Reader, v interface{}) error { 62 | _, ok := v.(unmarshalFaster) 63 | _, ok2 := v.(json.Unmarshaler) 64 | if ok || ok2 { 65 | data, err := ioutil.ReadAll(r) 66 | if err != nil { 67 | return err 68 | } 69 | defer fflib.Pool(data) 70 | return d.Decode(data, v) 71 | } 72 | dec := json.NewDecoder(r) 73 | return dec.Decode(v) 74 | } 75 | 76 | // DecodeFast will unmarshal the data if fast unmarshal is available. 77 | // This function can be used if you want to be sure the fast 78 | // unmarshal is used or in testing. 79 | // If you would like to have fallback to encoding/json you can use the 80 | // regular Decode() method. 81 | func (d *Decoder) DecodeFast(data []byte, v interface{}) error { 82 | f, ok := v.(unmarshalFaster) 83 | if !ok { 84 | return errors.New("ffjson unmarshal not available for type " + reflect.TypeOf(v).String()) 85 | } 86 | if d.fs == nil { 87 | d.fs = fflib.NewFFLexer(data) 88 | } else { 89 | d.fs.Reset(data) 90 | } 91 | return f.UnmarshalJSONFFLexer(d.fs, fflib.FFParse_map_start) 92 | } 93 | -------------------------------------------------------------------------------- /tests/types/ff/everything.go: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2014 Paul Querna 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | * 16 | */ 17 | 18 | package ff 19 | 20 | type SweetInterface interface { 21 | Cats() int 22 | } 23 | 24 | type Cats struct { 25 | FieldOnCats int 26 | } 27 | 28 | func (c *Cats) Cats() int { 29 | return 42 30 | } 31 | 32 | type Embed struct { 33 | SuperBool bool 34 | } 35 | 36 | type Everything struct { 37 | Embed 38 | Bool bool 39 | Int int 40 | Int8 int8 41 | Int16 int16 42 | Int32 int32 43 | Int64 int64 44 | Uint uint 45 | Uint8 uint8 46 | Uint16 uint16 47 | Uint32 uint32 48 | Uint64 uint64 49 | Uintptr uintptr 50 | Float32 float32 51 | Float64 float64 52 | Array [2]int 53 | Slice []int 54 | Map map[string]int 55 | String string 56 | StringPointer *string 57 | Int64Pointer *int64 58 | FooStruct *Foo 59 | MySweetInterface SweetInterface 60 | MapMap map[string]map[string]string 61 | MapArraySlice map[string][3][]int 62 | nonexported 63 | } 64 | 65 | type nonexported struct { 66 | Something int8 67 | } 68 | 69 | type Foo struct { 70 | Bar int 71 | } 72 | 73 | func NewEverything(e *Everything) { 74 | e.SuperBool = true 75 | e.Bool = true 76 | e.Int = 1 77 | e.Int8 = 2 78 | e.Int16 = 3 79 | e.Int32 = -4 80 | e.Int64 = 2 ^ 59 81 | e.Uint = 100 82 | e.Uint8 = 101 83 | e.Uint16 = 102 84 | e.Uint64 = 103 85 | e.Uintptr = 104 86 | e.Float32 = 3.14 87 | e.Float64 = 3.15 88 | e.Array = [2]int{11, 12} 89 | e.Slice = []int{1, 2, 3} 90 | e.Map = map[string]int{ 91 | "foo": 1, 92 | "bar": 2, 93 | } 94 | e.String = "snowman->☃" 95 | e.FooStruct = &Foo{Bar: 1} 96 | e.Something = 99 97 | e.MySweetInterface = &Cats{} 98 | e.MapMap = map[string]map[string]string{ 99 | "a": map[string]string{"b": "2", "c": "3", "d": "4"}, 100 | "e": map[string]string{}, 101 | "f": map[string]string{"g": "9"}, 102 | } 103 | e.MapArraySlice = map[string][3][]int{ 104 | "a": [3][]int{ 105 | 0: []int{1, 2, 3}, 106 | 1: []int{}, 107 | 2: []int{4}, 108 | }, 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /fflib/v1/buffer_pool.go: -------------------------------------------------------------------------------- 1 | // Copyright 2009 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | // +build go1.3 6 | 7 | package v1 8 | 9 | // Allocation pools for Buffers. 10 | 11 | import "sync" 12 | 13 | var pools [14]sync.Pool 14 | var pool64 *sync.Pool 15 | 16 | func init() { 17 | var i uint 18 | // TODO(pquerna): add science here around actual pool sizes. 19 | for i = 6; i < 20; i++ { 20 | n := 1 << i 21 | pools[poolNum(n)].New = func() interface{} { return make([]byte, 0, n) } 22 | } 23 | pool64 = &pools[0] 24 | } 25 | 26 | // This returns the pool number that will give a buffer of 27 | // at least 'i' bytes. 28 | func poolNum(i int) int { 29 | // TODO(pquerna): convert to log2 w/ bsr asm instruction: 30 | // 31 | if i <= 64 { 32 | return 0 33 | } else if i <= 128 { 34 | return 1 35 | } else if i <= 256 { 36 | return 2 37 | } else if i <= 512 { 38 | return 3 39 | } else if i <= 1024 { 40 | return 4 41 | } else if i <= 2048 { 42 | return 5 43 | } else if i <= 4096 { 44 | return 6 45 | } else if i <= 8192 { 46 | return 7 47 | } else if i <= 16384 { 48 | return 8 49 | } else if i <= 32768 { 50 | return 9 51 | } else if i <= 65536 { 52 | return 10 53 | } else if i <= 131072 { 54 | return 11 55 | } else if i <= 262144 { 56 | return 12 57 | } else if i <= 524288 { 58 | return 13 59 | } else { 60 | return -1 61 | } 62 | } 63 | 64 | // Send a buffer to the Pool to reuse for other instances. 65 | // You may no longer utilize the content of the buffer, since it may be used 66 | // by other goroutines. 67 | func Pool(b []byte) { 68 | if b == nil { 69 | return 70 | } 71 | c := cap(b) 72 | 73 | // Our smallest buffer is 64 bytes, so we discard smaller buffers. 74 | if c < 64 { 75 | return 76 | } 77 | 78 | // We need to put the incoming buffer into the NEXT buffer, 79 | // since a buffer guarantees AT LEAST the number of bytes available 80 | // that is the top of this buffer. 81 | // That is the reason for dividing the cap by 2, so it gets into the NEXT bucket. 82 | // We add 2 to avoid rounding down if size is exactly power of 2. 83 | pn := poolNum((c + 2) >> 1) 84 | if pn != -1 { 85 | pools[pn].Put(b[0:0]) 86 | } 87 | // if we didn't have a slot for this []byte, we just drop it and let the GC 88 | // take care of it. 89 | } 90 | 91 | // makeSlice allocates a slice of size n -- it will attempt to use a pool'ed 92 | // instance whenever possible. 93 | func makeSlice(n int) []byte { 94 | if n <= 64 { 95 | return pool64.Get().([]byte)[0:n] 96 | } 97 | 98 | pn := poolNum(n) 99 | 100 | if pn != -1 { 101 | return pools[pn].Get().([]byte)[0:n] 102 | } else { 103 | return make([]byte, n) 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /tests/go.stripe/stripe_test.go: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2014 Paul Querna 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | * 16 | */ 17 | 18 | package goser 19 | 20 | import ( 21 | "encoding/json" 22 | base "github.com/pquerna/ffjson/tests/go.stripe/base" 23 | ff "github.com/pquerna/ffjson/tests/go.stripe/ff" 24 | "testing" 25 | ) 26 | 27 | func TestRoundTrip(t *testing.T) { 28 | var customerTripped ff.Customer 29 | customer := ff.NewCustomer() 30 | 31 | buf1, err := json.Marshal(&customer) 32 | if err != nil { 33 | t.Fatalf("Marshal: %v", err) 34 | } 35 | 36 | err = json.Unmarshal(buf1, &customerTripped) 37 | if err != nil { 38 | print(string(buf1)) 39 | t.Fatalf("Unmarshal: %v", err) 40 | } 41 | } 42 | 43 | func BenchmarkMarshalJSON(b *testing.B) { 44 | cust := base.NewCustomer() 45 | 46 | buf, err := json.Marshal(&cust) 47 | if err != nil { 48 | b.Fatalf("Marshal: %v", err) 49 | } 50 | b.SetBytes(int64(len(buf))) 51 | 52 | b.ResetTimer() 53 | for i := 0; i < b.N; i++ { 54 | _, err := json.Marshal(&cust) 55 | if err != nil { 56 | b.Fatalf("Marshal: %v", err) 57 | } 58 | } 59 | } 60 | 61 | func BenchmarkFFMarshalJSON(b *testing.B) { 62 | cust := ff.NewCustomer() 63 | 64 | buf, err := cust.MarshalJSON() 65 | if err != nil { 66 | b.Fatalf("Marshal: %v", err) 67 | } 68 | b.SetBytes(int64(len(buf))) 69 | 70 | b.ResetTimer() 71 | for i := 0; i < b.N; i++ { 72 | _, err := cust.MarshalJSON() 73 | if err != nil { 74 | b.Fatalf("Marshal: %v", err) 75 | } 76 | } 77 | } 78 | 79 | type fatalF interface { 80 | Fatalf(format string, args ...interface{}) 81 | } 82 | 83 | func getBaseData(b fatalF) []byte { 84 | cust := base.NewCustomer() 85 | buf, err := json.MarshalIndent(&cust, "", " ") 86 | if err != nil { 87 | b.Fatalf("Marshal: %v", err) 88 | } 89 | return buf 90 | } 91 | 92 | func BenchmarkUnmarshalJSON(b *testing.B) { 93 | rec := base.Customer{} 94 | buf := getBaseData(b) 95 | b.SetBytes(int64(len(buf))) 96 | 97 | b.ResetTimer() 98 | for i := 0; i < b.N; i++ { 99 | err := json.Unmarshal(buf, &rec) 100 | if err != nil { 101 | b.Fatalf("Marshal: %v", err) 102 | } 103 | } 104 | } 105 | 106 | func BenchmarkFFUnmarshalJSON(b *testing.B) { 107 | rec := ff.Customer{} 108 | buf := getBaseData(b) 109 | b.SetBytes(int64(len(buf))) 110 | 111 | b.ResetTimer() 112 | for i := 0; i < b.N; i++ { 113 | err := rec.UnmarshalJSON(buf) 114 | if err != nil { 115 | b.Fatalf("UnmarshalJSON: %v", err) 116 | } 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /fflib/v1/fold.go: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2014 Paul Querna 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | * 16 | */ 17 | 18 | /* Portions of this file are on Go stdlib's encoding/json/fold.go */ 19 | // Copyright 2009 The Go Authors. All rights reserved. 20 | // Use of this source code is governed by a BSD-style 21 | // license that can be found in the LICENSE file. 22 | 23 | package v1 24 | 25 | import ( 26 | "unicode/utf8" 27 | ) 28 | 29 | const ( 30 | caseMask = ^byte(0x20) // Mask to ignore case in ASCII. 31 | kelvin = '\u212a' 32 | smallLongEss = '\u017f' 33 | ) 34 | 35 | // equalFoldRight is a specialization of bytes.EqualFold when s is 36 | // known to be all ASCII (including punctuation), but contains an 's', 37 | // 'S', 'k', or 'K', requiring a Unicode fold on the bytes in t. 38 | // See comments on foldFunc. 39 | func EqualFoldRight(s, t []byte) bool { 40 | for _, sb := range s { 41 | if len(t) == 0 { 42 | return false 43 | } 44 | tb := t[0] 45 | if tb < utf8.RuneSelf { 46 | if sb != tb { 47 | sbUpper := sb & caseMask 48 | if 'A' <= sbUpper && sbUpper <= 'Z' { 49 | if sbUpper != tb&caseMask { 50 | return false 51 | } 52 | } else { 53 | return false 54 | } 55 | } 56 | t = t[1:] 57 | continue 58 | } 59 | // sb is ASCII and t is not. t must be either kelvin 60 | // sign or long s; sb must be s, S, k, or K. 61 | tr, size := utf8.DecodeRune(t) 62 | switch sb { 63 | case 's', 'S': 64 | if tr != smallLongEss { 65 | return false 66 | } 67 | case 'k', 'K': 68 | if tr != kelvin { 69 | return false 70 | } 71 | default: 72 | return false 73 | } 74 | t = t[size:] 75 | 76 | } 77 | if len(t) > 0 { 78 | return false 79 | } 80 | return true 81 | } 82 | 83 | // asciiEqualFold is a specialization of bytes.EqualFold for use when 84 | // s is all ASCII (but may contain non-letters) and contains no 85 | // special-folding letters. 86 | // See comments on foldFunc. 87 | func AsciiEqualFold(s, t []byte) bool { 88 | if len(s) != len(t) { 89 | return false 90 | } 91 | for i, sb := range s { 92 | tb := t[i] 93 | if sb == tb { 94 | continue 95 | } 96 | if ('a' <= sb && sb <= 'z') || ('A' <= sb && sb <= 'Z') { 97 | if sb&caseMask != tb&caseMask { 98 | return false 99 | } 100 | } else { 101 | return false 102 | } 103 | } 104 | return true 105 | } 106 | 107 | // simpleLetterEqualFold is a specialization of bytes.EqualFold for 108 | // use when s is all ASCII letters (no underscores, etc) and also 109 | // doesn't contain 'k', 'K', 's', or 'S'. 110 | // See comments on foldFunc. 111 | func SimpleLetterEqualFold(s, t []byte) bool { 112 | if len(s) != len(t) { 113 | return false 114 | } 115 | for i, b := range s { 116 | if b&caseMask != t[i]&caseMask { 117 | return false 118 | } 119 | } 120 | return true 121 | } 122 | -------------------------------------------------------------------------------- /tests/goser/goser_test.go: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2014 Paul Querna 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | * 16 | */ 17 | 18 | package goser 19 | 20 | import ( 21 | "encoding/json" 22 | "fmt" 23 | base "github.com/pquerna/ffjson/tests/goser/base" 24 | ff "github.com/pquerna/ffjson/tests/goser/ff" 25 | "reflect" 26 | "testing" 27 | ) 28 | 29 | func TestRoundTrip(t *testing.T) { 30 | var record ff.Log 31 | var recordTripped ff.Log 32 | ff.NewLog(&record) 33 | 34 | buf1, err := json.Marshal(&record) 35 | if err != nil { 36 | t.Fatalf("Marshal: %v", err) 37 | } 38 | err = json.Unmarshal(buf1, &recordTripped) 39 | if err != nil { 40 | t.Fatalf("Unmarshal: %v", err) 41 | } 42 | 43 | good := reflect.DeepEqual(record, recordTripped) 44 | if !good { 45 | t.Fatalf("Expected: %v\n Got: %v", record, recordTripped) 46 | } 47 | } 48 | 49 | func BenchmarkMarshalJSON(b *testing.B) { 50 | var record base.Log 51 | base.NewLog(&record) 52 | 53 | buf, err := json.Marshal(&record) 54 | if err != nil { 55 | b.Fatalf("Marshal: %v", err) 56 | } 57 | b.SetBytes(int64(len(buf))) 58 | 59 | b.ResetTimer() 60 | for i := 0; i < b.N; i++ { 61 | _, err := json.Marshal(&record) 62 | if err != nil { 63 | b.Fatalf("Marshal: %v", err) 64 | } 65 | } 66 | } 67 | 68 | func BenchmarkFFMarshalJSON(b *testing.B) { 69 | var record ff.Log 70 | ff.NewLog(&record) 71 | 72 | buf, err := record.MarshalJSON() 73 | if err != nil { 74 | b.Fatalf("Marshal: %v", err) 75 | } 76 | b.SetBytes(int64(len(buf))) 77 | 78 | b.ResetTimer() 79 | for i := 0; i < b.N; i++ { 80 | _, err := record.MarshalJSON() 81 | if err != nil { 82 | b.Fatalf("Marshal: %v", err) 83 | } 84 | } 85 | } 86 | 87 | type fatalF interface { 88 | Fatalf(format string, args ...interface{}) 89 | } 90 | 91 | func getBaseData(b fatalF) []byte { 92 | var record base.Log 93 | base.NewLog(&record) 94 | buf, err := json.MarshalIndent(&record, "", " ") 95 | if err != nil { 96 | b.Fatalf("Marshal: %v", err) 97 | } 98 | return buf 99 | } 100 | 101 | func BenchmarkUnmarshalJSON(b *testing.B) { 102 | rec := base.Log{} 103 | buf := getBaseData(b) 104 | b.SetBytes(int64(len(buf))) 105 | 106 | b.ResetTimer() 107 | for i := 0; i < b.N; i++ { 108 | err := json.Unmarshal(buf, &rec) 109 | if err != nil { 110 | b.Fatalf("Marshal: %v", err) 111 | } 112 | } 113 | } 114 | 115 | func BenchmarkFFUnmarshalJSON(b *testing.B) { 116 | rec := ff.Log{} 117 | buf := getBaseData(b) 118 | b.SetBytes(int64(len(buf))) 119 | 120 | b.ResetTimer() 121 | for i := 0; i < b.N; i++ { 122 | err := rec.UnmarshalJSON(buf) 123 | if err != nil { 124 | b.Fatalf("UnmarshalJSON: %v", err) 125 | } 126 | } 127 | } 128 | 129 | func TestUnmarshal(t *testing.T) { 130 | rec := ff.Log{} 131 | buf := getBaseData(t) 132 | 133 | err := rec.UnmarshalJSON(buf) 134 | if err != nil { 135 | t.Fatalf("Unmarshal: %v from %s", err, string(buf)) 136 | } 137 | 138 | rec2 := base.Log{} 139 | json.Unmarshal(buf, &rec2) 140 | 141 | a := fmt.Sprintf("%v", rec) 142 | b := fmt.Sprintf("%v", rec2) 143 | if a != b { 144 | t.Fatalf("Expected: %v\n Got: %v\n from: %s", rec2, rec, string(buf)) 145 | } 146 | } 147 | -------------------------------------------------------------------------------- /tests/types/types_test.go: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2014 Paul Querna 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | * 16 | */ 17 | 18 | package types 19 | 20 | import ( 21 | "encoding/json" 22 | ff "github.com/pquerna/ffjson/tests/types/ff" 23 | "reflect" 24 | "testing" 25 | ) 26 | 27 | func TestRoundTrip(t *testing.T) { 28 | var record ff.Everything 29 | var recordTripped ff.Everything 30 | ff.NewEverything(&record) 31 | 32 | buf1, err := json.Marshal(&record) 33 | if err != nil { 34 | t.Fatalf("Marshal: %v", err) 35 | } 36 | 37 | recordTripped.MySweetInterface = &ff.Cats{} 38 | err = json.Unmarshal(buf1, &recordTripped) 39 | if err != nil { 40 | t.Fatalf("Unmarshal: %v", err) 41 | } 42 | 43 | good := reflect.DeepEqual(record.FooStruct, recordTripped.FooStruct) 44 | if !good { 45 | t.Fatalf("Expected: %v\n Got: %v", *record.FooStruct, *recordTripped.FooStruct) 46 | } 47 | 48 | record.FooStruct = nil 49 | recordTripped.FooStruct = nil 50 | 51 | good = reflect.DeepEqual(record, recordTripped) 52 | if !good { 53 | t.Fatalf("Expected: %v\n Got: %v", record, recordTripped) 54 | } 55 | 56 | if recordTripped.SuperBool != true { 57 | t.Fatal("Embeded struct didn't Unmarshal") 58 | } 59 | 60 | if recordTripped.Something != 99 { 61 | t.Fatal("Embeded nonexported-struct didn't Unmarshal") 62 | } 63 | } 64 | 65 | func TestUnmarshalEmpty(t *testing.T) { 66 | record := ff.Everything{} 67 | err := record.UnmarshalJSON([]byte(`{}`)) 68 | if err != nil { 69 | t.Fatalf("UnmarshalJSON: %v", err) 70 | } 71 | } 72 | 73 | const ( 74 | everythingJson = `{ 75 | "Bool": true, 76 | "Int": 1, 77 | "Int8": 2, 78 | "Int16": 3, 79 | "Int32": -4, 80 | "Int64": 57, 81 | "Uint": 100, 82 | "Uint8": 101, 83 | "Uint16": 102, 84 | "Uint32": 0, 85 | "Uint64": 103, 86 | "Uintptr": 104, 87 | "Float32": 3.14, 88 | "Float64": 3.15, 89 | "Array": [ 90 | 1, 91 | 2, 92 | 3 93 | ], 94 | "Map": { 95 | "bar": 2, 96 | "foo": 1 97 | }, 98 | "String": "snowman☃\uD801\uDC37", 99 | "StringPointer": null, 100 | "Int64Pointer": null, 101 | "FooStruct": { 102 | "Bar": 1 103 | }, 104 | "Something": 99 105 | }` 106 | ) 107 | 108 | func TestUnmarshalFull(t *testing.T) { 109 | record := ff.Everything{} 110 | // TODO(pquerna): add unicode snowman 111 | // TODO(pquerna): handle Bar subtype 112 | err := record.UnmarshalJSON([]byte(everythingJson)) 113 | if err != nil { 114 | t.Fatalf("UnmarshalJSON: %v", err) 115 | } 116 | 117 | expect := "snowman☃𐐷" 118 | if record.String != expect { 119 | t.Fatalf("record.String decoding problem, expected: %v got: %v", expect, record.String) 120 | } 121 | 122 | if record.Something != 99 { 123 | t.Fatalf("record.Something decoding problem, expected: 99 got: %v", record.Something) 124 | } 125 | } 126 | 127 | func TestUnmarshalNullPointer(t *testing.T) { 128 | record := ff.Everything{} 129 | err := record.UnmarshalJSON([]byte(`{"FooStruct": null,"Something":99}`)) 130 | if err != nil { 131 | t.Fatalf("UnmarshalJSON: %v", err) 132 | } 133 | if record.FooStruct != nil { 134 | t.Fatalf("record.Something decoding problem, expected: nil got: %v", record.FooStruct) 135 | } 136 | } 137 | -------------------------------------------------------------------------------- /ffjson/marshal.go: -------------------------------------------------------------------------------- 1 | package ffjson 2 | 3 | /** 4 | * Copyright 2015 Paul Querna, Klaus Post 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | * 18 | */ 19 | 20 | import ( 21 | "encoding/json" 22 | "errors" 23 | fflib "github.com/pquerna/ffjson/fflib/v1" 24 | "reflect" 25 | ) 26 | 27 | type marshalerFaster interface { 28 | MarshalJSONBuf(buf fflib.EncodingBuffer) error 29 | } 30 | 31 | type unmarshalFaster interface { 32 | UnmarshalJSONFFLexer(l *fflib.FFLexer, state fflib.FFParseState) error 33 | } 34 | 35 | // Marshal will act the same way as json.Marshal, except 36 | // it will choose the ffjson marshal function before falling 37 | // back to using json.Marshal. 38 | // Using this function will bypass the internal copying and parsing 39 | // the json library normally does, which greatly speeds up encoding time. 40 | // It is ok to call this function even if no ffjson code has been 41 | // generated for the data type you pass in the interface. 42 | func Marshal(v interface{}) ([]byte, error) { 43 | f, ok := v.(marshalerFaster) 44 | if ok { 45 | buf := fflib.Buffer{} 46 | err := f.MarshalJSONBuf(&buf) 47 | b := buf.Bytes() 48 | if err != nil { 49 | if len(b) > 0 { 50 | Pool(b) 51 | } 52 | return nil, err 53 | } 54 | return b, nil 55 | } 56 | 57 | j, ok := v.(json.Marshaler) 58 | if ok { 59 | return j.MarshalJSON() 60 | } 61 | return json.Marshal(v) 62 | } 63 | 64 | // MarshalFast will marshal the data if fast marshal is available. 65 | // This function can be used if you want to be sure the fast 66 | // marshal is used or in testing. 67 | // If you would like to have fallback to encoding/json you can use the 68 | // Marshal() method. 69 | func MarshalFast(v interface{}) ([]byte, error) { 70 | _, ok := v.(marshalerFaster) 71 | if !ok { 72 | return nil, errors.New("ffjson marshal not available for type " + reflect.TypeOf(v).String()) 73 | } 74 | return Marshal(v) 75 | } 76 | 77 | // Unmarshal will act the same way as json.Unmarshal, except 78 | // it will choose the ffjson unmarshal function before falling 79 | // back to using json.Unmarshal. 80 | // The overhead of unmarshal is lower than on Marshal, 81 | // however this should still provide a speedup for your encoding. 82 | // It is ok to call this function even if no ffjson code has been 83 | // generated for the data type you pass in the interface. 84 | func Unmarshal(data []byte, v interface{}) error { 85 | f, ok := v.(unmarshalFaster) 86 | if ok { 87 | fs := fflib.NewFFLexer(data) 88 | return f.UnmarshalJSONFFLexer(fs, fflib.FFParse_map_start) 89 | } 90 | 91 | j, ok := v.(json.Unmarshaler) 92 | if ok { 93 | return j.UnmarshalJSON(data) 94 | } 95 | return json.Unmarshal(data, v) 96 | } 97 | 98 | // UnmarshalFast will unmarshal the data if fast marshall is available. 99 | // This function can be used if you want to be sure the fast 100 | // unmarshal is used or in testing. 101 | // If you would like to have fallback to encoding/json you can use the 102 | // Unmarshal() method. 103 | func UnmarshalFast(data []byte, v interface{}) error { 104 | _, ok := v.(unmarshalFaster) 105 | if !ok { 106 | return errors.New("ffjson unmarshal not available for type " + reflect.TypeOf(v).String()) 107 | } 108 | return Unmarshal(data, v) 109 | } 110 | -------------------------------------------------------------------------------- /generator/parser.go: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2014 Paul Querna 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | * 16 | */ 17 | 18 | package generator 19 | 20 | import ( 21 | "flag" 22 | "fmt" 23 | "github.com/pquerna/ffjson/shared" 24 | "go/ast" 25 | "go/doc" 26 | "go/parser" 27 | "go/token" 28 | "regexp" 29 | "strings" 30 | ) 31 | 32 | var noEncoder = flag.Bool("noencoder", false, "Do not generate encoder functions") 33 | var noDecoder = flag.Bool("nodecoder", false, "Do not generate decoder functions") 34 | 35 | type StructField struct { 36 | Name string 37 | } 38 | 39 | type StructInfo struct { 40 | Name string 41 | Options shared.StructOptions 42 | } 43 | 44 | func NewStructInfo(name string) *StructInfo { 45 | return &StructInfo{ 46 | Name: name, 47 | Options: shared.StructOptions{ 48 | SkipDecoder: *noDecoder, 49 | SkipEncoder: *noEncoder, 50 | }, 51 | } 52 | } 53 | 54 | var skipre = regexp.MustCompile("(.*)ffjson:(\\s*)((skip)|(ignore))(.*)") 55 | var skipdec = regexp.MustCompile("(.*)ffjson:(\\s*)((skipdecoder)|(nodecoder))(.*)") 56 | var skipenc = regexp.MustCompile("(.*)ffjson:(\\s*)((skipencoder)|(noencoder))(.*)") 57 | 58 | func shouldInclude(d *ast.Object) (bool, error) { 59 | ts, ok := d.Decl.(*ast.TypeSpec) 60 | if !ok { 61 | return false, fmt.Errorf("Unknown type without TypeSec: %v", d) 62 | } 63 | 64 | _, ok = ts.Type.(*ast.StructType) 65 | if !ok { 66 | ident, ok := ts.Type.(*ast.Ident) 67 | if !ok || ident.Name == "" { 68 | return false, nil 69 | } 70 | 71 | // It must be in this package, and not a pointer alias 72 | if strings.Contains(ident.Name, ".") || strings.Contains(ident.Name, "*") { 73 | return false, nil 74 | } 75 | 76 | // if Obj is nil, we have an external type or built-in. 77 | if ident.Obj == nil || ident.Obj.Decl == nil { 78 | return false, nil 79 | } 80 | return shouldInclude(ident.Obj) 81 | } 82 | return true, nil 83 | } 84 | 85 | func ExtractStructs(inputPath string) (string, []*StructInfo, error) { 86 | fset := token.NewFileSet() 87 | 88 | f, err := parser.ParseFile(fset, inputPath, nil, parser.ParseComments) 89 | 90 | if err != nil { 91 | return "", nil, err 92 | } 93 | 94 | packageName := f.Name.String() 95 | structs := make(map[string]*StructInfo) 96 | 97 | for k, d := range f.Scope.Objects { 98 | if d.Kind == ast.Typ { 99 | incl, err := shouldInclude(d) 100 | if err != nil { 101 | return "", nil, err 102 | } 103 | if incl { 104 | stobj := NewStructInfo(k) 105 | 106 | structs[k] = stobj 107 | } 108 | } 109 | } 110 | 111 | files := map[string]*ast.File{ 112 | inputPath: f, 113 | } 114 | 115 | pkg, _ := ast.NewPackage(fset, files, nil, nil) 116 | 117 | d := doc.New(pkg, f.Name.String(), doc.AllDecls) 118 | for _, t := range d.Types { 119 | if skipre.MatchString(t.Doc) { 120 | delete(structs, t.Name) 121 | } else { 122 | if skipdec.MatchString(t.Doc) { 123 | s, ok := structs[t.Name] 124 | if ok { 125 | s.Options.SkipDecoder = true 126 | } 127 | } 128 | if skipenc.MatchString(t.Doc) { 129 | s, ok := structs[t.Name] 130 | if ok { 131 | s.Options.SkipEncoder = true 132 | } 133 | } 134 | } 135 | } 136 | 137 | rv := make([]*StructInfo, 0) 138 | for _, v := range structs { 139 | rv = append(rv, v) 140 | } 141 | return packageName, rv, nil 142 | } 143 | -------------------------------------------------------------------------------- /fflib/v1/iota.go: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2014 Paul Querna 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | * 16 | */ 17 | 18 | /* Portions of this file are on Go stdlib's strconv/iota.go */ 19 | // Copyright 2009 The Go Authors. All rights reserved. 20 | // Use of this source code is governed by a BSD-style 21 | // license that can be found in the LICENSE file. 22 | 23 | package v1 24 | 25 | import ( 26 | "io" 27 | ) 28 | 29 | const ( 30 | digits = "0123456789abcdefghijklmnopqrstuvwxyz" 31 | digits01 = "0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789" 32 | digits10 = "0000000000111111111122222222223333333333444444444455555555556666666666777777777788888888889999999999" 33 | ) 34 | 35 | var shifts = [len(digits) + 1]uint{ 36 | 1 << 1: 1, 37 | 1 << 2: 2, 38 | 1 << 3: 3, 39 | 1 << 4: 4, 40 | 1 << 5: 5, 41 | } 42 | 43 | var smallNumbers = [][]byte{ 44 | []byte("0"), 45 | []byte("1"), 46 | []byte("2"), 47 | []byte("3"), 48 | []byte("4"), 49 | []byte("5"), 50 | []byte("6"), 51 | []byte("7"), 52 | []byte("8"), 53 | []byte("9"), 54 | []byte("10"), 55 | } 56 | 57 | type FormatBitsWriter interface { 58 | io.Writer 59 | io.ByteWriter 60 | } 61 | 62 | type FormatBitsScratch struct{} 63 | 64 | // 65 | // DEPRECIATED: `scratch` is no longer used, FormatBits2 is available. 66 | // 67 | // FormatBits computes the string representation of u in the given base. 68 | // If neg is set, u is treated as negative int64 value. If append_ is 69 | // set, the string is appended to dst and the resulting byte slice is 70 | // returned as the first result value; otherwise the string is returned 71 | // as the second result value. 72 | // 73 | func FormatBits(scratch *FormatBitsScratch, dst FormatBitsWriter, u uint64, base int, neg bool) { 74 | FormatBits2(dst, u, base, neg) 75 | } 76 | 77 | // FormatBits2 computes the string representation of u in the given base. 78 | // If neg is set, u is treated as negative int64 value. If append_ is 79 | // set, the string is appended to dst and the resulting byte slice is 80 | // returned as the first result value; otherwise the string is returned 81 | // as the second result value. 82 | // 83 | func FormatBits2(dst FormatBitsWriter, u uint64, base int, neg bool) { 84 | if base < 2 || base > len(digits) { 85 | panic("strconv: illegal AppendInt/FormatInt base") 86 | } 87 | // fast path for small common numbers 88 | if u <= 10 { 89 | if neg { 90 | dst.WriteByte('-') 91 | } 92 | dst.Write(smallNumbers[u]) 93 | return 94 | } 95 | 96 | // 2 <= base && base <= len(digits) 97 | 98 | var a = makeSlice(65) 99 | // var a [64 + 1]byte // +1 for sign of 64bit value in base 2 100 | i := len(a) 101 | 102 | if neg { 103 | u = -u 104 | } 105 | 106 | // convert bits 107 | if base == 10 { 108 | // common case: use constants for / and % because 109 | // the compiler can optimize it into a multiply+shift, 110 | // and unroll loop 111 | for u >= 100 { 112 | i -= 2 113 | q := u / 100 114 | j := uintptr(u - q*100) 115 | a[i+1] = digits01[j] 116 | a[i+0] = digits10[j] 117 | u = q 118 | } 119 | if u >= 10 { 120 | i-- 121 | q := u / 10 122 | a[i] = digits[uintptr(u-q*10)] 123 | u = q 124 | } 125 | 126 | } else if s := shifts[base]; s > 0 { 127 | // base is power of 2: use shifts and masks instead of / and % 128 | b := uint64(base) 129 | m := uintptr(b) - 1 // == 1<= b { 131 | i-- 132 | a[i] = digits[uintptr(u)&m] 133 | u >>= s 134 | } 135 | 136 | } else { 137 | // general case 138 | b := uint64(base) 139 | for u >= b { 140 | i-- 141 | a[i] = digits[uintptr(u%b)] 142 | u /= b 143 | } 144 | } 145 | 146 | // u < base 147 | i-- 148 | a[i] = digits[uintptr(u)] 149 | 150 | // add sign, if any 151 | if neg { 152 | i-- 153 | a[i] = '-' 154 | } 155 | 156 | dst.Write(a[i:]) 157 | 158 | Pool(a) 159 | 160 | return 161 | } 162 | -------------------------------------------------------------------------------- /inception/inception.go: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2014 Paul Querna 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | * 16 | */ 17 | 18 | package ffjsoninception 19 | 20 | import ( 21 | "errors" 22 | "fmt" 23 | "github.com/pquerna/ffjson/shared" 24 | "io/ioutil" 25 | "os" 26 | "reflect" 27 | "sort" 28 | ) 29 | 30 | type Inception struct { 31 | objs []*StructInfo 32 | InputPath string 33 | OutputPath string 34 | PackageName string 35 | PackagePath string 36 | OutputImports map[string]bool 37 | OutputFuncs []string 38 | q ConditionalWrite 39 | } 40 | 41 | func NewInception(inputPath string, packageName string, outputPath string) *Inception { 42 | return &Inception{ 43 | objs: make([]*StructInfo, 0), 44 | InputPath: inputPath, 45 | OutputPath: outputPath, 46 | PackageName: packageName, 47 | OutputFuncs: make([]string, 0), 48 | OutputImports: make(map[string]bool), 49 | } 50 | } 51 | 52 | func (i *Inception) AddMany(objs []shared.InceptionType) { 53 | for _, obj := range objs { 54 | i.Add(obj) 55 | } 56 | } 57 | 58 | func (i *Inception) Add(obj shared.InceptionType) { 59 | i.objs = append(i.objs, NewStructInfo(obj)) 60 | i.PackagePath = i.objs[0].Typ.PkgPath() 61 | } 62 | 63 | func (i *Inception) wantUnmarshal(si *StructInfo) bool { 64 | if si.Options.SkipDecoder { 65 | return false 66 | } 67 | typ := si.Typ 68 | umlx := typ.Implements(unmarshalFasterType) || reflect.PtrTo(typ).Implements(unmarshalFasterType) 69 | umlstd := typ.Implements(unmarshalerType) || reflect.PtrTo(typ).Implements(unmarshalerType) 70 | if umlstd && !umlx { 71 | // structure has UnmarshalJSON, but not our faster version -- skip it. 72 | return false 73 | } 74 | return true 75 | } 76 | 77 | func (i *Inception) wantMarshal(si *StructInfo) bool { 78 | if si.Options.SkipEncoder { 79 | return false 80 | } 81 | typ := si.Typ 82 | mlx := typ.Implements(marshalerFasterType) || reflect.PtrTo(typ).Implements(marshalerFasterType) 83 | mlstd := typ.Implements(marshalerType) || reflect.PtrTo(typ).Implements(marshalerType) 84 | if mlstd && !mlx { 85 | // structure has MarshalJSON, but not our faster version -- skip it. 86 | return false 87 | } 88 | return true 89 | } 90 | 91 | type sortedStructs []*StructInfo 92 | 93 | func (p sortedStructs) Len() int { return len(p) } 94 | func (p sortedStructs) Less(i, j int) bool { return p[i].Name < p[j].Name } 95 | func (p sortedStructs) Swap(i, j int) { p[i], p[j] = p[j], p[i] } 96 | func (p sortedStructs) Sort() { sort.Sort(p) } 97 | 98 | func (i *Inception) generateCode() error { 99 | // We sort the structs by name, so output if predictable. 100 | sorted := sortedStructs(i.objs) 101 | sorted.Sort() 102 | 103 | for _, si := range sorted { 104 | if i.wantMarshal(si) { 105 | err := CreateMarshalJSON(i, si) 106 | if err != nil { 107 | return err 108 | } 109 | } 110 | 111 | if i.wantUnmarshal(si) { 112 | err := CreateUnmarshalJSON(i, si) 113 | if err != nil { 114 | return err 115 | } 116 | } 117 | } 118 | return nil 119 | } 120 | 121 | func (i *Inception) handleError(err error) { 122 | fmt.Fprintf(os.Stderr, "Error: %s:\n\n", err) 123 | os.Exit(1) 124 | } 125 | 126 | func (i *Inception) Execute() { 127 | if len(os.Args) != 1 { 128 | i.handleError(errors.New(fmt.Sprintf("Internal ffjson error: inception executable takes no args: %v", os.Args))) 129 | return 130 | } 131 | 132 | err := i.generateCode() 133 | if err != nil { 134 | i.handleError(err) 135 | return 136 | } 137 | 138 | data, err := RenderTemplate(i) 139 | if err != nil { 140 | i.handleError(err) 141 | return 142 | } 143 | 144 | stat, err := os.Stat(i.InputPath) 145 | 146 | if err != nil { 147 | i.handleError(err) 148 | return 149 | } 150 | 151 | err = ioutil.WriteFile(i.OutputPath, data, stat.Mode()) 152 | 153 | if err != nil { 154 | i.handleError(err) 155 | return 156 | } 157 | 158 | } 159 | -------------------------------------------------------------------------------- /tests/ff_invalid_test.go: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2014 Paul Querna 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | * 16 | */ 17 | 18 | package tff 19 | 20 | import ( 21 | fflib "github.com/pquerna/ffjson/fflib/v1" 22 | 23 | _ "encoding/json" 24 | "testing" 25 | ) 26 | 27 | // Test data from https://github.com/akheron/jansson/tree/master/test/suites/invalid 28 | // jansson, Copyright (c) 2009-2014 Petri Lehtinen 29 | // (MIT Licensed) 30 | 31 | func TestInvalidApostrophe(t *testing.T) { 32 | testExpectedError(t, 33 | &fflib.LexerError{}, 34 | `'`, 35 | &Xstring{}) 36 | } 37 | 38 | func TestInvalidASCIIUnicodeIdentifier(t *testing.T) { 39 | testExpectedError(t, 40 | &fflib.LexerError{}, 41 | `aå`, 42 | &Xstring{}) 43 | } 44 | 45 | func TestInvalidBraceComma(t *testing.T) { 46 | testExpectedError(t, 47 | &fflib.LexerError{}, 48 | `{,}`, 49 | &Xstring{}) 50 | } 51 | 52 | func TestInvalidBracketComma(t *testing.T) { 53 | testExpectedError(t, 54 | &fflib.LexerError{}, 55 | `[,]`, 56 | &Xarray{}) 57 | } 58 | 59 | func TestInvalidBracketValueComma(t *testing.T) { 60 | testExpectedError(t, 61 | &fflib.LexerError{}, 62 | `[1,`, 63 | &Xarray{}) 64 | } 65 | 66 | func TestInvalidEmptyValue(t *testing.T) { 67 | testExpectedError(t, 68 | &fflib.LexerError{}, 69 | ``, 70 | &Xarray{}) 71 | } 72 | 73 | func TestInvalidGarbageAfterNewline(t *testing.T) { 74 | testExpectedError(t, 75 | &fflib.LexerError{}, 76 | "[1,2,3]\nfoo", 77 | &Xarray{}) 78 | } 79 | 80 | func TestInvalidGarbageAtEnd(t *testing.T) { 81 | testExpectedError(t, 82 | &fflib.LexerError{}, 83 | "[1,2,3]foo", 84 | &Xarray{}) 85 | } 86 | 87 | func TestInvalidIntStartingWithZero(t *testing.T) { 88 | testExpectedError(t, 89 | &fflib.LexerError{}, 90 | "012", 91 | &Xint64{}) 92 | } 93 | 94 | func TestInvalidEscape(t *testing.T) { 95 | testExpectedError(t, 96 | &fflib.LexerError{}, 97 | `"\a <-- invalid escape"`, 98 | &Xstring{}) 99 | } 100 | 101 | func TestInvalidIdentifier(t *testing.T) { 102 | testExpectedError(t, 103 | &fflib.LexerError{}, 104 | `troo`, 105 | &Xbool{}) 106 | } 107 | 108 | func TestInvalidNegativeInt(t *testing.T) { 109 | testExpectedError(t, 110 | &fflib.LexerError{}, 111 | `-123foo`, 112 | &Xint{}) 113 | } 114 | 115 | func TestInvalidNegativeFloat(t *testing.T) { 116 | testExpectedError(t, 117 | &fflib.LexerError{}, 118 | `-124.123foo`, 119 | &Xfloat64{}) 120 | } 121 | 122 | func TestInvalidSecondSurrogate(t *testing.T) { 123 | testExpectedError(t, 124 | &fflib.LexerError{}, 125 | `"\uD888\u3210 (first surrogate and invalid second surrogate)"`, 126 | &Xstring{}) 127 | } 128 | 129 | func TestInvalidLoneOpenBrace(t *testing.T) { 130 | testExpectedError(t, 131 | &fflib.LexerError{}, 132 | `{`, 133 | &Xstring{}) 134 | } 135 | 136 | func TestInvalidLoneOpenBracket(t *testing.T) { 137 | testExpectedError(t, 138 | &fflib.LexerError{}, 139 | `[`, 140 | &Xarray{}) 141 | } 142 | 143 | func TestInvalidLoneCloseBrace(t *testing.T) { 144 | testExpectedError(t, 145 | &fflib.LexerError{}, 146 | `}`, 147 | &Xstring{}) 148 | } 149 | 150 | func TestInvalidHighBytes(t *testing.T) { 151 | testExpectedError(t, 152 | &fflib.LexerError{}, 153 | string('\xFF'), 154 | &Xstring{}) 155 | } 156 | 157 | func TestInvalidLoneCloseBracket(t *testing.T) { 158 | testExpectedError(t, 159 | &fflib.LexerError{}, 160 | `]`, 161 | &Xarray{}) 162 | } 163 | 164 | func TestInvalidMinusSignWithoutNumber(t *testing.T) { 165 | testExpectedError(t, 166 | &fflib.LexerError{}, 167 | `-`, 168 | &Xint{}) 169 | } 170 | 171 | func TestInvalidNullByte(t *testing.T) { 172 | testExpectedError(t, 173 | &fflib.LexerError{}, 174 | "\u0000", 175 | &Xstring{}) 176 | } 177 | 178 | func TestInvalidNullByteInString(t *testing.T) { 179 | testExpectedError(t, 180 | &fflib.LexerError{}, 181 | "\"\u0000 <- null byte\"", 182 | &Xstring{}) 183 | } 184 | 185 | func TestInvalidFloatGarbageAfterE(t *testing.T) { 186 | testExpectedError(t, 187 | &fflib.LexerError{}, 188 | `1ea`, 189 | &Xfloat64{}) 190 | } 191 | -------------------------------------------------------------------------------- /tests/goser/base/goser.go: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2014 Paul Querna 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | * 16 | */ 17 | 18 | package goser 19 | 20 | import ( 21 | "io" 22 | "net" 23 | "time" 24 | ) 25 | 26 | type CacheStatus int32 27 | 28 | const ( 29 | CacheStatus_CACHESTATUS_UNKNOWN CacheStatus = 0 30 | CacheStatus_MISS CacheStatus = 1 31 | CacheStatus_EXPIRED CacheStatus = 2 32 | CacheStatus_HIT CacheStatus = 3 33 | ) 34 | 35 | type HTTP_Protocol int32 36 | 37 | const ( 38 | HTTP_HTTP_PROTOCOL_UNKNOWN HTTP_Protocol = 0 39 | HTTP_HTTP10 HTTP_Protocol = 1 40 | HTTP_HTTP11 HTTP_Protocol = 2 41 | ) 42 | 43 | type HTTP_Method int32 44 | 45 | const ( 46 | HTTP_METHOD_UNKNOWN HTTP_Method = 0 47 | HTTP_GET HTTP_Method = 1 48 | HTTP_POST HTTP_Method = 2 49 | HTTP_DELETE HTTP_Method = 3 50 | HTTP_PUT HTTP_Method = 4 51 | HTTP_HEAD HTTP_Method = 5 52 | HTTP_PURGE HTTP_Method = 6 53 | HTTP_OPTIONS HTTP_Method = 7 54 | HTTP_PROPFIND HTTP_Method = 8 55 | HTTP_MKCOL HTTP_Method = 9 56 | HTTP_PATCH HTTP_Method = 10 57 | ) 58 | 59 | type Origin_Protocol int32 60 | 61 | const ( 62 | Origin_ORIGIN_PROTOCOL_UNKNOWN Origin_Protocol = 0 63 | Origin_HTTP Origin_Protocol = 1 64 | Origin_HTTPS Origin_Protocol = 2 65 | ) 66 | 67 | type HTTP struct { 68 | Protocol HTTP_Protocol `json:"protocol"` 69 | Status uint32 `json:"status"` 70 | HostStatus uint32 `json:"hostStatus"` 71 | UpStatus uint32 `json:"upStatus"` 72 | Method HTTP_Method `json:"method"` 73 | ContentType string `json:"contentType"` 74 | UserAgent string `json:"userAgent"` 75 | Referer string `json:"referer"` 76 | RequestURI string `json:"requestURI"` 77 | XXX_unrecognized []byte `json:"-"` 78 | } 79 | 80 | type Origin struct { 81 | Ip IP `json:"ip"` 82 | Port uint32 `json:"port"` 83 | Hostname string `json:"hostname"` 84 | Protocol Origin_Protocol `json:"protocol"` 85 | } 86 | 87 | type ZonePlan int32 88 | 89 | const ( 90 | ZonePlan_ZONEPLAN_UNKNOWN ZonePlan = 0 91 | ZonePlan_FREE ZonePlan = 1 92 | ZonePlan_PRO ZonePlan = 2 93 | ZonePlan_BIZ ZonePlan = 3 94 | ZonePlan_ENT ZonePlan = 4 95 | ) 96 | 97 | type Country int32 98 | 99 | const ( 100 | Country_UNKNOWN Country = 0 101 | Country_US Country = 238 102 | ) 103 | 104 | type Log struct { 105 | Timestamp int64 `json:"timestamp"` 106 | ZoneId uint32 `json:"zoneId"` 107 | ZonePlan ZonePlan `json:"zonePlan"` 108 | Http HTTP `json:"http"` 109 | Origin Origin `json:"origin"` 110 | Country Country `json:"country"` 111 | CacheStatus CacheStatus `json:"cacheStatus"` 112 | ServerIp IP `json:"serverIp"` 113 | ServerName string `json:"serverName"` 114 | RemoteIp IP `json:"remoteIp"` 115 | BytesDlv uint64 `json:"bytesDlv"` 116 | RayId string `json:"rayId"` 117 | XXX_unrecognized []byte `json:"-"` 118 | } 119 | 120 | type IP net.IP 121 | 122 | func (ip IP) MarshalJSON() ([]byte, error) { 123 | return []byte("\"" + net.IP(ip).String() + "\""), nil 124 | } 125 | 126 | func (ip *IP) UnmarshalJSON(data []byte) error { 127 | if len(data) < 2 { 128 | return io.ErrShortBuffer 129 | } 130 | *ip = IP(net.ParseIP(string(data[1 : len(data)-1])).To4()) 131 | return nil 132 | } 133 | 134 | const userAgent = "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/33.0.1750.146 Safari/537.36" 135 | 136 | func NewLog(record *Log) { 137 | record.Timestamp = time.Now().UnixNano() 138 | record.ZoneId = 123456 139 | record.ZonePlan = ZonePlan_FREE 140 | 141 | record.Http = HTTP{ 142 | Protocol: HTTP_HTTP11, 143 | Status: 200, 144 | HostStatus: 503, 145 | UpStatus: 520, 146 | Method: HTTP_GET, 147 | ContentType: "text/html", 148 | UserAgent: userAgent, 149 | Referer: "https://www.cloudflare.com/", 150 | RequestURI: "/cdn-cgi/trace", 151 | } 152 | 153 | record.Origin = Origin{ 154 | Ip: IP(net.IPv4(1, 2, 3, 4).To4()), 155 | Port: 8080, 156 | Hostname: "www.example.com", 157 | Protocol: Origin_HTTPS, 158 | } 159 | 160 | record.Country = Country_US 161 | record.CacheStatus = CacheStatus_HIT 162 | record.ServerIp = IP(net.IPv4(192, 168, 1, 1).To4()) 163 | record.ServerName = "metal.cloudflare.com" 164 | record.RemoteIp = IP(net.IPv4(10, 1, 2, 3).To4()) 165 | record.BytesDlv = 123456 166 | record.RayId = "10c73629cce30078-LAX" 167 | } 168 | -------------------------------------------------------------------------------- /tests/goser/ff/goser.go: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2014 Paul Querna 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | * 16 | */ 17 | 18 | package goser 19 | 20 | import ( 21 | fflib "github.com/pquerna/ffjson/fflib/v1" 22 | 23 | "io" 24 | "net" 25 | "time" 26 | ) 27 | 28 | type CacheStatus int32 29 | 30 | const ( 31 | CacheStatus_CACHESTATUS_UNKNOWN CacheStatus = 0 32 | CacheStatus_MISS CacheStatus = 1 33 | CacheStatus_EXPIRED CacheStatus = 2 34 | CacheStatus_HIT CacheStatus = 3 35 | ) 36 | 37 | type HTTP_Protocol int32 38 | 39 | const ( 40 | HTTP_HTTP_PROTOCOL_UNKNOWN HTTP_Protocol = 0 41 | HTTP_HTTP10 HTTP_Protocol = 1 42 | HTTP_HTTP11 HTTP_Protocol = 2 43 | ) 44 | 45 | type HTTP_Method int32 46 | 47 | const ( 48 | HTTP_METHOD_UNKNOWN HTTP_Method = 0 49 | HTTP_GET HTTP_Method = 1 50 | HTTP_POST HTTP_Method = 2 51 | HTTP_DELETE HTTP_Method = 3 52 | HTTP_PUT HTTP_Method = 4 53 | HTTP_HEAD HTTP_Method = 5 54 | HTTP_PURGE HTTP_Method = 6 55 | HTTP_OPTIONS HTTP_Method = 7 56 | HTTP_PROPFIND HTTP_Method = 8 57 | HTTP_MKCOL HTTP_Method = 9 58 | HTTP_PATCH HTTP_Method = 10 59 | ) 60 | 61 | type Origin_Protocol int32 62 | 63 | const ( 64 | Origin_ORIGIN_PROTOCOL_UNKNOWN Origin_Protocol = 0 65 | Origin_HTTP Origin_Protocol = 1 66 | Origin_HTTPS Origin_Protocol = 2 67 | ) 68 | 69 | type HTTP struct { 70 | Protocol HTTP_Protocol `json:"protocol"` 71 | Status uint32 `json:"status"` 72 | HostStatus uint32 `json:"hostStatus"` 73 | UpStatus uint32 `json:"upStatus"` 74 | Method HTTP_Method `json:"method"` 75 | ContentType string `json:"contentType"` 76 | UserAgent string `json:"userAgent"` 77 | Referer string `json:"referer"` 78 | RequestURI string `json:"requestURI"` 79 | XXX_unrecognized []byte `json:"-"` 80 | } 81 | 82 | type Origin struct { 83 | Ip IP `json:"ip"` 84 | Port uint32 `json:"port"` 85 | Hostname string `json:"hostname"` 86 | Protocol Origin_Protocol `json:"protocol"` 87 | } 88 | 89 | type ZonePlan int32 90 | 91 | const ( 92 | ZonePlan_ZONEPLAN_UNKNOWN ZonePlan = 0 93 | ZonePlan_FREE ZonePlan = 1 94 | ZonePlan_PRO ZonePlan = 2 95 | ZonePlan_BIZ ZonePlan = 3 96 | ZonePlan_ENT ZonePlan = 4 97 | ) 98 | 99 | type Country int32 100 | 101 | const ( 102 | Country_UNKNOWN Country = 0 103 | Country_US Country = 238 104 | ) 105 | 106 | type Log struct { 107 | Timestamp int64 `json:"timestamp"` 108 | ZoneId uint32 `json:"zoneId"` 109 | ZonePlan ZonePlan `json:"zonePlan"` 110 | Http HTTP `json:"http"` 111 | Origin Origin `json:"origin"` 112 | Country Country `json:"country"` 113 | CacheStatus CacheStatus `json:"cacheStatus"` 114 | ServerIp IP `json:"serverIp"` 115 | ServerName string `json:"serverName"` 116 | RemoteIp IP `json:"remoteIp"` 117 | BytesDlv uint64 `json:"bytesDlv"` 118 | RayId string `json:"rayId"` 119 | XXX_unrecognized []byte `json:"-"` 120 | } 121 | 122 | type IP net.IP 123 | 124 | func (ip IP) MarshalJSON() ([]byte, error) { 125 | return []byte("\"" + net.IP(ip).String() + "\""), nil 126 | } 127 | 128 | func (ip IP) MarshalJSONBuf(buf fflib.EncodingBuffer) error { 129 | buf.WriteByte('"') 130 | buf.WriteString(net.IP(ip).String()) 131 | buf.WriteByte('"') 132 | return nil 133 | } 134 | 135 | func (ip *IP) UnmarshalJSON(data []byte) error { 136 | if len(data) < 2 { 137 | return io.ErrShortBuffer 138 | } 139 | *ip = IP(net.ParseIP(string(data[1 : len(data)-1])).To4()) 140 | return nil 141 | } 142 | 143 | const userAgent = "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/33.0.1750.146 Safari/537.36" 144 | 145 | func NewLog(record *Log) { 146 | record.Timestamp = time.Now().UnixNano() 147 | record.ZoneId = 123456 148 | record.ZonePlan = ZonePlan_FREE 149 | 150 | record.Http = HTTP{ 151 | Protocol: HTTP_HTTP11, 152 | Status: 200, 153 | HostStatus: 503, 154 | UpStatus: 520, 155 | Method: HTTP_GET, 156 | ContentType: "text/html", 157 | UserAgent: userAgent, 158 | Referer: "https://www.cloudflare.com/", 159 | RequestURI: "/cdn-cgi/trace", 160 | } 161 | 162 | record.Origin = Origin{ 163 | Ip: IP(net.IPv4(1, 2, 3, 4).To4()), 164 | Port: 8080, 165 | Hostname: "www.example.com", 166 | Protocol: Origin_HTTPS, 167 | } 168 | 169 | record.Country = Country_US 170 | record.CacheStatus = CacheStatus_HIT 171 | record.ServerIp = IP(net.IPv4(192, 168, 1, 1).To4()) 172 | record.ServerName = "metal.cloudflare.com" 173 | record.RemoteIp = IP(net.IPv4(10, 1, 2, 3).To4()) 174 | record.BytesDlv = 123456 175 | record.RayId = "10c73629cce30078-LAX" 176 | } 177 | -------------------------------------------------------------------------------- /tests/ff_string_test.go: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2014 Paul Querna 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | * 16 | */ 17 | 18 | package tff 19 | 20 | import ( 21 | "testing" 22 | "strings" 23 | "runtime" 24 | ) 25 | 26 | // Test data from https://github.com/akheron/jansson/tree/master/test/suites/valid 27 | // jansson, Copyright (c) 2009-2014 Petri Lehtinen 28 | // (MIT Licensed) 29 | 30 | func TestString(t *testing.T) { 31 | testType(t, &Tstring{}, &Xstring{}) 32 | testType(t, &Tmystring{}, &Xmystring{}) 33 | testType(t, &TmystringPtr{}, &XmystringPtr{}) 34 | } 35 | 36 | func TestMapStringString(t *testing.T) { 37 | m := map[string]string{"陫ʋsş\")珷<ºɖgȏ哙ȍ": "2ħ籦ö嗏ʑ>季"} 38 | testCycle(t, &TMapStringString{X: m}, &XMapStringString{X: m}) 39 | } 40 | 41 | func TestMapStringStringLong(t *testing.T) { 42 | m := map[string]string{"ɥ³ƞsɁ8^ʥǔTĪȸŹă": "ɩÅ議Ǹ轺@)蓳嗘TʡȂ", "丯Ƙ枛牐ɺ皚|": "\\p[", "ȉ": "ģ毋Ó6dz娝嘚", "ʒUɦOŖ": "斎AO6ĴC浔Ű壝ž", "/C龷ȪÆl殛瓷雼浢Ü礽绅": "D¡", "Lɋ聻鎥ʟ<$洅ɹ7\\弌Þ帺萸Do©": "A", "yǠ/淹\\韲翁&ʢsɜ": "`诫z徃鷢6ȥ啕禗Ǐ2啗塧ȱ蓿彭聡A", "瓧嫭塓烀罁胾^拜": "ǒɿʒ刽ʼn掏1ſ盷褎weLJ", "姥呄鐊唊飙Ş-U圴÷a/ɔ}摁(": "瓘ǓvjĜ蛶78Ȋ²@H", "IJ斬³;": "鯿r", "勽Ƙq/Ź u衲": "ŭDz鯰硰{舁", "枊a8衍`Ĩɘ.蘯6ċV夸eɑeʤ脽ě": "6/ʕVŚ(ĿȊ甞谐颋DžSǡƏS$+", "1ØœȠƬQg鄠": "军g>郵[+扴ȨŮ+朷Ǝ膯lj", "礶惇¸t颟.鵫ǚ灄鸫rʤî萨z": "", "ȶ网棊ʢ=wǕɳɷ9Ì": "'WKw(ğ儴Ůĺ}潷ʒ胵輓Ɔ", "}ȧ外ĺ稥氹Ç|¶鎚¡ Ɠ(嘒ėf倐": "窮秳ķ蟒苾h^", "?瞲Ť倱<įXŋ朘瑥A徙": "nh0åȂ町恰nj揠8lj黳鈫ʕ禒", "丩ŽoǠŻʘY賃ɪ鐊": "ľǎɳ,ǿ飏騀呣ǎ", "ȇe媹Hǝ呮}臷Ľð»ųKĵ": "踪鄌eÞȦY籎顒ǥŴ唼Ģ猇õǶț", "偐ę腬瓷碑=ɉ鎷卩蝾H韹寬娬ï瓼猀2": "ǰ溟ɴ扵閝ȝ鐵儣廡ɑ龫`劳", "ʮ馜ü": "", "șƶ4ĩĉş蝿ɖȃ賲鐅臬dH巧": "_瀹鞎sn芞QÄȻȊ+?", "E@Ȗs«ö": "蚛隖<ǶĬ4y£軶ǃ*ʙ嫙&蒒5靇C'", "忄*齧獚敆Ȏ": "螩B", "圠=l畣潁谯耨V6&]鴍Ɋ恧ȭ%ƎÜ": "涽託仭w-檮", "ʌ鴜": "琔n宂¬轚9Ȏ瀮昃2Ō¾\\", "ƅTG": "ǺƶȤ^}穠C]躢|)黰eȪ嵛4$%Q", "ǹ_Áȉ彂Ŵ廷s": "", "t莭琽§ć\\ ïì": "", "擓ƖHVe熼'FD剂讼ɓȌʟni酛": "/ɸɎ R§耶FfBls3!", "狞夌碕ʂɭ": "Ƽ@hDrȮO励鹗塢", "ʁgɸ=ǤÆ": "?讦ĭÐ", "陫ʋsş\")珷<ºɖgȏ哙ȍ": "2ħ籦ö嗏ʑ>季", "": "昕Ĭ", "Ⱦdz@ùƸʋŀ": "ǐƲE'iþŹʣy豎@ɀ羭,铻OŤǢʭ", ">犵殇ŕ-Ɂ圯W:ĸ輦唊#v铿ʩȂ4": "屡ʁ", "1Rƥ贫d飼$俊跾|@?鷅bȻN": "H炮掊°nʮ閼咎櫸eʔŊƞ究:ho", "ƻ悖ȩ0Ƹ[": "Ndǂ>5姣>懔%熷谟þ蛯ɰ", "ŵw^Ü郀叚Fi皬择": ":5塋訩塶\"=y钡n)İ笓", "'容": "誒j剐", "猤痈C*ĕ": "鴈o_鹈ɹ坼É/pȿŘ阌"} 43 | testCycle(t, &TMapStringString{X: m}, &XMapStringString{X: m}) 44 | } 45 | 46 | func TestStringEscapedControlCharacter(t *testing.T) { 47 | testExpectedXVal(t, 48 | "\x12 escaped control character", 49 | `\u0012 escaped control character`, 50 | &Xstring{}) 51 | } 52 | 53 | func TestStringOneByteUTF8(t *testing.T) { 54 | testExpectedXVal(t, 55 | ", one-byte UTF-8", 56 | `\u002c one-byte UTF-8`, 57 | &Xstring{}) 58 | } 59 | 60 | func TestStringUtf8Escape(t *testing.T) { 61 | testExpectedXVal(t, 62 | "2ħ籦ö嗏ʑ>嫀", 63 | `2ħ籦ö嗏ʑ\u003e嫀`, 64 | &Xstring{}) 65 | } 66 | 67 | func TestStringTwoByteUTF8(t *testing.T) { 68 | testExpectedXVal(t, 69 | "ģ two-byte UTF-8", 70 | `\u0123 two-byte UTF-8`, 71 | &Xstring{}) 72 | } 73 | 74 | func TestStringThreeByteUTF8(t *testing.T) { 75 | testExpectedXVal(t, 76 | "ࠡ three-byte UTF-8", 77 | `\u0821 three-byte UTF-8`, 78 | &Xstring{}) 79 | } 80 | 81 | func TestStringEsccapes(t *testing.T) { 82 | testExpectedXVal(t, 83 | `"\`+"\b\f\n\r\t", 84 | `\"\\\b\f\n\r\t`, 85 | &Xstring{}) 86 | 87 | testExpectedXVal(t, 88 | `/`, 89 | `\/`, 90 | &Xstring{}) 91 | } 92 | 93 | func TestStringSomeUTF8(t *testing.T) { 94 | testExpectedXVal(t, 95 | `€þıœəßð some utf-8 ĸʒ×ŋµåäö𝄞`, 96 | `€þıœəßð some utf-8 ĸʒ×ŋµåäö𝄞`, 97 | &Xstring{}) 98 | } 99 | 100 | func TestBytesInString(t *testing.T) { 101 | testExpectedXVal(t, 102 | string('\xff')+` <- xFF byte`, 103 | string('\xff')+` <- xFF byte`, 104 | &Xstring{}) 105 | } 106 | 107 | func TestString4ByteSurrogate(t *testing.T) { 108 | testExpectedXVal(t, 109 | "𝄞 surrogate, four-byte UTF-8", 110 | `\uD834\uDD1E surrogate, four-byte UTF-8`, 111 | &Xstring{}) 112 | } 113 | 114 | func TestStringNull(t *testing.T) { 115 | testExpectedXValBare(t, 116 | "foobar", 117 | `null`, 118 | &Xstring{X: "foobar"}) 119 | } 120 | 121 | func TestStringQuoted(t *testing.T) { 122 | ver := runtime.Version() 123 | if strings.Contains(ver, "go1.3") || strings.Contains(ver, "go1.2") { 124 | t.Skipf("Test requires go v1.4 or later, this is %s", ver) 125 | } 126 | 127 | testStrQuoted(t, "\x12 escaped control character") 128 | testStrQuoted(t, `\u0012 escaped control character`) 129 | testStrQuoted(t, ", one-byte UTF-8") 130 | testStrQuoted(t, `\u002c one-byte UTF-8`) 131 | testStrQuoted(t, "2ħ籦ö嗏ʑ>嫀") 132 | testStrQuoted(t, `2ħ籦ö嗏ʑ\u003e嫀`) 133 | testStrQuoted(t, "ģ two-byte UTF-8") 134 | testStrQuoted(t, `\u0123 two-byte UTF-8`) 135 | testStrQuoted(t, "ࠡ three-byte UTF-8") 136 | testStrQuoted(t, `\u0821 three-byte UTF-8`) 137 | testStrQuoted(t, `"\`+"\b\f\n\r\t") 138 | testStrQuoted(t, "𝄞 surrogate, four-byte UTF-8") 139 | testStrQuoted(t, string('\xff')+` <- xFF byte`) 140 | testStrQuoted(t, `€þıœəßð some utf-8 ĸʒ×ŋµåäö𝄞`) 141 | testStrQuoted(t, `\/`) 142 | testStrQuoted(t, `/`) 143 | testStrQuoted(t, `\"\\\b\f\n\r\t`) 144 | testStrQuoted(t, `\uD834\uDD1E surrogate, four-byte UTF-8`) 145 | testStrQuoted(t, `null`) 146 | } 147 | 148 | func testStrQuoted(t *testing.T, str string) { 149 | testCycle(t, &TstringTagged{X: str}, &XstringTagged{X: str}) 150 | testCycle(t, &TstringTaggedPtr{X: &str}, &XstringTaggedPtr{X: &str}) 151 | } -------------------------------------------------------------------------------- /fflib/v1/internal/atoi.go: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2014 Paul Querna 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | * 16 | */ 17 | 18 | /* Portions of this file are on Go stdlib's strconv/atoi.go */ 19 | // Copyright 2009 The Go Authors. All rights reserved. 20 | // Use of this source code is governed by a BSD-style 21 | // license that can be found in the LICENSE file. 22 | 23 | package internal 24 | 25 | import ( 26 | "errors" 27 | "strconv" 28 | ) 29 | 30 | // ErrRange indicates that a value is out of range for the target type. 31 | var ErrRange = errors.New("value out of range") 32 | 33 | // ErrSyntax indicates that a value does not have the right syntax for the target type. 34 | var ErrSyntax = errors.New("invalid syntax") 35 | 36 | // A NumError records a failed conversion. 37 | type NumError struct { 38 | Func string // the failing function (ParseBool, ParseInt, ParseUint, ParseFloat) 39 | Num string // the input 40 | Err error // the reason the conversion failed (ErrRange, ErrSyntax) 41 | } 42 | 43 | func (e *NumError) Error() string { 44 | return "strconv." + e.Func + ": " + "parsing " + strconv.Quote(e.Num) + ": " + e.Err.Error() 45 | } 46 | 47 | func syntaxError(fn, str string) *NumError { 48 | return &NumError{fn, str, ErrSyntax} 49 | } 50 | 51 | func rangeError(fn, str string) *NumError { 52 | return &NumError{fn, str, ErrRange} 53 | } 54 | 55 | const intSize = 32 << uint(^uint(0)>>63) 56 | 57 | // IntSize is the size in bits of an int or uint value. 58 | const IntSize = intSize 59 | 60 | // Return the first number n such that n*base >= 1<<64. 61 | func cutoff64(base int) uint64 { 62 | if base < 2 { 63 | return 0 64 | } 65 | return (1<<64-1)/uint64(base) + 1 66 | } 67 | 68 | // ParseUint is like ParseInt but for unsigned numbers, and oeprating on []byte 69 | func ParseUint(s []byte, base int, bitSize int) (n uint64, err error) { 70 | var cutoff, maxVal uint64 71 | 72 | if bitSize == 0 { 73 | bitSize = int(IntSize) 74 | } 75 | 76 | s0 := s 77 | switch { 78 | case len(s) < 1: 79 | err = ErrSyntax 80 | goto Error 81 | 82 | case 2 <= base && base <= 36: 83 | // valid base; nothing to do 84 | 85 | case base == 0: 86 | // Look for octal, hex prefix. 87 | switch { 88 | case s[0] == '0' && len(s) > 1 && (s[1] == 'x' || s[1] == 'X'): 89 | base = 16 90 | s = s[2:] 91 | if len(s) < 1 { 92 | err = ErrSyntax 93 | goto Error 94 | } 95 | case s[0] == '0': 96 | base = 8 97 | default: 98 | base = 10 99 | } 100 | 101 | default: 102 | err = errors.New("invalid base " + strconv.Itoa(base)) 103 | goto Error 104 | } 105 | 106 | n = 0 107 | cutoff = cutoff64(base) 108 | maxVal = 1<= base { 126 | n = 0 127 | err = ErrSyntax 128 | goto Error 129 | } 130 | 131 | if n >= cutoff { 132 | // n*base overflows 133 | n = 1<<64 - 1 134 | err = ErrRange 135 | goto Error 136 | } 137 | n *= uint64(base) 138 | 139 | n1 := n + uint64(v) 140 | if n1 < n || n1 > maxVal { 141 | // n+v overflows 142 | n = 1<<64 - 1 143 | err = ErrRange 144 | goto Error 145 | } 146 | n = n1 147 | } 148 | 149 | return n, nil 150 | 151 | Error: 152 | return n, &NumError{"ParseUint", string(s0), err} 153 | } 154 | 155 | // ParseInt interprets a string s in the given base (2 to 36) and 156 | // returns the corresponding value i. If base == 0, the base is 157 | // implied by the string's prefix: base 16 for "0x", base 8 for 158 | // "0", and base 10 otherwise. 159 | // 160 | // The bitSize argument specifies the integer type 161 | // that the result must fit into. Bit sizes 0, 8, 16, 32, and 64 162 | // correspond to int, int8, int16, int32, and int64. 163 | // 164 | // The errors that ParseInt returns have concrete type *NumError 165 | // and include err.Num = s. If s is empty or contains invalid 166 | // digits, err.Err = ErrSyntax and the returned value is 0; 167 | // if the value corresponding to s cannot be represented by a 168 | // signed integer of the given size, err.Err = ErrRange and the 169 | // returned value is the maximum magnitude integer of the 170 | // appropriate bitSize and sign. 171 | func ParseInt(s []byte, base int, bitSize int) (i int64, err error) { 172 | const fnParseInt = "ParseInt" 173 | 174 | if bitSize == 0 { 175 | bitSize = int(IntSize) 176 | } 177 | 178 | // Empty string bad. 179 | if len(s) == 0 { 180 | return 0, syntaxError(fnParseInt, string(s)) 181 | } 182 | 183 | // Pick off leading sign. 184 | s0 := s 185 | neg := false 186 | if s[0] == '+' { 187 | s = s[1:] 188 | } else if s[0] == '-' { 189 | neg = true 190 | s = s[1:] 191 | } 192 | 193 | // Convert unsigned and check range. 194 | var un uint64 195 | un, err = ParseUint(s, base, bitSize) 196 | if err != nil && err.(*NumError).Err != ErrRange { 197 | err.(*NumError).Func = fnParseInt 198 | err.(*NumError).Num = string(s0) 199 | return 0, err 200 | } 201 | cutoff := uint64(1 << uint(bitSize-1)) 202 | if !neg && un >= cutoff { 203 | return int64(cutoff - 1), rangeError(fnParseInt, string(s0)) 204 | } 205 | if neg && un > cutoff { 206 | return -int64(cutoff), rangeError(fnParseInt, string(s0)) 207 | } 208 | n := int64(un) 209 | if neg { 210 | n = -n 211 | } 212 | return n, nil 213 | } 214 | -------------------------------------------------------------------------------- /generator/inceptionmain.go: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2014 Paul Querna 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | * 16 | */ 17 | 18 | package generator 19 | 20 | import ( 21 | "bytes" 22 | "errors" 23 | "fmt" 24 | "go/format" 25 | "io/ioutil" 26 | "os" 27 | "os/exec" 28 | "path/filepath" 29 | "strings" 30 | "text/template" 31 | 32 | "github.com/pquerna/ffjson/shared" 33 | ) 34 | 35 | const inceptionMainTemplate = ` 36 | // DO NOT EDIT! 37 | // Code generated by ffjson 38 | // DO NOT EDIT! 39 | 40 | package main 41 | 42 | import ( 43 | "github.com/pquerna/ffjson/inception" 44 | importedinceptionpackage "{{.ImportName}}" 45 | ) 46 | 47 | func main() { 48 | i := ffjsoninception.NewInception("{{.InputPath}}", "{{.PackageName}}", "{{.OutputPath}}") 49 | i.AddMany(importedinceptionpackage.FFJSONExpose()) 50 | i.Execute() 51 | } 52 | ` 53 | 54 | const ffjsonExposeTemplate = ` 55 | // Code generated by ffjson 56 | // 57 | // This should be automatically deleted by running 'ffjson', 58 | // if leftover, please delete it. 59 | 60 | package {{.PackageName}} 61 | 62 | import ( 63 | ffjsonshared "github.com/pquerna/ffjson/shared" 64 | ) 65 | 66 | func FFJSONExpose() []ffjsonshared.InceptionType { 67 | rv := make([]ffjsonshared.InceptionType, 0) 68 | {{range .StructNames}} 69 | rv = append(rv, ffjsonshared.InceptionType{Obj: {{.Name}}{}, Options: ffjson{{printf "%#v" .Options}} } ) 70 | {{end}} 71 | return rv 72 | } 73 | ` 74 | 75 | type structName struct { 76 | Name string 77 | Options shared.StructOptions 78 | } 79 | 80 | type templateCtx struct { 81 | StructNames []structName 82 | ImportName string 83 | PackageName string 84 | InputPath string 85 | OutputPath string 86 | } 87 | 88 | type InceptionMain struct { 89 | goCmd string 90 | inputPath string 91 | exposePath string 92 | outputPath string 93 | TempMainPath string 94 | tempDir string 95 | tempMain *os.File 96 | tempExpose *os.File 97 | } 98 | 99 | func NewInceptionMain(goCmd string, inputPath string, outputPath string) *InceptionMain { 100 | exposePath := getExposePath(inputPath) 101 | return &InceptionMain{ 102 | goCmd: goCmd, 103 | inputPath: inputPath, 104 | outputPath: outputPath, 105 | exposePath: exposePath, 106 | } 107 | } 108 | 109 | func getImportName(inputPath string) (string, error) { 110 | p, err := filepath.Abs(inputPath) 111 | if err != nil { 112 | return "", err 113 | } 114 | 115 | dpath := filepath.Dir(p) 116 | 117 | gopaths := strings.Split(os.Getenv("GOPATH"), string(os.PathListSeparator)) 118 | 119 | for _, path := range gopaths { 120 | gpath, err := filepath.Abs(path) 121 | if err != nil { 122 | continue 123 | } 124 | rel, err := filepath.Rel(filepath.ToSlash(gpath), dpath) 125 | if err != nil { 126 | return "", err 127 | } 128 | 129 | if len(rel) < 4 || rel[:4] != "src"+string(os.PathSeparator) { 130 | continue 131 | } 132 | return rel[4:], nil 133 | } 134 | return "", errors.New(fmt.Sprintf("Could not find source directory: GOPATH=%q REL=%q", gopaths, dpath)) 135 | 136 | } 137 | 138 | func getExposePath(inputPath string) string { 139 | return inputPath[0:len(inputPath)-3] + "_ffjson_expose.go" 140 | } 141 | 142 | func (im *InceptionMain) renderTpl(f *os.File, t *template.Template, tc *templateCtx) error { 143 | buf := new(bytes.Buffer) 144 | err := t.Execute(buf, tc) 145 | if err != nil { 146 | return err 147 | } 148 | formatted, err := format.Source(buf.Bytes()) 149 | if err != nil { 150 | return err 151 | } 152 | _, err = f.Write(formatted) 153 | return err 154 | } 155 | 156 | func (im *InceptionMain) Generate(packageName string, si []*StructInfo, importName string) error { 157 | var err error 158 | 159 | if importName == "" { 160 | importName, err = getImportName(im.inputPath) 161 | if err != nil { 162 | return err 163 | } 164 | } 165 | 166 | im.tempDir, err = ioutil.TempDir(filepath.Dir(im.inputPath), "ffjson-inception") 167 | if err != nil { 168 | return err 169 | } 170 | 171 | importName = filepath.ToSlash(importName) 172 | // for `go run` to work, we must have a file ending in ".go". 173 | im.tempMain, err = TempFileWithPostfix(im.tempDir, "ffjson-inception", ".go") 174 | if err != nil { 175 | return err 176 | } 177 | 178 | im.TempMainPath = im.tempMain.Name() 179 | sn := make([]structName, len(si)) 180 | for i, st := range si { 181 | sn[i].Name = st.Name 182 | sn[i].Options = st.Options 183 | } 184 | 185 | tc := &templateCtx{ 186 | ImportName: importName, 187 | PackageName: packageName, 188 | StructNames: sn, 189 | InputPath: im.inputPath, 190 | OutputPath: im.outputPath, 191 | } 192 | 193 | t := template.Must(template.New("inception.go").Parse(inceptionMainTemplate)) 194 | 195 | err = im.renderTpl(im.tempMain, t, tc) 196 | if err != nil { 197 | return err 198 | } 199 | 200 | im.tempExpose, err = os.Create(im.exposePath) 201 | if err != nil { 202 | return err 203 | } 204 | 205 | t = template.Must(template.New("ffjson_expose.go").Parse(ffjsonExposeTemplate)) 206 | 207 | err = im.renderTpl(im.tempExpose, t, tc) 208 | if err != nil { 209 | return err 210 | } 211 | 212 | return nil 213 | } 214 | 215 | func (im *InceptionMain) Run() error { 216 | var out bytes.Buffer 217 | var errOut bytes.Buffer 218 | 219 | cmd := exec.Command(im.goCmd, "run", "-a", im.TempMainPath) 220 | cmd.Stdout = &out 221 | cmd.Stderr = &errOut 222 | 223 | err := cmd.Run() 224 | 225 | if err != nil { 226 | return errors.New( 227 | fmt.Sprintf("Go Run Failed for: %s\nSTDOUT:\n%s\nSTDERR:\n%s\n", 228 | im.TempMainPath, 229 | string(out.Bytes()), 230 | string(errOut.Bytes()))) 231 | } 232 | 233 | defer func() { 234 | if im.tempExpose != nil { 235 | im.tempExpose.Close() 236 | } 237 | 238 | if im.tempMain != nil { 239 | im.tempMain.Close() 240 | } 241 | 242 | os.Remove(im.TempMainPath) 243 | os.Remove(im.exposePath) 244 | os.Remove(im.tempDir) 245 | }() 246 | 247 | return nil 248 | } 249 | -------------------------------------------------------------------------------- /tests/encode_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2011 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package tff 6 | 7 | import ( 8 | "bytes" 9 | "encoding/json" 10 | "testing" 11 | ) 12 | 13 | func TestOmitEmpty(t *testing.T) { 14 | var o Optionals 15 | o.Sw = "something" 16 | o.Mr = map[string]interface{}{} 17 | o.Mo = map[string]interface{}{} 18 | 19 | got, err := json.MarshalIndent(&o, "", " ") 20 | if err != nil { 21 | t.Fatal(err) 22 | } 23 | if got := string(got); got != optionalsExpected { 24 | t.Errorf(" got: %s\nwant: %s\n", got, optionalsExpected) 25 | } 26 | } 27 | 28 | func TestOmitEmptyAll(t *testing.T) { 29 | var o OmitAll 30 | 31 | got, err := json.MarshalIndent(&o, "", " ") 32 | if err != nil { 33 | t.Fatal(err) 34 | } 35 | if got := string(got); got != omitAllExpected { 36 | t.Errorf(" got: %s\nwant: %s\n", got, omitAllExpected) 37 | } 38 | } 39 | 40 | func TestNoExported(t *testing.T) { 41 | var o NoExported 42 | 43 | got, err := json.MarshalIndent(&o, "", " ") 44 | if err != nil { 45 | t.Fatal(err) 46 | } 47 | if got := string(got); got != noExportedExpected { 48 | t.Errorf(" got: %s\nwant: %s\n", got, noExportedExpected) 49 | } 50 | } 51 | 52 | func TestOmitFirst(t *testing.T) { 53 | var o OmitFirst 54 | 55 | got, err := json.MarshalIndent(&o, "", " ") 56 | if err != nil { 57 | t.Fatal(err) 58 | } 59 | if got := string(got); got != omitFirstExpected { 60 | t.Errorf(" got: %s\nwant: %s\n", got, omitFirstExpected) 61 | } 62 | } 63 | 64 | func TestOmitLast(t *testing.T) { 65 | var o OmitLast 66 | 67 | got, err := json.MarshalIndent(&o, "", " ") 68 | if err != nil { 69 | t.Fatal(err) 70 | } 71 | if got := string(got); got != omitLastExpected { 72 | t.Errorf(" got: %s\nwant: %s\n", got, omitLastExpected) 73 | } 74 | } 75 | 76 | func TestStringTag(t *testing.T) { 77 | var s StringTag 78 | s.BoolStr = true 79 | s.IntStr = 42 80 | s.StrStr = "xzbit" 81 | got, err := json.MarshalIndent(&s, "", " ") 82 | if err != nil { 83 | t.Fatal(err) 84 | } 85 | if got := string(got); got != stringTagExpected { 86 | t.Fatalf(" got: %s\nwant: %s\n", got, stringTagExpected) 87 | } 88 | } 89 | 90 | func TestUnsupportedValues(t *testing.T) { 91 | for _, v := range unsupportedValues { 92 | if _, err := json.Marshal(v); err != nil { 93 | if _, ok := err.(*json.UnsupportedValueError); !ok { 94 | t.Errorf("for %v, got %T want UnsupportedValueError", v, err) 95 | } 96 | } else { 97 | t.Errorf("for %v, expected error", v) 98 | } 99 | } 100 | } 101 | 102 | func TestRefValMarshal(t *testing.T) { 103 | var s = struct { 104 | R0 Ref 105 | R1 *Ref 106 | R2 RefText 107 | R3 *RefText 108 | V0 Val 109 | V1 *Val 110 | V2 ValText 111 | V3 *ValText 112 | }{ 113 | R0: 12, 114 | R1: new(Ref), 115 | R2: 14, 116 | R3: new(RefText), 117 | V0: 13, 118 | V1: new(Val), 119 | V2: 15, 120 | V3: new(ValText), 121 | } 122 | const want = `{"R0":"ref","R1":"ref","R2":"\"ref\"","R3":"\"ref\"","V0":"val","V1":"val","V2":"\"val\"","V3":"\"val\""}` 123 | b, err := json.Marshal(&s) 124 | if err != nil { 125 | t.Fatalf("Marshal: %v", err) 126 | } 127 | if got := string(b); got != want { 128 | t.Errorf("got %q, want %q", got, want) 129 | } 130 | } 131 | 132 | func TestMarshalerEscaping(t *testing.T) { 133 | var c C 134 | want := `"\u003c\u0026\u003e"` 135 | b, err := json.Marshal(c) 136 | if err != nil { 137 | t.Fatalf("Marshal(c): %v", err) 138 | } 139 | if got := string(b); got != want { 140 | t.Errorf("Marshal(c) = %#q, want %#q", got, want) 141 | } 142 | 143 | var ct CText 144 | want = `"\"\u003c\u0026\u003e\""` 145 | b, err = json.Marshal(ct) 146 | if err != nil { 147 | t.Fatalf("Marshal(ct): %v", err) 148 | } 149 | if got := string(b); got != want { 150 | t.Errorf("Marshal(ct) = %#q, want %#q", got, want) 151 | } 152 | } 153 | 154 | func TestAnonymousNonstruct(t *testing.T) { 155 | var i IntType = 11 156 | a := MyStruct{i} 157 | const want = `{"IntType":11}` 158 | 159 | b, err := json.Marshal(a) 160 | if err != nil { 161 | t.Fatalf("Marshal: %v", err) 162 | } 163 | if got := string(b); got != want { 164 | t.Errorf("got %q, want %q", got, want) 165 | } 166 | } 167 | 168 | // Issue 5245. 169 | func TestEmbeddedBug(t *testing.T) { 170 | v := BugB{ 171 | BugA{"A"}, 172 | "B", 173 | } 174 | b, err := json.Marshal(v) 175 | if err != nil { 176 | t.Fatal("Marshal:", err) 177 | } 178 | want := `{"S":"B"}` 179 | got := string(b) 180 | if got != want { 181 | t.Fatalf("Marshal: got %s want %s", got, want) 182 | } 183 | // Now check that the duplicate field, S, does not appear. 184 | x := BugX{ 185 | A: 23, 186 | } 187 | b, err = json.Marshal(x) 188 | if err != nil { 189 | t.Fatal("Marshal:", err) 190 | } 191 | want = `{"A":23}` 192 | got = string(b) 193 | if got != want { 194 | t.Fatalf("Marshal: got %s want %s", got, want) 195 | } 196 | } 197 | 198 | // Test that a field with a tag dominates untagged fields. 199 | func TestTaggedFieldDominates(t *testing.T) { 200 | v := BugY{ 201 | BugA{"BugA"}, 202 | BugD{"BugD"}, 203 | } 204 | b, err := json.Marshal(v) 205 | if err != nil { 206 | t.Fatal("Marshal:", err) 207 | } 208 | want := `{"S":"BugD"}` 209 | got := string(b) 210 | if got != want { 211 | t.Fatalf("Marshal: got %s want %s", got, want) 212 | } 213 | } 214 | 215 | func TestDuplicatedFieldDisappears(t *testing.T) { 216 | v := BugZ{ 217 | BugA{"BugA"}, 218 | BugC{"BugC"}, 219 | BugY{ 220 | BugA{"nested BugA"}, 221 | BugD{"nested BugD"}, 222 | }, 223 | } 224 | b, err := json.Marshal(v) 225 | if err != nil { 226 | t.Fatal("Marshal:", err) 227 | } 228 | want := `{}` 229 | got := string(b) 230 | if got != want { 231 | t.Fatalf("Marshal: got %s want %s", got, want) 232 | } 233 | } 234 | 235 | func TestIssue6458(t *testing.T) { 236 | type Foo struct { 237 | M json.RawMessage 238 | } 239 | x := Foo{json.RawMessage(`"foo"`)} 240 | 241 | b, err := json.Marshal(&x) 242 | if err != nil { 243 | t.Fatal(err) 244 | } 245 | if want := `{"M":"foo"}`; string(b) != want { 246 | t.Errorf("Marshal(&x) = %#q; want %#q", b, want) 247 | } 248 | 249 | b, err = json.Marshal(x) 250 | if err != nil { 251 | t.Fatal(err) 252 | } 253 | 254 | if want := `{"M":"ImZvbyI="}`; string(b) != want { 255 | t.Errorf("Marshal(x) = %#q; want %#q", b, want) 256 | } 257 | } 258 | 259 | func TestHTMLEscape(t *testing.T) { 260 | var b, want bytes.Buffer 261 | m := `{"M":"foo &` + "\xe2\x80\xa8 \xe2\x80\xa9" + `"}` 262 | want.Write([]byte(`{"M":"\u003chtml\u003efoo \u0026\u2028 \u2029\u003c/html\u003e"}`)) 263 | json.HTMLEscape(&b, []byte(m)) 264 | if !bytes.Equal(b.Bytes(), want.Bytes()) { 265 | t.Errorf("HTMLEscape(&b, []byte(m)) = %s; want %s", b.Bytes(), want.Bytes()) 266 | } 267 | } 268 | -------------------------------------------------------------------------------- /tests/go.stripe/base/customer.go: -------------------------------------------------------------------------------- 1 | package stripe 2 | 3 | import ( 4 | "time" 5 | ) 6 | 7 | // Customer encapsulates details about a Customer registered in Stripe. 8 | // 9 | // see https://stripe.com/docs/api#customer_object 10 | type Customer struct { 11 | Id string `json:"id"` 12 | Desc string `json:"description,omitempty"` 13 | Email string `json:"email,omitempty"` 14 | Created int64 `json:"created"` 15 | Balance int64 `json:"account_balance"` 16 | Delinquent bool `json:"delinquent"` 17 | Cards CardData `json:"cards,omitempty"` 18 | Discount *Discount `json:"discount,omitempty"` 19 | Subscription *Subscription `json:"subscription,omitempty"` 20 | Livemode bool `json:"livemode"` 21 | DefaultCard string `json:"default_card"` 22 | } 23 | 24 | type CardData struct { 25 | Object string `json:"object"` 26 | Count int `json:"count"` 27 | Url string `json:"url"` 28 | Data []*Card `json:"data"` 29 | } 30 | 31 | // Credit Card Types accepted by the Stripe API. 32 | const ( 33 | AmericanExpress = "American Express" 34 | DinersClub = "Diners Club" 35 | Discover = "Discover" 36 | JCB = "JCB" 37 | MasterCard = "MasterCard" 38 | Visa = "Visa" 39 | UnknownCard = "Unknown" 40 | ) 41 | 42 | // Card represents details about a Credit Card entered into Stripe. 43 | type Card struct { 44 | Id string `json:"id"` 45 | Name string `json:"name,omitempty"` 46 | Type string `json:"type"` 47 | ExpMonth int `json:"exp_month"` 48 | ExpYear int `json:"exp_year"` 49 | Last4 string `json:"last4"` 50 | Fingerprint string `json:"fingerprint"` 51 | Country string `json:"country,omitempty"` 52 | AddrUess1 string `json:"address_line1,omitempty"` 53 | Address2 string `json:"address_line2,omitempty"` 54 | AddressCountry string `json:"address_country,omitempty"` 55 | AddressState string `json:"address_state,omitempty"` 56 | AddressZip string `json:"address_zip,omitempty"` 57 | AddressCity string `json:"address_city"` 58 | AddressLine1Check string `json:"address_line1_check,omitempty"` 59 | AddressZipCheck string `json:"address_zip_check,omitempty"` 60 | CVCCheck string `json:"cvc_check,omitempty"` 61 | } 62 | 63 | // Discount represents the actual application of a coupon to a particular 64 | // customer. 65 | // 66 | // see https://stripe.com/docs/api#discount_object 67 | type Discount struct { 68 | Id string `json:"id"` 69 | Customer string `json:"customer"` 70 | Start int64 `json:"start"` 71 | End int64 `json:"end"` 72 | Coupon *Coupon `json:"coupon"` 73 | } 74 | 75 | // Coupon represents percent-off discount you might want to apply to a customer. 76 | // 77 | // see https://stripe.com/docs/api#coupon_object 78 | type Coupon struct { 79 | Id string `json:"id"` 80 | Duration string `json:"duration"` 81 | PercentOff int `json:"percent_off"` 82 | DurationInMonths int `json:"duration_in_months,omitempty"` 83 | MaxRedemptions int `json:"max_redemptions,omitempty"` 84 | RedeemBy int64 `json:"redeem_by,omitempty"` 85 | TimesRedeemed int `json:"times_redeemed,omitempty"` 86 | Livemode bool `json:"livemode"` 87 | } 88 | 89 | // Subscription Statuses 90 | const ( 91 | SubscriptionTrialing = "trialing" 92 | SubscriptionActive = "active" 93 | SubscriptionPastDue = "past_due" 94 | SubscriptionCanceled = "canceled" 95 | SubscriptionUnpaid = "unpaid" 96 | ) 97 | 98 | // Subscriptions represents a recurring charge a customer's card. 99 | // 100 | // see https://stripe.com/docs/api#subscription_object 101 | type Subscription struct { 102 | Customer string `json:"customer"` 103 | Status string `json:"status"` 104 | Plan *Plan `json:"plan"` 105 | Start int64 `json:"start"` 106 | EndedAt int64 `json:"ended_at"` 107 | CurrentPeriodStart int64 `json:"current_period_start"` 108 | CurrentPeriodEnd int64 `json:"current_period_end"` 109 | TrialStart int64 `json:"trial_start"` 110 | TrialEnd int64 `json:"trial_end"` 111 | CanceledAt int64 `json:"canceled_at"` 112 | CancelAtPeriodEnd bool `json:"cancel_at_period_end"` 113 | Quantity int64 `json"quantity"` 114 | } 115 | 116 | // Plan holds details about pricing information for different products and 117 | // feature levels on your site. For example, you might have a $10/month plan 118 | // for basic features and a different $20/month plan for premium features. 119 | // 120 | // see https://stripe.com/docs/api#plan_object 121 | type Plan struct { 122 | Id string `json:"id"` 123 | Name string `json:"name"` 124 | Amount int64 `json:"amount"` 125 | Interval string `json:"interval"` 126 | IntervalCount int `json:"interval_count"` 127 | Currency string `json:"currency"` 128 | TrialPeriodDays int `json:"trial_period_days"` 129 | Livemode bool `json:"livemode"` 130 | } 131 | 132 | func NewCustomer() *Customer { 133 | 134 | return &Customer{ 135 | Id: "hooN5ne7ug", 136 | Desc: "A very nice customer.", 137 | Email: "customer@example.com", 138 | Created: time.Now().UnixNano(), 139 | Balance: 10, 140 | Delinquent: false, 141 | Cards: CardData{ 142 | Object: "A92F4CFE-8B6B-4176-873E-887AC0D120EB", 143 | Count: 1, 144 | Url: "https://stripe.example.com/card/A92F4CFE-8B6B-4176-873E-887AC0D120EB", 145 | Data: []*Card{ 146 | &Card{ 147 | Name: "John Smith", 148 | Id: "7526EC97-A0B6-47B2-AAE5-17443626A116", 149 | Fingerprint: "4242424242424242", 150 | ExpYear: time.Now().Year() + 1, 151 | ExpMonth: 1, 152 | }, 153 | }, 154 | }, 155 | Discount: &Discount{ 156 | Id: "Ee9ieZ8zie", 157 | Customer: "hooN5ne7ug", 158 | Start: time.Now().UnixNano(), 159 | End: time.Now().UnixNano(), 160 | Coupon: &Coupon{ 161 | Id: "ieQuo5Aiph", 162 | Duration: "2m", 163 | PercentOff: 10, 164 | DurationInMonths: 2, 165 | MaxRedemptions: 1, 166 | RedeemBy: time.Now().UnixNano(), 167 | TimesRedeemed: 1, 168 | Livemode: true, 169 | }, 170 | }, 171 | Subscription: &Subscription{ 172 | Customer: "hooN5ne7ug", 173 | Status: SubscriptionActive, 174 | Plan: &Plan{ 175 | Id: "gaiyeLua5u", 176 | Name: "Great Plan (TM)", 177 | Amount: 10, 178 | Interval: "monthly", 179 | IntervalCount: 3, 180 | Currency: "USD", 181 | TrialPeriodDays: 15, 182 | Livemode: true, 183 | }, 184 | Start: time.Now().UnixNano(), 185 | EndedAt: 0, 186 | CurrentPeriodStart: time.Now().UnixNano(), 187 | CurrentPeriodEnd: time.Now().UnixNano(), 188 | TrialStart: time.Now().UnixNano(), 189 | TrialEnd: time.Now().UnixNano(), 190 | CanceledAt: 0, 191 | CancelAtPeriodEnd: false, 192 | Quantity: 2, 193 | }, 194 | Livemode: true, 195 | DefaultCard: "7526EC97-A0B6-47B2-AAE5-17443626A116", 196 | } 197 | } 198 | -------------------------------------------------------------------------------- /tests/go.stripe/ff/customer.go: -------------------------------------------------------------------------------- 1 | package stripe 2 | 3 | import ( 4 | "time" 5 | ) 6 | 7 | // Customer encapsulates details about a Customer registered in Stripe. 8 | // 9 | // see https://stripe.com/docs/api#customer_object 10 | type Customer struct { 11 | Id string `json:"id"` 12 | Desc string `json:"description,omitempty"` 13 | Email string `json:"email,omitempty"` 14 | Created int64 `json:"created"` 15 | Balance int64 `json:"account_balance"` 16 | Delinquent bool `json:"delinquent"` 17 | Cards CardData `json:"cards,omitempty"` 18 | Discount *Discount `json:"discount,omitempty"` 19 | Subscription *Subscription `json:"subscription,omitempty"` 20 | Livemode bool `json:"livemode"` 21 | DefaultCard string `json:"default_card"` 22 | } 23 | 24 | type CardData struct { 25 | Object string `json:"object"` 26 | Count int `json:"count"` 27 | Url string `json:"url"` 28 | Data []*Card `json:"data"` 29 | } 30 | 31 | // Credit Card Types accepted by the Stripe API. 32 | const ( 33 | AmericanExpress = "American Express" 34 | DinersClub = "Diners Club" 35 | Discover = "Discover" 36 | JCB = "JCB" 37 | MasterCard = "MasterCard" 38 | Visa = "Visa" 39 | UnknownCard = "Unknown" 40 | ) 41 | 42 | // Card represents details about a Credit Card entered into Stripe. 43 | type Card struct { 44 | Id string `json:"id"` 45 | Name string `json:"name,omitempty"` 46 | Type string `json:"type"` 47 | ExpMonth int `json:"exp_month"` 48 | ExpYear int `json:"exp_year"` 49 | Last4 string `json:"last4"` 50 | Fingerprint string `json:"fingerprint"` 51 | Country string `json:"country,omitempty"` 52 | AddrUess1 string `json:"address_line1,omitempty"` 53 | Address2 string `json:"address_line2,omitempty"` 54 | AddressCountry string `json:"address_country,omitempty"` 55 | AddressState string `json:"address_state,omitempty"` 56 | AddressZip string `json:"address_zip,omitempty"` 57 | AddressCity string `json:"address_city"` 58 | AddressLine1Check string `json:"address_line1_check,omitempty"` 59 | AddressZipCheck string `json:"address_zip_check,omitempty"` 60 | CVCCheck string `json:"cvc_check,omitempty"` 61 | } 62 | 63 | // Discount represents the actual application of a coupon to a particular 64 | // customer. 65 | // 66 | // see https://stripe.com/docs/api#discount_object 67 | type Discount struct { 68 | Id string `json:"id"` 69 | Customer string `json:"customer"` 70 | Start int64 `json:"start"` 71 | End int64 `json:"end"` 72 | Coupon *Coupon `json:"coupon"` 73 | } 74 | 75 | // Coupon represents percent-off discount you might want to apply to a customer. 76 | // 77 | // see https://stripe.com/docs/api#coupon_object 78 | type Coupon struct { 79 | Id string `json:"id"` 80 | Duration string `json:"duration"` 81 | PercentOff int `json:"percent_off"` 82 | DurationInMonths int `json:"duration_in_months,omitempty"` 83 | MaxRedemptions int `json:"max_redemptions,omitempty"` 84 | RedeemBy int64 `json:"redeem_by,omitempty"` 85 | TimesRedeemed int `json:"times_redeemed,omitempty"` 86 | Livemode bool `json:"livemode"` 87 | } 88 | 89 | // Subscription Statuses 90 | const ( 91 | SubscriptionTrialing = "trialing" 92 | SubscriptionActive = "active" 93 | SubscriptionPastDue = "past_due" 94 | SubscriptionCanceled = "canceled" 95 | SubscriptionUnpaid = "unpaid" 96 | ) 97 | 98 | // Subscriptions represents a recurring charge a customer's card. 99 | // 100 | // see https://stripe.com/docs/api#subscription_object 101 | type Subscription struct { 102 | Customer string `json:"customer"` 103 | Status string `json:"status"` 104 | Plan *Plan `json:"plan"` 105 | Start int64 `json:"start"` 106 | EndedAt int64 `json:"ended_at"` 107 | CurrentPeriodStart int64 `json:"current_period_start"` 108 | CurrentPeriodEnd int64 `json:"current_period_end"` 109 | TrialStart int64 `json:"trial_start"` 110 | TrialEnd int64 `json:"trial_end"` 111 | CanceledAt int64 `json:"canceled_at"` 112 | CancelAtPeriodEnd bool `json:"cancel_at_period_end"` 113 | Quantity int64 `json"quantity"` 114 | } 115 | 116 | // Plan holds details about pricing information for different products and 117 | // feature levels on your site. For example, you might have a $10/month plan 118 | // for basic features and a different $20/month plan for premium features. 119 | // 120 | // see https://stripe.com/docs/api#plan_object 121 | type Plan struct { 122 | Id string `json:"id"` 123 | Name string `json:"name"` 124 | Amount int64 `json:"amount"` 125 | Interval string `json:"interval"` 126 | IntervalCount int `json:"interval_count"` 127 | Currency string `json:"currency"` 128 | TrialPeriodDays int `json:"trial_period_days"` 129 | Livemode bool `json:"livemode"` 130 | } 131 | 132 | func NewCustomer() *Customer { 133 | 134 | return &Customer{ 135 | Id: "hooN5ne7ug", 136 | Desc: "A very nice customer.", 137 | Email: "customer@example.com", 138 | Created: time.Now().UnixNano(), 139 | Balance: 10, 140 | Delinquent: false, 141 | Cards: CardData{ 142 | Object: "A92F4CFE-8B6B-4176-873E-887AC0D120EB", 143 | Count: 1, 144 | Url: "https://stripe.example.com/card/A92F4CFE-8B6B-4176-873E-887AC0D120EB", 145 | Data: []*Card{ 146 | &Card{ 147 | Name: "John Smith", 148 | Id: "7526EC97-A0B6-47B2-AAE5-17443626A116", 149 | Fingerprint: "4242424242424242", 150 | ExpYear: time.Now().Year() + 1, 151 | ExpMonth: 1, 152 | }, 153 | }, 154 | }, 155 | Discount: &Discount{ 156 | Id: "Ee9ieZ8zie", 157 | Customer: "hooN5ne7ug", 158 | Start: time.Now().UnixNano(), 159 | End: time.Now().UnixNano(), 160 | Coupon: &Coupon{ 161 | Id: "ieQuo5Aiph", 162 | Duration: "2m", 163 | PercentOff: 10, 164 | DurationInMonths: 2, 165 | MaxRedemptions: 1, 166 | RedeemBy: time.Now().UnixNano(), 167 | TimesRedeemed: 1, 168 | Livemode: true, 169 | }, 170 | }, 171 | Subscription: &Subscription{ 172 | Customer: "hooN5ne7ug", 173 | Status: SubscriptionActive, 174 | Plan: &Plan{ 175 | Id: "gaiyeLua5u", 176 | Name: "Great Plan (TM)", 177 | Amount: 10, 178 | Interval: "monthly", 179 | IntervalCount: 3, 180 | Currency: "USD", 181 | TrialPeriodDays: 15, 182 | Livemode: true, 183 | }, 184 | Start: time.Now().UnixNano(), 185 | EndedAt: 0, 186 | CurrentPeriodStart: time.Now().UnixNano(), 187 | CurrentPeriodEnd: time.Now().UnixNano(), 188 | TrialStart: time.Now().UnixNano(), 189 | TrialEnd: time.Now().UnixNano(), 190 | CanceledAt: 0, 191 | CancelAtPeriodEnd: false, 192 | Quantity: 2, 193 | }, 194 | Livemode: true, 195 | DefaultCard: "7526EC97-A0B6-47B2-AAE5-17443626A116", 196 | } 197 | } 198 | -------------------------------------------------------------------------------- /inception/decoder.go: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2014 Paul Querna 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | * 16 | */ 17 | 18 | package ffjsoninception 19 | 20 | import ( 21 | "fmt" 22 | "reflect" 23 | "strings" 24 | 25 | "github.com/pquerna/ffjson/shared" 26 | ) 27 | 28 | var validValues []string = []string{ 29 | "FFTok_left_brace", 30 | "FFTok_left_bracket", 31 | "FFTok_integer", 32 | "FFTok_double", 33 | "FFTok_string", 34 | "FFTok_bool", 35 | "FFTok_null", 36 | } 37 | 38 | func CreateUnmarshalJSON(ic *Inception, si *StructInfo) error { 39 | out := "" 40 | ic.OutputImports[`fflib "github.com/pquerna/ffjson/fflib/v1"`] = true 41 | if len(si.Fields) > 0 { 42 | ic.OutputImports[`"bytes"`] = true 43 | } 44 | ic.OutputImports[`"fmt"`] = true 45 | 46 | out += tplStr(decodeTpl["header"], header{ 47 | IC: ic, 48 | SI: si, 49 | }) 50 | 51 | out += tplStr(decodeTpl["ujFunc"], ujFunc{ 52 | SI: si, 53 | IC: ic, 54 | ValidValues: validValues, 55 | }) 56 | 57 | ic.OutputFuncs = append(ic.OutputFuncs, out) 58 | 59 | return nil 60 | } 61 | 62 | func handleField(ic *Inception, name string, typ reflect.Type, ptr bool, quoted bool) string { 63 | return handleFieldAddr(ic, name, false, typ, ptr, quoted) 64 | } 65 | 66 | func handleFieldAddr(ic *Inception, name string, takeAddr bool, typ reflect.Type, ptr bool, quoted bool) string { 67 | out := fmt.Sprintf("/* handler: %s type=%v kind=%v quoted=%t*/\n", name, typ, typ.Kind(), quoted) 68 | 69 | umlx := typ.Implements(unmarshalFasterType) || typeInInception(ic, typ, shared.MustDecoder) 70 | umlx = umlx || reflect.PtrTo(typ).Implements(unmarshalFasterType) 71 | 72 | umlstd := typ.Implements(unmarshalerType) || reflect.PtrTo(typ).Implements(unmarshalerType) 73 | 74 | out += tplStr(decodeTpl["handleUnmarshaler"], handleUnmarshaler{ 75 | IC: ic, 76 | Name: name, 77 | Typ: typ, 78 | Ptr: reflect.Ptr, 79 | TakeAddr: takeAddr || ptr, 80 | UnmarshalJSONFFLexer: umlx, 81 | Unmarshaler: umlstd, 82 | }) 83 | 84 | if umlx || umlstd { 85 | return out 86 | } 87 | 88 | // TODO(pquerna): generic handling of token type mismatching struct type 89 | switch typ.Kind() { 90 | case reflect.Int, 91 | reflect.Int8, 92 | reflect.Int16, 93 | reflect.Int32, 94 | reflect.Int64: 95 | 96 | allowed := buildTokens(quoted, "FFTok_string", "FFTok_integer", "FFTok_null") 97 | out += getAllowTokens(typ.Name(), allowed...) 98 | 99 | out += getNumberHandler(ic, name, takeAddr || ptr, typ, "ParseInt") 100 | 101 | case reflect.Uint, 102 | reflect.Uint8, 103 | reflect.Uint16, 104 | reflect.Uint32, 105 | reflect.Uint64: 106 | 107 | allowed := buildTokens(quoted, "FFTok_string", "FFTok_integer", "FFTok_null") 108 | out += getAllowTokens(typ.Name(), allowed...) 109 | 110 | out += getNumberHandler(ic, name, takeAddr || ptr, typ, "ParseUint") 111 | 112 | case reflect.Float32, 113 | reflect.Float64: 114 | 115 | allowed := buildTokens(quoted, "FFTok_string", "FFTok_double", "FFTok_integer", "FFTok_null") 116 | out += getAllowTokens(typ.Name(), allowed...) 117 | 118 | out += getNumberHandler(ic, name, takeAddr || ptr, typ, "ParseFloat") 119 | 120 | case reflect.Bool: 121 | ic.OutputImports[`"bytes"`] = true 122 | ic.OutputImports[`"errors"`] = true 123 | 124 | allowed := buildTokens(quoted, "FFTok_string", "FFTok_bool", "FFTok_null") 125 | out += getAllowTokens(typ.Name(), allowed...) 126 | 127 | out += tplStr(decodeTpl["handleBool"], handleBool{ 128 | Name: name, 129 | Typ: typ, 130 | TakeAddr: takeAddr || ptr, 131 | }) 132 | 133 | case reflect.Ptr: 134 | out += tplStr(decodeTpl["handlePtr"], handlePtr{ 135 | IC: ic, 136 | Name: name, 137 | Typ: typ, 138 | Quoted: quoted, 139 | }) 140 | 141 | case reflect.Array, 142 | reflect.Slice: 143 | out += getArrayHandler(ic, name, typ) 144 | 145 | case reflect.String: 146 | out += tplStr(decodeTpl["handleString"], handleString{ 147 | IC: ic, 148 | Name: name, 149 | Typ: typ, 150 | TakeAddr: takeAddr || ptr, 151 | Quoted: quoted, 152 | }) 153 | case reflect.Interface: 154 | ic.OutputImports[`"encoding/json"`] = true 155 | out += tplStr(decodeTpl["handleFallback"], handleFallback{ 156 | Name: name, 157 | Typ: typ, 158 | Kind: typ.Kind(), 159 | }) 160 | case reflect.Map: 161 | out += tplStr(decodeTpl["handleObject"], handleObject{ 162 | IC: ic, 163 | Name: name, 164 | Typ: typ, 165 | Ptr: reflect.Ptr, 166 | TakeAddr: takeAddr || ptr, 167 | }) 168 | default: 169 | ic.OutputImports[`"encoding/json"`] = true 170 | out += tplStr(decodeTpl["handleFallback"], handleFallback{ 171 | Name: name, 172 | Typ: typ, 173 | Kind: typ.Kind(), 174 | }) 175 | } 176 | 177 | return out 178 | } 179 | 180 | func getArrayHandler(ic *Inception, name string, typ reflect.Type) string { 181 | if typ.Kind() == reflect.Slice && typ.Elem().Kind() == reflect.Uint8 { 182 | ic.OutputImports[`"encoding/base64"`] = true 183 | useReflectToSet := false 184 | if typ.Elem().Name() != "byte" { 185 | ic.OutputImports[`"reflect"`] = true 186 | useReflectToSet = true 187 | } 188 | 189 | return tplStr(decodeTpl["handleByteSlice"], handleArray{ 190 | IC: ic, 191 | Name: name, 192 | Typ: typ, 193 | Ptr: reflect.Ptr, 194 | UseReflectToSet: useReflectToSet, 195 | }) 196 | } 197 | 198 | if typ.Elem().Kind() == reflect.Struct && typ.Elem().Name() != "" { 199 | goto sliceOrArray 200 | } 201 | 202 | if (typ.Elem().Kind() == reflect.Struct || typ.Elem().Kind() == reflect.Map) || 203 | typ.Elem().Kind() == reflect.Array || typ.Elem().Kind() == reflect.Slice && 204 | typ.Elem().Name() == "" { 205 | ic.OutputImports[`"encoding/json"`] = true 206 | 207 | return tplStr(decodeTpl["handleFallback"], handleFallback{ 208 | Name: name, 209 | Typ: typ, 210 | Kind: typ.Kind(), 211 | }) 212 | } 213 | 214 | sliceOrArray: 215 | 216 | if typ.Kind() == reflect.Array { 217 | return tplStr(decodeTpl["handleArray"], handleArray{ 218 | IC: ic, 219 | Name: name, 220 | Typ: typ, 221 | Ptr: reflect.Ptr, 222 | }) 223 | } 224 | 225 | return tplStr(decodeTpl["handleSlice"], handleArray{ 226 | IC: ic, 227 | Name: name, 228 | Typ: typ, 229 | Ptr: reflect.Ptr, 230 | }) 231 | } 232 | 233 | func getAllowTokens(name string, tokens ...string) string { 234 | return tplStr(decodeTpl["allowTokens"], allowTokens{ 235 | Name: name, 236 | Tokens: tokens, 237 | }) 238 | } 239 | 240 | func getNumberHandler(ic *Inception, name string, takeAddr bool, typ reflect.Type, parsefunc string) string { 241 | return tplStr(decodeTpl["handlerNumeric"], handlerNumeric{ 242 | IC: ic, 243 | Name: name, 244 | ParseFunc: parsefunc, 245 | TakeAddr: takeAddr, 246 | Typ: typ, 247 | }) 248 | } 249 | 250 | func getNumberSize(typ reflect.Type) string { 251 | return fmt.Sprintf("%d", typ.Bits()) 252 | } 253 | 254 | func getType(ic *Inception, name string, typ reflect.Type) string { 255 | s := typ.Name() 256 | 257 | if typ.PkgPath() != "" && typ.PkgPath() != ic.PackagePath { 258 | ic.OutputImports[`"`+typ.PkgPath()+`"`] = true 259 | s = typ.String() 260 | } 261 | 262 | if s == "" { 263 | return typ.String() 264 | } 265 | 266 | return s 267 | } 268 | 269 | func buildTokens(containsOptional bool, optional string, required ...string) []string { 270 | if containsOptional { 271 | return append(required, optional) 272 | } 273 | 274 | return required 275 | } 276 | 277 | func unquoteField(quoted bool) string { 278 | // The outer quote of a string is already stripped out by 279 | // the lexer. We need to check if the inner string is also 280 | // quoted. If so, we will decode it as json string. If decoding 281 | // fails, we will use the original string 282 | if quoted { 283 | return ` 284 | unquoted, ok := fflib.UnquoteBytes(outBuf) 285 | if ok { 286 | outBuf = unquoted 287 | } 288 | ` 289 | } 290 | return "" 291 | } 292 | 293 | func getTmpVarFor(name string) string { 294 | return "tmp_" + strings.Replace(name, ".", "__", -1) 295 | } 296 | -------------------------------------------------------------------------------- /fflib/v1/lexer_test.go: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2014 Paul Querna 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | * 16 | */ 17 | 18 | package v1 19 | 20 | import ( 21 | "bytes" 22 | "errors" 23 | "strconv" 24 | "testing" 25 | ) 26 | 27 | func scanAll(ffl *FFLexer) []FFTok { 28 | rv := make([]FFTok, 0, 0) 29 | for { 30 | tok := ffl.Scan() 31 | rv = append(rv, tok) 32 | if tok == FFTok_eof || tok == FFTok_error { 33 | break 34 | } 35 | } 36 | 37 | return rv 38 | } 39 | 40 | func assertTokensEqual(t *testing.T, a []FFTok, b []FFTok) { 41 | 42 | if len(a) != len(b) { 43 | t.Fatalf("Token lists of mixed length: expected=%v found=%v", a, b) 44 | return 45 | } 46 | 47 | for i, v := range a { 48 | if b[i] != v { 49 | t.Fatalf("Invalid Token: expected=%d found=%d token=%d", 50 | v, b, i) 51 | return 52 | } 53 | } 54 | } 55 | 56 | func scanToTok(ffl *FFLexer, targetTok FFTok) error { 57 | _, err := scanToTokCount(ffl, targetTok) 58 | return err 59 | } 60 | 61 | func scanToTokCount(ffl *FFLexer, targetTok FFTok) (int, error) { 62 | c := 0 63 | for { 64 | tok := ffl.Scan() 65 | c++ 66 | 67 | if tok == targetTok { 68 | return c, nil 69 | } 70 | 71 | if tok == FFTok_error { 72 | return c, errors.New("Hit error before target token") 73 | } 74 | if tok == FFTok_eof { 75 | return c, errors.New("Hit EOF before target token") 76 | } 77 | } 78 | 79 | return c, errors.New("Could not find target token.") 80 | } 81 | 82 | func TestBasicLexing(t *testing.T) { 83 | ffl := NewFFLexer([]byte(`{}`)) 84 | toks := scanAll(ffl) 85 | assertTokensEqual(t, []FFTok{ 86 | FFTok_left_bracket, 87 | FFTok_right_bracket, 88 | FFTok_eof, 89 | }, toks) 90 | } 91 | 92 | func TestHelloWorld(t *testing.T) { 93 | ffl := NewFFLexer([]byte(`{"hello":"world"}`)) 94 | toks := scanAll(ffl) 95 | assertTokensEqual(t, []FFTok{ 96 | FFTok_left_bracket, 97 | FFTok_string, 98 | FFTok_colon, 99 | FFTok_string, 100 | FFTok_right_bracket, 101 | FFTok_eof, 102 | }, toks) 103 | 104 | ffl = NewFFLexer([]byte(`{"hello": 1}`)) 105 | toks = scanAll(ffl) 106 | assertTokensEqual(t, []FFTok{ 107 | FFTok_left_bracket, 108 | FFTok_string, 109 | FFTok_colon, 110 | FFTok_integer, 111 | FFTok_right_bracket, 112 | FFTok_eof, 113 | }, toks) 114 | 115 | ffl = NewFFLexer([]byte(`{"hello": 1.0}`)) 116 | toks = scanAll(ffl) 117 | assertTokensEqual(t, []FFTok{ 118 | FFTok_left_bracket, 119 | FFTok_string, 120 | FFTok_colon, 121 | FFTok_double, 122 | FFTok_right_bracket, 123 | FFTok_eof, 124 | }, toks) 125 | 126 | ffl = NewFFLexer([]byte(`{"hello": 1e2}`)) 127 | toks = scanAll(ffl) 128 | assertTokensEqual(t, []FFTok{ 129 | FFTok_left_bracket, 130 | FFTok_string, 131 | FFTok_colon, 132 | FFTok_double, 133 | FFTok_right_bracket, 134 | FFTok_eof, 135 | }, toks) 136 | 137 | ffl = NewFFLexer([]byte(`{"hello": {}}`)) 138 | toks = scanAll(ffl) 139 | assertTokensEqual(t, []FFTok{ 140 | FFTok_left_bracket, 141 | FFTok_string, 142 | FFTok_colon, 143 | FFTok_left_bracket, 144 | FFTok_right_bracket, 145 | FFTok_right_bracket, 146 | FFTok_eof, 147 | }, toks) 148 | 149 | ffl = NewFFLexer([]byte(`{"hello": {"blah": null}}`)) 150 | toks = scanAll(ffl) 151 | assertTokensEqual(t, []FFTok{ 152 | FFTok_left_bracket, 153 | FFTok_string, 154 | FFTok_colon, 155 | FFTok_left_bracket, 156 | FFTok_string, 157 | FFTok_colon, 158 | FFTok_null, 159 | FFTok_right_bracket, 160 | FFTok_right_bracket, 161 | FFTok_eof, 162 | }, toks) 163 | 164 | ffl = NewFFLexer([]byte(`{"hello": /* comment */ 0}`)) 165 | toks = scanAll(ffl) 166 | assertTokensEqual(t, []FFTok{ 167 | FFTok_left_bracket, 168 | FFTok_string, 169 | FFTok_colon, 170 | FFTok_comment, 171 | FFTok_integer, 172 | FFTok_right_bracket, 173 | FFTok_eof, 174 | }, toks) 175 | 176 | ffl = NewFFLexer([]byte(`{"hello": / comment`)) 177 | toks = scanAll(ffl) 178 | assertTokensEqual(t, []FFTok{ 179 | FFTok_left_bracket, 180 | FFTok_string, 181 | FFTok_colon, 182 | FFTok_error, 183 | }, toks) 184 | 185 | ffl = NewFFLexer([]byte(`{"陫ʋsş\")珷\u003cºɖgȏ哙ȍ":"2ħ籦ö嗏ʑ\u003e季"}`)) 186 | toks = scanAll(ffl) 187 | assertTokensEqual(t, []FFTok{ 188 | FFTok_left_bracket, 189 | FFTok_string, 190 | FFTok_colon, 191 | FFTok_string, 192 | FFTok_right_bracket, 193 | FFTok_eof, 194 | }, toks) 195 | 196 | ffl = NewFFLexer([]byte(`{"X":{"陫ʋsş\")珷\u003cºɖgȏ哙ȍ":"2ħ籦ö嗏ʑ\u003e季"}}`)) 197 | toks = scanAll(ffl) 198 | assertTokensEqual(t, []FFTok{ 199 | FFTok_left_bracket, 200 | FFTok_string, 201 | FFTok_colon, 202 | FFTok_left_bracket, 203 | FFTok_string, 204 | FFTok_colon, 205 | FFTok_string, 206 | FFTok_right_bracket, 207 | FFTok_right_bracket, 208 | FFTok_eof, 209 | }, toks) 210 | } 211 | 212 | func tDouble(t *testing.T, input string, target float64) { 213 | ffl := NewFFLexer([]byte(input)) 214 | err := scanToTok(ffl, FFTok_double) 215 | if err != nil { 216 | t.Fatalf("scanToTok failed, couldnt find double: %v input: %v", err, input) 217 | } 218 | 219 | f64, err := strconv.ParseFloat(ffl.Output.String(), 64) 220 | if err != nil { 221 | t.Fatalf("ParseFloat failed, shouldnt of: %v input: %v", err, input) 222 | } 223 | 224 | if int64(f64*1000) != int64(target*1000) { 225 | t.Fatalf("ffl.Output: expected f64 '%v', got: %v from: %v input: %v", 226 | target, f64, ffl.Output.String(), input) 227 | } 228 | 229 | err = scanToTok(ffl, FFTok_eof) 230 | if err != nil { 231 | t.Fatal("Failed to find EOF after double. input: %v", input) 232 | } 233 | } 234 | 235 | func TestDouble(t *testing.T) { 236 | tDouble(t, `{"a": 1.2}`, 1.2) 237 | tDouble(t, `{"a": 1.2e2}`, 1.2e2) 238 | tDouble(t, `{"a": -1.2e2}`, -1.2e2) 239 | tDouble(t, `{"a": 1.2e-2}`, 1.2e-2) 240 | tDouble(t, `{"a": -1.2e-2}`, -1.2e-2) 241 | } 242 | 243 | func tInt(t *testing.T, input string, target int64) { 244 | ffl := NewFFLexer([]byte(input)) 245 | err := scanToTok(ffl, FFTok_integer) 246 | if err != nil { 247 | t.Fatalf("scanToTok failed, couldnt find int: %v input: %v", err, input) 248 | } 249 | 250 | // Bit sizes 0, 8, 16, 32, and 64 correspond to int, int8, int16, int32, and int64. 251 | i64, err := strconv.ParseInt(ffl.Output.String(), 10, 64) 252 | if err != nil { 253 | t.Fatalf("ParseInt failed, shouldnt of: %v input: %v", err, input) 254 | } 255 | 256 | if i64 != target { 257 | t.Fatalf("ffl.Output: expected i64 '%v', got: %v from: %v", target, i64, ffl.Output.String()) 258 | } 259 | 260 | err = scanToTok(ffl, FFTok_eof) 261 | if err != nil { 262 | t.Fatal("Failed to find EOF after int. input: %v", input) 263 | } 264 | } 265 | 266 | func TestInt(t *testing.T) { 267 | tInt(t, `{"a": 2000}`, 2000) 268 | tInt(t, `{"a": -2000}`, -2000) 269 | tInt(t, `{"a": 0}`, 0) 270 | tInt(t, `{"a": -0}`, -0) 271 | } 272 | 273 | func tError(t *testing.T, input string, targetCount int, targetError FFErr) { 274 | ffl := NewFFLexer([]byte(input)) 275 | count, err := scanToTokCount(ffl, FFTok_error) 276 | if err != nil { 277 | t.Fatalf("scanToTok failed, couldnt find error token: %v input: %v", err, input) 278 | } 279 | 280 | if count != targetCount { 281 | t.Fatalf("Expected error token at offset %v, but found it at %v input: %v", 282 | count, targetCount, input) 283 | } 284 | 285 | if ffl.Error != targetError { 286 | t.Fatalf("Expected error token %v, but got %v input: %v", 287 | targetError, ffl.Error, input) 288 | } 289 | 290 | line, char := ffl.reader.PosWithLine() 291 | if line == 0 || char == 0 { 292 | t.Fatalf("ffl.PosWithLine(): expected >=0 values. line=%v char=%v", 293 | line, char) 294 | } 295 | 296 | berr := ffl.WrapErr(ffl.Error.ToError()) 297 | if berr == nil { 298 | t.Fatalf("expected error") 299 | } 300 | 301 | } 302 | 303 | func TestInvalid(t *testing.T) { 304 | tError(t, `{"a": nul}`, 4, FFErr_invalid_string) 305 | tError(t, `{"a": 1.a}`, 4, FFErr_missing_integer_after_decimal) 306 | } 307 | 308 | func TestCapture(t *testing.T) { 309 | ffl := NewFFLexer([]byte(`{"hello": {"blah": [null, 1]}}`)) 310 | 311 | err := scanToTok(ffl, FFTok_left_bracket) 312 | if err != nil { 313 | t.Fatalf("scanToTok failed: %v", err) 314 | } 315 | 316 | err = scanToTok(ffl, FFTok_left_bracket) 317 | if err != nil { 318 | t.Fatalf("scanToTok failed: %v", err) 319 | } 320 | 321 | buf, err := ffl.CaptureField(FFTok_left_bracket) 322 | if err != nil { 323 | t.Fatalf("CaptureField failed: %v", err) 324 | } 325 | 326 | if bytes.Compare(buf, []byte(`{"blah": [null, 1]}`)) != 0 { 327 | t.Fatalf("didnt capture subfield: buf: %v", string(buf)) 328 | } 329 | } 330 | -------------------------------------------------------------------------------- /inception/reflect.go: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2014 Paul Querna 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | * 16 | */ 17 | 18 | package ffjsoninception 19 | 20 | import ( 21 | fflib "github.com/pquerna/ffjson/fflib/v1" 22 | "github.com/pquerna/ffjson/shared" 23 | 24 | "bytes" 25 | "encoding/json" 26 | "reflect" 27 | "unicode/utf8" 28 | ) 29 | 30 | type StructField struct { 31 | Name string 32 | JsonName string 33 | FoldFuncName string 34 | Typ reflect.Type 35 | OmitEmpty bool 36 | ForceString bool 37 | HasMarshalJSON bool 38 | HasUnmarshalJSON bool 39 | Pointer bool 40 | Tagged bool 41 | } 42 | 43 | type FieldByJsonName []*StructField 44 | 45 | func (a FieldByJsonName) Len() int { return len(a) } 46 | func (a FieldByJsonName) Swap(i, j int) { a[i], a[j] = a[j], a[i] } 47 | func (a FieldByJsonName) Less(i, j int) bool { return a[i].JsonName < a[j].JsonName } 48 | 49 | type StructInfo struct { 50 | Name string 51 | Obj interface{} 52 | Typ reflect.Type 53 | Fields []*StructField 54 | Options shared.StructOptions 55 | } 56 | 57 | func NewStructInfo(obj shared.InceptionType) *StructInfo { 58 | t := reflect.TypeOf(obj.Obj) 59 | return &StructInfo{ 60 | Obj: obj.Obj, 61 | Name: t.Name(), 62 | Typ: t, 63 | Fields: extractFields(obj.Obj), 64 | Options: obj.Options, 65 | } 66 | } 67 | 68 | func (si *StructInfo) FieldsByFirstByte() map[string][]*StructField { 69 | rv := make(map[string][]*StructField) 70 | for _, f := range si.Fields { 71 | b := string(f.JsonName[1]) 72 | rv[b] = append(rv[b], f) 73 | } 74 | return rv 75 | } 76 | 77 | func (si *StructInfo) ReverseFields() []*StructField { 78 | var i int 79 | rv := make([]*StructField, 0) 80 | for i = len(si.Fields) - 1; i >= 0; i-- { 81 | rv = append(rv, si.Fields[i]) 82 | } 83 | return rv 84 | } 85 | 86 | const ( 87 | caseMask = ^byte(0x20) // Mask to ignore case in ASCII. 88 | ) 89 | 90 | func foldFunc(key []byte) string { 91 | nonLetter := false 92 | special := false // special letter 93 | for _, b := range key { 94 | if b >= utf8.RuneSelf { 95 | return "bytes.EqualFold" 96 | } 97 | upper := b & caseMask 98 | if upper < 'A' || upper > 'Z' { 99 | nonLetter = true 100 | } else if upper == 'K' || upper == 'S' { 101 | // See above for why these letters are special. 102 | special = true 103 | } 104 | } 105 | if special { 106 | return "fflib.EqualFoldRight" 107 | } 108 | if nonLetter { 109 | return "fflib.AsciiEqualFold" 110 | } 111 | return "fflib.SimpleLetterEqualFold" 112 | } 113 | 114 | type MarshalerFaster interface { 115 | MarshalJSONBuf(buf fflib.EncodingBuffer) error 116 | } 117 | 118 | type UnmarshalFaster interface { 119 | UnmarshalJSONFFLexer(l *fflib.FFLexer, state fflib.FFParseState) error 120 | } 121 | 122 | var marshalerType = reflect.TypeOf(new(json.Marshaler)).Elem() 123 | var marshalerFasterType = reflect.TypeOf(new(MarshalerFaster)).Elem() 124 | var unmarshalerType = reflect.TypeOf(new(json.Unmarshaler)).Elem() 125 | var unmarshalFasterType = reflect.TypeOf(new(UnmarshalFaster)).Elem() 126 | 127 | // extractFields returns a list of fields that JSON should recognize for the given type. 128 | // The algorithm is breadth-first search over the set of structs to include - the top struct 129 | // and then any reachable anonymous structs. 130 | func extractFields(obj interface{}) []*StructField { 131 | t := reflect.TypeOf(obj) 132 | // Anonymous fields to explore at the current level and the next. 133 | current := []StructField{} 134 | next := []StructField{{Typ: t}} 135 | 136 | // Count of queued names for current level and the next. 137 | count := map[reflect.Type]int{} 138 | nextCount := map[reflect.Type]int{} 139 | 140 | // Types already visited at an earlier level. 141 | visited := map[reflect.Type]bool{} 142 | 143 | // Fields found. 144 | var fields []*StructField 145 | 146 | for len(next) > 0 { 147 | current, next = next, current[:0] 148 | count, nextCount = nextCount, map[reflect.Type]int{} 149 | 150 | for _, f := range current { 151 | if visited[f.Typ] { 152 | continue 153 | } 154 | visited[f.Typ] = true 155 | 156 | // Scan f.typ for fields to include. 157 | for i := 0; i < f.Typ.NumField(); i++ { 158 | sf := f.Typ.Field(i) 159 | if sf.PkgPath != "" { // unexported 160 | continue 161 | } 162 | tag := sf.Tag.Get("json") 163 | if tag == "-" { 164 | continue 165 | } 166 | name, opts := parseTag(tag) 167 | if !isValidTag(name) { 168 | name = "" 169 | } 170 | 171 | ft := sf.Type 172 | ptr := false 173 | if ft.Kind() == reflect.Ptr { 174 | ptr = true 175 | } 176 | 177 | if ft.Name() == "" && ft.Kind() == reflect.Ptr { 178 | // Follow pointer. 179 | ft = ft.Elem() 180 | } 181 | 182 | // Record found field and index sequence. 183 | if name != "" || !sf.Anonymous || ft.Kind() != reflect.Struct { 184 | tagged := name != "" 185 | if name == "" { 186 | name = sf.Name 187 | } 188 | 189 | var buf bytes.Buffer 190 | fflib.WriteJsonString(&buf, name) 191 | 192 | field := &StructField{ 193 | Name: sf.Name, 194 | JsonName: string(buf.Bytes()), 195 | FoldFuncName: foldFunc([]byte(name)), 196 | Typ: ft, 197 | HasMarshalJSON: ft.Implements(marshalerType), 198 | HasUnmarshalJSON: ft.Implements(unmarshalerType), 199 | OmitEmpty: opts.Contains("omitempty"), 200 | ForceString: opts.Contains("string"), 201 | Pointer: ptr, 202 | Tagged: tagged, 203 | } 204 | 205 | fields = append(fields, field) 206 | 207 | if count[f.Typ] > 1 { 208 | // If there were multiple instances, add a second, 209 | // so that the annihilation code will see a duplicate. 210 | // It only cares about the distinction between 1 or 2, 211 | // so don't bother generating any more copies. 212 | fields = append(fields, fields[len(fields)-1]) 213 | } 214 | continue 215 | } 216 | 217 | // Record new anonymous struct to explore in next round. 218 | nextCount[ft]++ 219 | if nextCount[ft] == 1 { 220 | next = append(next, StructField{ 221 | Name: ft.Name(), 222 | Typ: ft, 223 | }) 224 | } 225 | } 226 | } 227 | } 228 | 229 | // Delete all fields that are hidden by the Go rules for embedded fields, 230 | // except that fields with JSON tags are promoted. 231 | 232 | // The fields are sorted in primary order of name, secondary order 233 | // of field index length. Loop over names; for each name, delete 234 | // hidden fields by choosing the one dominant field that survives. 235 | out := fields[:0] 236 | for advance, i := 0, 0; i < len(fields); i += advance { 237 | // One iteration per name. 238 | // Find the sequence of fields with the name of this first field. 239 | fi := fields[i] 240 | name := fi.JsonName 241 | for advance = 1; i+advance < len(fields); advance++ { 242 | fj := fields[i+advance] 243 | if fj.JsonName != name { 244 | break 245 | } 246 | } 247 | if advance == 1 { // Only one field with this name 248 | out = append(out, fi) 249 | continue 250 | } 251 | dominant, ok := dominantField(fields[i : i+advance]) 252 | if ok { 253 | out = append(out, dominant) 254 | } 255 | } 256 | 257 | fields = out 258 | 259 | return fields 260 | } 261 | 262 | // dominantField looks through the fields, all of which are known to 263 | // have the same name, to find the single field that dominates the 264 | // others using Go's embedding rules, modified by the presence of 265 | // JSON tags. If there are multiple top-level fields, the boolean 266 | // will be false: This condition is an error in Go and we skip all 267 | // the fields. 268 | func dominantField(fields []*StructField) (*StructField, bool) { 269 | tagged := -1 // Index of first tagged field. 270 | for i, f := range fields { 271 | if f.Tagged { 272 | if tagged >= 0 { 273 | // Multiple tagged fields at the same level: conflict. 274 | // Return no field. 275 | return nil, false 276 | } 277 | tagged = i 278 | } 279 | } 280 | if tagged >= 0 { 281 | return fields[tagged], true 282 | } 283 | // All remaining fields have the same length. If there's more than one, 284 | // we have a conflict (two fields named "X" at the same level) and we 285 | // return no field. 286 | if len(fields) > 1 { 287 | return nil, false 288 | } 289 | return fields[0], true 290 | } 291 | -------------------------------------------------------------------------------- /fflib/v1/decimal.go: -------------------------------------------------------------------------------- 1 | // Copyright 2009 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | // Multiprecision decimal numbers. 6 | // For floating-point formatting only; not general purpose. 7 | // Only operations are assign and (binary) left/right shift. 8 | // Can do binary floating point in multiprecision decimal precisely 9 | // because 2 divides 10; cannot do decimal floating point 10 | // in multiprecision binary precisely. 11 | 12 | package v1 13 | 14 | type decimal struct { 15 | d [800]byte // digits 16 | nd int // number of digits used 17 | dp int // decimal point 18 | neg bool 19 | trunc bool // discarded nonzero digits beyond d[:nd] 20 | } 21 | 22 | func (a *decimal) String() string { 23 | n := 10 + a.nd 24 | if a.dp > 0 { 25 | n += a.dp 26 | } 27 | if a.dp < 0 { 28 | n += -a.dp 29 | } 30 | 31 | buf := make([]byte, n) 32 | w := 0 33 | switch { 34 | case a.nd == 0: 35 | return "0" 36 | 37 | case a.dp <= 0: 38 | // zeros fill space between decimal point and digits 39 | buf[w] = '0' 40 | w++ 41 | buf[w] = '.' 42 | w++ 43 | w += digitZero(buf[w : w+-a.dp]) 44 | w += copy(buf[w:], a.d[0:a.nd]) 45 | 46 | case a.dp < a.nd: 47 | // decimal point in middle of digits 48 | w += copy(buf[w:], a.d[0:a.dp]) 49 | buf[w] = '.' 50 | w++ 51 | w += copy(buf[w:], a.d[a.dp:a.nd]) 52 | 53 | default: 54 | // zeros fill space between digits and decimal point 55 | w += copy(buf[w:], a.d[0:a.nd]) 56 | w += digitZero(buf[w : w+a.dp-a.nd]) 57 | } 58 | return string(buf[0:w]) 59 | } 60 | 61 | func digitZero(dst []byte) int { 62 | for i := range dst { 63 | dst[i] = '0' 64 | } 65 | return len(dst) 66 | } 67 | 68 | // trim trailing zeros from number. 69 | // (They are meaningless; the decimal point is tracked 70 | // independent of the number of digits.) 71 | func trim(a *decimal) { 72 | for a.nd > 0 && a.d[a.nd-1] == '0' { 73 | a.nd-- 74 | } 75 | if a.nd == 0 { 76 | a.dp = 0 77 | } 78 | } 79 | 80 | // Assign v to a. 81 | func (a *decimal) Assign(v uint64) { 82 | var buf [24]byte 83 | 84 | // Write reversed decimal in buf. 85 | n := 0 86 | for v > 0 { 87 | v1 := v / 10 88 | v -= 10 * v1 89 | buf[n] = byte(v + '0') 90 | n++ 91 | v = v1 92 | } 93 | 94 | // Reverse again to produce forward decimal in a.d. 95 | a.nd = 0 96 | for n--; n >= 0; n-- { 97 | a.d[a.nd] = buf[n] 98 | a.nd++ 99 | } 100 | a.dp = a.nd 101 | trim(a) 102 | } 103 | 104 | // Maximum shift that we can do in one pass without overflow. 105 | // Signed int has 31 bits, and we have to be able to accommodate 9<>k == 0; r++ { 116 | if r >= a.nd { 117 | if n == 0 { 118 | // a == 0; shouldn't get here, but handle anyway. 119 | a.nd = 0 120 | return 121 | } 122 | for n>>k == 0 { 123 | n = n * 10 124 | r++ 125 | } 126 | break 127 | } 128 | c := int(a.d[r]) 129 | n = n*10 + c - '0' 130 | } 131 | a.dp -= r - 1 132 | 133 | // Pick up a digit, put down a digit. 134 | for ; r < a.nd; r++ { 135 | c := int(a.d[r]) 136 | dig := n >> k 137 | n -= dig << k 138 | a.d[w] = byte(dig + '0') 139 | w++ 140 | n = n*10 + c - '0' 141 | } 142 | 143 | // Put down extra digits. 144 | for n > 0 { 145 | dig := n >> k 146 | n -= dig << k 147 | if w < len(a.d) { 148 | a.d[w] = byte(dig + '0') 149 | w++ 150 | } else if dig > 0 { 151 | a.trunc = true 152 | } 153 | n = n * 10 154 | } 155 | 156 | a.nd = w 157 | trim(a) 158 | } 159 | 160 | // Cheat sheet for left shift: table indexed by shift count giving 161 | // number of new digits that will be introduced by that shift. 162 | // 163 | // For example, leftcheats[4] = {2, "625"}. That means that 164 | // if we are shifting by 4 (multiplying by 16), it will add 2 digits 165 | // when the string prefix is "625" through "999", and one fewer digit 166 | // if the string prefix is "000" through "624". 167 | // 168 | // Credit for this trick goes to Ken. 169 | 170 | type leftCheat struct { 171 | delta int // number of new digits 172 | cutoff string // minus one digit if original < a. 173 | } 174 | 175 | var leftcheats = []leftCheat{ 176 | // Leading digits of 1/2^i = 5^i. 177 | // 5^23 is not an exact 64-bit floating point number, 178 | // so have to use bc for the math. 179 | /* 180 | seq 27 | sed 's/^/5^/' | bc | 181 | awk 'BEGIN{ print "\tleftCheat{ 0, \"\" }," } 182 | { 183 | log2 = log(2)/log(10) 184 | printf("\tleftCheat{ %d, \"%s\" },\t// * %d\n", 185 | int(log2*NR+1), $0, 2**NR) 186 | }' 187 | */ 188 | {0, ""}, 189 | {1, "5"}, // * 2 190 | {1, "25"}, // * 4 191 | {1, "125"}, // * 8 192 | {2, "625"}, // * 16 193 | {2, "3125"}, // * 32 194 | {2, "15625"}, // * 64 195 | {3, "78125"}, // * 128 196 | {3, "390625"}, // * 256 197 | {3, "1953125"}, // * 512 198 | {4, "9765625"}, // * 1024 199 | {4, "48828125"}, // * 2048 200 | {4, "244140625"}, // * 4096 201 | {4, "1220703125"}, // * 8192 202 | {5, "6103515625"}, // * 16384 203 | {5, "30517578125"}, // * 32768 204 | {5, "152587890625"}, // * 65536 205 | {6, "762939453125"}, // * 131072 206 | {6, "3814697265625"}, // * 262144 207 | {6, "19073486328125"}, // * 524288 208 | {7, "95367431640625"}, // * 1048576 209 | {7, "476837158203125"}, // * 2097152 210 | {7, "2384185791015625"}, // * 4194304 211 | {7, "11920928955078125"}, // * 8388608 212 | {8, "59604644775390625"}, // * 16777216 213 | {8, "298023223876953125"}, // * 33554432 214 | {8, "1490116119384765625"}, // * 67108864 215 | {9, "7450580596923828125"}, // * 134217728 216 | } 217 | 218 | // Is the leading prefix of b lexicographically less than s? 219 | func prefixIsLessThan(b []byte, s string) bool { 220 | for i := 0; i < len(s); i++ { 221 | if i >= len(b) { 222 | return true 223 | } 224 | if b[i] != s[i] { 225 | return b[i] < s[i] 226 | } 227 | } 228 | return false 229 | } 230 | 231 | // Binary shift left (/ 2) by k bits. k <= maxShift to avoid overflow. 232 | func leftShift(a *decimal, k uint) { 233 | delta := leftcheats[k].delta 234 | if prefixIsLessThan(a.d[0:a.nd], leftcheats[k].cutoff) { 235 | delta-- 236 | } 237 | 238 | r := a.nd // read index 239 | w := a.nd + delta // write index 240 | n := 0 241 | 242 | // Pick up a digit, put down a digit. 243 | for r--; r >= 0; r-- { 244 | n += (int(a.d[r]) - '0') << k 245 | quo := n / 10 246 | rem := n - 10*quo 247 | w-- 248 | if w < len(a.d) { 249 | a.d[w] = byte(rem + '0') 250 | } else if rem != 0 { 251 | a.trunc = true 252 | } 253 | n = quo 254 | } 255 | 256 | // Put down extra digits. 257 | for n > 0 { 258 | quo := n / 10 259 | rem := n - 10*quo 260 | w-- 261 | if w < len(a.d) { 262 | a.d[w] = byte(rem + '0') 263 | } else if rem != 0 { 264 | a.trunc = true 265 | } 266 | n = quo 267 | } 268 | 269 | a.nd += delta 270 | if a.nd >= len(a.d) { 271 | a.nd = len(a.d) 272 | } 273 | a.dp += delta 274 | trim(a) 275 | } 276 | 277 | // Binary shift left (k > 0) or right (k < 0). 278 | func (a *decimal) Shift(k int) { 279 | switch { 280 | case a.nd == 0: 281 | // nothing to do: a == 0 282 | case k > 0: 283 | for k > maxShift { 284 | leftShift(a, maxShift) 285 | k -= maxShift 286 | } 287 | leftShift(a, uint(k)) 288 | case k < 0: 289 | for k < -maxShift { 290 | rightShift(a, maxShift) 291 | k += maxShift 292 | } 293 | rightShift(a, uint(-k)) 294 | } 295 | } 296 | 297 | // If we chop a at nd digits, should we round up? 298 | func shouldRoundUp(a *decimal, nd int) bool { 299 | if nd < 0 || nd >= a.nd { 300 | return false 301 | } 302 | if a.d[nd] == '5' && nd+1 == a.nd { // exactly halfway - round to even 303 | // if we truncated, a little higher than what's recorded - always round up 304 | if a.trunc { 305 | return true 306 | } 307 | return nd > 0 && (a.d[nd-1]-'0')%2 != 0 308 | } 309 | // not halfway - digit tells all 310 | return a.d[nd] >= '5' 311 | } 312 | 313 | // Round a to nd digits (or fewer). 314 | // If nd is zero, it means we're rounding 315 | // just to the left of the digits, as in 316 | // 0.09 -> 0.1. 317 | func (a *decimal) Round(nd int) { 318 | if nd < 0 || nd >= a.nd { 319 | return 320 | } 321 | if shouldRoundUp(a, nd) { 322 | a.RoundUp(nd) 323 | } else { 324 | a.RoundDown(nd) 325 | } 326 | } 327 | 328 | // Round a down to nd digits (or fewer). 329 | func (a *decimal) RoundDown(nd int) { 330 | if nd < 0 || nd >= a.nd { 331 | return 332 | } 333 | a.nd = nd 334 | trim(a) 335 | } 336 | 337 | // Round a up to nd digits (or fewer). 338 | func (a *decimal) RoundUp(nd int) { 339 | if nd < 0 || nd >= a.nd { 340 | return 341 | } 342 | 343 | // round up 344 | for i := nd - 1; i >= 0; i-- { 345 | c := a.d[i] 346 | if c < '9' { // can stop after this digit 347 | a.d[i]++ 348 | a.nd = i + 1 349 | return 350 | } 351 | } 352 | 353 | // Number is all 9s. 354 | // Change to single 1 with adjusted decimal point. 355 | a.d[0] = '1' 356 | a.nd = 1 357 | a.dp++ 358 | } 359 | 360 | // Extract integer part, rounded appropriately. 361 | // No guarantees about overflow. 362 | func (a *decimal) RoundedInteger() uint64 { 363 | if a.dp > 20 { 364 | return 0xFFFFFFFFFFFFFFFF 365 | } 366 | var i int 367 | n := uint64(0) 368 | for i = 0; i < a.dp && i < a.nd; i++ { 369 | n = n*10 + uint64(a.d[i]-'0') 370 | } 371 | for ; i < a.dp; i++ { 372 | n *= 10 373 | } 374 | if shouldRoundUp(a, a.dp) { 375 | n++ 376 | } 377 | return n 378 | } 379 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ffjson: faster JSON for Go 2 | 3 | [![Build Status](https://travis-ci.org/pquerna/ffjson.svg?branch=master)](https://travis-ci.org/pquerna/ffjson) 4 | 5 | `ffjson` generates static `MarshalJSON` and `UnmarshalJSON` functions for structures in Go. The generated functions reduce the reliance unpon runtime reflection to do serialization and are generally 2 to 3 times faster. In cases where `ffjson` doesn't understand a Type involved, it falls back to `encoding/json`, meaning it is a safe drop in replacement. By using `ffjson` your JSON serialization just gets faster with no additional code changes. 6 | 7 | When you change your `struct`, you will need to run `ffjson` again (or make it part of your build tools). 8 | 9 | ## Blog Posts 10 | 11 | * 2014-03-31: [First Release and Background](https://journal.paul.querna.org/articles/2014/03/31/ffjson-faster-json-in-go/) 12 | 13 | ## Getting Started 14 | 15 | If `myfile.go` contains the `struct` types you would like to be faster, and assuming `GOPATH` is set to a reasonable value for an existing project (meaning that in this particular example if `myfile.go` is in the `myproject` directory, the project should be under `$GOPATH/src/myproject`), you can just run: 16 | 17 | go get -u github.com/pquerna/ffjson 18 | ffjson myfile.go 19 | git add myfile_ffjson.go 20 | 21 | 22 | ## Performance Status: 23 | 24 | * `MarshalJSON` is **2x to 3x** faster than `encoding/json`. 25 | * `UnmarshalJSON` is **2x to 3x** faster than `encoding/json`. 26 | 27 | ## Features 28 | 29 | * **Unmarshal Support:** Since v0.9, `ffjson` supports Unmarshaling of structures. 30 | * **Drop in Replacement:** Because `ffjson` implements the interfaces already defined by `encoding/json` the performance enhancements are transparent to users of your structures. 31 | * **Supports all types:** `ffjson` has native support for most of Go's types -- for any type it doesn't support with fast paths, it falls back to using `encoding/json`. This means all structures should work out of the box. If they don't, [open a issue!](https://github.com/pquerna/ffjson/issues) 32 | * **ffjson: skip**: If you have a structure you want `ffjson` to ignore, add `ffjson: skip` to the doc string for this structure. 33 | * **Extensive Tests:** `ffjson` contains an extensive test suite including fuzz'ing against the JSON parser. 34 | 35 | 36 | # Using ffjson 37 | 38 | `ffjson` generates code based upon existing `struct` types. For example, `ffjson foo.go` will by default create a new file `foo_ffjson.go` that contains serialization functions for all structs found in `foo.go`. 39 | 40 | ``` 41 | Usage of ffjson: 42 | 43 | ffjson [options] [input_file] 44 | 45 | ffjson generates Go code for optimized JSON serialization. 46 | 47 | -go-cmd="": Path to go command; Useful for `goapp` support. 48 | -import-name="": Override import name in case it cannot be detected. 49 | -nodecoder: Do not generate decoder functions 50 | -noencoder: Do not generate encoder functions 51 | -w="": Write generate code to this path instead of ${input}_ffjson.go. 52 | ``` 53 | 54 | Your code must be in a compilable state for `ffjson` to work. If you code doesn't compile ffjson will most likely exit with an error. 55 | 56 | ## Disabling code generation for structs 57 | 58 | You might not want all your structs to have JSON code generated. To completely disable generation for a struct, add `ffjson: skip` to the struct comment. For example: 59 | 60 | ```Go 61 | // ffjson: skip 62 | type struct Foo { 63 | Bar string 64 | } 65 | ``` 66 | 67 | You can also choose not to have either the decoder or encoder generated by including `ffjson: nodecoder` or `ffjson: noencoder` in your comment. For instance, this will only generate the encoder (marshal) part for this struct: 68 | ```Go 69 | // ffjson: nodecoder 70 | type struct Foo { 71 | Bar string 72 | } 73 | ``` 74 | 75 | You can also disable encoders/decoders entirely for a file by using the `-noencoder`/`-nodecoder` commandline flags. 76 | 77 | ## Using ffjson with `go generate` 78 | 79 | `ffjson` is a great fit with `go generate`. It allows you to specify the ffjson command inside inside you individual go files and run them all at once. This way you don't have to maintain a separate build file with the files you need to generate. 80 | 81 | Add this comment anywhere inside your go files: 82 | 83 | ```Go 84 | //go:generate ffjson $GOFILE 85 | ``` 86 | 87 | To re-generate ffjson for all files with the tag in a folder, simply execute: 88 | 89 | ```sh 90 | go generate 91 | ``` 92 | 93 | To generate for the current package and all sub-packages, use: 94 | 95 | ```sh 96 | go generate ./... 97 | ``` 98 | This is most of what you need to know about go generate, but you can sese more about [go generate on the golang blog](http://blog.golang.org/generate). 99 | 100 | ## Should I include ffjson files in VCS? 101 | 102 | That question is really up to you. If you don't, you will have a more complex build process. If you do, you have to keep the generated files updated if you change the content of your structs. 103 | 104 | That said, ffjson operates deterministically, so it will generate the same code every time it run, so unless your code changes, the generated content should not change. Note however that this is only true if you are using the same ffjson version, so if you have several people working on a project, you might need to synchronize your ffjson version. 105 | 106 | ## Performance pitfalls 107 | 108 | `ffjson` has a few cases where it will fall back to using the runtime encoder/decoder. Notable cases are: 109 | 110 | * Interface struct members. Since it isn't possible to know the type of these types before runtime, ffjson has to use the reflect based coder. 111 | * Structs with custom marshal/unmarshal. 112 | * Map with a complex value. Simple types like `map[string]int` is fine though. 113 | * Inline struct definitions `type A struct{B struct{ X int} }` are handled by the encoder, but currently has fallback in the decoder. 114 | * Slices of slices / slices of maps are currently falling back when generating the decoder. 115 | 116 | ## Reducing Garbage Collection 117 | 118 | `ffjson` already does a lot to help garbage generation. However whenever you go through the json.Marshal you get a new byte slice back. On very high throughput servers this can lead to increased GC pressure. 119 | 120 | ### Tip 1: Use ffjson.Marshal() / ffjson.Unmarshal() 121 | 122 | This is probably the easiest optimization for you. Instead of going through encoding/json, you can call ffjson. This will disable the checks that encoding/json does to the json when it receives it from struct functions. 123 | 124 | ```Go 125 | import "github.com/pquerna/ffjson/ffjson" 126 | 127 | // BEFORE: 128 | buf, err := json.Marshal(&item) 129 | 130 | // AFTER: 131 | buf, err := ffjson.Marshal(&item) 132 | ``` 133 | This simple change is likely to double the speed of your encoding/decoding. 134 | 135 | 136 | [![GoDoc][1]][2] 137 | [1]: https://godoc.org/github.com/pquerna/ffjson/ffjson?status.svg 138 | [2]: https://godoc.org/github.com/pquerna/ffjson/ffjson#Marshal 139 | 140 | ### Tip 2: Pooling the buffer 141 | 142 | On servers where you have a lot of concurrent encoding going on, you can hand back the byte buffer you get from json.Marshal once you are done using it. An example could look like this: 143 | ```Go 144 | import "github.com/pquerna/ffjson/ffjson" 145 | 146 | func Encode(item interface{}, out io.Writer) { 147 | // Encode 148 | buf, err := ffjson.Marshal(&item) 149 | 150 | // Write the buffer 151 | _,_ = out.Write(buf) 152 | 153 | // We are now no longer need the buffer so we pool it. 154 | ffjson.Pool(buf) 155 | } 156 | ``` 157 | Note that the buffers you put back in the pool can still be reclaimed by the garbage collector, so you wont risk your program building up a big memory use by pooling the buffers. 158 | 159 | [![GoDoc][1]][2] 160 | [1]: https://godoc.org/github.com/pquerna/ffjson/ffjson?status.svg 161 | [2]: https://godoc.org/github.com/pquerna/ffjson/ffjson#Pool 162 | 163 | ### Tip 3: Creating an Encoder 164 | 165 | There might be cases where you need to encode many objects at once. This could be a server backing up, writing a lot of entries to files, etc. 166 | 167 | To do this, there is an interface similar to `encoding/json`, that allow you to create a re-usable encoder. Here is an example where we want to encode an array of the `Item` type, with a comma between entries: 168 | ```Go 169 | import "github.com/pquerna/ffjson/ffjson" 170 | 171 | func EncodeItems(items []Item, out io.Writer) { 172 | // We create an encoder. 173 | enc := ffjson.NewEncoder(out) 174 | 175 | for i, item := range items { 176 | // Encode into the buffer 177 | err := enc.Encode(&item) 178 | 179 | // If err is nil, the content is written to out, so we can write to it as well. 180 | if i != len(items) -1 { 181 | _,_ = out.Write([]byte{','}) 182 | } 183 | } 184 | } 185 | ``` 186 | 187 | 188 | Documentation: [![GoDoc][1]][2] 189 | [1]: https://godoc.org/github.com/pquerna/ffjson/ffjson?status.svg 190 | [2]: https://godoc.org/github.com/pquerna/ffjson/ffjson#Encoder 191 | 192 | ##Tip 4: Avoid interfaces 193 | 194 | We don't want to dictate how you structure your data, but having interfaces in your code will make ffjson use the golang encoder for these. When ffjson has to do this, it may even become slower than using `json.Marshal` directly. 195 | 196 | To see where that happens, search the generated `_ffjson.go` file for the text `Falling back`, which will indicate where ffjson is unable to generate code for your data structure. 197 | 198 | ##Tip 5: `ffjson` all the things! 199 | 200 | You should not only create ffjson code for your main struct, but also any structs that is included/used in your json code. 201 | 202 | So if your struct looks like this: 203 | ```Go 204 | type Foo struct { 205 | V Bar 206 | } 207 | ``` 208 | You should also make sure that code is generated for `Bar` if it is placed in another file. Also note that currently it requires you to do this in order, since generating code for `Foo` will check if code for `Bar` exists. This is only an issue if `Foo` and `Bar` are placed in different files. We are currently working on allowing simultaneous generation of an entire package. 209 | 210 | 211 | ## Improvements, bugs, adding features, and taking ffjson new directions! 212 | 213 | Please [open issues in Github](https://github.com/pquerna/ffjson/issues) for ideas, bugs, and general thoughts. Pull requests are of course preferred :) 214 | 215 | ## Similar projects 216 | 217 | * [go-codec](https://github.com/ugorji/go/tree/master/codec#readme). Very good project, that also allows streaming en/decoding, but requires you to call the library to use. 218 | * [megajson](https://github.com/benbjohnson/megajson). This has limited support, and development seems to have almost stopped at the time of writing. 219 | 220 | # Credits 221 | 222 | `ffjson` has recieved significant contributions from: 223 | 224 | * [Klaus Post](https://github.com/klauspost) 225 | * [Paul Querna](https://github.com/pquerna) 226 | 227 | ## License 228 | 229 | `ffjson` is licensed under the [Apache License, Version 2.0](./LICENSE) 230 | 231 | -------------------------------------------------------------------------------- /fflib/v1/reader.go: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2014 Paul Querna 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | * 16 | */ 17 | 18 | package v1 19 | 20 | import ( 21 | "fmt" 22 | "io" 23 | "unicode" 24 | "unicode/utf16" 25 | ) 26 | 27 | const sliceStringMask = cIJC | cNFP 28 | 29 | type ffReader struct { 30 | s []byte 31 | i int 32 | l int 33 | } 34 | 35 | func newffReader(d []byte) *ffReader { 36 | return &ffReader{ 37 | s: d, 38 | i: 0, 39 | l: len(d), 40 | } 41 | } 42 | 43 | func (r *ffReader) Pos() int { 44 | return r.i 45 | } 46 | 47 | // Reset the reader, and add new input. 48 | func (r *ffReader) Reset(d []byte) { 49 | r.s = d 50 | r.i = 0 51 | r.l = len(d) 52 | } 53 | 54 | // Calcuates the Position with line and line offset, 55 | // because this isn't counted for performance reasons, 56 | // it will iterate the buffer from the beginning, and should 57 | // only be used in error-paths. 58 | func (r *ffReader) PosWithLine() (int, int) { 59 | currentLine := 1 60 | currentChar := 0 61 | 62 | for i := 0; i < r.i; i++ { 63 | c := r.s[i] 64 | currentChar++ 65 | if c == '\n' { 66 | currentLine++ 67 | currentChar = 0 68 | } 69 | } 70 | 71 | return currentLine, currentChar 72 | } 73 | 74 | func (r *ffReader) ReadByteNoWS() (byte, error) { 75 | if r.i >= r.l { 76 | return 0, io.EOF 77 | } 78 | 79 | j := r.i 80 | 81 | for { 82 | c := r.s[j] 83 | j++ 84 | 85 | // inline whitespace parsing gives another ~8% performance boost 86 | // for many kinds of nicely indented JSON. 87 | // ... and using a [255]bool instead of multiple ifs, gives another 2% 88 | /* 89 | if c != '\t' && 90 | c != '\n' && 91 | c != '\v' && 92 | c != '\f' && 93 | c != '\r' && 94 | c != ' ' { 95 | r.i = j 96 | return c, nil 97 | } 98 | */ 99 | if whitespaceLookupTable[c] == false { 100 | r.i = j 101 | return c, nil 102 | } 103 | 104 | if j >= r.l { 105 | return 0, io.EOF 106 | } 107 | } 108 | } 109 | 110 | func (r *ffReader) ReadByte() (byte, error) { 111 | if r.i >= r.l { 112 | return 0, io.EOF 113 | } 114 | 115 | r.i++ 116 | 117 | return r.s[r.i-1], nil 118 | } 119 | 120 | func (r *ffReader) UnreadByte() { 121 | if r.i <= 0 { 122 | panic("ffReader.UnreadByte: at beginning of slice") 123 | } 124 | r.i-- 125 | } 126 | 127 | func (r *ffReader) readU4(j int) (rune, error) { 128 | 129 | var u4 [4]byte 130 | for i := 0; i < 4; i++ { 131 | if j >= r.l { 132 | return -1, io.EOF 133 | } 134 | c := r.s[j] 135 | if byteLookupTable[c]&cVHC != 0 { 136 | u4[i] = c 137 | j++ 138 | continue 139 | } else { 140 | // TODO(pquerna): handle errors better. layering violation. 141 | return -1, fmt.Errorf("lex_string_invalid_hex_char: %v %v", c, string(u4[:])) 142 | } 143 | } 144 | 145 | // TODO(pquerna): utf16.IsSurrogate 146 | rr, err := ParseUint(u4[:], 16, 64) 147 | if err != nil { 148 | return -1, err 149 | } 150 | return rune(rr), nil 151 | } 152 | 153 | func (r *ffReader) handleEscaped(c byte, j int, out DecodingBuffer) (int, error) { 154 | if j >= r.l { 155 | return 0, io.EOF 156 | } 157 | 158 | c = r.s[j] 159 | j++ 160 | 161 | if c == 'u' { 162 | ru, err := r.readU4(j) 163 | if err != nil { 164 | return 0, err 165 | } 166 | 167 | if utf16.IsSurrogate(ru) { 168 | ru2, err := r.readU4(j + 6) 169 | if err != nil { 170 | return 0, err 171 | } 172 | out.Write(r.s[r.i : j-2]) 173 | r.i = j + 10 174 | j = r.i 175 | rval := utf16.DecodeRune(ru, ru2) 176 | if rval != unicode.ReplacementChar { 177 | out.WriteRune(rval) 178 | } else { 179 | return 0, fmt.Errorf("lex_string_invalid_unicode_surrogate: %v %v", ru, ru2) 180 | } 181 | } else { 182 | out.Write(r.s[r.i : j-2]) 183 | r.i = j + 4 184 | j = r.i 185 | out.WriteRune(ru) 186 | } 187 | return j, nil 188 | } else if byteLookupTable[c]&cVEC == 0 { 189 | return 0, fmt.Errorf("lex_string_invalid_escaped_char: %v", c) 190 | } else { 191 | out.Write(r.s[r.i : j-2]) 192 | r.i = j 193 | j = r.i 194 | 195 | switch c { 196 | case '"': 197 | out.WriteByte('"') 198 | case '\\': 199 | out.WriteByte('\\') 200 | case '/': 201 | out.WriteByte('/') 202 | case 'b': 203 | out.WriteByte('\b') 204 | case 'f': 205 | out.WriteByte('\f') 206 | case 'n': 207 | out.WriteByte('\n') 208 | case 'r': 209 | out.WriteByte('\r') 210 | case 't': 211 | out.WriteByte('\t') 212 | } 213 | } 214 | 215 | return j, nil 216 | } 217 | 218 | func (r *ffReader) SliceString(out DecodingBuffer) error { 219 | var c byte 220 | // TODO(pquerna): string_with_escapes? de-escape here? 221 | j := r.i 222 | 223 | for { 224 | if j >= r.l { 225 | return io.EOF 226 | } 227 | 228 | j, c = scanString(r.s, j) 229 | 230 | if c == '"' { 231 | if j != r.i { 232 | out.Write(r.s[r.i : j-1]) 233 | r.i = j 234 | } 235 | return nil 236 | } else if c == '\\' { 237 | var err error 238 | j, err = r.handleEscaped(c, j, out) 239 | if err != nil { 240 | return err 241 | } 242 | } else if byteLookupTable[c]&cIJC != 0 { 243 | return fmt.Errorf("lex_string_invalid_json_char: %v", c) 244 | } 245 | continue 246 | } 247 | 248 | panic("ffjson: SliceString unreached exit") 249 | } 250 | 251 | // TODO(pquerna): consider combining wibth the normal byte mask. 252 | var whitespaceLookupTable [256]bool = [256]bool{ 253 | false, /* 0 */ 254 | false, /* 1 */ 255 | false, /* 2 */ 256 | false, /* 3 */ 257 | false, /* 4 */ 258 | false, /* 5 */ 259 | false, /* 6 */ 260 | false, /* 7 */ 261 | false, /* 8 */ 262 | true, /* 9 */ 263 | true, /* 10 */ 264 | true, /* 11 */ 265 | true, /* 12 */ 266 | true, /* 13 */ 267 | false, /* 14 */ 268 | false, /* 15 */ 269 | false, /* 16 */ 270 | false, /* 17 */ 271 | false, /* 18 */ 272 | false, /* 19 */ 273 | false, /* 20 */ 274 | false, /* 21 */ 275 | false, /* 22 */ 276 | false, /* 23 */ 277 | false, /* 24 */ 278 | false, /* 25 */ 279 | false, /* 26 */ 280 | false, /* 27 */ 281 | false, /* 28 */ 282 | false, /* 29 */ 283 | false, /* 30 */ 284 | false, /* 31 */ 285 | true, /* 32 */ 286 | false, /* 33 */ 287 | false, /* 34 */ 288 | false, /* 35 */ 289 | false, /* 36 */ 290 | false, /* 37 */ 291 | false, /* 38 */ 292 | false, /* 39 */ 293 | false, /* 40 */ 294 | false, /* 41 */ 295 | false, /* 42 */ 296 | false, /* 43 */ 297 | false, /* 44 */ 298 | false, /* 45 */ 299 | false, /* 46 */ 300 | false, /* 47 */ 301 | false, /* 48 */ 302 | false, /* 49 */ 303 | false, /* 50 */ 304 | false, /* 51 */ 305 | false, /* 52 */ 306 | false, /* 53 */ 307 | false, /* 54 */ 308 | false, /* 55 */ 309 | false, /* 56 */ 310 | false, /* 57 */ 311 | false, /* 58 */ 312 | false, /* 59 */ 313 | false, /* 60 */ 314 | false, /* 61 */ 315 | false, /* 62 */ 316 | false, /* 63 */ 317 | false, /* 64 */ 318 | false, /* 65 */ 319 | false, /* 66 */ 320 | false, /* 67 */ 321 | false, /* 68 */ 322 | false, /* 69 */ 323 | false, /* 70 */ 324 | false, /* 71 */ 325 | false, /* 72 */ 326 | false, /* 73 */ 327 | false, /* 74 */ 328 | false, /* 75 */ 329 | false, /* 76 */ 330 | false, /* 77 */ 331 | false, /* 78 */ 332 | false, /* 79 */ 333 | false, /* 80 */ 334 | false, /* 81 */ 335 | false, /* 82 */ 336 | false, /* 83 */ 337 | false, /* 84 */ 338 | false, /* 85 */ 339 | false, /* 86 */ 340 | false, /* 87 */ 341 | false, /* 88 */ 342 | false, /* 89 */ 343 | false, /* 90 */ 344 | false, /* 91 */ 345 | false, /* 92 */ 346 | false, /* 93 */ 347 | false, /* 94 */ 348 | false, /* 95 */ 349 | false, /* 96 */ 350 | false, /* 97 */ 351 | false, /* 98 */ 352 | false, /* 99 */ 353 | false, /* 100 */ 354 | false, /* 101 */ 355 | false, /* 102 */ 356 | false, /* 103 */ 357 | false, /* 104 */ 358 | false, /* 105 */ 359 | false, /* 106 */ 360 | false, /* 107 */ 361 | false, /* 108 */ 362 | false, /* 109 */ 363 | false, /* 110 */ 364 | false, /* 111 */ 365 | false, /* 112 */ 366 | false, /* 113 */ 367 | false, /* 114 */ 368 | false, /* 115 */ 369 | false, /* 116 */ 370 | false, /* 117 */ 371 | false, /* 118 */ 372 | false, /* 119 */ 373 | false, /* 120 */ 374 | false, /* 121 */ 375 | false, /* 122 */ 376 | false, /* 123 */ 377 | false, /* 124 */ 378 | false, /* 125 */ 379 | false, /* 126 */ 380 | false, /* 127 */ 381 | false, /* 128 */ 382 | false, /* 129 */ 383 | false, /* 130 */ 384 | false, /* 131 */ 385 | false, /* 132 */ 386 | false, /* 133 */ 387 | false, /* 134 */ 388 | false, /* 135 */ 389 | false, /* 136 */ 390 | false, /* 137 */ 391 | false, /* 138 */ 392 | false, /* 139 */ 393 | false, /* 140 */ 394 | false, /* 141 */ 395 | false, /* 142 */ 396 | false, /* 143 */ 397 | false, /* 144 */ 398 | false, /* 145 */ 399 | false, /* 146 */ 400 | false, /* 147 */ 401 | false, /* 148 */ 402 | false, /* 149 */ 403 | false, /* 150 */ 404 | false, /* 151 */ 405 | false, /* 152 */ 406 | false, /* 153 */ 407 | false, /* 154 */ 408 | false, /* 155 */ 409 | false, /* 156 */ 410 | false, /* 157 */ 411 | false, /* 158 */ 412 | false, /* 159 */ 413 | false, /* 160 */ 414 | false, /* 161 */ 415 | false, /* 162 */ 416 | false, /* 163 */ 417 | false, /* 164 */ 418 | false, /* 165 */ 419 | false, /* 166 */ 420 | false, /* 167 */ 421 | false, /* 168 */ 422 | false, /* 169 */ 423 | false, /* 170 */ 424 | false, /* 171 */ 425 | false, /* 172 */ 426 | false, /* 173 */ 427 | false, /* 174 */ 428 | false, /* 175 */ 429 | false, /* 176 */ 430 | false, /* 177 */ 431 | false, /* 178 */ 432 | false, /* 179 */ 433 | false, /* 180 */ 434 | false, /* 181 */ 435 | false, /* 182 */ 436 | false, /* 183 */ 437 | false, /* 184 */ 438 | false, /* 185 */ 439 | false, /* 186 */ 440 | false, /* 187 */ 441 | false, /* 188 */ 442 | false, /* 189 */ 443 | false, /* 190 */ 444 | false, /* 191 */ 445 | false, /* 192 */ 446 | false, /* 193 */ 447 | false, /* 194 */ 448 | false, /* 195 */ 449 | false, /* 196 */ 450 | false, /* 197 */ 451 | false, /* 198 */ 452 | false, /* 199 */ 453 | false, /* 200 */ 454 | false, /* 201 */ 455 | false, /* 202 */ 456 | false, /* 203 */ 457 | false, /* 204 */ 458 | false, /* 205 */ 459 | false, /* 206 */ 460 | false, /* 207 */ 461 | false, /* 208 */ 462 | false, /* 209 */ 463 | false, /* 210 */ 464 | false, /* 211 */ 465 | false, /* 212 */ 466 | false, /* 213 */ 467 | false, /* 214 */ 468 | false, /* 215 */ 469 | false, /* 216 */ 470 | false, /* 217 */ 471 | false, /* 218 */ 472 | false, /* 219 */ 473 | false, /* 220 */ 474 | false, /* 221 */ 475 | false, /* 222 */ 476 | false, /* 223 */ 477 | false, /* 224 */ 478 | false, /* 225 */ 479 | false, /* 226 */ 480 | false, /* 227 */ 481 | false, /* 228 */ 482 | false, /* 229 */ 483 | false, /* 230 */ 484 | false, /* 231 */ 485 | false, /* 232 */ 486 | false, /* 233 */ 487 | false, /* 234 */ 488 | false, /* 235 */ 489 | false, /* 236 */ 490 | false, /* 237 */ 491 | false, /* 238 */ 492 | false, /* 239 */ 493 | false, /* 240 */ 494 | false, /* 241 */ 495 | false, /* 242 */ 496 | false, /* 243 */ 497 | false, /* 244 */ 498 | false, /* 245 */ 499 | false, /* 246 */ 500 | false, /* 247 */ 501 | false, /* 248 */ 502 | false, /* 249 */ 503 | false, /* 250 */ 504 | false, /* 251 */ 505 | false, /* 252 */ 506 | false, /* 253 */ 507 | false, /* 254 */ 508 | false, /* 255 */ 509 | } 510 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright [yyyy] [name of copyright owner] 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. 203 | -------------------------------------------------------------------------------- /fflib/v1/jsonstring.go: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2014 Paul Querna 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | * 16 | */ 17 | 18 | /* Portions of this file are on Go stdlib's encoding/json/encode.go */ 19 | // Copyright 2010 The Go Authors. All rights reserved. 20 | // Use of this source code is governed by a BSD-style 21 | // license that can be found in the LICENSE file. 22 | 23 | package v1 24 | 25 | import ( 26 | "io" 27 | "unicode/utf8" 28 | "strconv" 29 | "unicode/utf16" 30 | "unicode" 31 | ) 32 | 33 | const hex = "0123456789abcdef" 34 | 35 | type JsonStringWriter interface { 36 | io.Writer 37 | io.ByteWriter 38 | stringWriter 39 | } 40 | 41 | func WriteJsonString(buf JsonStringWriter, s string) { 42 | WriteJson(buf, []byte(s)) 43 | } 44 | 45 | /** 46 | * Function ported from encoding/json: func (e *encodeState) string(s string) (int, error) 47 | */ 48 | func WriteJson(buf JsonStringWriter, s []byte) { 49 | buf.WriteByte('"') 50 | start := 0 51 | for i := 0; i < len(s); { 52 | if b := s[i]; b < utf8.RuneSelf { 53 | /* 54 | if 0x20 <= b && b != '\\' && b != '"' && b != '<' && b != '>' && b != '&' { 55 | i++ 56 | continue 57 | } 58 | */ 59 | if lt[b] == true { 60 | i++ 61 | continue 62 | } 63 | 64 | if start < i { 65 | buf.Write(s[start:i]) 66 | } 67 | switch b { 68 | case '\\', '"': 69 | buf.WriteByte('\\') 70 | buf.WriteByte(b) 71 | case '\n': 72 | buf.WriteByte('\\') 73 | buf.WriteByte('n') 74 | case '\r': 75 | buf.WriteByte('\\') 76 | buf.WriteByte('r') 77 | default: 78 | // This encodes bytes < 0x20 except for \n and \r, 79 | // as well as < and >. The latter are escaped because they 80 | // can lead to security holes when user-controlled strings 81 | // are rendered into JSON and served to some browsers. 82 | buf.WriteString(`\u00`) 83 | buf.WriteByte(hex[b>>4]) 84 | buf.WriteByte(hex[b&0xF]) 85 | } 86 | i++ 87 | start = i 88 | continue 89 | } 90 | c, size := utf8.DecodeRune(s[i:]) 91 | if c == utf8.RuneError && size == 1 { 92 | if start < i { 93 | buf.Write(s[start:i]) 94 | } 95 | buf.WriteString(`\ufffd`) 96 | i += size 97 | start = i 98 | continue 99 | } 100 | // U+2028 is LINE SEPARATOR. 101 | // U+2029 is PARAGRAPH SEPARATOR. 102 | // They are both technically valid characters in JSON strings, 103 | // but don't work in JSONP, which has to be evaluated as JavaScript, 104 | // and can lead to security holes there. It is valid JSON to 105 | // escape them, so we do so unconditionally. 106 | // See http://timelessrepo.com/json-isnt-a-javascript-subset for discussion. 107 | if c == '\u2028' || c == '\u2029' { 108 | if start < i { 109 | buf.Write(s[start:i]) 110 | } 111 | buf.WriteString(`\u202`) 112 | buf.WriteByte(hex[c&0xF]) 113 | i += size 114 | start = i 115 | continue 116 | } 117 | i += size 118 | } 119 | if start < len(s) { 120 | buf.Write(s[start:]) 121 | } 122 | buf.WriteByte('"') 123 | } 124 | 125 | // UnquoteBytes will decode []byte containing json string to go string 126 | // ported from encoding/json/decode.go 127 | func UnquoteBytes(s []byte) (t []byte, ok bool) { 128 | if len(s) < 2 || s[0] != '"' || s[len(s)-1] != '"' { 129 | return 130 | } 131 | s = s[1 : len(s)-1] 132 | 133 | // Check for unusual characters. If there are none, 134 | // then no unquoting is needed, so return a slice of the 135 | // original bytes. 136 | r := 0 137 | for r < len(s) { 138 | c := s[r] 139 | if c == '\\' || c == '"' || c < ' ' { 140 | break 141 | } 142 | if c < utf8.RuneSelf { 143 | r++ 144 | continue 145 | } 146 | rr, size := utf8.DecodeRune(s[r:]) 147 | if rr == utf8.RuneError && size == 1 { 148 | break 149 | } 150 | r += size 151 | } 152 | if r == len(s) { 153 | return s, true 154 | } 155 | 156 | b := make([]byte, len(s)+2*utf8.UTFMax) 157 | w := copy(b, s[0:r]) 158 | for r < len(s) { 159 | // Out of room? Can only happen if s is full of 160 | // malformed UTF-8 and we're replacing each 161 | // byte with RuneError. 162 | if w >= len(b)-2*utf8.UTFMax { 163 | nb := make([]byte, (len(b)+utf8.UTFMax)*2) 164 | copy(nb, b[0:w]) 165 | b = nb 166 | } 167 | switch c := s[r]; { 168 | case c == '\\': 169 | r++ 170 | if r >= len(s) { 171 | return 172 | } 173 | switch s[r] { 174 | default: 175 | return 176 | case '"', '\\', '/', '\'': 177 | b[w] = s[r] 178 | r++ 179 | w++ 180 | case 'b': 181 | b[w] = '\b' 182 | r++ 183 | w++ 184 | case 'f': 185 | b[w] = '\f' 186 | r++ 187 | w++ 188 | case 'n': 189 | b[w] = '\n' 190 | r++ 191 | w++ 192 | case 'r': 193 | b[w] = '\r' 194 | r++ 195 | w++ 196 | case 't': 197 | b[w] = '\t' 198 | r++ 199 | w++ 200 | case 'u': 201 | r-- 202 | rr := getu4(s[r:]) 203 | if rr < 0 { 204 | return 205 | } 206 | r += 6 207 | if utf16.IsSurrogate(rr) { 208 | rr1 := getu4(s[r:]) 209 | if dec := utf16.DecodeRune(rr, rr1); dec != unicode.ReplacementChar { 210 | // A valid pair; consume. 211 | r += 6 212 | w += utf8.EncodeRune(b[w:], dec) 213 | break 214 | } 215 | // Invalid surrogate; fall back to replacement rune. 216 | rr = unicode.ReplacementChar 217 | } 218 | w += utf8.EncodeRune(b[w:], rr) 219 | } 220 | 221 | // Quote, control characters are invalid. 222 | case c == '"', c < ' ': 223 | return 224 | 225 | // ASCII 226 | case c < utf8.RuneSelf: 227 | b[w] = c 228 | r++ 229 | w++ 230 | 231 | // Coerce to well-formed UTF-8. 232 | default: 233 | rr, size := utf8.DecodeRune(s[r:]) 234 | r += size 235 | w += utf8.EncodeRune(b[w:], rr) 236 | } 237 | } 238 | return b[0:w], true 239 | } 240 | 241 | // getu4 decodes \uXXXX from the beginning of s, returning the hex value, 242 | // or it returns -1. 243 | func getu4(s []byte) rune { 244 | if len(s) < 6 || s[0] != '\\' || s[1] != 'u' { 245 | return -1 246 | } 247 | r, err := strconv.ParseUint(string(s[2:6]), 16, 64) 248 | if err != nil { 249 | return -1 250 | } 251 | return rune(r) 252 | } 253 | 254 | // TODO(pquerna): consider combining wibth the normal byte mask. 255 | var lt [256]bool = [256]bool{ 256 | false, /* 0 */ 257 | false, /* 1 */ 258 | false, /* 2 */ 259 | false, /* 3 */ 260 | false, /* 4 */ 261 | false, /* 5 */ 262 | false, /* 6 */ 263 | false, /* 7 */ 264 | false, /* 8 */ 265 | false, /* 9 */ 266 | false, /* 10 */ 267 | false, /* 11 */ 268 | false, /* 12 */ 269 | false, /* 13 */ 270 | false, /* 14 */ 271 | false, /* 15 */ 272 | false, /* 16 */ 273 | false, /* 17 */ 274 | false, /* 18 */ 275 | false, /* 19 */ 276 | false, /* 20 */ 277 | false, /* 21 */ 278 | false, /* 22 */ 279 | false, /* 23 */ 280 | false, /* 24 */ 281 | false, /* 25 */ 282 | false, /* 26 */ 283 | false, /* 27 */ 284 | false, /* 28 */ 285 | false, /* 29 */ 286 | false, /* 30 */ 287 | false, /* 31 */ 288 | true, /* 32 */ 289 | true, /* 33 */ 290 | false, /* 34 */ 291 | true, /* 35 */ 292 | true, /* 36 */ 293 | true, /* 37 */ 294 | false, /* 38 */ 295 | true, /* 39 */ 296 | true, /* 40 */ 297 | true, /* 41 */ 298 | true, /* 42 */ 299 | true, /* 43 */ 300 | true, /* 44 */ 301 | true, /* 45 */ 302 | true, /* 46 */ 303 | true, /* 47 */ 304 | true, /* 48 */ 305 | true, /* 49 */ 306 | true, /* 50 */ 307 | true, /* 51 */ 308 | true, /* 52 */ 309 | true, /* 53 */ 310 | true, /* 54 */ 311 | true, /* 55 */ 312 | true, /* 56 */ 313 | true, /* 57 */ 314 | true, /* 58 */ 315 | true, /* 59 */ 316 | false, /* 60 */ 317 | true, /* 61 */ 318 | false, /* 62 */ 319 | true, /* 63 */ 320 | true, /* 64 */ 321 | true, /* 65 */ 322 | true, /* 66 */ 323 | true, /* 67 */ 324 | true, /* 68 */ 325 | true, /* 69 */ 326 | true, /* 70 */ 327 | true, /* 71 */ 328 | true, /* 72 */ 329 | true, /* 73 */ 330 | true, /* 74 */ 331 | true, /* 75 */ 332 | true, /* 76 */ 333 | true, /* 77 */ 334 | true, /* 78 */ 335 | true, /* 79 */ 336 | true, /* 80 */ 337 | true, /* 81 */ 338 | true, /* 82 */ 339 | true, /* 83 */ 340 | true, /* 84 */ 341 | true, /* 85 */ 342 | true, /* 86 */ 343 | true, /* 87 */ 344 | true, /* 88 */ 345 | true, /* 89 */ 346 | true, /* 90 */ 347 | true, /* 91 */ 348 | false, /* 92 */ 349 | true, /* 93 */ 350 | true, /* 94 */ 351 | true, /* 95 */ 352 | true, /* 96 */ 353 | true, /* 97 */ 354 | true, /* 98 */ 355 | true, /* 99 */ 356 | true, /* 100 */ 357 | true, /* 101 */ 358 | true, /* 102 */ 359 | true, /* 103 */ 360 | true, /* 104 */ 361 | true, /* 105 */ 362 | true, /* 106 */ 363 | true, /* 107 */ 364 | true, /* 108 */ 365 | true, /* 109 */ 366 | true, /* 110 */ 367 | true, /* 111 */ 368 | true, /* 112 */ 369 | true, /* 113 */ 370 | true, /* 114 */ 371 | true, /* 115 */ 372 | true, /* 116 */ 373 | true, /* 117 */ 374 | true, /* 118 */ 375 | true, /* 119 */ 376 | true, /* 120 */ 377 | true, /* 121 */ 378 | true, /* 122 */ 379 | true, /* 123 */ 380 | true, /* 124 */ 381 | true, /* 125 */ 382 | true, /* 126 */ 383 | true, /* 127 */ 384 | true, /* 128 */ 385 | true, /* 129 */ 386 | true, /* 130 */ 387 | true, /* 131 */ 388 | true, /* 132 */ 389 | true, /* 133 */ 390 | true, /* 134 */ 391 | true, /* 135 */ 392 | true, /* 136 */ 393 | true, /* 137 */ 394 | true, /* 138 */ 395 | true, /* 139 */ 396 | true, /* 140 */ 397 | true, /* 141 */ 398 | true, /* 142 */ 399 | true, /* 143 */ 400 | true, /* 144 */ 401 | true, /* 145 */ 402 | true, /* 146 */ 403 | true, /* 147 */ 404 | true, /* 148 */ 405 | true, /* 149 */ 406 | true, /* 150 */ 407 | true, /* 151 */ 408 | true, /* 152 */ 409 | true, /* 153 */ 410 | true, /* 154 */ 411 | true, /* 155 */ 412 | true, /* 156 */ 413 | true, /* 157 */ 414 | true, /* 158 */ 415 | true, /* 159 */ 416 | true, /* 160 */ 417 | true, /* 161 */ 418 | true, /* 162 */ 419 | true, /* 163 */ 420 | true, /* 164 */ 421 | true, /* 165 */ 422 | true, /* 166 */ 423 | true, /* 167 */ 424 | true, /* 168 */ 425 | true, /* 169 */ 426 | true, /* 170 */ 427 | true, /* 171 */ 428 | true, /* 172 */ 429 | true, /* 173 */ 430 | true, /* 174 */ 431 | true, /* 175 */ 432 | true, /* 176 */ 433 | true, /* 177 */ 434 | true, /* 178 */ 435 | true, /* 179 */ 436 | true, /* 180 */ 437 | true, /* 181 */ 438 | true, /* 182 */ 439 | true, /* 183 */ 440 | true, /* 184 */ 441 | true, /* 185 */ 442 | true, /* 186 */ 443 | true, /* 187 */ 444 | true, /* 188 */ 445 | true, /* 189 */ 446 | true, /* 190 */ 447 | true, /* 191 */ 448 | true, /* 192 */ 449 | true, /* 193 */ 450 | true, /* 194 */ 451 | true, /* 195 */ 452 | true, /* 196 */ 453 | true, /* 197 */ 454 | true, /* 198 */ 455 | true, /* 199 */ 456 | true, /* 200 */ 457 | true, /* 201 */ 458 | true, /* 202 */ 459 | true, /* 203 */ 460 | true, /* 204 */ 461 | true, /* 205 */ 462 | true, /* 206 */ 463 | true, /* 207 */ 464 | true, /* 208 */ 465 | true, /* 209 */ 466 | true, /* 210 */ 467 | true, /* 211 */ 468 | true, /* 212 */ 469 | true, /* 213 */ 470 | true, /* 214 */ 471 | true, /* 215 */ 472 | true, /* 216 */ 473 | true, /* 217 */ 474 | true, /* 218 */ 475 | true, /* 219 */ 476 | true, /* 220 */ 477 | true, /* 221 */ 478 | true, /* 222 */ 479 | true, /* 223 */ 480 | true, /* 224 */ 481 | true, /* 225 */ 482 | true, /* 226 */ 483 | true, /* 227 */ 484 | true, /* 228 */ 485 | true, /* 229 */ 486 | true, /* 230 */ 487 | true, /* 231 */ 488 | true, /* 232 */ 489 | true, /* 233 */ 490 | true, /* 234 */ 491 | true, /* 235 */ 492 | true, /* 236 */ 493 | true, /* 237 */ 494 | true, /* 238 */ 495 | true, /* 239 */ 496 | true, /* 240 */ 497 | true, /* 241 */ 498 | true, /* 242 */ 499 | true, /* 243 */ 500 | true, /* 244 */ 501 | true, /* 245 */ 502 | true, /* 246 */ 503 | true, /* 247 */ 504 | true, /* 248 */ 505 | true, /* 249 */ 506 | true, /* 250 */ 507 | true, /* 251 */ 508 | true, /* 252 */ 509 | true, /* 253 */ 510 | true, /* 254 */ 511 | true, /* 255 */ 512 | } 513 | --------------------------------------------------------------------------------