遇到了使用指针进行结构体编组的问题。

huangapple go评论85阅读模式
英文:

having trouble Marshalling struct with pointer

问题

我正在编写一个自定义的Marshalar,将一个结构体转换为一个扁平的列式文件。如果我的结构体没有指针,一切都很顺利。但是当我有一个指向另一个结构体的指针时,我永远无法获得那个数据的Marshalled。我使用标签来指示Marshalar在哪里放置数据以及其长度。

你无法将SubTest3结构体显示为结构体,而是始终显示为指针(变量vKind)。你有什么想法我做错了什么?

这个测试通过...

type Test1 struct {
	Field1   string      `flatparse:"col=1,len=10"`
	Field2   uint        `flatparse:"col=11,len=1"`
	Field3t  bool        `flatparse:"col=12,len=1,override=boolYN"`
	Field3f  bool        `flatparse:"col=13,len=1,override=boolYN"`
	Field4   int         `flatparse:"col=14,len=4"`
	Field5   float32     `flatparse:"col=18,len=11,dec=2"`
	Field6   float64     `flatparse:"col=29,len=11,dec=2"`
	Segments [5]SubTest1 `flatparse:"col=40,len=6"`
}

type SubTest1 struct {
	Array1 uint `flatparse:"col=1,len=3"`
	Array2 uint `flatparse:"col=4,len=3"`
}

func TestMarshal(t *testing.T) {
	t.Run("fields and array marshal test", func(t *testing.T) {
		test1 := &Test1{
			Field1:  "ABCDEFGHIJ",
			Field2:  1,
			Field3t: true,
			Field3f: false,
			Field4:  3,
			Field5:  12345.67,
			Field6:  -9876.55,
		}
		test1.Segments[0].Array1 = 1
		test1.Segments[0].Array2 = 2
		test1.Segments[1].Array1 = 3
		test1.Segments[1].Array2 = 4

		var results []byte
		err := Marshal(test1, &results)
		if err != nil {
			t.Error(err)
		} else {
			want := "ABCDEFGHIJ1YN+003+0012345.67-0009876.55001002003004000000000000000000"
			if string(results) != want {
				t.Errorf("error got: %s  want %s", string(results), want)
			}
		}
	})
})

希望对你有所帮助!如果你有其他问题,请随时问我。

英文:

I am writing a custom Marshallar to convert a struct to a flat columnar file. If my struct has no pointers, everything works great. but when i have a pointer to another struct, i never get that data Marshalled. I am using tags to instruct the Marshallar where to place the data, as well as its length.

package flatparse

import (
	"reflect"

	"github.com/pkg/errors"
)

func Marshal(v interface{}, results *[]byte) error {
	fpTag := &flatparseTag{}

	if reflect.TypeOf(v).Kind() == reflect.Ptr {
		vType := reflect.TypeOf(v).Elem()
		vKind := vType.Kind()

		//Only process if kind is Struct
		if vKind == reflect.Struct {
			//Dereference pointer to struct
			vStruct := reflect.ValueOf(v).Elem()
			maxField := vStruct.NumField()
			//Loop through struct fields/properties
			for i := 0; i < maxField; i++ {
				//Get underlying type of field
				fieldType := vStruct.Field(i).Type()
				fieldTag, tagFlag := vType.Field(i).Tag.Lookup("flatparse")
				if tagFlag {
					tagParseErr := parseFlatparseTag(fieldTag, fpTag)
					if tagParseErr != nil {
						return errors.Wrapf(tagParseErr, "Marshal: Failed to parse field tag %s", fieldTag)
					}
					err := writeBasedOnKind(fieldType.Kind(), vStruct.Field(i), results, fpTag)
					if err != nil {
						return err
					}
				}
			}
		}
		return nil
	}
	return errors.Errorf("Marshal: Marshal not complete. %s is not a pointer", reflect.TypeOf(v))
}
package flatparse

import (
	"fmt"
	"reflect"
	"strconv"

	"github.com/pkg/errors"
)

//writeBasedOnKind performs write of field value to byte array based on kind
func writeBasedOnKind(kind reflect.Kind, field reflect.Value, returnData *[]byte, fpTag *flatparseTag) error {
	var err error
	err = nil
	switch kind {
	case reflect.Bool:
		return writeBool(kind, field, returnData, fpTag)
	case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
		return writeUint(kind, field, returnData, fpTag)
	case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
		return writeInt(kind, field, returnData, fpTag)
	case reflect.Float32, reflect.Float64:
		return writeFloat(kind, field, returnData, fpTag)
	case reflect.String:
		return writeString(kind, field, returnData, fpTag)
	case reflect.Struct:
		return Marshal(field.Addr().Interface(), returnData)
	case reflect.Ptr:
		if field.Elem().Kind() == reflect.Struct {
			return Marshal(field.Addr().Interface(), returnData)
		} else {
			return writeBasedOnKind(field.Elem().Kind(), field.Elem(), returnData, fpTag)
		}
	case reflect.Array:
		for i := 0; i < field.Len(); i++ {
			var arrayResults []byte
			err = writeBasedOnKind(field.Type().Elem().Kind(), field.Index(i), &arrayResults, fpTag)
			if err != nil {
				return err
			}
			col := fpTag.col + (fpTag.length * i) - 1
			temp := *returnData
			if len(temp) < col+fpTag.length {
				temp = make([]byte, col+fpTag.length)
				copy(temp, *returnData)
			}
			copy(temp[col:col+len(arrayResults)], arrayResults)
			*returnData = temp
		}
		return nil
	case reflect.Slice:
		if fpTag.occurs < 1 {
			err = errors.Errorf("flatparse.writeBasedOnKind: Occurs clause must be provided when using slice. `flatparse:\"col,len,occurs\"`")
		}
		for i := 0; i < fpTag.occurs; i++ {
			var arrayResults []byte
			err = writeBasedOnKind(field.Type().Elem().Kind(), field.Index(i), &arrayResults, fpTag)
			if err != nil {
				return err
			}
			col := fpTag.col + (fpTag.length * i) - 1
			temp := *returnData
			if len(temp) < col+fpTag.length {
				temp = make([]byte, col+fpTag.length)
				copy(temp, *returnData)
			}
			copy(temp[col:col+len(arrayResults)], arrayResults)
			*returnData = temp
		}
		return nil
	}
	return errors.Wrap(err, "flatparse.writeBasedOnKind: writementError")
}

