mirror of https://github.com/goccy/go-json.git
Support MarshalJSON/MarshalText
This commit is contained in:
parent
786e87181f
commit
33dcd50a5d
20
encode.go
20
encode.go
|
@ -45,9 +45,15 @@ func (m *opcodeMap) set(k uintptr, op *opcodeSet) {
|
|||
m.Store(k, op)
|
||||
}
|
||||
|
||||
type marshalText interface {
|
||||
MarshalText() ([]byte, error)
|
||||
}
|
||||
|
||||
var (
|
||||
encPool sync.Pool
|
||||
cachedOpcode opcodeMap
|
||||
encPool sync.Pool
|
||||
cachedOpcode opcodeMap
|
||||
marshalJSONType reflect.Type
|
||||
marshalTextType reflect.Type
|
||||
)
|
||||
|
||||
func init() {
|
||||
|
@ -60,6 +66,8 @@ func init() {
|
|||
},
|
||||
}
|
||||
cachedOpcode = opcodeMap{}
|
||||
marshalJSONType = reflect.TypeOf((*Marshaler)(nil)).Elem()
|
||||
marshalTextType = reflect.TypeOf((*marshalText)(nil)).Elem()
|
||||
}
|
||||
|
||||
// NewEncoder returns a new encoder that writes to w.
|
||||
|
@ -137,9 +145,7 @@ func (e *Encoder) encodeForMarshal(v interface{}) ([]byte, error) {
|
|||
func (e *Encoder) encode(v interface{}) error {
|
||||
header := (*interfaceHeader)(unsafe.Pointer(&v))
|
||||
typ := header.typ
|
||||
if typ.Kind() == reflect.Ptr {
|
||||
typ = typ.Elem()
|
||||
}
|
||||
|
||||
typeptr := uintptr(unsafe.Pointer(typ))
|
||||
if codeSet := cachedOpcode.get(typeptr); codeSet != nil {
|
||||
var code *opcode
|
||||
|
@ -159,11 +165,11 @@ func (e *Encoder) encode(v interface{}) error {
|
|||
// to noescape trick for header.typ ( reflect.*rtype )
|
||||
copiedType := (*rtype)(unsafe.Pointer(typeptr))
|
||||
|
||||
codeIndent, err := e.compile(copiedType, true)
|
||||
codeIndent, err := e.compileHead(copiedType, true)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
code, err := e.compile(copiedType, false)
|
||||
code, err := e.compileHead(copiedType, false)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
|
@ -9,7 +9,24 @@ import (
|
|||
"golang.org/x/xerrors"
|
||||
)
|
||||
|
||||
func (e *Encoder) compileHead(typ *rtype, withIndent bool) (*opcode, error) {
|
||||
if typ.Implements(marshalJSONType) {
|
||||
return newOpCode(opMarshalJSON, typ, e.indent, newEndOp(e.indent)), nil
|
||||
} else if typ.Implements(marshalTextType) {
|
||||
return newOpCode(opMarshalText, typ, e.indent, newEndOp(e.indent)), nil
|
||||
}
|
||||
if typ.Kind() == reflect.Ptr {
|
||||
typ = typ.Elem()
|
||||
}
|
||||
return e.compile(typ, withIndent)
|
||||
}
|
||||
|
||||
func (e *Encoder) compile(typ *rtype, withIndent bool) (*opcode, error) {
|
||||
if typ.Implements(marshalJSONType) {
|
||||
return newOpCode(opMarshalJSON, typ, e.indent, newEndOp(e.indent)), nil
|
||||
} else if typ.Implements(marshalTextType) {
|
||||
return newOpCode(opMarshalText, typ, e.indent, newEndOp(e.indent)), nil
|
||||
}
|
||||
switch typ.Kind() {
|
||||
case reflect.Ptr:
|
||||
return e.compilePtr(typ, withIndent)
|
||||
|
|
|
@ -27,6 +27,8 @@ const (
|
|||
opBool
|
||||
opInterface
|
||||
opPtr
|
||||
opMarshalJSON
|
||||
opMarshalText
|
||||
|
||||
opSliceHead
|
||||
opSliceElem
|
||||
|
@ -292,6 +294,10 @@ func (t opType) String() string {
|
|||
return "INTERFACE"
|
||||
case opPtr:
|
||||
return "PTR"
|
||||
case opMarshalJSON:
|
||||
return "MARSHAL_JSON"
|
||||
case opMarshalText:
|
||||
return "MARSHAL_TEXT"
|
||||
|
||||
case opSliceHead:
|
||||
return "SLICE_HEAD"
|
||||
|
|
|
@ -2,6 +2,7 @@ package json_test
|
|||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/goccy/go-json"
|
||||
)
|
||||
|
@ -206,6 +207,25 @@ func Test_Marshal(t *testing.T) {
|
|||
})
|
||||
}
|
||||
|
||||
type marshalJSON struct{}
|
||||
|
||||
func (*marshalJSON) MarshalJSON() ([]byte, error) {
|
||||
return []byte(`1`), nil
|
||||
}
|
||||
|
||||
func Test_MarshalJSON(t *testing.T) {
|
||||
t.Run("*struct", func(t *testing.T) {
|
||||
bytes, err := json.Marshal(&marshalJSON{})
|
||||
assertErr(t, err)
|
||||
assertEq(t, "MarshalJSON", "1", string(bytes))
|
||||
})
|
||||
t.Run("time", func(t *testing.T) {
|
||||
bytes, err := json.Marshal(time.Time{})
|
||||
assertErr(t, err)
|
||||
assertEq(t, "MarshalJSON", `"0001-01-01T00:00:00Z"`, string(bytes))
|
||||
})
|
||||
}
|
||||
|
||||
func Test_MarshalIndent(t *testing.T) {
|
||||
prefix := "-"
|
||||
indent := "\t"
|
||||
|
|
26
encode_vm.go
26
encode_vm.go
|
@ -74,6 +74,32 @@ func (e *Encoder) run(code *opcode) error {
|
|||
c.ptr = uintptr(header.ptr)
|
||||
c.beforeLastCode().next = code.next
|
||||
code = c
|
||||
case opMarshalJSON:
|
||||
ptr := code.ptr
|
||||
v := *(*interface{})(unsafe.Pointer(&interfaceHeader{
|
||||
typ: code.typ,
|
||||
ptr: unsafe.Pointer(ptr),
|
||||
}))
|
||||
bytes, err := v.(Marshaler).MarshalJSON()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
e.encodeBytes(bytes)
|
||||
code = code.next
|
||||
code.ptr = ptr
|
||||
case opMarshalText:
|
||||
ptr := code.ptr
|
||||
v := *(*interface{})(unsafe.Pointer(&interfaceHeader{
|
||||
typ: code.typ,
|
||||
ptr: unsafe.Pointer(ptr),
|
||||
}))
|
||||
bytes, err := v.(marshalText).MarshalText()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
e.encodeBytes(bytes)
|
||||
code = code.next
|
||||
code.ptr = ptr
|
||||
case opSliceHead:
|
||||
p := code.ptr
|
||||
headerCode := code.toSliceHeaderCode()
|
||||
|
|
138
json.go
138
json.go
|
@ -2,6 +2,141 @@ package json
|
|||
|
||||
import "bytes"
|
||||
|
||||
// Marshaler is the interface implemented by types that
|
||||
// can marshal themselves into valid JSON.
|
||||
type Marshaler interface {
|
||||
MarshalJSON() ([]byte, error)
|
||||
}
|
||||
|
||||
// Marshal returns the JSON encoding of v.
|
||||
//
|
||||
// Marshal traverses the value v recursively.
|
||||
// If an encountered value implements the Marshaler interface
|
||||
// and is not a nil pointer, Marshal calls its MarshalJSON method
|
||||
// to produce JSON. If no MarshalJSON method is present but the
|
||||
// value implements encoding.TextMarshaler instead, Marshal calls
|
||||
// its MarshalText method and encodes the result as a JSON string.
|
||||
// The nil pointer exception is not strictly necessary
|
||||
// but mimics a similar, necessary exception in the behavior of
|
||||
// UnmarshalJSON.
|
||||
//
|
||||
// Otherwise, Marshal uses the following type-dependent default encodings:
|
||||
//
|
||||
// Boolean values encode as JSON booleans.
|
||||
//
|
||||
// Floating point, integer, and Number values encode as JSON numbers.
|
||||
//
|
||||
// String values encode as JSON strings coerced to valid UTF-8,
|
||||
// replacing invalid bytes with the Unicode replacement rune.
|
||||
// The angle brackets "<" and ">" are escaped to "\u003c" and "\u003e"
|
||||
// to keep some browsers from misinterpreting JSON output as HTML.
|
||||
// Ampersand "&" is also escaped to "\u0026" for the same reason.
|
||||
// This escaping can be disabled using an Encoder that had SetEscapeHTML(false)
|
||||
// called on it.
|
||||
//
|
||||
// Array and slice values encode as JSON arrays, except that
|
||||
// []byte encodes as a base64-encoded string, and a nil slice
|
||||
// encodes as the null JSON value.
|
||||
//
|
||||
// Struct values encode as JSON objects.
|
||||
// Each exported struct field becomes a member of the object, using the
|
||||
// field name as the object key, unless the field is omitted for one of the
|
||||
// reasons given below.
|
||||
//
|
||||
// The encoding of each struct field can be customized by the format string
|
||||
// stored under the "json" key in the struct field's tag.
|
||||
// The format string gives the name of the field, possibly followed by a
|
||||
// comma-separated list of options. The name may be empty in order to
|
||||
// specify options without overriding the default field name.
|
||||
//
|
||||
// The "omitempty" option specifies that the field should be omitted
|
||||
// from the encoding if the field has an empty value, defined as
|
||||
// false, 0, a nil pointer, a nil interface value, and any empty array,
|
||||
// slice, map, or string.
|
||||
//
|
||||
// As a special case, if the field tag is "-", the field is always omitted.
|
||||
// Note that a field with name "-" can still be generated using the tag "-,".
|
||||
//
|
||||
// Examples of struct field tags and their meanings:
|
||||
//
|
||||
// // Field appears in JSON as key "myName".
|
||||
// Field int `json:"myName"`
|
||||
//
|
||||
// // Field appears in JSON as key "myName" and
|
||||
// // the field is omitted from the object if its value is empty,
|
||||
// // as defined above.
|
||||
// Field int `json:"myName,omitempty"`
|
||||
//
|
||||
// // Field appears in JSON as key "Field" (the default), but
|
||||
// // the field is skipped if empty.
|
||||
// // Note the leading comma.
|
||||
// Field int `json:",omitempty"`
|
||||
//
|
||||
// // Field is ignored by this package.
|
||||
// Field int `json:"-"`
|
||||
//
|
||||
// // Field appears in JSON as key "-".
|
||||
// Field int `json:"-,"`
|
||||
//
|
||||
// The "string" option signals that a field is stored as JSON inside a
|
||||
// JSON-encoded string. It applies only to fields of string, floating point,
|
||||
// integer, or boolean types. This extra level of encoding is sometimes used
|
||||
// when communicating with JavaScript programs:
|
||||
//
|
||||
// Int64String int64 `json:",string"`
|
||||
//
|
||||
// The key name will be used if it's a non-empty string consisting of
|
||||
// only Unicode letters, digits, and ASCII punctuation except quotation
|
||||
// marks, backslash, and comma.
|
||||
//
|
||||
// Anonymous struct fields are usually marshaled as if their inner exported fields
|
||||
// were fields in the outer struct, subject to the usual Go visibility rules amended
|
||||
// as described in the next paragraph.
|
||||
// An anonymous struct field with a name given in its JSON tag is treated as
|
||||
// having that name, rather than being anonymous.
|
||||
// An anonymous struct field of interface type is treated the same as having
|
||||
// that type as its name, rather than being anonymous.
|
||||
//
|
||||
// The Go visibility rules for struct fields are amended for JSON when
|
||||
// deciding which field to marshal or unmarshal. If there are
|
||||
// multiple fields at the same level, and that level is the least
|
||||
// nested (and would therefore be the nesting level selected by the
|
||||
// usual Go rules), the following extra rules apply:
|
||||
//
|
||||
// 1) Of those fields, if any are JSON-tagged, only tagged fields are considered,
|
||||
// even if there are multiple untagged fields that would otherwise conflict.
|
||||
//
|
||||
// 2) If there is exactly one field (tagged or not according to the first rule), that is selected.
|
||||
//
|
||||
// 3) Otherwise there are multiple fields, and all are ignored; no error occurs.
|
||||
//
|
||||
// Handling of anonymous struct fields is new in Go 1.1.
|
||||
// Prior to Go 1.1, anonymous struct fields were ignored. To force ignoring of
|
||||
// an anonymous struct field in both current and earlier versions, give the field
|
||||
// a JSON tag of "-".
|
||||
//
|
||||
// Map values encode as JSON objects. The map's key type must either be a
|
||||
// string, an integer type, or implement encoding.TextMarshaler. The map keys
|
||||
// are sorted and used as JSON object keys by applying the following rules,
|
||||
// subject to the UTF-8 coercion described for string values above:
|
||||
// - string keys are used directly
|
||||
// - encoding.TextMarshalers are marshaled
|
||||
// - integer keys are converted to strings
|
||||
//
|
||||
// Pointer values encode as the value pointed to.
|
||||
// A nil pointer encodes as the null JSON value.
|
||||
//
|
||||
// Interface values encode as the value contained in the interface.
|
||||
// A nil interface value encodes as the null JSON value.
|
||||
//
|
||||
// Channel, complex, and function values cannot be encoded in JSON.
|
||||
// Attempting to encode such a value causes Marshal to return
|
||||
// an UnsupportedTypeError.
|
||||
//
|
||||
// JSON cannot represent cyclic data structures and Marshal does not
|
||||
// handle them. Passing cyclic structures to Marshal will result in
|
||||
// an infinite recursion.
|
||||
//
|
||||
func Marshal(v interface{}) ([]byte, error) {
|
||||
var b *bytes.Buffer
|
||||
enc := NewEncoder(b)
|
||||
|
@ -14,6 +149,9 @@ func Marshal(v interface{}) ([]byte, error) {
|
|||
return bytes, nil
|
||||
}
|
||||
|
||||
// MarshalIndent is like Marshal but applies Indent to format the output.
|
||||
// Each JSON element in the output will begin on a new line beginning with prefix
|
||||
// followed by one or more copies of indent according to the indentation nesting.
|
||||
func MarshalIndent(v interface{}, prefix, indent string) ([]byte, error) {
|
||||
var b *bytes.Buffer
|
||||
enc := NewEncoder(b)
|
||||
|
|
Loading…
Reference in New Issue