创建自定义类型,在检查类型时看起来像另一个类型(Golang)。

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

Create custom type that will seem like the another when checking types Golang

问题

我是一名经验丰富的Python程序员,但对于Golang还是新手,所以如果这是一个显而易见或愚蠢的问题,请原谅。我想创建自己的类型,希望它的行为与基本类型完全相同,只是覆盖了几个方法。这样做的原因是因为我正在使用的几个库正在检查类型是否为time.Time,而我希望它匹配。

type PythonTime struct {
    time.Time
}

var pythonTimeFormatStr = "2006-01-02 15:04:05-0700"

func (self *PythonTime) UnmarshalJSON(b []byte) (err error) {
    // removes prepending/trailing " in the string
    if b[0] == '"' && b[len(b)-1] == '"' {
        b = b[1 : len(b)-1]
    }
    self.Time, err = time.Parse(pythonTimeFormatStr, string(b))
    return
}

func (self *PythonTime) MarshalJSON() ([]byte, error) {
    return []byte(self.Time.Format(pythonTimeFormatStr)), nil
}

type OtherType struct {
    Uuid     string     `json:"uuid"`
    Second   PythonTime `json:"second"`
    Location string     `json:"location"`
    Action   string     `json:"action"`
    Duration int        `json:"duration"`
    Value    string     `json:"value"`
}

上述代码对于JSON的编组和解组工作正常。然而,对于我正在使用的库(gocql和cqlr),它们会检查类型是否为time.Time类型,以便在将其放入C*之前进行一些其他修改。如何使我的PythonTime类型等同于time.Time类型,或者如何为OtherType对象的使用覆盖默认的编组和解组方法?

我的临时解决方案是修改他们的代码,并为PythonTime类型添加一个特殊情况,执行与time.Time类型相同的操作。然而,这导致了循环导入,并不是最好的解决方案。以下是我修改后的代码:

func marshalTimestamp(info TypeInfo, value interface{}) ([]byte, error) {
    switch v := value.(type) {
    case Marshaler:
        return v.MarshalCQL(info)
    case int64:
        return encBigInt(v), nil
    case time.Time:
        if v.IsZero() {
            return []byte{}, nil
        }
        x := int64(v.UTC().Unix()*1e3) + int64(v.UTC().Nanosecond()/1e6)
        return encBigInt(x), nil
    case models.PythonTime:
        x := int64(v.UTC().Unix()*1e3) + int64(v.UTC().Nanosecond()/1e6)
        return encBigInt(x), nil
    }

    if value == nil {
        return nil, nil
    }

    rv := reflect.ValueOf(value)
    switch rv.Type().Kind() {
    case reflect.Int64:
        return encBigInt(rv.Int()), nil
    }
    return nil, marshalErrorf("can not marshal %T into %s", value, info)
}

希望这可以帮助到你。如果你有任何其他问题,请随时问我。

英文:

I am a experienced python programmer but I am still new to Golang so my apologies if this is an obvious or silly question. But I am trying to create my own type that I want to act exactly like the base type with the exception of several methods being overridden. The reason for this is because several libraries I am using are checking the type against time.Time and I want it to match.

type PythonTime struct {
	time.Time
}

var pythonTimeFormatStr = "2006-01-02 15:04:05-0700"

func (self *PythonTime) UnmarshalJSON(b []byte) (err error) {
	// removes prepending/trailing " in the string
	if b[0] == '"' && b[len(b)-1] == '"' {
		b = b[1 : len(b)-1]
	}
	self.Time, err = time.Parse(pythonTimeFormatStr, string(b))
	return
}

func (self *PythonTime) MarshalJSON() ([]byte, error) {
	return []byte(self.Time.Format(pythonTimeFormatStr)), nil
}

type OtherType struct {
	Uuid     string     `json:"uuid`
	Second   PythonTime `json:"second"`
	Location string     `json:"location"`
	Action   string     `json:"action"`
	Duration int        `json:"duration"`
	Value    string     `json:"value"`
}

