diff --git a/go.mod b/go.mod index 1053de0..92c04ec 100644 --- a/go.mod +++ b/go.mod @@ -1,4 +1,4 @@ -module github.com/google/go-querystring +module github.com/schmorrison/go-querystring go 1.10 diff --git a/query/encode.go b/query/encode.go index 91198f8..8086918 100644 --- a/query/encode.go +++ b/query/encode.go @@ -22,6 +22,7 @@ package query import ( "bytes" + "encoding/json" "fmt" "net/url" "reflect" @@ -119,6 +120,14 @@ type Encoder interface { // // "user[name]=acme&user[addr][postcode]=1234&user[addr][city]=SFO" // +// Structs can be encoded as JSON by including the "json" option in that fields +// tag. The entire struct is then passed to encoding/json.Marshal where those +// tag signatures apply. +// +// // Encoding a struct as JSON +// Field A `url: "myName,json"` +// // Result: myName={"someField": "cat", "numField": 1} +// // All other values are encoded using their default string representation. // // Multiple fields that encode to the same URL parameter name will be included @@ -252,8 +261,18 @@ func reflectValue(values url.Values, val reflect.Value, scope string) error { } if sv.Kind() == reflect.Struct { - if err := reflectValue(values, sv, name); err != nil { - return err + if opts.Contains("json") { + var b []byte + b, err := json.Marshal(sv.Interface()) + if err != nil { + return err + } + + values.Add(name, valueString(reflect.ValueOf(string(b)), opts, sf)) + } else { + if err := reflectValue(values, sv, name); err != nil { + return err + } } continue } diff --git a/query/encode_test.go b/query/encode_test.go index 8487858..2463927 100644 --- a/query/encode_test.go +++ b/query/encode_test.go @@ -404,6 +404,75 @@ func TestValues_EmbeddedStructs(t *testing.T) { } } +func TestValues_StructsAsJSON(t *testing.T) { + type Nested struct { + L bool `json:"l"` + M bool `json:"m"` + } + type Inner struct { + A string `json:"a"` + B int `json:"b"` + T Nested `json:"t"` + } + + type Outer struct { + S Inner `url:"str,json"` + P *Inner `url:"ptr,json,omitempty"` + } + + tests := []struct { + input interface{} + want url.Values + }{ + { + Outer{ + S: Inner{ + A: "abc", + B: 5, + T: Nested{ + L: true, + M: false, + }, + }, + P: nil, + }, + url.Values{ + "str": {`{"a":"abc","b":5,"t":{"l":true,"m":false}}`}, + }, + }, + { + Outer{ + P: &Inner{ + A: "def", + B: 22, + T: Nested{ + L: true, + M: true, + }, + }, + }, + url.Values{ + "str": {`{"a":"","b":0,"t":{"l":false,"m":false}}`}, + "ptr": {`{"a":"def","b":22,"t":{"l":true,"m":true}}`}, + }, + }, + { + Outer{}, + url.Values{ + "str": {`{"a":"","b":0,"t":{"l":false,"m":false}}`}, + }, + }, + { + nil, + url.Values{}, + }, + } + + for _, tt := range tests { + testValue(t, tt.input, tt.want) + } +} + func TestValues_InvalidInput(t *testing.T) { _, err := Values("") if err == nil {