mirror of
https://github.com/zeromicro/go-zero.git
synced 2026-05-07 15:10:01 +08:00
chore: refactor jsonx.Marshal (#5045)
Signed-off-by: Kevin Wan <wanjunfeng@gmail.com>
This commit is contained in:
@@ -8,9 +8,25 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Marshal marshals v into json bytes.
|
// Marshal marshals v into json bytes, without escaping HTML and removes the trailing newline.
|
||||||
func Marshal(v any) ([]byte, error) {
|
func Marshal(v any) ([]byte, error) {
|
||||||
return json.Marshal(v)
|
// why not use json.Marshal? https://github.com/golang/go/issues/28453
|
||||||
|
// it changes the behavior of json.Marshal, like & -> \u0026, < -> \u003c, > -> \u003e
|
||||||
|
// which is not what we want in API responses
|
||||||
|
var buf bytes.Buffer
|
||||||
|
enc := json.NewEncoder(&buf)
|
||||||
|
enc.SetEscapeHTML(false)
|
||||||
|
if err := enc.Encode(v); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
bs := buf.Bytes()
|
||||||
|
// Remove trailing newline added by json.Encoder.Encode
|
||||||
|
if len(bs) > 0 && bs[len(bs)-1] == '\n' {
|
||||||
|
bs = bs[:len(bs)-1]
|
||||||
|
}
|
||||||
|
|
||||||
|
return bs, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// MarshalToString marshals v into a string.
|
// MarshalToString marshals v into a string.
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package jsonx
|
package jsonx
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
@@ -101,3 +102,105 @@ func TestUnmarshalFromReaderError(t *testing.T) {
|
|||||||
err := UnmarshalFromReader(strings.NewReader(s), &v)
|
err := UnmarshalFromReader(strings.NewReader(s), &v)
|
||||||
assert.NotNil(t, err)
|
assert.NotNil(t, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func Test_doMarshalJson(t *testing.T) {
|
||||||
|
type args struct {
|
||||||
|
v any
|
||||||
|
}
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
args args
|
||||||
|
want []byte
|
||||||
|
wantErr assert.ErrorAssertionFunc
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "nil",
|
||||||
|
args: args{nil},
|
||||||
|
want: []byte("null"),
|
||||||
|
wantErr: assert.NoError,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "string",
|
||||||
|
args: args{"hello"},
|
||||||
|
want: []byte(`"hello"`),
|
||||||
|
wantErr: assert.NoError,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "int",
|
||||||
|
args: args{42},
|
||||||
|
want: []byte("42"),
|
||||||
|
wantErr: assert.NoError,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "bool",
|
||||||
|
args: args{true},
|
||||||
|
want: []byte("true"),
|
||||||
|
wantErr: assert.NoError,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "struct",
|
||||||
|
args: args{
|
||||||
|
struct {
|
||||||
|
Name string `json:"name"`
|
||||||
|
}{Name: "test"},
|
||||||
|
},
|
||||||
|
want: []byte(`{"name":"test"}`),
|
||||||
|
wantErr: assert.NoError,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "slice",
|
||||||
|
args: args{[]int{1, 2, 3}},
|
||||||
|
want: []byte("[1,2,3]"),
|
||||||
|
wantErr: assert.NoError,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "map",
|
||||||
|
args: args{map[string]int{"a": 1, "b": 2}},
|
||||||
|
want: []byte(`{"a":1,"b":2}`),
|
||||||
|
wantErr: assert.NoError,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "unmarshalable type",
|
||||||
|
args: args{complex(1, 2)},
|
||||||
|
want: nil,
|
||||||
|
wantErr: assert.Error,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "channel type",
|
||||||
|
args: args{make(chan int)},
|
||||||
|
want: nil,
|
||||||
|
wantErr: assert.Error,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "url with query params",
|
||||||
|
args: args{"https://example.com/api?name=test&age=25"},
|
||||||
|
want: []byte(`"https://example.com/api?name=test&age=25"`),
|
||||||
|
wantErr: assert.NoError,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "url with encoded query params",
|
||||||
|
args: args{"https://example.com/api?data=hello%20world&special=%26%3D"},
|
||||||
|
want: []byte(`"https://example.com/api?data=hello%20world&special=%26%3D"`),
|
||||||
|
wantErr: assert.NoError,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "url with multiple query params",
|
||||||
|
args: args{"http://localhost:8080/users?page=1&limit=10&sort=name&order=asc"},
|
||||||
|
want: []byte(`"http://localhost:8080/users?page=1&limit=10&sort=name&order=asc"`),
|
||||||
|
wantErr: assert.NoError,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
tt := tt
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
got, err := Marshal(tt.args.v)
|
||||||
|
if !tt.wantErr(t, err, fmt.Sprintf("Marshal(%v)", tt.args.v)) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
assert.Equalf(t, string(tt.want), string(got), "Marshal(%v)", tt.args.v)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,15 +1,14 @@
|
|||||||
package httpx
|
package httpx
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
|
"github.com/zeromicro/go-zero/core/jsonx"
|
||||||
"github.com/zeromicro/go-zero/core/logc"
|
"github.com/zeromicro/go-zero/core/logc"
|
||||||
"github.com/zeromicro/go-zero/core/logx"
|
"github.com/zeromicro/go-zero/core/logx"
|
||||||
"github.com/zeromicro/go-zero/rest/internal/errcode"
|
"github.com/zeromicro/go-zero/rest/internal/errcode"
|
||||||
@@ -173,28 +172,8 @@ func doHandleError(w http.ResponseWriter, err error, handler func(error) (int, a
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func doMarshalJson(v any) ([]byte, error) {
|
|
||||||
// why not use json.Marshal? https://github.com/golang/go/issues/28453
|
|
||||||
// it change the behavior of json.Marshal, like & -> \u0026, < -> \u003c, > -> \u003e
|
|
||||||
// which is not what we want in logic response
|
|
||||||
var buf bytes.Buffer
|
|
||||||
enc := json.NewEncoder(&buf)
|
|
||||||
enc.SetEscapeHTML(false)
|
|
||||||
if err := enc.Encode(v); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
bs := buf.Bytes()
|
|
||||||
// Remove trailing newline added by json.Encoder.Encode
|
|
||||||
if len(bs) > 0 && bs[len(bs)-1] == '\n' {
|
|
||||||
bs = bs[:len(bs)-1]
|
|
||||||
}
|
|
||||||
|
|
||||||
return bs, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func doWriteJson(w http.ResponseWriter, code int, v any) error {
|
func doWriteJson(w http.ResponseWriter, code int, v any) error {
|
||||||
bs, err := doMarshalJson(v)
|
bs, err := jsonx.Marshal(v)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||||
return fmt.Errorf("marshal json failed, error: %w", err)
|
return fmt.Errorf("marshal json failed, error: %w", err)
|
||||||
|
|||||||
@@ -443,105 +443,3 @@ func TestWriteJsonCtxMarshalFailed(t *testing.T) {
|
|||||||
})
|
})
|
||||||
assert.Equal(t, http.StatusInternalServerError, w.code)
|
assert.Equal(t, http.StatusInternalServerError, w.code)
|
||||||
}
|
}
|
||||||
|
|
||||||
func Test_doMarshalJson(t *testing.T) {
|
|
||||||
type args struct {
|
|
||||||
v any
|
|
||||||
}
|
|
||||||
|
|
||||||
tests := []struct {
|
|
||||||
name string
|
|
||||||
args args
|
|
||||||
want []byte
|
|
||||||
wantErr assert.ErrorAssertionFunc
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
name: "nil",
|
|
||||||
args: args{nil},
|
|
||||||
want: []byte("null"),
|
|
||||||
wantErr: assert.NoError,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "string",
|
|
||||||
args: args{"hello"},
|
|
||||||
want: []byte(`"hello"`),
|
|
||||||
wantErr: assert.NoError,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "int",
|
|
||||||
args: args{42},
|
|
||||||
want: []byte("42"),
|
|
||||||
wantErr: assert.NoError,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "bool",
|
|
||||||
args: args{true},
|
|
||||||
want: []byte("true"),
|
|
||||||
wantErr: assert.NoError,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "struct",
|
|
||||||
args: args{
|
|
||||||
struct {
|
|
||||||
Name string `json:"name"`
|
|
||||||
}{Name: "test"},
|
|
||||||
},
|
|
||||||
want: []byte(`{"name":"test"}`),
|
|
||||||
wantErr: assert.NoError,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "slice",
|
|
||||||
args: args{[]int{1, 2, 3}},
|
|
||||||
want: []byte("[1,2,3]"),
|
|
||||||
wantErr: assert.NoError,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "map",
|
|
||||||
args: args{map[string]int{"a": 1, "b": 2}},
|
|
||||||
want: []byte(`{"a":1,"b":2}`),
|
|
||||||
wantErr: assert.NoError,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "unmarshalable type",
|
|
||||||
args: args{complex(1, 2)},
|
|
||||||
want: nil,
|
|
||||||
wantErr: assert.Error,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "channel type",
|
|
||||||
args: args{make(chan int)},
|
|
||||||
want: nil,
|
|
||||||
wantErr: assert.Error,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "url with query params",
|
|
||||||
args: args{"https://example.com/api?name=test&age=25"},
|
|
||||||
want: []byte(`"https://example.com/api?name=test&age=25"`),
|
|
||||||
wantErr: assert.NoError,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "url with encoded query params",
|
|
||||||
args: args{"https://example.com/api?data=hello%20world&special=%26%3D"},
|
|
||||||
want: []byte(`"https://example.com/api?data=hello%20world&special=%26%3D"`),
|
|
||||||
wantErr: assert.NoError,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "url with multiple query params",
|
|
||||||
args: args{"http://localhost:8080/users?page=1&limit=10&sort=name&order=asc"},
|
|
||||||
want: []byte(`"http://localhost:8080/users?page=1&limit=10&sort=name&order=asc"`),
|
|
||||||
wantErr: assert.NoError,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, tt := range tests {
|
|
||||||
tt := tt
|
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
|
||||||
got, err := doMarshalJson(tt.args.v)
|
|
||||||
if !tt.wantErr(t, err, fmt.Sprintf("doMarshalJson(%v)", tt.args.v)) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
assert.Equalf(t, string(tt.want), string(got), "doMarshalJson(%v)", tt.args.v)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
Reference in New Issue
Block a user