I am really new to Go, so want some advice. I have a struct:
type Employee struct {
Name string
Designation string
Department string
Salary int
Email string
}
I want to concatenate the string fields into a type of employee description. So that, I can say: toString(employee) and get:
John Smith Manager Sales john.smith@example.com
I tried to fetch each field, check if they are empty and put them in a slice and join them at the end
employeeDescArr := make([]string, 0, 4)
if strings.TrimSpace(value) != "" {
append(employee.GetName(), value)
}...
return strings.Join(employeeDescArr[:], " ")
I think this method is very verbose and shows lack of Go skills. Is it better to use a string Builder instead? Is there a way to iterate through all fields of a struct in a Reflection way and join them?
Loop through the string fields and collect non-empty strings. Join the fields.
func (e *Employee) String() string {
var parts []string
for _, s := range []string{e.Name, e.Designation, e.Department, e.Email} {
if strings.TrimSpace(s) != "" {
parts = append(parts, s)
}
}
return strings.Join(parts, " ")
}
Because the strings.Join function is implemented using strings.Builder, there's no benefit to replacing strings.Join with application code that uses strings.Builder.
Here's how to use reflect to avoid listing the fields in the string function:
var stringType = reflect.TypeOf("")
func (e *Employee) String() string {
v := reflect.ValueOf(e).Elem()
var parts []string
for i := 0; i < v.NumField(); i++ {
f := v.Field(i)
if f.Type() == stringType {
s := f.String()
if strings.TrimSpace(s) != "" {
parts = append(parts, s)
}
}
}
return strings.Join(parts, " ")
}
If you want to include all fields (include non-strings and empty strings), then you can fmt.Sprint(e)
to get a string. See https://play.golang.org/p/yntZxQ-Xs6C.
You can make it less verbose by writing an utility function to make the addition with the "non-blank-string" check.
Also, you could make your type implement implement an String()
method which has the advantage it will make it print as you wish when used in fmt
print functions.
This addToString
function is generic so you could reuse it if doing this for other types:
func addToString(original string, addition interface{}) string {
additionStr := fmt.Sprint(addition)
if additionStr != "" {
if original != "" {
original += " "
}
original += additionStr
}
return original
}
Then you can implement it like this, which is not so verbose:
type Employee struct {
Name string
Designation string
Department string
Salary int
Email string
}
func (e *Employee) String() string {
theString := ""
theString = addToString(theString, e.Name)
theString = addToString(theString, e.Designation)
theString = addToString(theString, e.Department)
theString = addToString(theString, e.Salary)
theString = addToString(theString, e.Email)
return theString
}
And use it like this:
func main() {
emp := &Employee{
Name: "Jonh",
Department: "Some dept",
}
fmt.Println(emp.String())
fmt.Println(emp)
}
Which will output:
Jonh Some dept 0
Jonh Some dept 0
I think you would want to use the Stringer interface instead. ie:
package main
import (
"fmt"
"strings"
"strconv"
)
type Employee struct {
Name string
Designation string
Department string
Salary int
Email string
}
func main() {
emp1:=Employee{Name:"Cetin", Department:"MS", Salary:50}
emp2:=Employee{Name:"David", Designation:"Designation", Email:"david@nowhere.com"}
emp3:=Employee{Department:"Space", Salary:10}
fmt.Println(emp1)
fmt.Println(emp2)
fmt.Println(emp3)
}
func (e Employee) String() string {
var salary string
if e.Salary > 0 {
salary = strconv.Itoa(e.Salary) + " "
} else {
salary = ""
}
return strings.TrimSpace(
strings.TrimSpace(
strings.TrimSpace(e.Name + " " + e.Designation) + " " +
e.Department) + " " +
salary +
e.Email)
}
Playground: https://play.golang.org/p/L8ft7SeXpqt
PS: I later noticed you only want string fields, but didn't remove salary anyway.
import "fmt"
Stringer is implemented by any value that has a String method, which defines the “native” format for that value. The String method is used to print values passed as an operand to any format that accepts a string or to an unformatted printer such as Print.
type Stringer interface { String() string }
Write a String
method for type Employee
.
For example,
package main
import (
"fmt"
"strings"
)
type Employee struct {
Name string
Designation string
Department string
Salary int
Email string
}
func appendItem(items *strings.Builder, item string) {
if len(item) > 0 {
if items.Len() > 0 {
items.WriteByte(' ')
}
items.WriteString(item)
}
}
func (e Employee) String() string {
s := new(strings.Builder)
appendItem(s, e.Name)
appendItem(s, e.Designation)
appendItem(s, e.Department)
appendItem(s, e.Email)
return s.String()
}
func main() {
ee := Employee{
Name: "John Smith",
Designation: "Manager",
Department: "Sales",
Email: "john.smith@example.com",
Salary: 42000,
}
fmt.Println(ee)
}
Playground: https://play.golang.org/p/EPBjgi8usJ-
Output:
John Smith Manager Sales john.smith@example.com
I want to concatenate the string fields into a type of employee description.
Is there a way to iterate through all fields of a struct in a Reflection way and join them?
[Reflection is] a powerful tool that should be used with care and avoided unless strictly necessary. Rob Pike
The Go Blog: The Laws of Reflection
Reflection is never clear. Rob Pike
Go Proverbs - Rob Pike - Gopherfest - November 18, 2015
Go compiled code is efficient. The Go reflect package functions are interpreted at run time.
Iterating through all fields of a struct has the same bug as SELECT * FROM table;
in SQL. The values returned are determined at run time, not compile time.
If your case, The business requirement is to hide confidential fields, like salary, and limit the fields displayed to a few key descriptive fields. Inevitably, fields will be added to the struct. The "concatenate the string fields" specification is unlikely to be correct now or in the future.