Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ generate: build
./tests/unknown_fields.go \
./tests/type_declaration.go \
./tests/members_escaped.go \
./tests/interface_type.go \
./tests/intern.go \
./tests/nocopy.go \
./tests/escaping.go \
Expand Down
35 changes: 25 additions & 10 deletions gen/encoder.go
Original file line number Diff line number Diff line change
Expand Up @@ -264,23 +264,39 @@ func (g *Generator) genTypeEncoderNoCheck(t reflect.Type, in string, tags fieldT
case reflect.Interface:
if t.NumMethod() != 0 {
if g.interfaceIsEasyjsonMarshaller(t) {
fmt.Fprintln(g.out, ws+in+".MarshalEasyJSON(out)")
fmt.Fprintln(g.out, ws+" if easyjson.IsNilInterface("+in+") {")
fmt.Fprintln(g.out, ws+" out.RawString(`null`)")
fmt.Fprintln(g.out, ws+" } else {")
fmt.Fprintln(g.out, ws+" "+in+".MarshalEasyJSON(out)")
fmt.Fprintln(g.out, ws+" }")
} else if g.interfaceIsJSONMarshaller(t) {
fmt.Fprintln(g.out, ws+"if m, ok := "+in+".(easyjson.Marshaler); ok {")
fmt.Fprintln(g.out, ws+" m.MarshalEasyJSON(out)")
fmt.Fprintln(g.out, ws+" if easyjson.IsNilInterface("+in+") {")
fmt.Fprintln(g.out, ws+" out.RawString(`null`)")
fmt.Fprintln(g.out, ws+" } else {")
fmt.Fprintln(g.out, ws+" m.MarshalEasyJSON(out)")
fmt.Fprintln(g.out, ws+" }")
fmt.Fprintln(g.out, ws+"} else {")
fmt.Fprintln(g.out, ws+in+".MarshalJSON()")
fmt.Fprintln(g.out, ws+" if easyjson.IsNilInterface("+in+") {")
fmt.Fprintln(g.out, ws+" out.RawString(`null`)")
fmt.Fprintln(g.out, ws+" } else {")
fmt.Fprintln(g.out, ws+" "+in+".MarshalJSON()")
fmt.Fprintln(g.out, ws+" }")
fmt.Fprintln(g.out, ws+"}")
} else {
return fmt.Errorf("interface type %v not supported: only interface{} and interfaces that implement json or easyjson Marshaling are allowed", t)
}
} else {
fmt.Fprintln(g.out, ws+"if m, ok := "+in+".(easyjson.Marshaler); ok {")
fmt.Fprintln(g.out, ws+" m.MarshalEasyJSON(out)")
fmt.Fprintln(g.out, ws+"} else if m, ok := "+in+".(json.Marshaler); ok {")
fmt.Fprintln(g.out, ws+" out.Raw(m.MarshalJSON())")
fmt.Fprintln(g.out, ws+"} else {")
fmt.Fprintln(g.out, ws+" out.Raw(json.Marshal("+in+"))")
fmt.Fprintln(g.out, ws+"if easyjson.IsNilInterface("+in+") {")
fmt.Fprintln(g.out, ws+" out.RawString(`null`)")
fmt.Fprintln(g.out, ws+" } else {")
fmt.Fprintln(g.out, ws+" if m, ok := "+in+".(easyjson.Marshaler); ok {")
fmt.Fprintln(g.out, ws+" m.MarshalEasyJSON(out)")
fmt.Fprintln(g.out, ws+" } else if m, ok := "+in+".(json.Marshaler); ok {")
fmt.Fprintln(g.out, ws+" out.Raw(m.MarshalJSON())")
fmt.Fprintln(g.out, ws+" } else {")
fmt.Fprintln(g.out, ws+" out.Raw(json.Marshal("+in+"))")
fmt.Fprintln(g.out, ws+" }")
fmt.Fprintln(g.out, ws+"}")
}
default:
Expand Down Expand Up @@ -419,7 +435,6 @@ func (g *Generator) genStructEncoder(t reflect.Type) error {
firstCondition := true
for i, f := range fs {
firstCondition, err = g.genStructFieldEncoder(t, f, i == 0, firstCondition)

if err != nil {
return err
}
Expand Down
11 changes: 5 additions & 6 deletions helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ package easyjson

import (
"io"
"io/ioutil"
"net/http"
"strconv"
"unsafe"
Expand Down Expand Up @@ -43,14 +42,14 @@ type UnknownsMarshaler interface {
MarshalUnknowns(w *jwriter.Writer, first bool)
}

func isNilInterface(i interface{}) bool {
func IsNilInterface(i interface{}) bool {
return (*[2]uintptr)(unsafe.Pointer(&i))[1] == 0
}

// Marshal returns data as a single byte slice. Method is suboptimal as the data is likely to be copied
// from a chain of smaller chunks.
func Marshal(v Marshaler) ([]byte, error) {
if isNilInterface(v) {
if IsNilInterface(v) {
return nullBytes, nil
}

Expand All @@ -61,7 +60,7 @@ func Marshal(v Marshaler) ([]byte, error) {

// MarshalToWriter marshals the data to an io.Writer.
func MarshalToWriter(v Marshaler, w io.Writer) (written int, err error) {
if isNilInterface(v) {
if IsNilInterface(v) {
return w.Write(nullBytes)
}

Expand All @@ -75,7 +74,7 @@ func MarshalToWriter(v Marshaler, w io.Writer) (written int, err error) {
// false if an error occurred before any http.ResponseWriter methods were actually
// invoked (in this case a 500 reply is possible).
func MarshalToHTTPResponseWriter(v Marshaler, w http.ResponseWriter) (started bool, written int, err error) {
if isNilInterface(v) {
if IsNilInterface(v) {
w.Header().Set("Content-Type", "application/json")
w.Header().Set("Content-Length", strconv.Itoa(len(nullBytes)))
written, err = w.Write(nullBytes)
Expand Down Expand Up @@ -104,7 +103,7 @@ func Unmarshal(data []byte, v Unmarshaler) error {

// UnmarshalFromReader reads all the data in the reader and decodes as JSON into the object.
func UnmarshalFromReader(r io.Reader, v Unmarshaler) error {
data, err := ioutil.ReadAll(r)
data, err := io.ReadAll(r)
if err != nil {
return err
}
Expand Down
2 changes: 1 addition & 1 deletion helpers_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import "testing"
func BenchmarkNilCheck(b *testing.B) {
var a *int
for i := 0; i < b.N; i++ {
if !isNilInterface(a) {
if !IsNilInterface(a) {
b.Fatal("expected it to be nil")
}
}
Expand Down
72 changes: 72 additions & 0 deletions tests/interface_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
package tests

import (
"bytes"
"testing"
)

func TestInterfaceMarshal(t *testing.T) {
inner := InterfaceType{
Field1: 1,
}
wrapper := WrapperType{
Inner: inner,
}

data, err := wrapper.MarshalJSON()
if err != nil {
t.Fatalf("MarshalJSON failed: %v", err)
}

expected := []byte(`{"Inner":{"Field1":1}}`)
if !bytes.Equal(data, expected) {
t.Fatalf("MarshalJSON failed: got=%s, expected=%s", string(data), string(expected))
}
}

func TestInterfaceUnmarshal(t *testing.T) {
data := []byte(`{"Inner":{"Field1":1}}`)

var inner InterfaceType
wrapper := WrapperType{Inner: &inner}
err := wrapper.UnmarshalJSON(data)
if err != nil {
t.Fatalf("UnmarshalJSON failed: %v", err)
}

if inner.Field1 != 1 {
t.Fatalf("UnmarshalJSON failed: got=%d, expected=%d", inner.Field1, 1)
}
}

func TestInterfaceMarshalNil(t *testing.T) {
var inner *InterfaceType
wrapper := WrapperType{
Inner: inner,
}

data, err := wrapper.MarshalJSON()
if err != nil {
t.Fatalf("MarshalJSON failed: %v", err)
}

expected := []byte(`{"Inner":null}`)
if !bytes.Equal(data, expected) {
t.Fatalf("MarshalJSON failed: got=%s, expected=%s", string(data), string(expected))
}
}

func TestInterfaceUnmarshalNil(t *testing.T) {
data := []byte(`{"Inner":null}`)

var inner InterfaceType
wrapper := WrapperType{Inner: &inner}
err := wrapper.UnmarshalJSON(data)
if err != nil {
t.Fatalf("UnmarshalJSON failed: %v", err)
}

if inner.Field1 != 0 {
t.Fatalf("UnmarshalJSON failed: got=%d, expected=0", inner.Field1)
}
}
11 changes: 11 additions & 0 deletions tests/interface_type.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package tests

// easyjson:json
type WrapperType struct {
Inner any `json:"Inner"`
}

// easyjson:json
type InterfaceType struct {
Field1 int `json:"Field1"`
}