package json
import (
"errors"
"fmt"
"io"
"reflect"
"sync"
"encoding/json/internal"
"encoding/json/internal/jsonflags"
"encoding/json/internal/jsonopts"
"encoding/json/jsontext"
)
var errUnsupportedMutation = errors .New ("unsupported calls must not read or write any tokens" )
var errNonSingularValue = errors .New ("must read or write exactly one value" )
type Marshalers = typedMarshalers
func JoinMarshalers (ms ...*Marshalers ) *Marshalers {
return newMarshalers (ms ...)
}
type Unmarshalers = typedUnmarshalers
func JoinUnmarshalers (us ...*Unmarshalers ) *Unmarshalers {
return newUnmarshalers (us ...)
}
type typedMarshalers = typedArshalers [jsontext .Encoder ]
type typedUnmarshalers = typedArshalers [jsontext .Decoder ]
type typedArshalers[Coder any ] struct {
nonComparable
fncVals []typedArshaler [Coder ]
fncCache sync .Map
fromAny bool
}
type typedMarshaler = typedArshaler [jsontext .Encoder ]
type typedUnmarshaler = typedArshaler [jsontext .Decoder ]
type typedArshaler[Coder any ] struct {
typ reflect .Type
fnc func (*Coder , addressableValue , *jsonopts .Struct ) error
maySkip bool
}
func newMarshalers(ms ...*Marshalers ) *Marshalers { return newTypedArshalers (ms ...) }
func newUnmarshalers(us ...*Unmarshalers ) *Unmarshalers { return newTypedArshalers (us ...) }
func newTypedArshalers[Coder any ](as ...*typedArshalers [Coder ]) *typedArshalers [Coder ] {
var a typedArshalers [Coder ]
for _ , a2 := range as {
if a2 != nil {
a .fncVals = append (a .fncVals , a2 .fncVals ...)
a .fromAny = a .fromAny || a2 .fromAny
}
}
if len (a .fncVals ) == 0 {
return nil
}
return &a
}
func (a *typedArshalers [Coder ]) lookup (fnc func (*Coder , addressableValue , *jsonopts .Struct ) error , t reflect .Type ) (func (*Coder , addressableValue , *jsonopts .Struct ) error , bool ) {
if a == nil {
return fnc , false
}
if v , ok := a .fncCache .Load (t ); ok {
if v == nil {
return fnc , false
}
return v .(func (*Coder , addressableValue , *jsonopts .Struct ) error ), true
}
var fncs []func (*Coder , addressableValue , *jsonopts .Struct ) error
for _ , fncVal := range a .fncVals {
if !castableTo (t , fncVal .typ ) {
continue
}
fncs = append (fncs , fncVal .fnc )
if !fncVal .maySkip {
break
}
}
if len (fncs ) == 0 {
a .fncCache .Store (t , nil )
return fnc , false
}
fncDefault := fnc
fnc = func (c *Coder , v addressableValue , o *jsonopts .Struct ) error {
for _ , fnc := range fncs {
if err := fnc (c , v , o ); !errors .Is (err , errors .ErrUnsupported ) {
return err
}
}
return fncDefault (c , v , o )
}
v , _ := a .fncCache .LoadOrStore (t , fnc )
return v .(func (*Coder , addressableValue , *jsonopts .Struct ) error ), true
}
func MarshalFunc [T any ](fn func (T ) ([]byte , error )) *Marshalers {
t := reflect .TypeFor [T ]()
assertCastableTo (t , true )
typFnc := typedMarshaler {
typ : t ,
fnc : func (enc *jsontext .Encoder , va addressableValue , mo *jsonopts .Struct ) error {
v , _ := reflect .TypeAssert [T ](va .castTo (t ))
val , err := fn (v )
if err != nil {
err = wrapErrUnsupported (err , "marshal function of type func(T) ([]byte, error)" )
if mo .Flags .Get (jsonflags .ReportErrorsWithLegacySemantics ) {
return internal .NewMarshalerError (va .Addr ().Interface (), err , "MarshalFunc" )
}
err = newMarshalErrorBefore (enc , t , err )
return collapseSemanticErrors (err )
}
if err := enc .WriteValue (val ); err != nil {
if mo .Flags .Get (jsonflags .ReportErrorsWithLegacySemantics ) {
return internal .NewMarshalerError (va .Addr ().Interface (), err , "MarshalFunc" )
}
if isSyntacticError (err ) {
err = newMarshalErrorBefore (enc , t , err )
}
return err
}
return nil
},
}
return &Marshalers {fncVals : []typedMarshaler {typFnc }, fromAny : castableToFromAny (t )}
}
func MarshalToFunc [T any ](fn func (*jsontext .Encoder , T ) error ) *Marshalers {
t := reflect .TypeFor [T ]()
assertCastableTo (t , true )
typFnc := typedMarshaler {
typ : t ,
fnc : func (enc *jsontext .Encoder , va addressableValue , mo *jsonopts .Struct ) error {
xe := export .Encoder (enc )
prevDepth , prevLength := xe .Tokens .DepthLength ()
xe .Flags .Set (jsonflags .WithinArshalCall | 1 )
v , _ := reflect .TypeAssert [T ](va .castTo (t ))
err := fn (enc , v )
xe .Flags .Set (jsonflags .WithinArshalCall | 0 )
currDepth , currLength := xe .Tokens .DepthLength ()
if err == nil && (prevDepth != currDepth || prevLength +1 != currLength ) {
err = errNonSingularValue
}
if err != nil {
if errors .Is (err , errors .ErrUnsupported ) {
if prevDepth == currDepth && prevLength == currLength {
return err
}
err = errUnsupportedMutation
}
if mo .Flags .Get (jsonflags .ReportErrorsWithLegacySemantics ) {
return internal .NewMarshalerError (va .Addr ().Interface (), err , "MarshalToFunc" )
}
if !export .IsIOError (err ) {
err = newSemanticErrorWithPosition (enc , t , prevDepth , prevLength , err )
}
return err
}
return nil
},
maySkip : true ,
}
return &Marshalers {fncVals : []typedMarshaler {typFnc }, fromAny : castableToFromAny (t )}
}
func UnmarshalFunc [T any ](fn func ([]byte , T ) error ) *Unmarshalers {
t := reflect .TypeFor [T ]()
assertCastableTo (t , false )
typFnc := typedUnmarshaler {
typ : t ,
fnc : func (dec *jsontext .Decoder , va addressableValue , uo *jsonopts .Struct ) error {
val , err := dec .ReadValue ()
if err != nil {
return err
}
v , _ := reflect .TypeAssert [T ](va .castTo (t ))
err = fn (val , v )
if err != nil {
err = wrapErrUnsupported (err , "unmarshal function of type func([]byte, T) error" )
if uo .Flags .Get (jsonflags .ReportErrorsWithLegacySemantics ) {
return err
}
err = newUnmarshalErrorAfter (dec , t , err )
return collapseSemanticErrors (err )
}
return nil
},
}
return &Unmarshalers {fncVals : []typedUnmarshaler {typFnc }, fromAny : castableToFromAny (t )}
}
func UnmarshalFromFunc [T any ](fn func (*jsontext .Decoder , T ) error ) *Unmarshalers {
t := reflect .TypeFor [T ]()
assertCastableTo (t , false )
typFnc := typedUnmarshaler {
typ : t ,
fnc : func (dec *jsontext .Decoder , va addressableValue , uo *jsonopts .Struct ) error {
xd := export .Decoder (dec )
prevDepth , prevLength := xd .Tokens .DepthLength ()
if prevDepth == 1 && xd .AtEOF () {
return io .EOF
}
xd .Flags .Set (jsonflags .WithinArshalCall | 1 )
v , _ := reflect .TypeAssert [T ](va .castTo (t ))
err := fn (dec , v )
xd .Flags .Set (jsonflags .WithinArshalCall | 0 )
currDepth , currLength := xd .Tokens .DepthLength ()
if err == nil && (prevDepth != currDepth || prevLength +1 != currLength ) {
err = errNonSingularValue
}
if err != nil {
if errors .Is (err , errors .ErrUnsupported ) {
if prevDepth == currDepth && prevLength == currLength {
return err
}
err = errUnsupportedMutation
}
if uo .Flags .Get (jsonflags .ReportErrorsWithLegacySemantics ) {
if err2 := xd .SkipUntil (prevDepth , prevLength +1 ); err2 != nil {
return err2
}
return err
}
if !isSyntacticError (err ) && !export .IsIOError (err ) {
err = newSemanticErrorWithPosition (dec , t , prevDepth , prevLength , err )
}
return err
}
return nil
},
maySkip : true ,
}
return &Unmarshalers {fncVals : []typedUnmarshaler {typFnc }, fromAny : castableToFromAny (t )}
}
func assertCastableTo(to reflect .Type , marshal bool ) {
switch to .Kind () {
case reflect .Interface :
return
case reflect .Pointer :
if to .Name () == "" {
return
}
default :
if marshal {
return
}
}
if marshal {
panic (fmt .Sprintf ("input type %v must be an interface type, an unnamed pointer type, or a non-pointer type" , to ))
} else {
panic (fmt .Sprintf ("input type %v must be an interface type or an unnamed pointer type" , to ))
}
}
func castableTo(from , to reflect .Type ) bool {
switch to .Kind () {
case reflect .Interface :
return reflect .PointerTo (from ).Implements (to )
case reflect .Pointer :
return reflect .PointerTo (from ) == to
default :
return from == to
}
}
func (va addressableValue ) castTo (to reflect .Type ) reflect .Value {
switch to .Kind () {
case reflect .Interface :
return va .Addr ().Convert (to )
case reflect .Pointer :
return va .Addr ()
default :
return va .Value
}
}
func castableToFromAny(to reflect .Type ) bool {
for _ , from := range []reflect .Type {anyType , boolType , stringType , float64Type , mapStringAnyType , sliceAnyType } {
if castableTo (from , to ) {
return true
}
}
return false
}
The pages are generated with Golds v0.8.7 . (GOOS=linux GOARCH=amd64)
Golds is a Go 101 project developed by Tapir Liu .
PR and bug reports are welcome and can be submitted to the issue list .
Please follow @zigo_101 (reachable from the left QR code) to get the latest news of Golds .