Browse Source

codec: Support encoding structs as arrays (not just as maps).

Encoding struct as array allows type-safe arrays of different go types.

Also, cleanup decode.go by removing rawToStringOverride.
This was a vestige of the old msgpack extension support which should never
have bled into decode.go.

Updates #13 .
Ugorji Nwoke 12 years ago
parent
commit
67e784dc3e
6 changed files with 82 additions and 48 deletions
  1. 1 0
      codec/binc.go
  2. 8 9
      codec/codecs_test.go
  3. 1 9
      codec/decode.go
  4. 52 16
      codec/encode.go
  5. 17 9
      codec/helper.go
  6. 3 5
      codec/msgpack.go

+ 1 - 0
codec/binc.go

@@ -26,6 +26,7 @@ import (
 //Note that these EXCEPTIONS are temporary and full support is possible and may happen soon.
 type BincHandle struct {
 	encdecHandle
+	EncodeOptions
 	DecodeOptions
 }
 

+ 8 - 9
codec/codecs_test.go

@@ -79,7 +79,8 @@ func init() {
 		fmt.Printf("====> depth: %v, ts: %#v\n", 2, ts0)
 	}
 
-	testMsgpackH.AddExt(byteSliceTyp, 0, testMsgpackH.BinaryEncodeExt, testMsgpackH.BinaryDecodeExt)
+	testMsgpackH.RawToString = true 
+	//testMsgpackH.AddExt(byteSliceTyp, 0, testMsgpackH.BinaryEncodeExt, testMsgpackH.BinaryDecodeExt)
 	testMsgpackH.AddExt(timeTyp, 1, testMsgpackH.TimeEncodeExt, testMsgpackH.TimeDecodeExt)
 }
 
@@ -507,17 +508,17 @@ func doTestCodecTableOne(t *testing.T, testNil bool, h Handle,
 func testCodecTableOne(t *testing.T, h Handle) {
 	// func TestMsgpackAllExperimental(t *testing.T) {
 	// dopts := testDecOpts(nil, nil, false, true, true),
-	var oldWriteExt bool
+	var oldWriteExt, oldRawToString bool
 	switch v := h.(type) {
 	case *MsgpackHandle:
-		oldWriteExt = v.WriteExt
-		v.WriteExt = true
+		oldWriteExt, v.WriteExt = v.WriteExt, true 
+		oldRawToString, v.RawToString = v.RawToString, true 
 	}
 	doTestCodecTableOne(t, false, h, table, tableVerify)
 	//if true { panic("") }
 	switch v := h.(type) {
 	case *MsgpackHandle:
-		v.WriteExt = oldWriteExt
+		v.WriteExt, v.RawToString = oldWriteExt, oldRawToString
 	}
 	// func TestMsgpackAll(t *testing.T) {
 	
@@ -530,11 +531,9 @@ func testCodecTableOne(t *testing.T, h Handle) {
 	var oldMapType reflect.Type
 	switch v := h.(type) {
 	case *MsgpackHandle:
-		oldMapType = v.MapType
-		v.MapType = mapStringIntfTyp
+		oldMapType, v.MapType = v.MapType, mapStringIntfTyp
 	case *BincHandle:
-		oldMapType = v.MapType
-		v.MapType = mapStringIntfTyp
+		oldMapType, v.MapType = v.MapType, mapStringIntfTyp
 	}
 	//skip time.Time, []interface{} containing time.Time, last map, and newStruc
 	doTestCodecTableOne(t, true, h, table[:idxTime], tableTestNilVerify[:idxTime])

+ 1 - 9
codec/decode.go

@@ -115,8 +115,6 @@ type decHandle struct {
 	// put word-aligned fields first (before bools, etc)
 	exts     []decExtTypeTagFn
 	extFuncs map[uintptr]decExtTagFn
-	// if an extension for byte slice is defined, then always decode Raw as strings
-	rawToStringOverride bool
 }
 
 type DecodeOptions struct {
@@ -151,17 +149,11 @@ func (o *decHandle) addDecodeExt(rtid uintptr, rt reflect.Type, tag byte, fn fun
 					break
 				}
 			}
-			if rtid == byteSliceTypId {
-				o.rawToStringOverride = false
-			}
 		}
 	}
 	if fn != nil {
 		o.extFuncs[rtid] = decExtTagFn{fn, tag}
 		o.exts = append(o.exts, decExtTypeTagFn{rtid, rt, decExtTagFn{fn, tag}})
-		if rtid == byteSliceTypId {
-			o.rawToStringOverride = true
-		}
 	}
 }
 
