瀏覽代碼

Decoding of structs working.

Gustavo Niemeyer 15 年之前
父節點
當前提交
c7f6f9c6e6
共有 3 個文件被更改,包括 206 次插入9 次删除
  1. 27 5
      all_test.go
  2. 76 4
      decode.go
  3. 103 0
      goyaml.go

+ 27 - 5
goyaml_test.go → all_test.go

@@ -21,7 +21,7 @@ type testItem struct {
 }
 
 
-var twoWayTests = []testItem{
+var unmarshalTests = []testItem{
     // It will encode either value as a string if asked for.
     {"hello: world", map[string]string{"hello": "world"}},
     {"hello: true", map[string]string{"hello": "true"}},
@@ -33,6 +33,7 @@ var twoWayTests = []testItem{
     {"hello: 0b10", map[string]interface{}{"hello": 2}},
     {"hello: 0xA", map[string]interface{}{"hello": 10}},
     {"hello: 4294967296", map[string]interface{}{"hello": int64(4294967296)}},
+    {"hello: 4294967296", map[string]int64{"hello": int64(4294967296)}},
     {"hello: 0.1", map[string]interface{}{"hello": 0.1}},
     {"hello: .1", map[string]interface{}{"hello": 0.1}},
     {"hello: .Inf", map[string]interface{}{"hello": math.Inf(+1)}},
@@ -47,12 +48,14 @@ var twoWayTests = []testItem{
     //{"sexa: 190:20:30.15", map[string]interface{}{"sexa": 0}}, // Unsupported
     {"neginf: -.inf", map[string]interface{}{"neginf": math.Inf(-1)}},
     {"notanum: .NaN", map[string]interface{}{"notanum": math.NaN}},
+    {"fixed: 685_230.15", map[string]float{"fixed": 685230.15}},
 
     // Bools from spec
     {"canonical: y", map[string]interface{}{"canonical": true}},
     {"answer: NO", map[string]interface{}{"answer": false}},
     {"logical: True", map[string]interface{}{"logical": true}},
     {"option: on", map[string]interface{}{"option": true}},
+    {"option: on", map[string]bool{"option": true}},
 
     // Ints from spec
     {"canonical: 685230", map[string]interface{}{"canonical": 685230}},
@@ -61,23 +64,42 @@ var twoWayTests = []testItem{
     {"hexa: 0x_0A_74_AE", map[string]interface{}{"hexa": 685230}},
     {"bin: 0b1010_0111_0100_1010_1110", map[string]interface{}{"bin": 685230}},
     //{"sexa: 190:20:30", map[string]interface{}{"sexa": 0}}, // Unsupported
+    {"decimal: +685_230", map[string]int{"decimal": 685230}},
 
     // Sequence
+    {"seq: [A,B]", map[string]interface{}{"seq": []interface{}{"A", "B"}}},
     {"seq: [A,B,C]", map[string][]string{"seq": []string{"A", "B", "C"}}},
     {"seq: [A,1,C]", map[string][]string{"seq": []string{"A", "1", "C"}}},
     {"seq: [A,1,C]", map[string][]int{"seq": []int{1}}},
+    {"seq: [A,1,C]", map[string]interface{}{"seq": []interface{}{"A", 1, "C"}}},
+
+    // Map inside interface with no type hints.
+    {"a: {b: c}",
+     map[string]interface{}{"a": map[interface{}]interface{}{"b": "c"}}},
+
+    // Structs and type conversions.
+    {"hello: world", &struct{Hello string}{"world"}},
+    {"a: {b: c}", &struct{A struct{B string}}{struct{B string}{"c"}}},
+    {"a: {b: c}", &struct{A *struct{B string}}{&struct{B string}{"c"}}},
+    {"a: 1", &struct{A int}{1}},
+    {"a: [1, 2]", &struct{A []int}{[]int{1, 2}}},
+    {"a: 1", &struct{B int}{0}},
+    {"a: 1", &struct{B int "a"}{1}},
+    {"a: y", &struct{A bool}{true}},
 }
 
 
-func (s *S) TestHelloWorld(c *C) {
-    for _, item := range twoWayTests {
+func (s *S) TestUnmarshal(c *C) {
+    for _, item := range unmarshalTests {
         t := reflect.NewValue(item.value).Type()
         var value interface{}
         if t, ok := t.(*reflect.MapType); ok {
             value = reflect.MakeMap(t).Interface()
         } else {
-            zero := reflect.MakeZero(reflect.NewValue(item.value).Type())
-            value = zero.Interface()
+            pt := reflect.NewValue(item.value).Type().(*reflect.PtrType)
+            pv := reflect.MakeZero(pt).(*reflect.PtrValue)
+            pv.PointTo(reflect.MakeZero(pt.Elem()))
+            value = pv.Interface()
         }
         err := goyaml.Unmarshal([]byte(item.data), value)
         c.Assert(err, IsNil)

+ 76 - 4
decode.go

@@ -65,6 +65,12 @@ func (d *decoder) skip(_type C.yaml_event_type_t) {
     d.next()
 }
 
+var blackHole = reflect.NewValue(true)
+
+func (d *decoder) drop() {
+    d.unmarshal(blackHole)
+}
+
 func (d *decoder) unmarshal(out reflect.Value) bool {
     switch d.event._type {
     case C.YAML_SCALAR_EVENT:
@@ -111,7 +117,19 @@ func (d *decoder) scalar(out reflect.Value) (ok bool) {
             ok = true
         case int64:
             out.Set(resolved)
-            // ok = true // XXX TEST ME
+            ok = true
+        }
+    case *reflect.BoolValue:
+        switch resolved := resolved.(type) {
+        case bool:
+            out.Set(resolved)
+            ok = true
+        }
+    case *reflect.FloatValue:
+        switch resolved := resolved.(type) {
+        case float:
+            out.Set(float64(resolved))
+            ok = true
         }
     default:
         panic("Can't handle scalar type yet: " + out.Type().String())
@@ -121,6 +139,12 @@ func (d *decoder) scalar(out reflect.Value) (ok bool) {
 }
 
 func (d *decoder) sequence(out reflect.Value) bool {
+    if iface, ok := out.(*reflect.InterfaceValue); ok {
+        // No type hints. Will have to use a generic sequence.
+        out = reflect.NewValue(make([]interface{}, 0))
+        iface.SetValue(out)
+    }
+
     sv, ok := out.(*reflect.SliceValue)
     if !ok {
         d.skip(C.YAML_SEQUENCE_END_EVENT)
@@ -140,10 +164,36 @@ func (d *decoder) sequence(out reflect.Value) bool {
     return true
 }
 
+func indirect(out reflect.Value) reflect.Value {
+    for {
+        switch v := out.(type) {
+        case *reflect.PtrValue:
+            if v.IsNil() {
+                out = reflect.MakeZero(v.Type().(*reflect.PtrType).Elem())
+                v.PointTo(out)
+            } else {
+                out = v.Elem()
+            }
+        default:
+            return out
+        }
+    }
+    panic("Unreachable")
+}
+
 func (d *decoder) mapping(out reflect.Value) bool {
-    //if iface, ok := out.(*reflect.InterfaceValue); ok {
+    out = indirect(out)
+
+    if s, ok := out.(*reflect.StructValue); ok {
+        return d.mappingStruct(s)
+    }
+
+    if iface, ok := out.(*reflect.InterfaceValue); ok {
+        // No type hints. Will have to use a generic map.
+        out = reflect.NewValue(make(map[interface{}]interface{}))
+        iface.SetValue(out)
+    }
 
-    // XXX What if it's an interface{}?
     mv, ok := out.(*reflect.MapValue)
     if !ok {
         d.skip(C.YAML_MAPPING_END_EVENT)
@@ -164,7 +214,29 @@ func (d *decoder) mapping(out reflect.Value) bool {
         }
     }
     d.next()
-    return false
+    return true
+}
+
+func (d *decoder) mappingStruct(out *reflect.StructValue) bool {
+    fields, err := getStructFields(out.Type().(*reflect.StructType))
+    if err != nil {
+        panic(err)
+    }
+    name := reflect.NewValue("").(*reflect.StringValue)
+    fieldsMap := fields.Map
+    d.next()
+    for d.event._type != C.YAML_MAPPING_END_EVENT {
+        if d.unmarshal(name) {
+            if info, ok := fieldsMap[name.Get()]; ok {
+                d.unmarshal(out.Field(info.Num))
+                continue
+            }
+        }
+        // Can't unmarshal name, or it's not present in struct.
+        d.drop()
+    }
+    d.next()
+    return true
 }
 
 func GoYString(s *C.yaml_char_t) string {

+ 103 - 0
goyaml.go

@@ -2,6 +2,8 @@ package goyaml
 
 import (
     "reflect"
+    "strings"
+    "sync"
     "os"
 )
 
@@ -12,3 +14,104 @@ func Unmarshal(in []byte, out interface{}) os.Error {
     d.unmarshal(reflect.NewValue(out))
     return nil
 }
+
+
+// --------------------------------------------------------------------------
+// Maintain a mapping of keys to structure field indexes
+
+// The code in this section was copied from gobson.
+
+type structFields struct {
+    Map map[string]fieldInfo
+    List []fieldInfo
+}
+
+type fieldInfo struct {
+    Key string
+    Num int
+    Conditional bool
+}
+
+var fieldMap = make(map[string]*structFields)
+var fieldMapMutex sync.RWMutex
+
+func getStructFields(st *reflect.StructType) (*structFields, os.Error) {
+    path := st.PkgPath()
+    name := st.Name()
+
+    fullName := path + "." + name
+    fieldMapMutex.RLock()
+    fields, found := fieldMap[fullName]
+    fieldMapMutex.RUnlock()
+    if found {
+        return fields, nil
+    }
+
+    n := st.NumField()
+    fieldsMap := make(map[string]fieldInfo)
+    fieldsList := make([]fieldInfo, n)
+    for i := 0; i != n; i++ {
+        field := st.Field(i)
+        if field.PkgPath != "" {
+            continue // Private field
+        }
+
+        info := fieldInfo{Num: i}
+
+        if s := strings.LastIndex(field.Tag, "/"); s != -1 {
+            for _, c := range field.Tag[s+1:] {
+                switch c {
+                case int('c'):
+                    info.Conditional = true
+                default:
+                    panic("Unsupported field flag: " + string([]int{c}))
+                }
+            }
+            field.Tag = field.Tag[:s]
+        }
+
+        if field.Tag != "" {
+            info.Key = field.Tag
+        } else {
+            info.Key = strings.ToLower(field.Name)
+        }
+
+        if _, found = fieldsMap[info.Key]; found {
+            msg := "Duplicated key '" + info.Key + "' in struct " + st.String()
+            return nil, os.NewError(msg)
+        }
+
+        fieldsList[len(fieldsMap)] = info
+        fieldsMap[info.Key] = info
+    }
+
+    fields = &structFields{fieldsMap, fieldsList[:len(fieldsMap)]}
+
+    if fullName != "." {
+        fieldMapMutex.Lock()
+        fieldMap[fullName] = fields
+        fieldMapMutex.Unlock()
+    }
+
+    return fields, nil
+}
+
+func isZero(v reflect.Value) bool {
+    switch v := v.(type) {
+    case *reflect.StringValue:
+        return len(v.Get()) == 0
+    case *reflect.InterfaceValue:
+        return v.IsNil()
+    case *reflect.SliceValue:
+        return v.Len() == 0
+    case *reflect.MapValue:
+        return v.Len() == 0
+    case *reflect.IntValue:
+        return v.Get() == 0
+    case *reflect.UintValue:
+        return v.Get() == 0
+    case *reflect.BoolValue:
+        return !v.Get()
+    }
+    return false
+}