func writeBool(kind reflect.Kind, field reflect.Value, returnData *[]byte, fpTag *flatparseTag) error {
	v := field.Bool()
	t := "1"
	f := "0"
	if fpTag.override == "boolYN" {
		t = "Y"
		f = "N"
	}
	if v {
		return copyData(returnData, t, fpTag)
	} else {
		return copyData(returnData, f, fpTag)
	}
}

func writeUint(kind reflect.Kind, field reflect.Value, returnData *[]byte, fpTag *flatparseTag) error {
	v := fmt.Sprintf("%0"+strconv.Itoa(fpTag.length)+"d", field.Uint())
	return copyData(returnData, v, fpTag)
}

func writeInt(kind reflect.Kind, field reflect.Value, returnData *[]byte, fpTag *flatparseTag) error {
	v := fmt.Sprintf("%+0"+strconv.Itoa(fpTag.length)+"d", field.Int())
	return copyData(returnData, v, fpTag)
}

func writeFloat(kind reflect.Kind, field reflect.Value, returnData *[]byte, fpTag *flatparseTag) error {
	sfmt := "%+0" + strconv.Itoa(fpTag.length) + "." + strconv.Itoa(fpTag.decimals) + "f"
	v := fmt.Sprintf(sfmt, field.Float())
	return copyData(returnData, v, fpTag)
}

func writeString(kind reflect.Kind, field reflect.Value, returnData *[]byte, fpTag *flatparseTag) error {
	v := fmt.Sprintf("%-"+strconv.Itoa(fpTag.length)+"s", field.String())
	return copyData(returnData, v, fpTag)
}

func copyData(returnData *[]byte, v string, fpTag *flatparseTag) error {
	data := *returnData
	if len(data) < fpTag.col+fpTag.length {
		temp := make([]byte, fpTag.col+fpTag.length-1)
		copy(temp, data)
		data = temp
	}
	copy(data[fpTag.col-1:fpTag.col+fpTag.length-1], v)
	*returnData = data
	return nil
}

Test...

type Test3 struct {
	Field1    string    `flatparse:"col=1,len=1"`
	SubFields *SubTest3 `flatparse:"col=2,len=6"`
}

type SubTest3 struct {
	SubField1 uint `flatparse:"col=2,len=3"`
	SubField2 uint `flatparse:"col=5,len=3"`
}

func TestMarshalPointers(t *testing.T) {
	t.Run("pointer marshal test", func(t *testing.T) {
		subfields := &SubTest3{
			SubField1: 1,
			SubField2: 2,
		}
		test3 := &Test3{
			Field1:    "A",
			SubFields: subfields,
		}

		var results []byte
		err := Marshal(test3, &results)
		if err != nil {
			t.Error(err)
		} else {
			want := "A001002"
			if string(results) != want {
				t.Errorf("error got: %s  want %s", string(results), want)
			}
		}
	})
}

I cannot get the SubTest3 struct to appear as a struct. In the Marshal code it always appears as a pointer (variable vKind). Any ideas what I am doing wrong?

This test passes...

type Test1 struct {
	Field1   string      `flatparse:"col=1,len=10"`
	Field2   uint        `flatparse:"col=11,len=1"`
	Field3t  bool        `flatparse:"col=12,len=1,override=boolYN"`
	Field3f  bool        `flatparse:"col=13,len=1,override=boolYN"`
	Field4   int         `flatparse:"col=14,len=4"`
	Field5   float32     `flatparse:"col=18,len=11,dec=2"`
	Field6   float64     `flatparse:"col=29,len=11,dec=2"`
	Segments [5]SubTest1 `flatparse:"col=40,len=6"`
}

type SubTest1 struct {
	Array1 uint `flatparse:"col=1,len=3"`
	Array2 uint `flatparse:"col=4,len=3"`
}

func TestMarshal(t *testing.T) {
	t.Run("fields and array marshal test", func(t *testing.T) {
		test1 := &Test1{
			Field1:  "ABCDEFGHIJ",
			Field2:  1,
			Field3t: true,
			Field3f: false,
			Field4:  3,
			Field5:  12345.67,
			Field6:  -9876.55,
		}
		test1.Segments[0].Array1 = 1
		test1.Segments[0].Array2 = 2
		test1.Segments[1].Array1 = 3
		test1.Segments[1].Array2 = 4

		var results []byte
		err := Marshal(test1, &results)
		if err != nil {
			t.Error(err)
		} else {
			want := "ABCDEFGHIJ1YN+003+0012345.67-0009876.55001002003004000000000000000000"
			if string(results) != want {
				t.Errorf("error got: %s  want %s", string(results), want)
			}
		}
	})
})

答案1

得分: 1

找到了问题。将代码中的部分修改为:

return Marshal(field.Interface(), returnData)

现在所有的测试都通过了。

英文:

Found the issue. Changed

return Marshal(field.Addr().Interface(), returnData)

to

return Marshal(field.Interface(), returnData)

Now all tests are passing

huangapple
  • 本文由 发表于 2022年4月20日 01:54:36
  • 转载请务必保留本文链接:https://go.coder-hub.com/71929271.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定