From 878bf42a301f160264abc8dbc3fb9048b6107c2b Mon Sep 17 00:00:00 2001
From: Chris Busbey <cbusbey@connamara.com>
Date: Sat, 24 Oct 2015 19:57:17 -0700
Subject: [PATCH] marshal, unmarshal

---
 marshal.go         | 130 +++++++++++++++++++++
 marshal_test.go    | 282 +++++++++++++++++++++++++++++++++++++++++++++
 repeating_group.go |   2 +-
 unmarshal.go       | 155 +++++++++++++++++++++++++
 unmarshal_test.go  | 228 ++++++++++++++++++++++++++++++++++++
 5 files changed, 796 insertions(+), 1 deletion(-)
 create mode 100644 marshal.go
 create mode 100644 marshal_test.go
 create mode 100644 unmarshal.go
 create mode 100644 unmarshal_test.go

diff --git a/marshal.go b/marshal.go
new file mode 100644
index 00000000..4c62a4b4
--- /dev/null
+++ b/marshal.go
@@ -0,0 +1,130 @@
+package quickfix
+
+import (
+	"reflect"
+	"strconv"
+	"time"
+)
+
+type encoder struct {
+	FieldMap
+}
+
+func (e encoder) encodeField(f reflect.StructField, t reflect.Type, v reflect.Value) {
+	if f.Tag.Get("fix") != "" {
+		fixTag, err := strconv.Atoi(f.Tag.Get("fix"))
+		if err != nil {
+			panic(err)
+		}
+		e.encodeValue(Tag(fixTag), v)
+	}
+
+	switch v.Kind() {
+	case reflect.Ptr:
+		if v.IsNil() {
+			return
+		}
+		e.encodeField(f, v.Elem().Type(), v.Elem())
+	case reflect.Struct:
+		for i := 0; i < t.NumField(); i++ {
+			e.encodeField(t.Field(i), t.Field(i).Type, v.Field(i))
+		}
+	}
+}
+
+func (e encoder) encodeValue(fixTag Tag, v reflect.Value) {
+	switch v.Kind() {
+	case reflect.Ptr:
+		if v.IsNil() {
+			return
+		}
+		e.encodeValue(fixTag, v.Elem())
+	case reflect.Struct:
+		switch t := v.Interface().(type) {
+		case time.Time:
+			e.FieldMap.SetField(fixTag, FIXUTCTimestamp{Value: t})
+		}
+	case reflect.String:
+		e.FieldMap.SetField(fixTag, FIXString(v.String()))
+	case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64,
+		reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
+		e.FieldMap.SetField(fixTag, FIXInt(v.Int()))
+	case reflect.Bool:
+		e.FieldMap.SetField(fixTag, FIXBoolean(v.Bool()))
+	case reflect.Float32, reflect.Float64:
+		e.FieldMap.SetField(fixTag, FIXFloat(v.Float()))
+	case reflect.Slice:
+		elem := v.Type().Elem()
+		if elem.Kind() != reflect.Struct {
+			panic("repeating group must be a slice of type struct")
+		}
+
+		template := make(GroupTemplate, elem.NumField())
+		for i := 0; i < elem.NumField(); i++ {
+			sf := elem.Field(i)
+			fixTag, err := strconv.Atoi(sf.Tag.Get("fix"))
+
+			if err != nil {
+				panic(err)
+			}
+
+			template[i] = GroupElement(Tag(fixTag))
+		}
+
+		repeatingGroup := RepeatingGroup{Tag: fixTag, GroupTemplate: template}
+
+		for i := 0; i < v.Len(); i++ {
+			group := repeatingGroup.Add()
+			groupEncoder := encoder{FieldMap: group.FieldMap}
+
+			sliceV := v.Index(i)
+			for i := 0; i < sliceV.NumField(); i++ {
+				sf := sliceV.Type().Field(i)
+				groupEncoder.encodeField(sf, sf.Type, sliceV.Field(i))
+			}
+		}
+
+		e.FieldMap.SetGroup(repeatingGroup)
+	}
+}
+
+// Marshal returns the Message encoding of v
+func Marshal(v interface{}) Message {
+	m := NewMessage()
+
+	reflectValue := reflect.ValueOf(v)
+	if !reflectValue.IsValid() {
+		panic("Message value is invalid")
+	}
+
+	var msgType string
+
+	reflectType := reflectValue.Type()
+	e := encoder{}
+	for i := 0; i < reflectType.NumField(); i++ {
+		sf := reflectType.Field(i)
+
+		switch sf.Name {
+		case "FIXMsgType":
+			msgType = sf.Tag.Get("fix")
+			if msgType == "" {
+				panic("FIXMsgType has no Tag Value")
+			}
+			continue
+		case "Header":
+			e.FieldMap = m.Header
+		case "Trailer":
+			e.FieldMap = m.Trailer
+		default:
+			e.FieldMap = m.Body
+		}
+
+		e.encodeField(sf, sf.Type, reflectValue.Field(i))
+	}
+
+	if msgType != "" {
+		m.Header.SetField(tagMsgType, FIXString(msgType))
+	}
+
+	return m
+}
diff --git a/marshal_test.go b/marshal_test.go
new file mode 100644
index 00000000..94aa9004
--- /dev/null
+++ b/marshal_test.go
@@ -0,0 +1,282 @@
+package quickfix_test
+
+import (
+	"bytes"
+	"github.com/quickfixgo/quickfix"
+	"github.com/quickfixgo/quickfix/field"
+	"testing"
+	"time"
+)
+
+func TestMarshal_FIXMsgType(t *testing.T) {
+	type Message struct {
+		FIXMsgType string `fix:"0"`
+	}
+	var m Message
+	fixMsg := quickfix.Marshal(m)
+	fixMsg.Build()
+
+	var msgType field.MsgTypeField
+	fixMsg.Header.Get(&msgType)
+	if msgType.FIXString != "0" {
+		t.Errorf("Expected %v got %v", "1", msgType)
+	}
+}
+
+func TestMarshal_Literals(t *testing.T) {
+	type Message struct {
+		FIXMsgType        string    `fix:"0"`
+		StringField       string    `fix:"114"`
+		IntField          int       `fix:"115"`
+		UnsetIntField     int       `fix:"116"`
+		BoolField         bool      `fix:"117"`
+		FloatField        float64   `fix:"118"`
+		UTCTimestampField time.Time `fix:"119"`
+	}
+
+	tVal, _ := time.Parse("2006-Jan-02", "2014-Jun-16")
+
+	m := Message{
+		StringField:       "world",
+		IntField:          5,
+		BoolField:         true,
+		FloatField:        100.02,
+		UTCTimestampField: tVal,
+	}
+	fixMsg := quickfix.Marshal(m)
+
+	var tests = []struct {
+		tag        quickfix.Tag
+		fieldValue quickfix.FieldValue
+		expected   []byte
+	}{
+		{quickfix.Tag(114), new(quickfix.FIXString), []byte("world")},
+		{quickfix.Tag(115), new(quickfix.FIXInt), []byte("5")},
+		{quickfix.Tag(116), new(quickfix.FIXInt), []byte("0")},
+		{quickfix.Tag(117), new(quickfix.FIXBoolean), []byte("Y")},
+		{quickfix.Tag(118), new(quickfix.FIXFloat), []byte("100.02")},
+		{quickfix.Tag(119), new(quickfix.FIXUTCTimestamp), []byte("20140616-00:00:00.000")},
+	}
+
+	for _, test := range tests {
+		if err := fixMsg.Body.GetField(test.tag, test.fieldValue); err != nil {
+			t.Error("Unexpected error", err)
+		}
+
+		if !bytes.Equal(test.expected, test.fieldValue.Write()) {
+			t.Errorf("Expected %s got %s", test.expected, test.fieldValue.Write())
+		}
+	}
+}
+
+func TestMarshal_LiteralsOptional(t *testing.T) {
+	type Message struct {
+		FIXMsgType          string   `fix:"0"`
+		StringPtrField      *string  `fix:"112"`
+		UnsetStringPtrField *string  `fix:"113"`
+		IntPtrField         *int     `fix:"115"`
+		BoolPtrField        *bool    `fix:"117"`
+		FloatPtrField       *float64 `fix:"119"`
+	}
+
+	strVal := "hello"
+	intVal := 42
+	boolVal := false
+	floatVal := 456.123
+	m := Message{
+		StringPtrField: &strVal,
+		IntPtrField:    &intVal,
+		BoolPtrField:   &boolVal,
+		FloatPtrField:  &floatVal,
+	}
+	fixMsg := quickfix.Marshal(m)
+
+	var tests = []struct {
+		tag        quickfix.Tag
+		fieldValue quickfix.FieldValue
+		fieldSet   bool
+		expected   []byte
+	}{
+		{quickfix.Tag(112), new(quickfix.FIXString), true, []byte("hello")},
+		{quickfix.Tag(113), new(quickfix.FIXString), false, []byte{}},
+		{quickfix.Tag(115), new(quickfix.FIXInt), true, []byte("42")},
+		{quickfix.Tag(117), new(quickfix.FIXBoolean), true, []byte("N")},
+		{quickfix.Tag(119), new(quickfix.FIXFloat), true, []byte("456.123")},
+	}
+
+	for _, test := range tests {
+		if !test.fieldSet {
+			if fixMsg.Body.Has(test.tag) {
+				t.Error("Unexpected field for tag", test.tag)
+			}
+			continue
+		}
+
+		if err := fixMsg.Body.GetField(test.tag, test.fieldValue); err != nil {
+			t.Error("Unexpected error", err)
+		}
+
+		if !bytes.Equal(test.expected, test.fieldValue.Write()) {
+			t.Errorf("Expected %s got %s", test.expected, test.fieldValue.Write())
+		}
+	}
+}
+
+func TestMarshal_Components(t *testing.T) {
+	type Component1 struct {
+		StringField string `fix:"8"`
+	}
+	type Component2 struct {
+		IntField int `fix:"1"`
+	}
+
+	type Message struct {
+		FIXMsgType string `fix:"0"`
+		Component1
+		StringField string `fix:"112"`
+		*Component2
+	}
+
+	m := Message{
+		Component1:  Component1{StringField: "hello"},
+		StringField: "world",
+	}
+	fixMsg := quickfix.Marshal(m)
+
+	var tests = []struct {
+		tag        quickfix.Tag
+		fieldValue quickfix.FieldValue
+		fieldSet   bool
+		expected   []byte
+	}{
+		{quickfix.Tag(8), new(quickfix.FIXString), true, []byte("hello")},
+		{quickfix.Tag(1), new(quickfix.FIXInt), false, []byte{}},
+		{quickfix.Tag(112), new(quickfix.FIXString), true, []byte("world")},
+	}
+
+	for _, test := range tests {
+		if !test.fieldSet {
+			if fixMsg.Body.Has(test.tag) {
+				t.Error("Unexpected field for tag", test.tag)
+			}
+			continue
+		}
+
+		if err := fixMsg.Body.GetField(test.tag, test.fieldValue); err != nil {
+			t.Error("Unexpected error", err)
+			continue
+		}
+
+		if !bytes.Equal(test.expected, test.fieldValue.Write()) {
+			t.Errorf("Expected %s got %s", test.expected, test.fieldValue.Write())
+		}
+	}
+}
+
+func TestMarshal_RepeatingGroups(t *testing.T) {
+	type Group1 struct {
+		StringField1 string  `fix:"8"`
+		StringField2 *string `fix:"9"`
+	}
+
+	type Group2 struct {
+		IntField1 int `fix:"1"`
+		IntField2 int `fix:"2"`
+	}
+
+	type Message struct {
+		FIXMsgType  string   `fix:"0"`
+		GroupField1 []Group1 `fix:"100"`
+		StringField string   `fix:"112"`
+		GroupField2 []Group2 `fix:"101"`
+	}
+
+	s := "world"
+	m := Message{
+		GroupField1: []Group1{Group1{StringField1: "hello", StringField2: &s}, Group1{StringField1: "goodbye"}, Group1{StringField1: "OHHAI", StringField2: &s}},
+		StringField: "world",
+		GroupField2: []Group2{Group2{IntField1: 1, IntField2: 42}},
+	}
+	fixMsg := quickfix.Marshal(m)
+
+	group1Template := quickfix.GroupTemplate{
+		quickfix.GroupElement(quickfix.Tag(8)),
+		quickfix.GroupElement(quickfix.Tag(9)),
+	}
+
+	group2Template := quickfix.GroupTemplate{
+		quickfix.GroupElement(quickfix.Tag(1)),
+		quickfix.GroupElement(quickfix.Tag(2)),
+	}
+
+	var tests = []struct {
+		groupTag          quickfix.Tag
+		expectedGroupSize int
+		template          quickfix.GroupTemplate
+	}{
+		{quickfix.Tag(100), 3, group1Template},
+		{quickfix.Tag(101), 1, group2Template},
+	}
+
+	for _, test := range tests {
+		if !fixMsg.Body.Has(test.groupTag) {
+			t.Errorf("Expected tag %v", test.groupTag)
+			continue
+		}
+
+		groupField := quickfix.RepeatingGroup{Tag: test.groupTag, GroupTemplate: test.template}
+		if err := fixMsg.Body.GetGroup(&groupField); err != nil {
+			t.Error("Unexpected error", err)
+			continue
+		}
+
+		if len(groupField.Groups) != test.expectedGroupSize {
+			t.Errorf("Expected group %v to have size %v, got %v", test.groupTag, test.expectedGroupSize, len(groupField.Groups))
+		}
+	}
+}
+
+func TestMarshal_HeaderTrailer(t *testing.T) {
+	type Header struct {
+		StringField string `fix:"49"`
+	}
+	type Trailer struct {
+		IntField int `fix:"89"`
+	}
+
+	type Message struct {
+		FIXMsgType  string `fix:"0"`
+		StringField string `fix:"112"`
+		Header
+		Trailer
+	}
+
+	m := Message{
+		Header:      Header{StringField: "hello"},
+		StringField: "world",
+		Trailer:     Trailer{IntField: 3},
+	}
+	fixMsg := quickfix.Marshal(m)
+
+	var tests = []struct {
+		tag        quickfix.Tag
+		fieldValue quickfix.FieldValue
+		fixMsgPart quickfix.FieldMap
+		expected   []byte
+	}{
+		{quickfix.Tag(35), new(quickfix.FIXString), fixMsg.Header, []byte("0")},
+		{quickfix.Tag(49), new(quickfix.FIXString), fixMsg.Header, []byte("hello")},
+		{quickfix.Tag(112), new(quickfix.FIXString), fixMsg.Body, []byte("world")},
+		{quickfix.Tag(89), new(quickfix.FIXInt), fixMsg.Trailer, []byte("3")},
+	}
+
+	for _, test := range tests {
+		if err := test.fixMsgPart.GetField(test.tag, test.fieldValue); err != nil {
+			t.Error("Unexpected error", err)
+		}
+
+		if !bytes.Equal(test.expected, test.fieldValue.Write()) {
+			t.Errorf("Expected %s got %s", test.expected, test.fieldValue.Write())
+		}
+	}
+}
diff --git a/repeating_group.go b/repeating_group.go
index 288223a4..dcdf0903 100644
--- a/repeating_group.go
+++ b/repeating_group.go
@@ -117,7 +117,6 @@ func (f RepeatingGroup) isDelimiter(t Tag) bool {
 }
 
 func (f *RepeatingGroup) Read(tv TagValues) (TagValues, error) {
-	var err error
 	expectedGroupSize, err := atoi(tv[0].Value)
 	if err != nil {
 		return tv, err
@@ -155,5 +154,6 @@ func (f *RepeatingGroup) Read(tv TagValues) (TagValues, error) {
 	if len(f.Groups) != expectedGroupSize {
 		return tv, fmt.Errorf("Only found %v instead of %v expected groups, is template wrong?", len(f.Groups), expectedGroupSize)
 	}
+
 	return tv, err
 }
diff --git a/unmarshal.go b/unmarshal.go
new file mode 100644
index 00000000..af6a063b
--- /dev/null
+++ b/unmarshal.go
@@ -0,0 +1,155 @@
+package quickfix
+
+import (
+	"reflect"
+	"strconv"
+	"time"
+)
+
+type decoder struct {
+	FieldMap
+}
+
+func (d decoder) decodeValue(fixTag Tag, t reflect.Type, v reflect.Value) MessageRejectError {
+	switch v.Kind() {
+	case reflect.Ptr:
+		v.Set(reflect.New(t.Elem()))
+		return d.decodeValue(fixTag, t.Elem(), v.Elem())
+	case reflect.Struct:
+		switch v.Interface().(type) {
+		case time.Time:
+			fieldValue := new(FIXUTCTimestamp)
+			if err := d.FieldMap.GetField(Tag(fixTag), fieldValue); err != nil {
+				return err
+			}
+			v.Set(reflect.ValueOf(fieldValue.Value))
+		}
+	case reflect.String:
+		var fieldValue FIXString
+		if err := d.FieldMap.GetField(Tag(fixTag), &fieldValue); err != nil {
+			return err
+		}
+		v.SetString(string(fieldValue))
+	case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64,
+		reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
+		var fieldValue FIXInt
+		if err := d.FieldMap.GetField(Tag(fixTag), &fieldValue); err != nil {
+			return err
+		}
+		v.SetInt(int64(fieldValue))
+	case reflect.Bool:
+		var fieldValue FIXBoolean
+		if err := d.FieldMap.GetField(Tag(fixTag), &fieldValue); err != nil {
+			return err
+		}
+		v.SetBool(bool(fieldValue))
+	case reflect.Float32, reflect.Float64:
+		var fieldValue FIXFloat
+		if err := d.FieldMap.GetField(Tag(fixTag), &fieldValue); err != nil {
+			return err
+		}
+		v.SetFloat(float64(fieldValue))
+	case reflect.Slice:
+		elem := v.Type().Elem()
+		if elem.Kind() != reflect.Struct {
+			panic("repeating group must be a slice of type struct")
+		}
+		template := make(GroupTemplate, elem.NumField())
+		for i := 0; i < elem.NumField(); i++ {
+			sf := elem.Field(i)
+			fixTag, err := strconv.Atoi(sf.Tag.Get("fix"))
+
+			if err != nil {
+				panic(err)
+			}
+
+			template[i] = GroupElement(Tag(fixTag))
+		}
+		repeatingGroup := RepeatingGroup{Tag: fixTag, GroupTemplate: template}
+		if err := d.FieldMap.GetGroup(&repeatingGroup); err != nil {
+			return err
+		}
+
+		v.Set(reflect.MakeSlice(v.Type(), len(repeatingGroup.Groups), len(repeatingGroup.Groups)))
+
+		for i := 0; i < len(repeatingGroup.Groups); i++ {
+			groupDecoder := decoder{FieldMap: repeatingGroup.Groups[i].FieldMap}
+
+			for j := 0; j < v.Type().Elem().NumField(); j++ {
+				sf := v.Type().Elem().Field(j)
+				sv := v.Index(i).Field(j)
+
+				if err := groupDecoder.decodeField(sf, sf.Type, sv); err != nil {
+					return err
+				}
+			}
+		}
+	}
+	return nil
+}
+
+func (d decoder) decodeField(sf reflect.StructField, t reflect.Type, v reflect.Value) MessageRejectError {
+	if sf.Tag.Get("fix") != "" {
+		fixTag, err := strconv.Atoi(sf.Tag.Get("fix"))
+		if err != nil {
+			panic(err)
+		}
+
+		if !d.FieldMap.Has(Tag(fixTag)) {
+			return nil
+		}
+
+		return d.decodeValue(Tag(fixTag), t, v)
+	}
+
+	switch t.Kind() {
+	case reflect.Ptr:
+		v.Set(reflect.New(t.Elem()))
+		return d.decodeField(sf, t.Elem(), v.Elem())
+	case reflect.Struct:
+		for i := 0; i < t.NumField(); i++ {
+			if err := d.decodeField(t.Field(i), t.Field(i).Type, v.Field(i)); err != nil {
+				return err
+			}
+		}
+	}
+
+	return nil
+}
+
+// Unmarshal populates v from a Message
+func Unmarshal(m Message, v interface{}) MessageRejectError {
+	reflectValue := reflect.ValueOf(v)
+
+	switch reflectValue.Kind() {
+	case reflect.Ptr:
+		reflectValue = reflectValue.Elem()
+	default:
+		panic("v not a pointer")
+	}
+
+	reflectType := reflectValue.Type()
+	d := decoder{}
+
+	for i := 0; i < reflectType.NumField(); i++ {
+		sf := reflectType.Field(i)
+		sv := reflectValue.Field(i)
+
+		switch sf.Name {
+		case "FIXMsgType":
+			continue
+		case "Header":
+			d.FieldMap = m.Header
+		case "Trailer":
+			d.FieldMap = m.Trailer
+		default:
+			d.FieldMap = m.Body
+		}
+
+		if err := d.decodeField(sf, sf.Type, sv); err != nil {
+			return err
+		}
+	}
+
+	return nil
+}
diff --git a/unmarshal_test.go b/unmarshal_test.go
new file mode 100644
index 00000000..1237e51b
--- /dev/null
+++ b/unmarshal_test.go
@@ -0,0 +1,228 @@
+package quickfix_test
+
+import (
+	"github.com/quickfixgo/quickfix"
+	"testing"
+	"time"
+)
+
+func TestUnmarshal_Literals(t *testing.T) {
+	type Message struct {
+		FIXMsgType        string    `fix:"0"`
+		StringField       string    `fix:"114"`
+		IntField          int       `fix:"115"`
+		UnsetIntField     int       `fix:"116"`
+		BoolField         bool      `fix:"117"`
+		FloatField        float64   `fix:"118"`
+		UTCTimestampField time.Time `fix:"119"`
+	}
+
+	fixMsg := quickfix.NewMessage()
+	fixMsg.Body.SetField(quickfix.Tag(114), quickfix.FIXString("world"))
+	fixMsg.Body.SetField(quickfix.Tag(115), quickfix.FIXInt(3))
+	fixMsg.Body.SetField(quickfix.Tag(117), quickfix.FIXBoolean(true))
+	fixMsg.Body.SetField(quickfix.Tag(118), quickfix.FIXFloat(500.123))
+	tVal, _ := time.Parse("2006-Jan-02", "2014-Jun-16")
+	fixMsg.Body.SetField(quickfix.Tag(119), quickfix.FIXUTCTimestamp{Value: tVal})
+
+	var msgOut Message
+	quickfix.Unmarshal(fixMsg, &msgOut)
+
+	var tests = []struct {
+		expected interface{}
+		actual   interface{}
+	}{
+		{"world", msgOut.StringField},
+		{3, msgOut.IntField},
+		{0, msgOut.UnsetIntField},
+		{true, msgOut.BoolField},
+		{500.123, msgOut.FloatField},
+		{tVal, msgOut.UTCTimestampField},
+	}
+
+	for _, test := range tests {
+		if test.expected != test.actual {
+			t.Errorf("Expected %v got %v", test.expected, test.actual)
+		}
+	}
+}
+
+func TestUnmarshal_LiteralsOptional(t *testing.T) {
+	type Message struct {
+		FIXMsgType           string     `fix:"0"`
+		StringPtrField       *string    `fix:"112"`
+		UnsetStringPtrField  *string    `fix:"113"`
+		IntPtrField          *int       `fix:"114"`
+		BoolPtrField         *bool      `fix:"115"`
+		FloatPtrField        *float64   `fix:"116"`
+		UTCTimestampPtrField *time.Time `fix:"117"`
+	}
+
+	fixMsg := quickfix.NewMessage()
+	fixMsg.Body.SetField(quickfix.Tag(112), quickfix.FIXString("world"))
+	fixMsg.Body.SetField(quickfix.Tag(114), quickfix.FIXInt(3))
+	fixMsg.Body.SetField(quickfix.Tag(115), quickfix.FIXBoolean(false))
+	fixMsg.Body.SetField(quickfix.Tag(116), quickfix.FIXFloat(500.123))
+	tVal, _ := time.Parse("2006-Jan-02", "2014-Jun-16")
+	fixMsg.Body.SetField(quickfix.Tag(117), quickfix.FIXUTCTimestamp{Value: tVal})
+
+	var msgOut Message
+	quickfix.Unmarshal(fixMsg, &msgOut)
+
+	var tests = []struct {
+		testName      string
+		expectNil     bool
+		actualIsNil   bool
+		expectedDeref interface{}
+		actualDeref   interface{}
+	}{
+		{"set string ptr", false, msgOut.StringPtrField == nil, "world", *msgOut.StringPtrField},
+		{"unset string ptr", true, msgOut.UnsetStringPtrField == nil, "", "unset"},
+		{"int ptr", false, msgOut.IntPtrField == nil, 3, *msgOut.IntPtrField},
+		{"bool ptr", false, msgOut.BoolPtrField == nil, false, *msgOut.BoolPtrField},
+		{"float ptr", false, msgOut.FloatPtrField == nil, 500.123, *msgOut.FloatPtrField},
+		{"timestamp ptr", false, msgOut.UTCTimestampPtrField == nil, tVal, *msgOut.UTCTimestampPtrField},
+	}
+
+	for _, test := range tests {
+		if test.expectNil {
+			if !test.actualIsNil {
+				t.Errorf("%v: Expected nil not %v", test.testName, test.actualDeref)
+			}
+			continue
+		}
+
+		if test.actualIsNil {
+			t.Errorf("%v: Did not expect nil", test.testName)
+			continue
+		}
+
+		if test.expectedDeref != test.actualDeref {
+			t.Errorf("Expected '%v' got '%v'", test.expectedDeref, test.actualDeref)
+		}
+	}
+}
+
+func TestUnmarshal_Components(t *testing.T) {
+	type Component1 struct {
+		StringField string `fix:"8"`
+	}
+	type Component2 struct {
+		IntField int `fix:"1"`
+	}
+
+	type Message struct {
+		FIXMsgType string `fix:"0"`
+		Component1
+		StringField string `fix:"112"`
+		*Component2
+	}
+
+	fixMsg := quickfix.NewMessage()
+	fixMsg.Body.SetField(quickfix.Tag(8), quickfix.FIXString("hello"))
+	fixMsg.Body.SetField(quickfix.Tag(112), quickfix.FIXString("world"))
+
+	var msgOut Message
+	quickfix.Unmarshal(fixMsg, &msgOut)
+
+	if msgOut.Component1.StringField != "hello" {
+		t.Errorf("Expected '%v' got '%v'", "hello", msgOut.Component1.StringField)
+	}
+
+	if msgOut.StringField != "world" {
+		t.Errorf("Expected '%v' got '%v'", "world", msgOut.StringField)
+	}
+}
+
+func TestUnmarshal_RepeatingGroups(t *testing.T) {
+	type Group1 struct {
+		StringField1 string  `fix:"8"`
+		StringField2 *string `fix:"9"`
+	}
+
+	type Group2 struct {
+		IntField1 int `fix:"1"`
+		IntField2 int `fix:"2"`
+	}
+
+	type Message struct {
+		FIXMsgType  string   `fix:"0"`
+		GroupField1 []Group1 `fix:"100"`
+		StringField string   `fix:"112"`
+		GroupField2 []Group2 `fix:"101"`
+	}
+
+	fixMsg := quickfix.NewMessage()
+	fixMsg.Body.SetField(quickfix.Tag(112), quickfix.FIXString("world"))
+
+	group1Template := quickfix.GroupTemplate{
+		quickfix.GroupElement(quickfix.Tag(8)),
+		quickfix.GroupElement(quickfix.Tag(9)),
+	}
+
+	group1 := quickfix.RepeatingGroup{Tag: quickfix.Tag(100), GroupTemplate: group1Template}
+	group1.Add().SetField(quickfix.Tag(8), quickfix.FIXString("hello")).SetField(quickfix.Tag(9), quickfix.FIXString("world"))
+	group1.Add().SetField(quickfix.Tag(8), quickfix.FIXString("goodbye"))
+	group1.Add().SetField(quickfix.Tag(8), quickfix.FIXString("OHHAI")).SetField(quickfix.Tag(9), quickfix.FIXString("world"))
+	fixMsg.Body.SetGroup(group1)
+
+	var msgOut Message
+	quickfix.Unmarshal(fixMsg, &msgOut)
+
+	if msgOut.StringField != "world" {
+		t.Errorf("Expected '%v' got '%v'", "world", msgOut.StringField)
+	}
+
+	if len(msgOut.GroupField1) != 3 {
+		t.Errorf("Expected group size '%v' got '%v'", 3, len(msgOut.GroupField1))
+	}
+
+	if msgOut.GroupField1[0].StringField1 != "hello" {
+		t.Errorf("Expected %v got %v", "hello", msgOut.GroupField1[0].StringField1)
+	}
+
+	if *(msgOut.GroupField1[0].StringField2) != "world" {
+		t.Errorf("Expected %v got %v", "world", *(msgOut.GroupField1[0].StringField2))
+	}
+
+	if msgOut.GroupField1[1].StringField1 != "goodbye" {
+		t.Errorf("Expected %v got %v", "goodbye", msgOut.GroupField1[1].StringField1)
+	}
+
+	if msgOut.GroupField1[1].StringField2 != nil {
+		t.Errorf("Expected stringfield 2 to be nil, got %v", *(msgOut.GroupField1[1].StringField2))
+	}
+}
+
+func TestUnmarshal_HeaderTrailer(t *testing.T) {
+	type Header struct {
+		StringField string `fix:"49"`
+	}
+	type Trailer struct {
+		IntField int `fix:"89"`
+	}
+
+	type Message struct {
+		FIXMsgType  string `fix:"0"`
+		StringField string `fix:"112"`
+		Header
+		Trailer
+	}
+
+	fixMsg := quickfix.NewMessage()
+	fixMsg.Header.SetField(quickfix.Tag(35), quickfix.FIXString("0"))
+	fixMsg.Header.SetField(quickfix.Tag(49), quickfix.FIXString("hello"))
+	fixMsg.Body.SetField(quickfix.Tag(112), quickfix.FIXString("world"))
+	fixMsg.Trailer.SetField(quickfix.Tag(89), quickfix.FIXInt(3))
+
+	var msgOut Message
+	quickfix.Unmarshal(fixMsg, &msgOut)
+
+	if msgOut.Header.StringField != "hello" {
+		t.Errorf("Expected '%v' got '%v'", "hello", msgOut.Header.StringField)
+	}
+	if msgOut.Trailer.IntField != 3 {
+		t.Errorf("Expected '%v' got '%v'", 3, msgOut.Trailer.IntField)
+	}
+
+}
-- 
GitLab