Home > other >  How to convert a struct into a flat array of paths?
How to convert a struct into a flat array of paths?

Time:11-05

If for example I have a struct such as:

type Example struct {
  Foo string
  Bar string
  Baz struct{
    A int
    B string
  }
  Qux []string
}

What would be the best approach to convert it into a flat slice of strings representing the dot path to each struct field?

I want an output that looks like the following slice:

["Example.Foo", "Example.Bar", "Example.Baz.A", "Example.Baz.B", "Example.Qux.0", "Example.Qux.1"]

The exact structs will be known at compile time. Also, the conversion from struct to a flat list is in a hot path so performance will be an important consideration.

Any hints would be appreciated!

CodePudding user response:

You have to code it yourself, with reflection.

This is a demonstrative function that prints the output you provided:

package main

import (
"fmt"
    "reflect"
    "strconv"
)

type Example struct {
    Foo string
    Bar string
    Baz struct{
        A int
        B string
    }
    Qux []string
}

func main() {
    example := Example{Qux: []string{"a", "b"}}
    t := reflect.ValueOf(example)
    prefix := t.Type().Name()
    fmt.Println(ToPathSlice(t, prefix, make([]string, 0)))
}

func ToPathSlice(t reflect.Value, name string, dst []string) []string {
    switch t.Kind() {
    case reflect.Ptr, reflect.Interface:
        return ToPathSlice(t.Elem(), name, dst)

    case reflect.Struct:
        for i := 0; i < t.NumField(); i   {
            fname := t.Type().Field(i).Name
            dst = ToPathSlice(t.Field(i), name "." fname, dst)
        }

    case reflect.Slice, reflect.Array:
        for i := 0; i < t.Len(); i   {
            dst = ToPathSlice(t.Index(i), name "." strconv.Itoa(i), dst)
        }

    default:
        return append(dst, name)
    }
    return dst
}

Will print:

[Example.Foo Example.Bar Example.Baz.A Example.Baz.B Example.Qux.0 Example.Qux.1]

Note that:

  • reflection comes at a performance penalty; if you are concerned about this, you should profile the relevant code path to see if it's a deal breaker
  • the above code is contrived, for example it doesn't handle maps, it doesn't handle nil, etc.; you can expand it yourself
  • in your desired output, the indices of slice/array fields is printed. Slices don't have inherent length as arrays. In order to know the length of slices, you have to work with reflect.Value. This IMO makes the code more awkward. If you can accept not printing indices for slices, then you can work with reflect.Type.

Playground: https://play.golang.org/p/isNFSfFiXOP

  •  Tags:  
  • go
  • Related