Go-模拟PHP序列化和反序列化【解决数字精度问题】

本代码片段根源是网上的序列化包,是保存到本地之后,进行的更改,其中引入了【decimal】包来解决反序列化数字精度问题,代码如下:

package utils

import (
	"bytes"
	"fmt"
	"reflect"
	"sort"
	"strconv"
	"strings"

	"github.com/shopspring/decimal"
)

const UNSERIALIZABLE_OBJECT_MAX_LEN = int64(10 * 1024 * 1024 * 1024)

func Marshal(value interface{}) ([]byte, error) {

	if value == nil {
		return MarshalNil(), nil
	}

	t := reflect.TypeOf(value)
	switch t.Kind() {
	case reflect.Bool:
		return MarshalBool(value.(bool)), nil
	case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64,
		reflect.Uint, reflect.Uint16, reflect.Uint32, reflect.Uint64,
		reflect.Float32, reflect.Float64:
		return MarshalNumber(value), nil
	case reflect.String:
		return MarshalString(value.(string)), nil
	case reflect.Map:
		return MarshalMap(value)
	case reflect.Slice:
		return MarshalSlice(value)
	case reflect.Struct:
		return []byte("d:" + value.(decimal.Decimal).String() + ";"), nil
	default:
		return nil, fmt.Errorf("Marshal: Unknown type %T with value %#v", t, value)
	}
}

func MarshalNil() []byte {
	return []byte("N;")
}

func MarshalBool(value bool) []byte {
	if value {
		return []byte("b:1;")
	}

	return []byte("b:0;")
}

func MarshalNumber(value interface{}) []byte {
	var val string

	isFloat := false

	switch value.(type) {
	default:
		val = "0"
	case int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64:
		val, _ = NumericalToString(value)
	case float32, float64:
		val, _ = NumericalToString(value)
		isFloat = true
	}

	if isFloat {
		return []byte("d:" + val + ";")

	} else {
		return []byte("i:" + val + ";")
	}
}

func MarshalString(value string) []byte {
	return []byte(fmt.Sprintf("s:%d:\"%s\";", len(value), value))
}

func MarshalMap(value interface{}) ([]byte, error) {

	s := reflect.ValueOf(value)

	mapKeys := s.MapKeys()
	sort.Slice(mapKeys, func(i, j int) bool {
		return LessValue(mapKeys[i], mapKeys[j])
	})

	var buffer bytes.Buffer
	for _, mapKey := range mapKeys {
		m, err := Marshal(mapKey.Interface())
		if err != nil {
			return nil, err
		}

		buffer.Write(m)

		m, err = Marshal(s.MapIndex(mapKey).Interface())
		if err != nil {
			return nil, err
		}

		buffer.Write(m)
	}

	return []byte(fmt.Sprintf("a:%d:{%s}", s.Len(), buffer.String())), nil
}

func MarshalSlice(value interface{}) ([]byte, error) {
	s := reflect.ValueOf(value)

	var buffer bytes.Buffer
	for i := 0; i < s.Len(); i++ {
		m, err := Marshal(i)
		if err != nil {
			return nil, err
		}

		buffer.Write(m)

		m, err = Marshal(s.Index(i).Interface())
		if err != nil {
			return nil, err
		}

		buffer.Write(m)
	}

	return []byte(fmt.Sprintf("a:%d:{%s}", s.Len(), buffer.String())), nil
}

// numericalValue returns the float64 representation of a value if it is a
// numerical type - integer, unsigned integer or float. If the value is not a
// numerical type then the second argument is false and the value returned
// should be disregarded.
func NumericalValue(value reflect.Value) (float64, bool) {
	switch value.Type().Kind() {
	case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
		return float64(value.Int()), true

	case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
		return float64(value.Uint()), true

	case reflect.Float32, reflect.Float64:
		return value.Float(), true

	default:
		return 0, false
	}
}

