I am implementing a String() method on a type I have. This type’s fields provide optional configuration options, and a user may choose to add any combination of those fields without necessarily needing all of them. The end result needs to be formatted as a JSON string (dictated by external service I’m consuming, not my choice).
Edit: a bit of additional context. The reason I need a String() method is because when I actually provide a value of this type to the external service, it will be formatted as a URL encoded string.
My approach was something along the lines of:
type AnotherStruct struct {
FieldA []string
FieldB []string
}
type Spec struct {
FieldA []string
FieldB int
FieldC AnotherStruct
}
func (s Spec) String() string {
args := make([]any, 0)
f := "{"
if s.FieldA != nil {
f += `"fieldA": %v, `
args = append(args, s.FieldA)
}
if s.FieldB != 0 {
f += `"fieldB": %d, `
args = append(args, s.FieldB)
}
if !reflect.DeepEqual(s.FieldC, AnotherStruct{}) {
f += `"fieldC": %v, `
args = append(args, s.FieldC)
}
f += "}"
return fmt.Sprintf(f, args...)
}
But this seems clunky. Is there a clean way to implement such a method in Go?
>Solution :
There’s no reason to create a String method in your case. Everything* you’re aiming to do is already handled by the standard library encoding/json package.
The idiomatic way to do what you’re after is with JSON struct tags:
type Spec struct {
FieldA []string `json:"fieldA,omitempty"`
FieldB int `json:"fieldB,omitempty"`
FieldC *AnotherStruct `json:"fieldC,omitempty"`
}
Then just call json.Marshal() on your value:
s := Spec{
FieldA: []string{"one", "two"},
// FieldB & FieldC omitted
}
output, err := json.Marshal(s)
if err != nil {
panic(err)
}
fmt.Println(string(output))
Prints:
{"fieldA":["one","two"]}
*The only change I had to make to conform to the standard library expectations was to make FieldC a pointer to AnotherStruct:
FieldC *AnotherStruct `json:"fieldC,omitempty"`
instead of
FieldC AnotherStruct `json:"fieldC,omitempty"`