9

This code:

type A struct {
    t time.Time
}

func main() {
    a := A{time.Now()}
    fmt.Println(a)
    fmt.Println(a.t)
}

prints:

{{63393490800 0 0x206da0}}
2009-11-10 23:00:00 +0000 UTC

A doesn't implement String(), so it's not a fmt.Stringer and prints its native representation. But is very tedious to implement String() for every single struct I want to print. Worse, I have to update the String()s if I add or remove some fields. Is there an easier way to print a struct, with its fields' String()s?

1 Answer 1

4

This is how the fmt package is implemented, so you can't change that.

But you can write a helper function which uses reflection (reflect package) to iterate over the fields of a struct, and can call the String() method on the fields if they have such a method.

Example implementation:

func PrintStruct(s interface{}, names bool) string {
    v := reflect.ValueOf(s)
    t := v.Type()
    // To avoid panic if s is not a struct:
    if t.Kind() != reflect.Struct {
        return fmt.Sprint(s)
    }

    b := &bytes.Buffer{}
    b.WriteString("{")
    for i := 0; i < v.NumField(); i++ {
        if i > 0 {
            b.WriteString(" ")
        }
        v2 := v.Field(i)
        if names {
            b.WriteString(t.Field(i).Name)
            b.WriteString(":")
        }
        if v2.CanInterface() {
            if st, ok := v2.Interface().(fmt.Stringer); ok {
                b.WriteString(st.String())
                continue
            }
        }
        fmt.Fprint(b, v2)
    }
    b.WriteString("}")
    return b.String()
}

Now when you want to print a struct, you can do:

fmt.Println(PrintStruct(a, true))

You may also choose to add a String() method to your struct which just has to call our PrintStruct() function:

func (a A) String() string {
    return PrintStruct(a, true)
}

Whenever you change your struct, you don't have to do anything with your String() method as it uses reflection to dynamically walk over all the fields.

Notes:

Since we're using reflection, you have to export the t time.Time field for this to work (also added a few extra fields for testing purposes):

type A struct {
    T          time.Time
    I          int
    unexported string
}

Testing it:

a := A{time.Now(), 2, "hi!"}
fmt.Println(a)
fmt.Println(PrintStruct(a, true))
fmt.Println(PrintStruct(a, false))
fmt.Println(PrintStruct("I'm not a struct", true))

Output (try it on the Go Playground):

{T:2009-11-10 23:00:00 +0000 UTC I:2 unexported:hi!}
{T:2009-11-10 23:00:00 +0000 UTC I:2 unexported:hi!}
{2009-11-10 23:00:00 +0000 UTC 2 hi!}
I'm not a struct
Sign up to request clarification or add additional context in comments.

1 Comment

That's exactly what I try to avoid. I want to use time.Time's String(), instead of exposing its internal implementation.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.