1

I'm trying to decode dynamic structures from network data, here is simplified version. The FmtA is [3]byte, and required to print as string. So, here is my stupid implementation by defining a Bytes3 data type. If use this method, I should define Bytes6, Bytes4, Bytes2.

Is there any better method to print all byte arrays as strings instead of byte arrays?

package main                                                                                                                                                           

import "fmt"                                                                    

type Bytes3 [3]byte                                                             
type FmtA struct {                                                            
        Field1 Bytes3                                                           
        Field2 [6]byte                                                          
        Field3 uint8                                                            
}                                                                               
type FmtB struct {                                                            
        Field1 uint16                                                           
        Field2 [4]byte                                                          
        Field3 [2]byte                                                          
}                                                                               

func (b Bytes3) String() string {                                               
        v := [3]byte(b)                                                         
        return string(v[:])                                                     
}                                                                               
func main() {                                                                   
        a := FmtA{[3]byte{'a', 'b', 'c'}, [6]byte{'d', 'e', 'f', 'g', 'h', 'i'},
                36}                                                             
        b := FmtB{42, [4]byte{'a', 'b', 'c', 'd'}, [2]byte{'e', 'f'}}           
        var i interface{}   // simulate the received variable type                                                     
        i = a                                                                   
        fmt.Printf("a=%+v\n", i)                                                
        i = b                                                                   
        fmt.Printf("b=%+v\n", i)                                                
        // Output:                                                              
        // a={Field1:abc Field2:[100 101 102 103 104 105] Field3:36}            
        // b={Field1:42 Field2:[97 98 99 100] Field3:[101 102]}                 
}
5
  • Side note: why are you doing i = a and i = b? Just put a and b directly into the Printf calls. Commented Aug 9, 2019 at 12:37
  • With an individual variables that are byte arrays or slices you can use fmt.Printf with a %s or %q formatting verb to output a string. For a type containing such fields you could implement fmt.Stringer or fmt.Formatter directly on that type (i.e. on FmtA and FmtB). Which (Byte# types or outer type formatting) is easier/nicer may be a matter of opinion; unless you already want/need formatting for the outer type. Commented Aug 9, 2019 at 12:42
  • also side note: it seems like you're using aliasing (type T = U) arbitrarily here. Unless you've intended to use aliasing for some specific reason, do not do that, just define the struct type like any other type, like your byte-array type. Commented Aug 9, 2019 at 13:07
  • @DaveC because the coming data is variable type, I use interface{} to accept it. I just want to print out the structure and try to use %+v feature. Commented Aug 9, 2019 at 13:16
  • @mkopriva, I've edited, just use (type T U) instead of (type T = U) Commented Aug 9, 2019 at 13:18

1 Answer 1

1

You could create a utility function that would take any struct, inspect the fields using reflection and format them accordingly (use the default for fields that are not byte arrays, but force byte arrays to print as strings).

Like for example:

func Struct2String(theStruct interface{}) string {
    reflectV := reflect.ValueOf(theStruct)
    structType := reflectV.Type()
    b := &bytes.Buffer{}
    b.WriteString("{")
    for i := 0; i < reflectV.NumField(); i++ {
        if i > 0 {
            b.WriteString(" ")
        }
        b.WriteString(structType.Field(i).Name)
        b.WriteString(": ")
        fieldValue := reflectV.Field(i)
        fieldType := reflectV.Field(i).Type()
        fieldKind := reflectV.Field(i).Kind()
        if (fieldKind == reflect.Slice || fieldKind == reflect.Array) && fieldType.Elem().Kind() == reflect.Uint8 {
            fmt.Fprintf(b, "%s", fieldValue)
        } else {
            fmt.Fprint(b, fieldValue)
        }
    }
    b.WriteString("}")
    return b.String()
}

Here you can see the example running with the structs you have defined in a Go playground:

https://play.golang.org/p/zGZM5S8UMWZ

Sign up to request clarification or add additional context in comments.

2 Comments

Although you can use reflection for this, reflection should be the last resort, it's slow, complicated, hard to read, easy to get wrong, etc.
@DaveC, I agree reflection can be complex to use but it might be the right tool for this since the question was explicitly asking for a way to avoid defining the "String" for each type, so this function would be generic. Also please note fmt.Print needs to also use reflection to iterate the fields, so probably this will not be slower for this particular case. In any case, just giving a response with a possible option here.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.