@@ -440,7 +432,7 @@ func (d *Decoder) decodeValue(rv reflect.Value) {
 				rvkencname := dd.decodeString()
 				// rvksi := sfi.getForEncName(rvkencname)
 				if k := sfi.indexForEncName(rvkencname); k > -1 {
-					sfik := sfi[k]
+					sfik := sfi.sis[k]
 					if sfik.i > -1 {
 						d.decodeValue(rv.Field(int(sfik.i)))
 					} else {

+ 52 - 16
codec/encode.go

@@ -52,6 +52,7 @@ type encDriver interface {
 type encodeHandleI interface {
 	getEncodeExt(rt uintptr) (tag byte, fn func(reflect.Value) ([]byte, error))
 	writeExt() bool
+	structToArray() bool
 }
 
 // An Encoder writes an object to an output stream in the codec format.
@@ -107,6 +108,11 @@ type encHandle struct {
 	exts     []encExtTypeTagFn
 }
 
+type EncodeOptions struct {
+	// Encode a struct as an array, and not as a map.
+	StructToArray bool
+}
+
 func (o *simpleIoEncWriterWriter) WriteByte(c byte) (err error) {
 	if o.bw != nil {
 		return o.bw.WriteByte(c)
@@ -168,6 +174,10 @@ func (o *encHandle) getEncodeExt(rt uintptr) (tag byte, fn func(reflect.Value) (
 	return
 }
 
+func (o *EncodeOptions) structToArray() bool {
+	return o.StructToArray
+}
+
 // NewEncoder returns an Encoder for encoding into an io.Writer.
 // 
 // For efficiency, Users are encouraged to pass in a memory buffered writer
@@ -206,10 +216,14 @@ func NewEncoderBytes(out *[]byte, h Handle) *Encoder {
 
 // Encode writes an object into a stream in the codec format.
 //
-// Struct values encode as maps. Each exported struct field is encoded unless:
-//    - the field's tag is "-", or
+// Struct values "usually" encode as maps. Each exported struct field is encoded unless:
+//    - the field's tag is "-", OR
 //    - the field is empty and its tag specifies the "omitempty" option.
 //
+// However, struct values may encode as arrays. This happens if:
+//    - StructToArray Encode option is set, OR
+//    - the tag on the _struct field sets the "toarray" option
+// 
 // The empty values are false, 0, any nil pointer or interface value,
 // and any array, slice, map, or string of length zero.
 //
@@ -234,6 +248,11 @@ func NewEncoderBytes(out *[]byte, h Handle) *Encoder {
 //          Field4 bool     `codec:"f4,omitempty"` //use key "f4". Omit if empty.
 //          ...
 //      }
+//      
+//      type MyStruct struct {
+//          _struct bool    `codec:",omitempty,toarray"`   //set omitempty for every field
+//                                                         //and encode struct as an array
+//      }   
 //
 // Note:
 //   - Encode will treat struct field names and keys in map[string]XXX as symbols.
@@ -429,35 +448,52 @@ func (e *Encoder) encodeValue(rv reflect.Value) {
 }
 
 func (e *Encoder) encStruct(sis structFieldInfos, rv reflect.Value) {
-	newlen := len(sis)
+	newlen := len(sis.sis)
 	rvals := make([]reflect.Value, newlen)
-	encnames := make([]string, newlen)
+	var encnames []string
+	toMap := !(sis.toArray || e.h.structToArray())
+	if toMap {
+		encnames = make([]string, newlen)
+	}
 	newlen = 0
 	// var rv0 reflect.Value
 	// for i := 0; i < l; i++ {
 	// 	si := sis[i]
-	for _, si := range sis {
-		if si.i > -1 {
+	for _, si := range sis.sis {
+		if si.i != -1 {
 			rvals[newlen] = rv.Field(int(si.i))
 		} else {
 			rvals[newlen] = rv.FieldByIndex(si.is)
 		}
-		if si.omitEmpty && isEmptyValue(rvals[newlen]) {
-			continue
+		if toMap {
+			if si.omitEmpty && isEmptyValue(rvals[newlen]) {
+				continue
+			}
+			encnames[newlen] = si.encName
+		} else {
+			if si.omitEmpty && isEmptyValue(rvals[newlen]) {
+				rvals[newlen] = reflect.Value{}
+			}
 		}
-		// sivals[newlen] = i
-		encnames[newlen] = si.encName
 		newlen++
 	}
-	ee := e.e //don't dereference everytime
-	ee.encodeMapPreamble(newlen)
-	for j := 0; j < newlen; j++ {
-		//e.encString(sis[sivals[j]].encName)
-		ee.encodeSymbol(encnames[j])
-		e.encodeValue(rvals[j])
+
+	if toMap {
+		ee := e.e //don't dereference everytime
+		ee.encodeMapPreamble(newlen)
+		for j := 0; j < newlen; j++ {
+			ee.encodeSymbol(encnames[j])
+			e.encodeValue(rvals[j])
+		}
+	} else {
+		e.e.encodeArrayPreamble(newlen)
+		for j := 0; j < newlen; j++ {
+			e.encodeValue(rvals[j])
+		}
 	}
 }
 
+
 // ----------------------------------------
 
 func (z *ioEncWriter) writeUint16(v uint16) {

+ 17 - 9
codec/helper.go

@@ -98,13 +98,17 @@ type structFieldInfo struct {
 	is        []int
 	i         int16 // field index in struct
 	omitEmpty bool
+	toArray   bool
 	// tag       string   // tag
 	// name      string   // field name
 	// encNameBs []byte   // encoded name as byte stream
 	// ikind     int      // kind of the field as an int i.e. int(reflect.Kind)
 }
 
-type structFieldInfos []structFieldInfo
+type structFieldInfos struct {
+	toArray bool 
+	sis     []structFieldInfo
+}
 
 type sfiSortedByEncName []*structFieldInfo
 
@@ -121,11 +125,11 @@ func (p sfiSortedByEncName) Swap(i, j int) {
 }
 
 func (sis structFieldInfos) indexForEncName(name string) int {
-	sislen := len(sis)
+	sislen := len(sis.sis)
 	if sislen < binarySearchThreshold {
 		// linear search. faster than binary search in my testing up to 16-field structs.
 		for i := 0; i < sislen; i++ {
-			if sis[i].encName == name {
+			if sis.sis[i].encName == name {
 				return i
 			}
 		}
@@ -135,13 +139,13 @@ func (sis structFieldInfos) indexForEncName(name string) int {
 		for i < j {
 			h = i + (j-i)/2
 			// i ≤ h < j
-			if sis[h].encName < name {
+			if sis.sis[h].encName < name {
 				i = h + 1 // preserves f(i-1) == false
 			} else {
 				j = h // preserves f(j) == true
 			}
 		}
-		if i < sislen && sis[i].encName == name {
+		if i < sislen && sis.sis[i].encName == name {
 			return i
 		}
 	}
@@ -166,15 +170,16 @@ func getStructFieldInfos(rtid uintptr, rt reflect.Type) (sis structFieldInfos) {
 	var siInfo *structFieldInfo
 	if f, ok := rt.FieldByName(structInfoFieldName); ok {
 		siInfo = parseStructFieldInfo(structInfoFieldName, f.Tag.Get(structTagName))
+		sis.toArray = siInfo.toArray
 	}
 	sisp := make([]*structFieldInfo, 0, rt.NumField())
 	rgetStructFieldInfos(rt, nil, make(map[string]bool), &sisp, siInfo)
 	sort.Sort(sfiSortedByEncName(sisp))
 
-	lsis := len(sisp)
-	sis = make([]structFieldInfo, lsis)
+	lsis := len(sisp)	
+	sis.sis = make([]structFieldInfo, lsis)
 	for i := 0; i < lsis; i++ {
-		sis[i] = *sisp[i]
+		sis.sis[i] = *sisp[i]
 	}
 	// sis = sisp
 	cachedStructFieldInfos[rtid] = sis
@@ -242,8 +247,11 @@ func parseStructFieldInfo(fname string, stag string) *structFieldInfo {
 					si.encName = s
 				}
 			} else {
-				if s == "omitempty" {
+				switch s {
+				case "omitempty":
 					si.omitEmpty = true
+				case "toarray":
+					si.toArray = true
 				}
 			}
 		}

+ 3 - 5
codec/msgpack.go

@@ -93,9 +93,6 @@ type msgpackSpecRpcCodec struct {
 //MsgpackHandle is a Handle for the Msgpack Schema-Free Encoding Format.
 type MsgpackHandle struct {
 	// RawToString controls how raw bytes are decoded into a nil interface{}.
-	// Note that setting an extension func for []byte ensures that raw bytes
-	// are decoded as strings, regardless of this setting.
-	// This setting is used only if an extension func isn't defined for []byte.
 	RawToString bool
 	// WriteExt flag supports encoding configured extensions with extension tags.
 	// It also controls whether other elements of the new spec are encoded (ie Str8).
@@ -110,6 +107,7 @@ type MsgpackHandle struct {
 	WriteExt bool
 
 	encdecHandle
+	EncodeOptions
 	DecodeOptions
 }
 
@@ -327,7 +325,7 @@ func (d *msgpackDecDriver) decodeNaked() (rv reflect.Value, ctx decodeNakedConte
 		case bd == mpStr8, bd == mpStr16, bd == mpStr32, bd >= mpFixStrMin && bd <= mpFixStrMax:
 			ctx = dncContainer
 			// v = containerRaw
-			if d.h.rawToStringOverride || d.h.RawToString {
+			if d.h.RawToString {
 				var rvm string
 				rv = reflect.ValueOf(&rvm).Elem()
 			} else {
@@ -581,7 +579,7 @@ func (d *msgpackDecDriver) currentEncodedType() decodeEncodedType {
 		case bd >= mpNegFixNumMin && bd <= mpNegFixNumMax:
 			d.bdType = detInt
 		case bd == mpStr8, bd == mpStr16, bd == mpStr32, bd >= mpFixStrMin && bd <= mpFixStrMax:
-			if d.h.rawToStringOverride || d.h.RawToString {
+			if d.h.RawToString {
 				d.bdType = detString
 			} else {
 				d.bdType = detBytes