// lessValue compares two reflect.Value instances and returns true if a is
// considered to be less than b.
//
// This function is used to sort keys for what amounts to associate arrays and
// objects in PHP. These are represented as slices and maps in Go. Since Go
// randomised map iterators we need to make sure we always return the keys of an
// associative array or object in a predicable order.
//
// The keys can be numerical, strings or a combination of both. We treat numbers
// (integers, unsigned integers and floats) as always less than strings. Numbers
// are ordered by magnitude (ignoring types) and strings are orders
// lexicographically.
//
// If keys are of any other type the behavior of the comparison is undefined. If
// there is a legitimate reason why keys could be other types then this function
// should be updated accordingly.
func LessValue(a, b reflect.Value) bool {
	aValue, aNumerical := NumericalValue(a)
	bValue, bNumerical := NumericalValue(b)

	if aNumerical && bNumerical {
		return aValue < bValue
	}

	if !aNumerical && !bNumerical {
		// In theory this should mean they are both strings. In reality
		// they could be any other type and the String() representation
		// will be something like "<bool>" if it is not a string. Since
		// distinct values of non-strings still return the same value
		// here that's what makes the ordering undefined.
		return strings.Compare(a.String(), b.String()) < 0
	}

	// Numerical values are always treated as less than other types
	// (including strings that might represent numbers themselves). The
	// inverse is also true.
	return aNumerical && !bNumerical
}

func LowerCaseFirstLetter(s string) string {
	return strings.ToLower(s[0:1]) + s[1:]
}

func NumericalToString(value interface{}) (string, bool) {
	var val string

	switch value.(type) {
	default:
		return "0", false
	case int:
		intVal, _ := value.(int)
		val = strconv.FormatInt(int64(intVal), 10)
	case int8:
		intVal, _ := value.(int8)
		val = strconv.FormatInt(int64(intVal), 10)
	case int16:
		intVal, _ := value.(int16)
		val = strconv.FormatInt(int64(intVal), 10)
	case int32:
		intVal, _ := value.(int32)
		val = strconv.FormatInt(int64(intVal), 10)
	case int64:
		intVal, _ := value.(int64)
		val = strconv.FormatInt(int64(intVal), 10)
	case uint:
		intVal, _ := value.(uint)
		val = strconv.FormatUint(uint64(intVal), 10)
	case uint8:
		intVal, _ := value.(uint8)
		val = strconv.FormatUint(uint64(intVal), 10)
	case uint16:
		intVal, _ := value.(uint16)
		val = strconv.FormatUint(uint64(intVal), 10)
	case uint32:
		intVal, _ := value.(uint32)
		val = strconv.FormatUint(uint64(intVal), 10)
	case uint64:
		intVal, _ := value.(uint64)
		val = strconv.FormatUint(uint64(intVal), 10)
	case float32:
		floatVal, _ := value.(float32)
		val = strconv.FormatFloat(float64(floatVal), 'f', -1, 32)
	case float64:
		floatVal, _ := value.(float64)
		val = strconv.FormatFloat(float64(floatVal), 'f', -1, 64)
	}
	return val, true
}

func UnMarshal(data []byte) (interface{}, error) {
	reader := bytes.NewReader(data)
	return unMarshalByReader(reader)
}

func unMarshalByReader(reader *bytes.Reader) (interface{}, error) {

	for {

		if token, _, err := reader.ReadRune(); err == nil {
			switch token {
			default:
				return nil, fmt.Errorf("UnMarshal: Unknown token %#U", token)
			case 'N':
				return unMarshalNil(reader)
			case 'b':
				return unMarshalBool(reader)
			case 'i':
				return unMarshalNumber(reader, false)
			case 'd':
				return unMarshalNumber(reader, true)
			case 's':
				return unMarshalString(reader, true)
			case 'a':
				return unMarshalArray(reader)
				// case 'O':

				// case 'C':

				// case 'R', 'r':

				// case 'x':

			}
		}
		return nil, nil
	}

}

func unMarshalNil(reader *bytes.Reader) (interface{}, error) {
	expect(reader, ';')

	return nil, nil
}

func unMarshalBool(reader *bytes.Reader) (interface{}, error) {
	var (
		raw rune
		err error
	)
	err = expect(reader, ':')
	if err != nil {
		return nil, err
	}

	if raw, _, err = reader.ReadRune(); err != nil {
		return nil, fmt.Errorf("UnMarshal: Error while reading bool value: %v", err)
	}

	err = expect(reader, ';')
	if err != nil {
		return nil, err
	}
	return raw == '1', nil
}

func unMarshalNumber(reader *bytes.Reader, isFloat bool) (interface{}, error) {
	var (
		raw string
		err error
		val interface{}
	)
	err = expect(reader, ':')
	if err != nil {
		return nil, err
	}

	if raw, err = readUntil(reader, ';'); err != nil {
		return nil, fmt.Errorf("UnMarshal: Error while reading number value: %v", err)
	} else {
		if isFloat {
			if s, err := decimal.NewFromString(raw); err != nil {
				return nil, fmt.Errorf("UnMarshal: Unable to convert %s to float: %v", raw, err)
			} else {
				val = s.Round(100)
			}
		} else {
			if val, err = strconv.Atoi(raw); err != nil {
				return nil, fmt.Errorf("UnMarshal: Unable to convert %s to int: %v", raw, err)
			}
		}
	}

	return val, nil
}

