Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to correctly serialize embedded struct with String() method to JSON string?

 package main

 import (
    "fmt"
    "encoding/json"
 )

 type Ticket struct {
    From string
    To   string
}

func (t Ticket) String() string {
    return fmt.Sprintf("%s - %s", t.From, t.To)
}

type Passenger struct {
    Name string `json:"Name"`
    Tkt  Ticket `json:"Ticket"`
}

func main() {
    p := Passenger{}
    p.Name = "John"
    p.Tkt.From = "New York"
    p.Tkt.To = "Washington"

    buf, _ := json.Marshal(p)
    fmt.Println(string(buf))
}

This code outputs:

 {"Name":"John","Ticket":{"From":"New York","To":"Washington"}}

But, using json.Marshal() method (it's easy and friendly for complex struct), how to make it output like this:

 {"Name":"John","Ticket":"New York - Washington"}
like image 539
Dr.Nemo Avatar asked Aug 10 '26 01:08

Dr.Nemo


1 Answers

To generate the JSON representation of a Go value, the encoding/json package checks if the value implements the json.Marshaler or the encoding.TextMarshaler interfaces, and if so, they are used / called (in this order). This is documented at json.Marshal():

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 json/encoding package doesn't care about the String() method. So if you want to control the JSON representation / output of your value (the Ticket struct), implement json.Marshaler on it (in which you may call String() to your liking):

func (t Ticket) MarshalJSON() ([]byte, error) {
    return []byte(`"` + t.String() + `"`), nil
}

Then the output will be as you desire:

{"Name":"John","Ticket":"New York - Washington"}

Try it on the Go Playground.

One thing to look out for: if the string produced by Ticket.String() would contain a quotation mark ", the output would become invalid JSON, or more likely json.Marshal() would return an error.

To take care of such escaping, best / easiest is to use the json package itself: tell it to JSON-encode the string result of Ticket.String():

func (t Ticket) MarshalJSON() ([]byte, error) {
    return json.Marshal(t.String())
}

Now if we test it like this:

p.Name = "John"
p.Tkt.From = "New\" York" // CONTAINS QUOTE
p.Tkt.To = "Washington"

buf, err := json.Marshal(p)
fmt.Println(string(buf), err)

The output will still be a valid JSON (try it on the Go Playground):

{"Name":"John","Ticket":"New\" York - Washington"} <nil>
like image 186
icza Avatar answered Aug 11 '26 15:08

icza



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!