So the the above works fine for marshalling and unmarshalling JSON. However, for my library that I am using (gocql and cqlr) they are checking if the type is a time.Time type so they can make some other modifications before putting it in C*. How do I get my PythonTime type to equate to either show as time.Time or override the default marshalling/unmarshalling for a time.Time object just for the use of my OtherType objects?

My temporary solution has been to modify their code and add a special case for the PythonTime type that does the same thing as the time.Time type. However, this is causing me circular imports and is not the best solution. Here is their code with my modifications.

func marshalTimestamp(info TypeInfo, value interface{}) ([]byte, error) {
	switch v := value.(type) {
	case Marshaler:
		return v.MarshalCQL(info)
	case int64:
		return encBigInt(v), nil
	case time.Time:
		if v.IsZero() {
			return []byte{}, nil
		}
		x := int64(v.UTC().Unix()*1e3) + int64(v.UTC().Nanosecond()/1e6)
		return encBigInt(x), nil
	case models.PythonTime:
		x := int64(v.UTC().Unix()*1e3) + int64(v.UTC().Nanosecond()/1e6)
		return encBigInt(x), nil
	}

	if value == nil {
		return nil, nil
	}

	rv := reflect.ValueOf(value)
	switch rv.Type().Kind() {
	case reflect.Int64:
		return encBigInt(rv.Int()), nil
	}
	return nil, marshalErrorf("can not marshal %T into %s", value, info)
}

答案1

得分: 2

不要这样做。你正在检查一个 time.Time 对象,而你应该检查它是否满足一个接口。

type TimeLike interface {
    Day() int
    Format(string) string
    ...  // 任何使代码成为“time”对象的东西!
    // 在这种情况下看起来是
    UTC() time.Time
    IsZero() bool
}

然后,任何期望一个可以用 PythonTime 替代的 time.Time 的代码,都应该期望一个 TimeLike

func Foo(value interface{}) int {
    switch v := value.(type) {
    case TimeLike:
        return v.Day()  // 对于 time.Time 或 models.PythonTime 都适用
    }
    return 0
}
英文:

Don't do this. You're checking for a time.Time object when you should be checking that it satisfies an interface.

type TimeLike interface {
    Day() int
    Format(string) string
    ...  // whatever makes a "time" object to your code!
    // looks like in this case it's
    UTC() time.Time
    IsZero() bool
}

then any code that expects a time.Time that can be substituted with a PythonTime, expect a TimeLike instead.

function Foo(value interface{}) int {
    switch v := value.(type) {
    case TimeLike:
        return v.Day()  // works for either time.Time or models.PythonTime
    }
    return 0
}

答案2

得分: 1

就像你在json.Marshalerjson.Unamrshaler中所做的那样,你也可以实现gocql.Marshalergocql.Unamrshaler接口。

func (t *PythonTime) MarshalCQL(info gocql.TypeInfo) ([]byte, error) {
    b := make([]byte, 8)
    x := t.UnixNano() / int64(time.Millisecond)
    binary.BigEndian.PutUint64(b, uint64(x))
    return b, nil
}

func (t *PythonTime) UnmarshalCQL(info gocql.TypeInfo, data []byte) error {
    x := int64(binary.BigEndian.Uint64(data)) * int64(time.Millisecond)
    t.Time = time.Unix(0, x)
    return nil
}

(注意,在CQL的上下文中未经测试,但这在自身中是往返的)

英文:

Just like you have done with the json.Marshaler and json.Unamrshaler, you can also implement the gocql.Marshaler gocql.Unamrshaler interfaces.

func (t *PythonTime) MarshalCQL(info gocql.TypeInfo) ([]byte, error) {
	b := make([]byte, 8)
	x := t.UnixNano() / int64(time.Millisecond)
	binary.BigEndian.PutUint64(b, uint64(x))
	return b, nil
}

func (t *PythonTime) UnmarshalCQL(info gocql.TypeInfo, data []byte) error {
	x := int64(binary.BigEndian.Uint64(data)) * int64(time.Millisecond)
	t.Time = time.Unix(0, x)
	return nil
}