func unMarshalString(reader *bytes.Reader, isFinal bool) (string, error) {
	var (
		err     error
		val     string
		strLen  int
		readLen int
	)

	strLen, err = readLength(reader)

	err = expect(reader, '"')
	if err != nil {
		return val, err
	}

	if strLen > 0 {
		buf := make([]byte, strLen, strLen)
		if readLen, err = reader.Read(buf); err != nil {
			return val, fmt.Errorf("UnMarshal: Error while reading string value: %v", err)
		} else {
			if readLen != strLen {
				return val, fmt.Errorf("UnMarshal: Unable to read string. Expected %d but have got %d bytes", strLen, readLen)
			} else {
				val = string(buf)
			}
		}
	}

	err = expect(reader, '"')
	if err != nil {
		return val, err
	}
	if isFinal {
		err = expect(reader, ';')
		if err != nil {
			return val, err
		}
	}
	return val, nil
}

func unMarshalArray(reader *bytes.Reader) (interface{}, error) {
	var arrLen int
	var err error
	val := make(map[string]interface{})

	arrLen, err = readLength(reader)

	if err != nil {
		return nil, err
	}
	err = expect(reader, '{')
	if err != nil {
		return nil, err
	}
	indexLen := 0
	for i := 0; i < arrLen; i++ {
		k, err := unMarshalByReader(reader)
		if err != nil {
			return nil, err
		}
		v, err := unMarshalByReader(reader)
		if err != nil {
			return nil, err
		}

		// if errKey == nil && errVal == nil {
		// val[k] = v
		switch t := k.(type) {
		default:
			return nil, fmt.Errorf("UnMarshal: Unexpected key type %T", t)
		case string:
			stringKey, _ := k.(string)
			val[stringKey] = v
		case int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64, float32, float64:
			// intKey, _ := k.(int)
			// val[strconv.Itoa(intKey)] = v
			stringKey, _ := NumericalToString(k)
			val[stringKey] = v

			if i == k {
				indexLen++
			}

		}
		// } else {
		// 	return nil, fmt.Errorf("UnMarshal: Error while reading key or(and) value of array")
		// }
	}

	err = expect(reader, '}')
	if err != nil {
		return nil, err
	}

	if indexLen == arrLen {
		var slice []interface{}
		for _, row := range val {
			slice = append(slice, row)
		}
		return slice, nil
	}

	return val, nil
}

func expect(reader *bytes.Reader, expected rune) error {
	if token, _, err := reader.ReadRune(); err != nil {
		return fmt.Errorf("UnMarshal: Error while reading expected rune %#U: %v", expected, err)
	} else if token != expected {
		return fmt.Errorf("UnMarshal: Expected %#U but have got %#U", expected, token)
	}
	return nil
}

func readUntil(reader *bytes.Reader, stop rune) (string, error) {
	var (
		token rune
		err   error
	)
	buf := bytes.NewBuffer([]byte{})

	for {
		if token, _, err = reader.ReadRune(); err != nil || token == stop {
			break
		} else {
			buf.WriteRune(token)
		}
	}

	return buf.String(), err
}

func readLength(reader *bytes.Reader) (int, error) {
	var (
		raw string
		err error
		val int
	)
	err = expect(reader, ':')
	if err != nil {
		return 0, err
	}

	if raw, err = readUntil(reader, ':'); err != nil {
		return 0, fmt.Errorf("UnMarshal: Error while reading lenght of value: %v", err)
	} else {
		if val, err = strconv.Atoi(raw); err != nil {
			return 0, fmt.Errorf("UnMarshal: Unable to convert %s to int: %v", raw, err)
		} else if int64(val) > UNSERIALIZABLE_OBJECT_MAX_LEN {
			return 0, fmt.Errorf("UnMarshal: Unserializable object length looks too big(%d). If you are sure you wanna unserialise it, please increase UNSERIALIZABLE_OBJECT_MAX_LEN const", val)
			val = 0
		}
	}
	return val, nil
}

工程目录结构如下:

 

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

luyaran

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值