Skip to content

Commit

Permalink
Add package to map properties to structs
Browse files Browse the repository at this point in the history
  • Loading branch information
nqv committed Sep 15, 2016
1 parent 3360d95 commit dc96ca5
Show file tree
Hide file tree
Showing 2 changed files with 156 additions and 0 deletions.
77 changes: 77 additions & 0 deletions structs/structs.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
// Package structs provides a function to unmarshal properties to structs.
//
// Use Go struct tag to define property mappings. For example:
//
// type Data struct {
// String string `cfg:"string"`
// Integer int `cfg:"integer"`
// Uinteger uint `cfg:"uinteger"`
// Bool bool `cfg:"bool"`
// Float float64 `cfg:"float"`
// }
//
// See strconv package to see how string value is converted to int, bool
// or float.
package structs

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

const (
tagName = "cfg"
)

// Unmarshal unmarshals properties p into v.
// v must be a pointer to a struct.
func Unmarshal(p map[string]string, v interface{}) error {
rv := reflect.ValueOf(v)
if rv.Kind() != reflect.Ptr {
return fmt.Errorf("value must be pointer to a struct: %+v", reflect.TypeOf(v))
}
val := rv.Elem()

for i := 0; i < val.NumField(); i++ {
tag := val.Type().Field(i).Tag.Get(tagName)
if tag == "" {
continue
}
strVal := p[tag]
if strVal == "" {
continue
}
field := val.Field(i)
switch field.Kind() {
case reflect.String:
field.SetString(strVal)
case reflect.Int, reflect.Int64, reflect.Int32, reflect.Int16, reflect.Int8:
x, err := strconv.ParseInt(strVal, 10, 0)
if err != nil {
return err
}
field.SetInt(x)
case reflect.Uint, reflect.Uint64, reflect.Uint32, reflect.Uint16, reflect.Uint8:
x, err := strconv.ParseUint(strVal, 10, 0)
if err != nil {
return err
}
field.SetUint(x)
case reflect.Bool:
x, err := strconv.ParseBool(strVal)
if err != nil {
return err
}
field.SetBool(x)
case reflect.Float64, reflect.Float32:
x, err := strconv.ParseFloat(strVal, 0)
if err != nil {
return err
}
field.SetFloat(x)
}

}
return nil
}
79 changes: 79 additions & 0 deletions structs/structs_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
package structs

import (
"fmt"
"math"
"testing"
)

func TestUnmarshal(t *testing.T) {
in := map[string]string{
"string": "a string",
"integer": "-1234",
"uinteger": "1234",
"bool": "true",
"float": "1.234",
}
var out struct {
String string `cfg:"string"`
Integer int `cfg:"integer"`
Uinteger uint `cfg:"uinteger"`
Bool bool `cfg:"bool"`
Float float64 `cfg:"float"`
}

err := Unmarshal(in, &out)
if err != nil {
t.Fatal(err)
}
if out.String != "a string" {
t.Errorf("unexpected string: %v", out.String)
}
if out.Integer != -1234 {
t.Errorf("unexpected integer: %v", out.Integer)
}
if out.Uinteger != 1234 {
t.Errorf("unexpected uinteger: %v", out.Uinteger)
}
if out.Bool != true {
t.Errorf("unexpected bool: %v", out.Bool)
}
if math.Abs(out.Float-1.234) > 0.001 {
t.Errorf("unexpected float: %v", out.Float)
}
}

func ExampleUnmarshal() {
in := map[string]string{
"name": "Foo Bar",
"age": "54",
"married": "true",
"height": "1.75",
}
var out struct {
Name string `cfg:"name"`
Age int `cfg:"age"`
Married bool `cfg:"married"`
Height float64 `cfg:"height"`
}
err := Unmarshal(in, &out)
if err != nil {
panic(err)
}
fmt.Printf("%+v\n", out)
// Output:
// {Name:Foo Bar Age:54 Married:true Height:1.75}
}

func TestUnmarshalInvalidInt(t *testing.T) {
in := map[string]string{
"integer": "abc",
}
var out struct {
Integer int `cfg:"integer"`
}
err := Unmarshal(in, &out)
if err == nil {
t.Fatal("error expected")
}
}

0 comments on commit dc96ca5

Please sign in to comment.