func (d *decoder) decodeStruct(name string, o *hcl.Object, result reflect.Value) error { if o.Type != hcl.ValueTypeObject { return fmt.Errorf("%s: not an object type for struct (%v)", name, o.Type) } // This slice will keep track of all the structs we'll be decoding. // There can be more than one struct if there are embedded structs // that are squashed. structs := make([]reflect.Value, 1, 5) structs[0] = result // Compile the list of all the fields that we're going to be decoding // from all the structs. fields := make(map[*reflect.StructField]reflect.Value) for len(structs) > 0 { structVal := structs[0] structs = structs[1:] structType := structVal.Type() for i := 0; i < structType.NumField(); i++ { fieldType := structType.Field(i) if fieldType.Anonymous { fieldKind := fieldType.Type.Kind() if fieldKind != reflect.Struct { return fmt.Errorf( "%s: unsupported type to struct: %s", fieldType.Name, fieldKind) } // We have an embedded field. We "squash" the fields down // if specified in the tag. squash := false tagParts := strings.Split(fieldType.Tag.Get(tagName), ",") for _, tag := range tagParts[1:] { if tag == "squash" { squash = true break } } if squash { structs = append( structs, result.FieldByName(fieldType.Name)) continue } } // Normal struct field, store it away fields[&fieldType] = structVal.Field(i) } } usedKeys := make(map[string]struct{}) decodedFields := make([]string, 0, len(fields)) decodedFieldsVal := make([]reflect.Value, 0) unusedKeysVal := make([]reflect.Value, 0) for fieldType, field := range fields { if !field.IsValid() { // This should never happen panic("field is not valid") } // If we can't set the field, then it is unexported or something, // and we just continue onwards. if !field.CanSet() { continue } fieldName := fieldType.Name // This is whether or not we expand the object into its children // later. expand := false tagValue := fieldType.Tag.Get(tagName) tagParts := strings.SplitN(tagValue, ",", 2) if len(tagParts) >= 2 { switch tagParts[1] { case "expand": expand = true case "decodedFields": decodedFieldsVal = append(decodedFieldsVal, field) continue case "key": field.SetString(o.Key) continue case "unusedKeys": unusedKeysVal = append(unusedKeysVal, field) continue } } if tagParts[0] != "" { fieldName = tagParts[0] } // Find the element matching this name obj := o.Get(fieldName, true) if obj == nil { continue } // Track the used key usedKeys[fieldName] = struct{}{} // Create the field name and decode. We range over the elements // because we actually want the value. fieldName = fmt.Sprintf("%s.%s", name, fieldName) for _, obj := range obj.Elem(expand) { if err := d.decode(fieldName, obj, field); err != nil { return err } } decodedFields = append(decodedFields, fieldType.Name) } if len(decodedFieldsVal) > 0 { // Sort it so that it is deterministic sort.Strings(decodedFields) for _, v := range decodedFieldsVal { v.Set(reflect.ValueOf(decodedFields)) } } // If we want to know what keys are unused, compile that if len(unusedKeysVal) > 0 { /* unusedKeys := make([]string, 0, int(obj.Len())-len(usedKeys)) for _, elem := range obj.Elem { k := elem.Key() if _, ok := usedKeys[k]; !ok { unusedKeys = append(unusedKeys, k) } } if len(unusedKeys) == 0 { unusedKeys = nil } for _, v := range unusedKeysVal { v.Set(reflect.ValueOf(unusedKeys)) } */ } return nil }