(note, untested in the context of CQL, but this does round-trip with itself)

答案3

得分: 0

很抱歉,这在Go语言中是行不通的。你最好的选择是创建一些导入和导出方法,这样你就可以将你的PythonTime转换为time.Time,反之亦然。这将为您提供所需的灵活性,并与其他库兼容。

以下是代码示例:

package main

import (
    "fmt"
    "reflect"
    "time"
)

func main() {
    p, e := NewFromTime(time.Now())
    if e != nil {
        panic(e)
    }

    v, e := p.MarshalJSON()
    if e != nil {
        panic(e)
    }

    fmt.Println(string(v), reflect.TypeOf(p))

    t, e := p.GetTime()
    if e != nil {
        panic(e)
    }

    fmt.Println(t.String(), reflect.TypeOf(t))
}

type PythonTime struct {
    time.Time
}

var pythonTimeFormatStr = "2006-01-02 15:04:05-0700"

func NewFromTime(t time.Time) (*PythonTime, error) {
    b, e := t.GobEncode()
    if e != nil {
        return nil, e
    }

    p := new(PythonTime)
    e = p.GobDecode(b)
    if e != nil {
        return nil, e
    }

    return p, nil
}

func (self *PythonTime) GetTime() (time.Time, error) {
    return time.Parse(pythonTimeFormatStr, self.Format(pythonTimeFormatStr))
}

func (self *PythonTime) UnmarshalJSON(b []byte) (err error) {
    // removes prepending/trailing " in the string
    if b[0] == '"' && b[len(b)-1] == '"' {
        b = b[1 : len(b)-1]
    }
    self.Time, err = time.Parse(pythonTimeFormatStr, string(b))
    return
}

func (self *PythonTime) MarshalJSON() ([]byte, error) {
    return []byte(self.Time.Format(pythonTimeFormatStr)), nil
}

这将输出如下:

2016-02-04 14:32:17-0700 *main.PythonTime

2016-02-04 14:32:17 -0700 MST time.Time

英文:

Unfortunately, that will not work in Go. Your best option would be to create some import and export methods, so that you can cast your PythonTime to a time.Time and vice versa. That will give you flexibility you desire along with compatibility with other libraries.

package main
import (
"fmt"
"reflect"
"time"
)
func main() {
p, e := NewFromTime(time.Now())
if e != nil {
panic(e)
}
v, e := p.MarshalJSON()
if e != nil {
panic(e)
}
fmt.Println(string(v), reflect.TypeOf(p))
t, e := p.GetTime()
if e != nil {
panic(e)
}
fmt.Println(t.String(), reflect.TypeOf(t))
}
type PythonTime struct {
time.Time
}
var pythonTimeFormatStr = "2006-01-02 15:04:05-0700"
func NewFromTime(t time.Time) (*PythonTime, error) {
b, e := t.GobEncode()
if e != nil {
return nil, e
}
p := new(PythonTime)
e = p.GobDecode(b)
if e != nil {
return nil, e
}
return p, nil
}
func (self *PythonTime) GetTime() (time.Time, error) {
return time.Parse(pythonTimeFormatStr, self.Format(pythonTimeFormatStr))
}
func (self *PythonTime) UnmarshalJSON(b []byte) (err error) {
// removes prepending/trailing " in the string
if b[0] == '"' && b[len(b)-1] == '"' {
b = b[1 : len(b)-1]
}
self.Time, err = time.Parse(pythonTimeFormatStr, string(b))
return
}
func (self *PythonTime) MarshalJSON() ([]byte, error) {
return []byte(self.Time.Format(pythonTimeFormatStr)), nil
}

That should give output like this:

2016-02-04 14:32:17-0700 *main.PythonTime

2016-02-04 14:32:17 -0700 MST time.Time

huangapple
  • 本文由 发表于 2016年2月5日 04:50:28
  • 转载请务必保留本文链接:https://go.coder-hub.com/35211363.html
匿名

发表评论

匿名网友

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

确定