-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
0 parents
commit 30e0c99
Showing
105 changed files
with
10,312 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
.idea | ||
*.test | ||
*.out | ||
*.png | ||
*.exe |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
MIT License | ||
|
||
Copyright (c) [year] [fullname] | ||
|
||
Permission is hereby granted, free of charge, to any person obtaining a copy | ||
of this software and associated documentation files (the "Software"), to deal | ||
in the Software without restriction, including without limitation the rights | ||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | ||
copies of the Software, and to permit persons to whom the Software is | ||
furnished to do so, subject to the following conditions: | ||
|
||
The above copyright notice and this permission notice shall be included in all | ||
copies or substantial portions of the Software. | ||
|
||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | ||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | ||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | ||
SOFTWARE. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
# Feature | ||
|
||
- [x] Skip object | ||
- [ ] Support other tag than `json` | ||
|
||
# Improvement | ||
|
||
- [x] Nested skip (by using a stack) | ||
- [x] Decode with stack (proven worse than use recursive directly) | ||
|
||
# Benchmark | ||
|
||
- [x] Reset | ||
- [x] Skip switch/slice | ||
|
||
# Benchmark with other library | ||
|
||
- [ ] jsoniter |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,202 @@ | ||
package jzon | ||
|
||
import ( | ||
"reflect" | ||
"sync" | ||
"sync/atomic" | ||
) | ||
|
||
var ( | ||
DefaultDecoder = NewDecoder(&DecoderOption{ | ||
// | ||
}) | ||
) | ||
|
||
type DecoderOption struct { | ||
// custom value decoders | ||
ValDecoders map[reflect.Type]ValDecoder | ||
|
||
// if the object key is case sensitive | ||
// `false` by default | ||
CaseSensitive bool | ||
|
||
// the tag name for structures | ||
// `json` by default | ||
Tag string | ||
} | ||
|
||
type decoderCache = map[rtype]ValDecoder | ||
|
||
type Decoder struct { | ||
cacheMu sync.Mutex | ||
decoderCache atomic.Value | ||
|
||
caseSensitive bool | ||
tag string | ||
} | ||
|
||
func NewDecoder(opt *DecoderOption) *Decoder { | ||
var dec Decoder | ||
// add decoders to cache | ||
m := decoderCache{} | ||
if opt != nil { | ||
for elemTyp, valDec := range opt.ValDecoders { | ||
m[rtypeOfType(reflect.PtrTo(elemTyp))] = valDec | ||
} | ||
dec.caseSensitive = opt.CaseSensitive | ||
dec.tag = opt.Tag | ||
} | ||
if dec.tag == "" { | ||
dec.tag = "json" | ||
} | ||
dec.decoderCache.Store(m) | ||
return &dec | ||
} | ||
|
||
func (dec *Decoder) Unmarshal(data []byte, obj interface{}) error { | ||
it := dec.NewIterator() | ||
err := it.Unmarshal(data, obj) | ||
dec.ReturnIterator(it) | ||
return err | ||
} | ||
|
||
func (dec *Decoder) getDecoderFromCache(rType rtype) ValDecoder { | ||
return dec.decoderCache.Load().(decoderCache)[rType] | ||
} | ||
|
||
// the typ must be a pointer type | ||
func (dec *Decoder) createDecoder(rType rtype, ptrType reflect.Type) ValDecoder { | ||
dec.cacheMu.Lock() | ||
defer dec.cacheMu.Unlock() | ||
cache := dec.decoderCache.Load().(decoderCache) | ||
// double check | ||
if vd := cache[rType]; vd != nil { | ||
return vd | ||
} | ||
// make copy | ||
newCache := decoderCache{} | ||
for k, v := range cache { | ||
newCache[k] = v | ||
} | ||
typesToCreate := []reflect.Type{ptrType} | ||
dec.createDecoderInternal(newCache, typesToCreate) | ||
dec.decoderCache.Store(newCache) | ||
return newCache[rType] | ||
} | ||
|
||
func (dec *Decoder) createDecoderInternal(cache decoderCache, typesToCreate []reflect.Type) { | ||
rebuildMap := decoderCache{} | ||
idx := len(typesToCreate) - 1 | ||
for idx >= 0 { | ||
// pop one | ||
ptrType := typesToCreate[idx] | ||
typesToCreate = typesToCreate[:idx] | ||
idx -= 1 | ||
|
||
rType := rtypeOfType(ptrType) | ||
if _, ok := cache[rType]; ok { // double check | ||
continue | ||
} | ||
// check global decoders | ||
if v, ok := globalValDecoders[rType]; ok { | ||
cache[rType] = v | ||
continue | ||
} | ||
// check json.Unmarshaler interface | ||
if ptrType.Implements(jsonUnmarshalerType) { | ||
cache[rType] = jsonUnmarshalerDecoder(rType) | ||
continue | ||
} | ||
if ptrType.Implements(textUnmarshalerType) { | ||
cache[rType] = textUnmarshalerDecoder(rType) | ||
continue | ||
} | ||
elem := ptrType.Elem() | ||
elemKind := elem.Kind() | ||
if elemNativeRType := kindMap[elemKind]; elemNativeRType != 0 { | ||
// TODO: shall we make this an option? | ||
// TODO: so that only the native type is affected? | ||
// check if the native type has a custom decoder | ||
if v, ok := cache[elemNativeRType]; ok { | ||
cache[rType] = v | ||
continue | ||
} | ||
// otherwise check default native type decoder | ||
if v := kindDecoders[elemKind]; v != nil { | ||
cache[rType] = v | ||
continue | ||
} | ||
} | ||
switch elemKind { | ||
case reflect.Interface: | ||
if elem.NumMethod() == 0 { | ||
cache[rType] = (*efaceDecoder)(nil) | ||
} else { | ||
cache[rType] = (*ifaceDecoder)(nil) | ||
} | ||
case reflect.Struct: | ||
vd := dec.newStructDecoder(elem) | ||
if vd == nil { | ||
// no field to unmarshal | ||
cache[rType] = (*skipDecoder)(nil) | ||
} else { | ||
for _, fi := range vd.fields { | ||
typesToCreate = append(typesToCreate, fi.ptrType) | ||
idx += 1 | ||
} | ||
cache[rType] = vd | ||
rebuildMap[rType] = vd | ||
} | ||
case reflect.Ptr: | ||
typesToCreate = append(typesToCreate, elem) | ||
idx += 1 | ||
vd := newPointerDecoder(elem) | ||
cache[rType] = vd | ||
rebuildMap[rType] = vd | ||
case reflect.Array: | ||
elemPtrType := reflect.PtrTo(elem.Elem()) | ||
typesToCreate = append(typesToCreate, elemPtrType) | ||
idx += 1 | ||
vd := newArrayDecoder(elem) | ||
cache[rType] = vd | ||
rebuildMap[rType] = vd | ||
case reflect.Slice: | ||
elemPtrType := reflect.PtrTo(elem.Elem()) | ||
typesToCreate = append(typesToCreate, elemPtrType) | ||
idx += 1 | ||
vd := newSliceDecoder(elem) | ||
cache[rType] = vd | ||
rebuildMap[rType] = vd | ||
case reflect.Map: | ||
vd := newMapDecoder(elem) | ||
if vd == nil { | ||
cache[rType] = notSupportedDecoder(ptrType.String()) | ||
} else { | ||
valuePtrType := reflect.PtrTo(elem.Elem()) | ||
typesToCreate = append(typesToCreate, valuePtrType) | ||
idx += 1 | ||
cache[rType] = vd | ||
rebuildMap[rType] = vd | ||
} | ||
default: | ||
cache[rType] = notSupportedDecoder(ptrType.String()) | ||
} | ||
} | ||
// rebuild some decoders | ||
for _, vd := range rebuildMap { | ||
switch x := vd.(type) { | ||
case *pointerDecoder: | ||
x.elemDec = cache[x.ptrRType] | ||
case *structDecoder: | ||
for _, field := range x.fields { | ||
field.decoder = cache[field.rtype] | ||
} | ||
case *arrayDecoder: | ||
x.elemDec = cache[x.elemPtrRType] | ||
case *sliceDecoder: | ||
x.elemDec = cache[x.elemPtrRType] | ||
case *mapDecoder: | ||
x.valDec = cache[x.valPtrRType] | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
package jzon | ||
|
||
func (dec *Decoder) NewIterator() *Iterator { | ||
it := defaultIteratorPool.BorrowIterator() | ||
it.decoder = dec | ||
return it | ||
} | ||
|
||
func (dec *Decoder) ReturnIterator(it *Iterator) { | ||
it.decoder = nil | ||
defaultIteratorPool.ReturnIterator(it) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
package jzon |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
package jzon | ||
|
||
import ( | ||
"reflect" | ||
"unsafe" | ||
) | ||
|
||
type rtype = uintptr | ||
|
||
type eface struct { | ||
rtype rtype | ||
data unsafe.Pointer | ||
} | ||
|
||
func packEFace(rtype rtype, data unsafe.Pointer) interface{} { | ||
var i interface{} | ||
e := (*eface)(unsafe.Pointer(&i)) | ||
e.rtype = rtype | ||
e.data = data | ||
return i | ||
} | ||
|
||
func rtypeOfType(typ reflect.Type) rtype { | ||
ef := (*eface)(unsafe.Pointer(&typ)) | ||
return rtype(ef.data) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
package jzon | ||
|
||
import ( | ||
"reflect" | ||
"testing" | ||
"unsafe" | ||
|
||
"github.com/stretchr/testify/require" | ||
) | ||
|
||
func TestEface(t *testing.T) { | ||
f := func(o interface{}) *eface { | ||
return (*eface)(unsafe.Pointer(&o)) | ||
} | ||
a := 1 | ||
b := 2 | ||
ef1 := f(&a) | ||
ef2 := f(&b) | ||
require.Equal(t, ef1.rtype, ef2.rtype) | ||
// require.Equal(t, uintptr(strconv.IntSize/8), uintptr(ef2.data)-uintptr(ef1.data)) | ||
t.Logf("%x %x", ef1.rtype, ef1.data) | ||
t.Logf("%x %x", ef2.rtype, ef2.data) | ||
|
||
// with reflect | ||
r := reflect.TypeOf(&a) | ||
ef3 := (*eface)(unsafe.Pointer(&r)) | ||
require.Equal(t, ef1.rtype, uintptr(ef3.data)) | ||
require.Equal(t, ef1.rtype, rtypeOfType(r)) | ||
|
||
// pack | ||
packed := packEFace(rtype(ef3.data), ef1.data) | ||
t.Logf("%+v", packed) | ||
v, ok := packed.(*int) | ||
t.Logf("%+v %+v", v, ok) | ||
require.True(t, ok) | ||
require.Equal(t, &a, v) | ||
} |
Oops, something went wrong.