| @@ -1,16 +1,9 @@ | |||||
| package mq | package mq | ||||
| import ( | import ( | ||||
| "bytes" | |||||
| "fmt" | |||||
| "reflect" | |||||
| "strings" | |||||
| "unsafe" | |||||
| jsoniter "github.com/json-iterator/go" | |||||
| "github.com/modern-go/reflect2" | |||||
| "gitlink.org.cn/cloudream/common/pkgs/types" | "gitlink.org.cn/cloudream/common/pkgs/types" | ||||
| myreflect "gitlink.org.cn/cloudream/common/utils/reflect" | myreflect "gitlink.org.cn/cloudream/common/utils/reflect" | ||||
| "gitlink.org.cn/cloudream/common/utils/serder" | |||||
| ) | ) | ||||
| const ( | const ( | ||||
| @@ -86,298 +79,20 @@ func MakeHeartbeatMessage() Message { | |||||
| return msg | return msg | ||||
| } | } | ||||
| type TypeUnionWithTypeName struct { | |||||
| Union types.TypeUnion | |||||
| TypeNameToType map[string]myreflect.Type | |||||
| } | |||||
| func (u *TypeUnionWithTypeName) Register(typ myreflect.Type) { | |||||
| u.Union.ElementTypes = append(msgBodyTypeUnion.Union.ElementTypes, typ) | |||||
| u.TypeNameToType[makeFullTypeName(typ)] = typ | |||||
| } | |||||
| var msgBodyTypeUnion *TypeUnionWithTypeName | |||||
| var msgBodyTypeUnion = serder.UseTypeUnionExternallyTagged(types.Ref(types.NewTypeUnion[MessageBody]())) | |||||
| // 所有新定义的Message都需要在init中调用此函数 | // 所有新定义的Message都需要在init中调用此函数 | ||||
| func RegisterMessage[T MessageBody]() { | func RegisterMessage[T MessageBody]() { | ||||
| msgBodyTypeUnion.Register(myreflect.TypeOf[T]()) | |||||
| } | |||||
| // 在序列化结构体中包含的UnionType类型字段时,会将字段值的实际类型保存在序列化后的结果中。 | |||||
| // 在反序列化时,会根据类型信息重建原本的字段值。 | |||||
| // 注:TypeUnion.UnionType必须是一个interface | |||||
| func RegisterUnionType(union types.TypeUnion) *TypeUnionWithTypeName { | |||||
| myUnion := &TypeUnionWithTypeName{ | |||||
| Union: union, | |||||
| TypeNameToType: make(map[string]reflect.Type), | |||||
| } | |||||
| for _, typ := range union.ElementTypes { | |||||
| myUnion.TypeNameToType[makeFullTypeName(typ)] = typ | |||||
| } | |||||
| if union.UnionType.NumMethod() == 0 { | |||||
| registerForEFace(myUnion) | |||||
| } else { | |||||
| registerForIFace(myUnion) | |||||
| } | |||||
| return myUnion | |||||
| } | |||||
| // 无方法的interface类型 | |||||
| func registerForEFace(myUnion *TypeUnionWithTypeName) { | |||||
| jsoniter.RegisterTypeEncoderFunc(myUnion.Union.UnionType.String(), | |||||
| func(ptr unsafe.Pointer, stream *jsoniter.Stream) { | |||||
| // 无方法的interface底层数据结构都是eface类型,所以可以直接转*any | |||||
| val := *(*any)(ptr) | |||||
| if val != nil { | |||||
| stream.WriteArrayStart() | |||||
| valType := myreflect.TypeOfValue(val) | |||||
| if !myUnion.Union.Include(valType) { | |||||
| stream.Error = fmt.Errorf("type %v is not in union %v", valType, myUnion.Union.UnionType) | |||||
| return | |||||
| } | |||||
| stream.WriteString(makeFullTypeName(valType)) | |||||
| stream.WriteRaw(",") | |||||
| stream.WriteVal(val) | |||||
| stream.WriteArrayEnd() | |||||
| } else { | |||||
| stream.WriteNil() | |||||
| } | |||||
| }, | |||||
| func(p unsafe.Pointer) bool { | |||||
| return false | |||||
| }) | |||||
| jsoniter.RegisterTypeDecoderFunc(myUnion.Union.UnionType.String(), | |||||
| func(ptr unsafe.Pointer, iter *jsoniter.Iterator) { | |||||
| // 无方法的interface底层都是eface结构体,所以可以直接转*any | |||||
| vp := (*any)(ptr) | |||||
| nextTkType := iter.WhatIsNext() | |||||
| if nextTkType == jsoniter.NilValue { | |||||
| iter.ReadNil() | |||||
| *vp = nil | |||||
| } else if nextTkType == jsoniter.ArrayValue { | |||||
| iter.ReadArray() | |||||
| typeStr := iter.ReadString() | |||||
| iter.ReadArray() | |||||
| typ, ok := myUnion.TypeNameToType[typeStr] | |||||
| if !ok { | |||||
| iter.ReportError("decode UnionType", fmt.Sprintf("unknow type string %s under %v", typeStr, myUnion.Union.UnionType)) | |||||
| return | |||||
| } | |||||
| // 如果目标类型已经是个指针类型*T,那么在New的时候就需要使用T, | |||||
| // 否则New出来的是会是**T,这将导致后续的反序列化出问题 | |||||
| if typ.Kind() == reflect.Pointer { | |||||
| val := reflect.New(typ.Elem()) | |||||
| iter.ReadVal(val.Interface()) | |||||
| *vp = val.Interface() | |||||
| } else { | |||||
| val := reflect.New(typ) | |||||
| iter.ReadVal(val.Interface()) | |||||
| *vp = val.Elem().Interface() | |||||
| } | |||||
| iter.ReadArray() | |||||
| } else { | |||||
| iter.ReportError("decode UnionType", fmt.Sprintf("unknow next token type %v", nextTkType)) | |||||
| return | |||||
| } | |||||
| }) | |||||
| } | |||||
| // 有方法的interface类型 | |||||
| func registerForIFace(myUnion *TypeUnionWithTypeName) { | |||||
| jsoniter.RegisterTypeEncoderFunc(myUnion.Union.UnionType.String(), | |||||
| func(ptr unsafe.Pointer, stream *jsoniter.Stream) { | |||||
| // 有方法的interface底层都是iface结构体,可以将其转成eface,转换后不损失类型信息 | |||||
| val := reflect2.IFaceToEFace(ptr) | |||||
| if val != nil { | |||||
| stream.WriteArrayStart() | |||||
| valType := myreflect.TypeOfValue(val) | |||||
| if !myUnion.Union.Include(valType) { | |||||
| stream.Error = fmt.Errorf("type %v is not in union %v", valType, myUnion.Union.UnionType) | |||||
| return | |||||
| } | |||||
| stream.WriteString(makeFullTypeName(valType)) | |||||
| stream.WriteRaw(",") | |||||
| stream.WriteVal(val) | |||||
| stream.WriteArrayEnd() | |||||
| } else { | |||||
| stream.WriteNil() | |||||
| } | |||||
| }, | |||||
| func(p unsafe.Pointer) bool { | |||||
| return false | |||||
| }) | |||||
| jsoniter.RegisterTypeDecoderFunc(myUnion.Union.UnionType.String(), | |||||
| func(ptr unsafe.Pointer, iter *jsoniter.Iterator) { | |||||
| nextTkType := iter.WhatIsNext() | |||||
| if nextTkType == jsoniter.NilValue { | |||||
| iter.ReadNil() | |||||
| } else if nextTkType == jsoniter.ArrayValue { | |||||
| iter.ReadArray() | |||||
| typeStr := iter.ReadString() | |||||
| iter.ReadArray() | |||||
| typ, ok := myUnion.TypeNameToType[typeStr] | |||||
| if !ok { | |||||
| iter.ReportError("decode UnionType", fmt.Sprintf("unknow type string %s under %v", typeStr, myUnion.Union.UnionType)) | |||||
| return | |||||
| } | |||||
| // 如果目标类型已经是个指针类型*T,那么在New的时候就需要使用T, | |||||
| // 否则New出来的是会是**T,这将导致后续的反序列化出问题 | |||||
| if typ.Kind() == reflect.Pointer { | |||||
| val := reflect.New(typ.Elem()) | |||||
| iter.ReadVal(val.Interface()) | |||||
| retVal := reflect.NewAt(myUnion.Union.UnionType, ptr) | |||||
| retVal.Elem().Set(val) | |||||
| } else { | |||||
| val := reflect.New(typ) | |||||
| iter.ReadVal(val.Interface()) | |||||
| retVal := reflect.NewAt(myUnion.Union.UnionType, ptr) | |||||
| retVal.Elem().Set(val.Elem()) | |||||
| } | |||||
| iter.ReadArray() | |||||
| } else { | |||||
| iter.ReportError("decode UnionType", fmt.Sprintf("unknow next token type %v", nextTkType)) | |||||
| return | |||||
| } | |||||
| }) | |||||
| } | |||||
| func makeFullTypeName(typ myreflect.Type) string { | |||||
| refs := 0 | |||||
| realType := typ | |||||
| for realType.Kind() == reflect.Pointer { | |||||
| refs++ | |||||
| realType = realType.Elem() | |||||
| } | |||||
| return fmt.Sprintf("%s%s.%s", strings.Repeat("*", refs), realType.PkgPath(), realType.Name()) | |||||
| } | |||||
| /* | |||||
| // 如果对一个类型T调用了此函数,那么在序列化结构体中包含的T类型字段时, | |||||
| // 会将字段值的实际类型保存在序列化后的结果中 | |||||
| // 在反序列化时,会根据类型信息重建原本的字段值。 | |||||
| // | |||||
| // 只会处理types指定的类型。 | |||||
| func RegisterTypeSet[T any](types ...myreflect.Type) *serder.UnionTypeInfo { | |||||
| eleTypes := serder.NewTypeNameResolver(true) | |||||
| set := serder.UnionTypeInfo{ | |||||
| UnionType: myreflect.TypeOf[T](), | |||||
| ElementTypes: eleTypes, | |||||
| } | |||||
| for _, t := range types { | |||||
| eleTypes.Register(t) | |||||
| err := msgBodyTypeUnion.Add(myreflect.TypeOf[T]()) | |||||
| if err != nil { | |||||
| panic(err) | |||||
| } | } | ||||
| TODO 暂时保留这一段代码,如果RegisterUnionType中的非泛型版本出了问题,则重新使用这一部分的代码 | |||||
| unionTypes[set.UnionType] = set | |||||
| jsoniter.RegisterTypeEncoderFunc(myreflect.TypeOf[T]().String(), | |||||
| func(ptr unsafe.Pointer, stream *jsoniter.Stream) { | |||||
| val := *((*T)(ptr)) | |||||
| var ifVal any = val | |||||
| if ifVal != nil { | |||||
| stream.WriteArrayStart() | |||||
| typeStr, err := set.ElementTypes.TypeToString(myreflect.TypeOfValue(val)) | |||||
| if err != nil { | |||||
| stream.Error = err | |||||
| return | |||||
| } | |||||
| stream.WriteString(typeStr) | |||||
| stream.WriteRaw(",") | |||||
| stream.WriteVal(val) | |||||
| stream.WriteArrayEnd() | |||||
| } else { | |||||
| stream.WriteNil() | |||||
| } | |||||
| }, | |||||
| func(p unsafe.Pointer) bool { | |||||
| return false | |||||
| }) | |||||
| jsoniter.RegisterTypeDecoderFunc(myreflect.TypeOf[T]().String(), | |||||
| func(ptr unsafe.Pointer, iter *jsoniter.Iterator) { | |||||
| vp := (*T)(ptr) | |||||
| nextTkType := iter.WhatIsNext() | |||||
| if nextTkType == jsoniter.NilValue { | |||||
| iter.ReadNil() | |||||
| var zero T | |||||
| *vp = zero | |||||
| } else if nextTkType == jsoniter.ArrayValue { | |||||
| iter.ReadArray() | |||||
| typeStr := iter.ReadString() | |||||
| iter.ReadArray() | |||||
| typ, err := set.ElementTypes.StringToType(typeStr) | |||||
| if err != nil { | |||||
| iter.ReportError("get type from string", err.Error()) | |||||
| return | |||||
| } | |||||
| val := reflect.New(typ) | |||||
| iter.ReadVal(val.Interface()) | |||||
| *vp = val.Elem().Interface().(T) | |||||
| iter.ReadArray() | |||||
| } else { | |||||
| iter.ReportError("parse TypeSet field", fmt.Sprintf("unknow next token type %v", nextTkType)) | |||||
| return | |||||
| } | |||||
| }) | |||||
| RegisterUnionType(serder.NewTypeUnion[T]("", serder.NewTypeNameResolver(true))) | |||||
| return &set | |||||
| } | } | ||||
| */ | |||||
| func Serialize(msg Message) ([]byte, error) { | func Serialize(msg Message) ([]byte, error) { | ||||
| buf := bytes.NewBuffer(nil) | |||||
| enc := jsoniter.NewEncoder(buf) | |||||
| err := enc.Encode(msg) | |||||
| if err != nil { | |||||
| return nil, err | |||||
| } | |||||
| return buf.Bytes(), nil | |||||
| return serder.ObjectToJSONEx(msg) | |||||
| } | } | ||||
| func Deserialize(data []byte) (*Message, error) { | func Deserialize(data []byte) (*Message, error) { | ||||
| dec := jsoniter.NewDecoder(bytes.NewBuffer(data)) | |||||
| var msg Message | |||||
| err := dec.Decode(&msg) | |||||
| if err != nil { | |||||
| return nil, err | |||||
| } | |||||
| return &msg, nil | |||||
| } | |||||
| func init() { | |||||
| msgBodyTypeUnion = RegisterUnionType(types.NewTypeUnion[MessageBody]()) | |||||
| return serder.JSONToObjectEx[*Message](data) | |||||
| } | } | ||||
| @@ -3,7 +3,6 @@ package mq | |||||
| import ( | import ( | ||||
| "bytes" | "bytes" | ||||
| "fmt" | "fmt" | ||||
| "reflect" | |||||
| "testing" | "testing" | ||||
| "unsafe" | "unsafe" | ||||
| @@ -11,6 +10,7 @@ import ( | |||||
| . "github.com/smartystreets/goconvey/convey" | . "github.com/smartystreets/goconvey/convey" | ||||
| "gitlink.org.cn/cloudream/common/pkgs/types" | "gitlink.org.cn/cloudream/common/pkgs/types" | ||||
| myreflect "gitlink.org.cn/cloudream/common/utils/reflect" | myreflect "gitlink.org.cn/cloudream/common/utils/reflect" | ||||
| "gitlink.org.cn/cloudream/common/utils/serder" | |||||
| ) | ) | ||||
| func TestMessage(t *testing.T) { | func TestMessage(t *testing.T) { | ||||
| @@ -146,7 +146,7 @@ func TestMessage(t *testing.T) { | |||||
| Value MyTypeUnion | Value MyTypeUnion | ||||
| } | } | ||||
| RegisterMessage[*Body]() | RegisterMessage[*Body]() | ||||
| RegisterUnionType(types.NewTypeUnion[MyTypeUnion]((*EleType1)(nil))) | |||||
| serder.UseTypeUnionExternallyTagged(types.Ref(types.NewTypeUnion[MyTypeUnion]((*EleType1)(nil)))) | |||||
| msg := MakeAppDataMessage(&Body{Value: &EleType1{ | msg := MakeAppDataMessage(&Body{Value: &EleType1{ | ||||
| Value: 1, | Value: 1, | ||||
| @@ -174,7 +174,7 @@ func TestMessage(t *testing.T) { | |||||
| Value MyTypeUnion | Value MyTypeUnion | ||||
| } | } | ||||
| RegisterMessage[*Body]() | RegisterMessage[*Body]() | ||||
| RegisterUnionType(types.NewTypeUnion[MyTypeUnion]((*EleType1)(nil))) | |||||
| serder.UseTypeUnionExternallyTagged(types.Ref(types.NewTypeUnion[MyTypeUnion]((*EleType1)(nil)))) | |||||
| msg := MakeAppDataMessage(&Body{Value: &EleType1{ | msg := MakeAppDataMessage(&Body{Value: &EleType1{ | ||||
| Value: 1, | Value: 1, | ||||
| @@ -200,7 +200,7 @@ func TestMessage(t *testing.T) { | |||||
| Value MyTypeUnion | Value MyTypeUnion | ||||
| } | } | ||||
| RegisterMessage[*Body]() | RegisterMessage[*Body]() | ||||
| RegisterUnionType(types.NewTypeUnion[MyTypeUnion]()) | |||||
| serder.UseTypeUnionExternallyTagged(types.Ref(types.NewTypeUnion[MyTypeUnion]())) | |||||
| msg := MakeAppDataMessage(&Body{Value: nil}) | msg := MakeAppDataMessage(&Body{Value: nil}) | ||||
| data, err := Serialize(msg) | data, err := Serialize(msg) | ||||
| @@ -220,26 +220,10 @@ func TestMessage(t *testing.T) { | |||||
| Value MyTypeUnion | Value MyTypeUnion | ||||
| } | } | ||||
| RegisterMessage[*Body]() | RegisterMessage[*Body]() | ||||
| RegisterUnionType(types.NewTypeUnion[MyTypeUnion]()) | |||||
| serder.UseTypeUnionExternallyTagged(types.Ref(types.NewTypeUnion[MyTypeUnion]())) | |||||
| msg := MakeAppDataMessage(&Body{Value: &struct{}{}}) | msg := MakeAppDataMessage(&Body{Value: &struct{}{}}) | ||||
| _, err := Serialize(msg) | _, err := Serialize(msg) | ||||
| So(err, ShouldNotBeNil) | So(err, ShouldNotBeNil) | ||||
| }) | }) | ||||
| } | |||||
| func TestMakeFullTypeName(t *testing.T) { | |||||
| Convey("指针类型", t, func() { | |||||
| type St struct{} | |||||
| typeName := makeFullTypeName(myreflect.TypeOf[St]()) | |||||
| So(typeName, ShouldEqual, "gitlink.org.cn/cloudream/common/pkgs/mq.St") | |||||
| typeName = makeFullTypeName(reflect.PointerTo(myreflect.TypeOf[St]())) | |||||
| So(typeName, ShouldEqual, "*gitlink.org.cn/cloudream/common/pkgs/mq.St") | |||||
| typeName = makeFullTypeName(reflect.PointerTo(reflect.PointerTo(myreflect.TypeOf[St]()))) | |||||
| So(typeName, ShouldEqual, "**gitlink.org.cn/cloudream/common/pkgs/mq.St") | |||||
| }) | |||||
| } | } | ||||
| @@ -0,0 +1,5 @@ | |||||
| package types | |||||
| func Ref[T any](val T) *T { | |||||
| return &val | |||||
| } | |||||
| @@ -1,6 +1,7 @@ | |||||
| package types | package types | ||||
| import ( | import ( | ||||
| "fmt" | |||||
| "reflect" | "reflect" | ||||
| myreflect "gitlink.org.cn/cloudream/common/utils/reflect" | myreflect "gitlink.org.cn/cloudream/common/utils/reflect" | ||||
| @@ -37,6 +38,11 @@ func (u *TypeUnion) Include(typ myreflect.Type) bool { | |||||
| return false | return false | ||||
| } | } | ||||
| func (u *TypeUnion) Add(typ myreflect.Type) { | |||||
| func (u *TypeUnion) Add(typ myreflect.Type) error { | |||||
| if !typ.AssignableTo(u.UnionType) { | |||||
| return fmt.Errorf("type is not assignable to union type") | |||||
| } | |||||
| u.ElementTypes = append(u.ElementTypes, typ) | u.ElementTypes = append(u.ElementTypes, typ) | ||||
| return nil | |||||
| } | } | ||||
| @@ -1,7 +1,6 @@ | |||||
| package schsdk | package schsdk | ||||
| import ( | import ( | ||||
| "gitlink.org.cn/cloudream/common/pkgs/mq" | |||||
| "gitlink.org.cn/cloudream/common/pkgs/types" | "gitlink.org.cn/cloudream/common/pkgs/types" | ||||
| cdssdk "gitlink.org.cn/cloudream/common/sdks/storage" | cdssdk "gitlink.org.cn/cloudream/common/sdks/storage" | ||||
| "gitlink.org.cn/cloudream/common/utils/serder" | "gitlink.org.cn/cloudream/common/utils/serder" | ||||
| @@ -38,8 +37,7 @@ var JobInfoTypeUnion = types.NewTypeUnion[JobInfo]( | |||||
| (*NormalJobInfo)(nil), | (*NormalJobInfo)(nil), | ||||
| (*ResourceJobInfo)(nil), | (*ResourceJobInfo)(nil), | ||||
| ) | ) | ||||
| var _ = serder.RegisterNewTaggedTypeUnion(JobInfoTypeUnion, "Type", "type") | |||||
| var _ = mq.RegisterUnionType(JobInfoTypeUnion) | |||||
| var _ = serder.UseTypeUnionInternallyTagged(&JobInfoTypeUnion, "type") | |||||
| type JobInfoBase struct { | type JobInfoBase struct { | ||||
| LocalJobID string `json:"localJobID"` | LocalJobID string `json:"localJobID"` | ||||
| @@ -50,16 +48,18 @@ func (i *JobInfoBase) GetLocalJobID() string { | |||||
| } | } | ||||
| type NormalJobInfo struct { | type NormalJobInfo struct { | ||||
| serder.Metadata `union:"Normal"` | |||||
| JobInfoBase | JobInfoBase | ||||
| Type string `json:"type" union:"Normal"` | |||||
| Type string `json:"type"` | |||||
| Files JobFilesInfo `json:"files"` | Files JobFilesInfo `json:"files"` | ||||
| Runtime JobRuntimeInfo `json:"runtime"` | Runtime JobRuntimeInfo `json:"runtime"` | ||||
| Resources JobResourcesInfo `json:"resources"` | Resources JobResourcesInfo `json:"resources"` | ||||
| } | } | ||||
| type ResourceJobInfo struct { | type ResourceJobInfo struct { | ||||
| serder.Metadata `union:"Resource"` | |||||
| JobInfoBase | JobInfoBase | ||||
| Type string `json:"type" union:"Resource"` | |||||
| Type string `json:"type"` | |||||
| BucketID int64 `json:"bucketID"` | BucketID int64 `json:"bucketID"` | ||||
| Redundancy cdssdk.TypedRedundancyInfo `json:"redundancy"` | Redundancy cdssdk.TypedRedundancyInfo `json:"redundancy"` | ||||
| TargetLocalJobID string `json:"targetLocalJobID"` | TargetLocalJobID string `json:"targetLocalJobID"` | ||||
| @@ -81,34 +81,37 @@ var FileInfoTypeUnion = types.NewTypeUnion[JobFileInfo]( | |||||
| (*ResourceJobFileInfo)(nil), | (*ResourceJobFileInfo)(nil), | ||||
| (*ImageJobFileInfo)(nil), | (*ImageJobFileInfo)(nil), | ||||
| ) | ) | ||||
| var _ = serder.RegisterNewTaggedTypeUnion(FileInfoTypeUnion, "Type", "type") | |||||
| var _ = mq.RegisterUnionType(FileInfoTypeUnion) | |||||
| var _ = serder.UseTypeUnionInternallyTagged(&FileInfoTypeUnion, "type") | |||||
| type JobFileInfoBase struct{} | type JobFileInfoBase struct{} | ||||
| func (i *JobFileInfoBase) Noop() {} | func (i *JobFileInfoBase) Noop() {} | ||||
| type PackageJobFileInfo struct { | type PackageJobFileInfo struct { | ||||
| serder.Metadata `union:"Package"` | |||||
| JobFileInfoBase | JobFileInfoBase | ||||
| Type string `json:"type" union:"Package"` | |||||
| Type string `json:"type"` | |||||
| PackageID int64 `json:"packageID"` | PackageID int64 `json:"packageID"` | ||||
| } | } | ||||
| type LocalJobFileInfo struct { | type LocalJobFileInfo struct { | ||||
| serder.Metadata `union:"LocalFile"` | |||||
| JobFileInfoBase | JobFileInfoBase | ||||
| Type string `json:"type" union:"LocalFile"` | |||||
| Type string `json:"type"` | |||||
| LocalPath string `json:"localPath"` | LocalPath string `json:"localPath"` | ||||
| } | } | ||||
| type ResourceJobFileInfo struct { | type ResourceJobFileInfo struct { | ||||
| serder.Metadata `union:"Resource"` | |||||
| JobFileInfoBase | JobFileInfoBase | ||||
| Type string `json:"type" union:"Resource"` | |||||
| Type string `json:"type"` | |||||
| ResourceLocalJobID string `json:"resourceLocalJobID"` | ResourceLocalJobID string `json:"resourceLocalJobID"` | ||||
| } | } | ||||
| type ImageJobFileInfo struct { | type ImageJobFileInfo struct { | ||||
| serder.Metadata `union:"Image"` | |||||
| JobFileInfoBase | JobFileInfoBase | ||||
| Type string `json:"type" union:"Image"` | |||||
| Type string `json:"type"` | |||||
| ImageID ImageID `json:"imageID"` | ImageID ImageID `json:"imageID"` | ||||
| } | } | ||||
| @@ -133,21 +136,6 @@ type JobResourcesInfo struct { | |||||
| Memory int64 `json:"memory"` | Memory int64 `json:"memory"` | ||||
| } | } | ||||
| func JobSetInfoFromJSON(data []byte) (*JobSetInfo, error) { | |||||
| mp := make(map[string]any) | |||||
| if err := serder.JSONToObject(data, &mp); err != nil { | |||||
| return nil, err | |||||
| } | |||||
| var ret JobSetInfo | |||||
| err := serder.MapToObject(mp, &ret) | |||||
| if err != nil { | |||||
| return nil, err | |||||
| } | |||||
| return &ret, nil | |||||
| } | |||||
| type JobSetFilesUploadScheme struct { | type JobSetFilesUploadScheme struct { | ||||
| LocalFileSchemes []LocalFileUploadScheme `json:"localFileUploadSchemes"` | LocalFileSchemes []LocalFileUploadScheme `json:"localFileUploadSchemes"` | ||||
| } | } | ||||
| @@ -1,7 +1,6 @@ | |||||
| package uopsdk | package uopsdk | ||||
| import ( | import ( | ||||
| "gitlink.org.cn/cloudream/common/pkgs/mq" | |||||
| "gitlink.org.cn/cloudream/common/pkgs/types" | "gitlink.org.cn/cloudream/common/pkgs/types" | ||||
| "gitlink.org.cn/cloudream/common/utils/serder" | "gitlink.org.cn/cloudream/common/utils/serder" | ||||
| ) | ) | ||||
| @@ -37,8 +36,7 @@ var ResourceDataTypeUnion = types.NewTypeUnion[ResourceData]( | |||||
| (*StorageResourceData)(nil), | (*StorageResourceData)(nil), | ||||
| (*MemoryResourceData)(nil), | (*MemoryResourceData)(nil), | ||||
| ) | ) | ||||
| var _ = serder.RegisterNewTaggedTypeUnion(ResourceDataTypeUnion, "Name", "name") | |||||
| var _ = mq.RegisterUnionType(ResourceDataTypeUnion) | |||||
| var _ = serder.UseTypeUnionInternallyTagged(&ResourceDataTypeUnion, "name") | |||||
| type ResourceDataBase struct{} | type ResourceDataBase struct{} | ||||
| @@ -50,8 +48,9 @@ type UnitValue[T any] struct { | |||||
| } | } | ||||
| type CPUResourceData struct { | type CPUResourceData struct { | ||||
| serder.Metadata `union:"CPU"` | |||||
| ResourceDataBase | ResourceDataBase | ||||
| Name ResourceType `json:"name" union:"CPU"` | |||||
| Name ResourceType `json:"name"` | |||||
| Total UnitValue[int64] `json:"total"` | Total UnitValue[int64] `json:"total"` | ||||
| Available UnitValue[int64] `json:"available"` | Available UnitValue[int64] `json:"available"` | ||||
| } | } | ||||
| @@ -65,8 +64,9 @@ func NewCPUResourceData(total UnitValue[int64], available UnitValue[int64]) *CPU | |||||
| } | } | ||||
| type NPUResourceData struct { | type NPUResourceData struct { | ||||
| serder.Metadata `union:"NPU"` | |||||
| ResourceDataBase | ResourceDataBase | ||||
| Name ResourceType `json:"name" union:"NPU"` | |||||
| Name ResourceType `json:"name"` | |||||
| Total UnitValue[int64] `json:"total"` | Total UnitValue[int64] `json:"total"` | ||||
| Available UnitValue[int64] `json:"available"` | Available UnitValue[int64] `json:"available"` | ||||
| } | } | ||||
| @@ -80,8 +80,9 @@ func NewNPUResourceData(total UnitValue[int64], available UnitValue[int64]) *NPU | |||||
| } | } | ||||
| type GPUResourceData struct { | type GPUResourceData struct { | ||||
| serder.Metadata `union:"GPU"` | |||||
| ResourceDataBase | ResourceDataBase | ||||
| Name ResourceType `json:"name" union:"GPU"` | |||||
| Name ResourceType `json:"name"` | |||||
| Total UnitValue[int64] `json:"total"` | Total UnitValue[int64] `json:"total"` | ||||
| Available UnitValue[int64] `json:"available"` | Available UnitValue[int64] `json:"available"` | ||||
| } | } | ||||
| @@ -95,8 +96,9 @@ func NewGPUResourceData(total UnitValue[int64], available UnitValue[int64]) *GPU | |||||
| } | } | ||||
| type MLUResourceData struct { | type MLUResourceData struct { | ||||
| serder.Metadata `union:"MLU"` | |||||
| ResourceDataBase | ResourceDataBase | ||||
| Name ResourceType `json:"name" union:"MLU"` | |||||
| Name ResourceType `json:"name"` | |||||
| Total UnitValue[int64] `json:"total"` | Total UnitValue[int64] `json:"total"` | ||||
| Available UnitValue[int64] `json:"available"` | Available UnitValue[int64] `json:"available"` | ||||
| } | } | ||||
| @@ -110,8 +112,9 @@ func NewMLUResourceData(total UnitValue[int64], available UnitValue[int64]) *MLU | |||||
| } | } | ||||
| type StorageResourceData struct { | type StorageResourceData struct { | ||||
| serder.Metadata `union:"STORAGE"` | |||||
| ResourceDataBase | ResourceDataBase | ||||
| Name ResourceType `json:"name" union:"STORAGE"` | |||||
| Name ResourceType `json:"name"` | |||||
| Total UnitValue[float64] `json:"total"` | Total UnitValue[float64] `json:"total"` | ||||
| Available UnitValue[float64] `json:"available"` | Available UnitValue[float64] `json:"available"` | ||||
| } | } | ||||
| @@ -125,8 +128,9 @@ func NewStorageResourceData(total UnitValue[float64], available UnitValue[float6 | |||||
| } | } | ||||
| type MemoryResourceData struct { | type MemoryResourceData struct { | ||||
| serder.Metadata `union:"MEMORY"` | |||||
| ResourceDataBase | ResourceDataBase | ||||
| Name ResourceType `json:"name" union:"MEMORY"` | |||||
| Name ResourceType `json:"name"` | |||||
| Total UnitValue[float64] `json:"total"` | Total UnitValue[float64] `json:"total"` | ||||
| Available UnitValue[float64] `json:"available"` | Available UnitValue[float64] `json:"available"` | ||||
| } | } | ||||
| @@ -1,19 +1,64 @@ | |||||
| package serder | package serder | ||||
| import ( | import ( | ||||
| "bytes" | |||||
| "encoding/json" | "encoding/json" | ||||
| "fmt" | "fmt" | ||||
| "io" | "io" | ||||
| "reflect" | "reflect" | ||||
| "strings" | "strings" | ||||
| "gitlink.org.cn/cloudream/common/pkgs/types" | |||||
| jsoniter "github.com/json-iterator/go" | |||||
| ) | ) | ||||
| var unionHandler = UnionHandler{ | |||||
| internallyTagged: make(map[reflect.Type]*TypeUnionInternallyTagged), | |||||
| externallyTagged: make(map[reflect.Type]*TypeUnionExternallyTagged), | |||||
| } | |||||
| var defaultAPI = func() jsoniter.API { | |||||
| api := jsoniter.Config{ | |||||
| EscapeHTML: true, | |||||
| }.Froze() | |||||
| api.RegisterExtension(&unionHandler) | |||||
| return api | |||||
| }() | |||||
| // 将对象转为JSON字符串。支持TypeUnion。 | |||||
| func ObjectToJSONEx[T any](obj T) ([]byte, error) { | |||||
| buf := new(bytes.Buffer) | |||||
| enc := defaultAPI.NewEncoder(buf) | |||||
| // 这里使用&obj而直接不使用obj的原因是,Encode的形参类型为any, | |||||
| // 如果T是一个interface类型,将obj传递进去后,内部拿到的类型将会是obj的实际类型, | |||||
| // 使用&obj,那么内部拿到的将会是*T类型,通过一层一层解引用查找Encoder时,能找到T对应的TypeUnion | |||||
| err := enc.Encode(&obj) | |||||
| if err != nil { | |||||
| return nil, err | |||||
| } | |||||
| return buf.Bytes(), nil | |||||
| } | |||||
| // 将JSON字符串转为对象。支持TypeUnion。 | |||||
| func JSONToObjectEx[T any](data []byte) (T, error) { | |||||
| var ret T | |||||
| dec := defaultAPI.NewDecoder(bytes.NewReader(data)) | |||||
| err := dec.Decode(&ret) | |||||
| if err != nil { | |||||
| return ret, err | |||||
| } | |||||
| return ret, nil | |||||
| } | |||||
| // 将对象转为JSON字符串。如果需要支持解析TypeUnion类型,则使用"Ex"结尾的同名函数。 | |||||
| func ObjectToJSON(obj any) ([]byte, error) { | func ObjectToJSON(obj any) ([]byte, error) { | ||||
| return json.Marshal(obj) | return json.Marshal(obj) | ||||
| } | } | ||||
| // 将对象转为JSON字符串。如果需要支持解析TypeUnion类型,则使用"Ex"结尾的同名函数。 | |||||
| func ObjectToJSONStream(obj any) io.ReadCloser { | func ObjectToJSONStream(obj any) io.ReadCloser { | ||||
| pr, pw := io.Pipe() | pr, pw := io.Pipe() | ||||
| enc := json.NewEncoder(pw) | enc := json.NewEncoder(pw) | ||||
| @@ -30,10 +75,12 @@ func ObjectToJSONStream(obj any) io.ReadCloser { | |||||
| return pr | return pr | ||||
| } | } | ||||
| // 将JSON字符串转为对象。如果需要支持解析TypeUnion类型,则使用"Ex"结尾的同名函数。 | |||||
| func JSONToObject(data []byte, obj any) error { | func JSONToObject(data []byte, obj any) error { | ||||
| return json.Unmarshal(data, obj) | return json.Unmarshal(data, obj) | ||||
| } | } | ||||
| // 将JSON字符串转为对象。如果需要支持解析TypeUnion类型,则使用"Ex"结尾的同名函数。 | |||||
| func JSONToObjectStream(str io.Reader, obj any) error { | func JSONToObjectStream(str io.Reader, obj any) error { | ||||
| dec := json.NewDecoder(str) | dec := json.NewDecoder(str) | ||||
| err := dec.Decode(obj) | err := dec.Decode(obj) | ||||
| @@ -49,86 +96,21 @@ type TypeResolver interface { | |||||
| StringToType(typeStr string) (reflect.Type, error) | StringToType(typeStr string) (reflect.Type, error) | ||||
| } | } | ||||
| var registeredTaggedTypeUnions []*TaggedUnionType | |||||
| type TaggedUnionType struct { | |||||
| Union types.TypeUnion | |||||
| StrcutTagField string | |||||
| JSONTagField string | |||||
| TagToType map[string]reflect.Type | |||||
| } | |||||
| // 根据指定的字段的值来区分不同的类型。值可以通过在字段增加“union”Tag来指定。如果没有指定,则使用类型名。 | |||||
| func NewTaggedTypeUnion(union types.TypeUnion, structTagField string, jsonTagField string) *TaggedUnionType { | |||||
| tagToType := make(map[string]reflect.Type) | |||||
| for _, typ := range union.ElementTypes { | |||||
| if structTagField == "" { | |||||
| tagToType[typ.Name()] = typ | |||||
| continue | |||||
| } | |||||
| // 如果ElementType是一个指向结构体的指针,那么就遍历结构体的字段(解引用) | |||||
| structType := typ | |||||
| for structType.Kind() == reflect.Pointer { | |||||
| structType = structType.Elem() | |||||
| } | |||||
| field, ok := structType.FieldByName(structTagField) | |||||
| if !ok { | |||||
| tagToType[typ.Name()] = typ | |||||
| continue | |||||
| } | |||||
| tag := field.Tag.Get("union") | |||||
| if tag == "" { | |||||
| tagToType[typ.Name()] = typ | |||||
| continue | |||||
| } | |||||
| tagToType[tag] = typ | |||||
| } | |||||
| return &TaggedUnionType{ | |||||
| Union: union, | |||||
| StrcutTagField: structTagField, | |||||
| JSONTagField: jsonTagField, | |||||
| TagToType: tagToType, | |||||
| } | |||||
| } | |||||
| // 注册一个TaggedTypeUnion | |||||
| func RegisterTaggedTypeUnion(union *TaggedUnionType) *TaggedUnionType { | |||||
| registeredTaggedTypeUnions = append(registeredTaggedTypeUnions, union) | |||||
| return union | |||||
| } | |||||
| // 创建并注册一个TaggedTypeUnion | |||||
| func RegisterNewTaggedTypeUnion(union types.TypeUnion, structTagField string, jsonTagField string) *TaggedUnionType { | |||||
| taggedUnion := NewTaggedTypeUnion(union, structTagField, jsonTagField) | |||||
| RegisterTaggedTypeUnion(taggedUnion) | |||||
| return taggedUnion | |||||
| } | |||||
| type MapToObjectOption struct { | type MapToObjectOption struct { | ||||
| UnionTypes []*TaggedUnionType // 转换过程中遇到这些类型时,会依据指定的字段的值,来决定转换后的实际类型 | |||||
| NoRegisteredUnionTypes bool // 是否不使用全局注册的UnionType | |||||
| NoRegisteredUnionTypes bool // 是否不使用全局注册的UnionType | |||||
| } | } | ||||
| // TODO 使用这个函数来处理TypeUnion的地方都可以直接使用Ex系列的函数 | |||||
| func MapToObject(m map[string]any, obj any, opt ...MapToObjectOption) error { | func MapToObject(m map[string]any, obj any, opt ...MapToObjectOption) error { | ||||
| var op MapToObjectOption | var op MapToObjectOption | ||||
| if len(opt) > 0 { | if len(opt) > 0 { | ||||
| op = opt[0] | op = opt[0] | ||||
| } | } | ||||
| unionTypeMapping := make(map[reflect.Type]*TaggedUnionType) | |||||
| for _, u := range op.UnionTypes { | |||||
| unionTypeMapping[u.Union.UnionType] = u | |||||
| } | |||||
| unionTypeMapping := make(map[reflect.Type]*TypeUnionInternallyTagged) | |||||
| if !op.NoRegisteredUnionTypes { | if !op.NoRegisteredUnionTypes { | ||||
| for _, u := range registeredTaggedTypeUnions { | |||||
| for _, u := range unionHandler.internallyTagged { | |||||
| unionTypeMapping[u.Union.UnionType] = u | unionTypeMapping[u.Union.UnionType] = u | ||||
| } | } | ||||
| } | } | ||||
| @@ -142,14 +124,14 @@ func MapToObject(m map[string]any, obj any, opt ...MapToObjectOption) error { | |||||
| } | } | ||||
| mp := from.Interface().(map[string]any) | mp := from.Interface().(map[string]any) | ||||
| tag, ok := mp[info.JSONTagField] | |||||
| tag, ok := mp[info.TagField] | |||||
| if !ok { | if !ok { | ||||
| return nil, fmt.Errorf("converting to %v: no tag field %s in map", toType, info.JSONTagField) | |||||
| return nil, fmt.Errorf("converting to %v: no tag field %s in map", toType, info.TagField) | |||||
| } | } | ||||
| tagStr, ok := tag.(string) | tagStr, ok := tag.(string) | ||||
| if !ok { | if !ok { | ||||
| return nil, fmt.Errorf("converting to %v: tag field %s value is %v, which is not a string", toType, info.JSONTagField, tag) | |||||
| return nil, fmt.Errorf("converting to %v: tag field %s value is %v, which is not a string", toType, info.TagField, tag) | |||||
| } | } | ||||
| eleType, ok := info.TagToType[tagStr] | eleType, ok := info.TagToType[tagStr] | ||||
| @@ -363,13 +363,15 @@ func Test_MapToObject(t *testing.T) { | |||||
| type UnionType interface{} | type UnionType interface{} | ||||
| type EleType1 struct { | type EleType1 struct { | ||||
| Type EleType `json:"type" union:"1"` | |||||
| Value1 string `json:"value1"` | |||||
| Metadata `union:"1"` | |||||
| Type EleType `json:"type"` | |||||
| Value1 string `json:"value1"` | |||||
| } | } | ||||
| type EleType2 struct { | type EleType2 struct { | ||||
| Type EleType `json:"type" union:"2"` | |||||
| Value2 int `json:"value2"` | |||||
| Metadata `union:"2"` | |||||
| Type EleType `json:"type"` | |||||
| Value2 int `json:"value2"` | |||||
| } | } | ||||
| type St struct { | type St struct { | ||||
| @@ -390,17 +392,12 @@ func Test_MapToObject(t *testing.T) { | |||||
| } | } | ||||
| var ret St | var ret St | ||||
| err := MapToObject(mp, &ret, MapToObjectOption{ | |||||
| UnionTypes: []*TaggedUnionType{ | |||||
| NewTaggedTypeUnion(types.NewTypeUnion[UnionType]( | |||||
| (*EleType1)(nil), | |||||
| (*EleType2)(nil), | |||||
| ), | |||||
| "Type", | |||||
| "type", | |||||
| ), | |||||
| }, | |||||
| }) | |||||
| union := types.NewTypeUnion[UnionType]( | |||||
| (*EleType1)(nil), | |||||
| (*EleType2)(nil), | |||||
| ) | |||||
| UseTypeUnionInternallyTagged(&union, "type") | |||||
| err := MapToObject(mp, &ret) | |||||
| So(err, ShouldBeNil) | So(err, ShouldBeNil) | ||||
| @@ -414,13 +411,15 @@ func Test_MapToObject(t *testing.T) { | |||||
| type UnionType interface{} | type UnionType interface{} | ||||
| type EleType1 struct { | type EleType1 struct { | ||||
| Type string `json:"type" union:"1"` | |||||
| Value1 string `json:"value1"` | |||||
| Metadata `union:"1"` | |||||
| Type string `json:"type"` | |||||
| Value1 string `json:"value1"` | |||||
| } | } | ||||
| type EleType2 struct { | type EleType2 struct { | ||||
| Type string `json:"type" union:"2"` | |||||
| Value2 int `json:"value2"` | |||||
| Metadata `union:"2"` | |||||
| Type string `json:"type"` | |||||
| Value2 int `json:"value2"` | |||||
| } | } | ||||
| mp := map[string]any{ | mp := map[string]any{ | ||||
| @@ -429,17 +428,12 @@ func Test_MapToObject(t *testing.T) { | |||||
| } | } | ||||
| var ret UnionType | var ret UnionType | ||||
| err := MapToObject(mp, &ret, MapToObjectOption{ | |||||
| UnionTypes: []*TaggedUnionType{ | |||||
| NewTaggedTypeUnion(types.NewTypeUnion[UnionType]( | |||||
| (*EleType1)(nil), | |||||
| (*EleType2)(nil), | |||||
| ), | |||||
| "Type", | |||||
| "type", | |||||
| ), | |||||
| }, | |||||
| }) | |||||
| union := types.NewTypeUnion[UnionType]( | |||||
| (*EleType1)(nil), | |||||
| (*EleType2)(nil), | |||||
| ) | |||||
| UseTypeUnionInternallyTagged(&union, "type") | |||||
| err := MapToObject(mp, &ret) | |||||
| So(err, ShouldBeNil) | So(err, ShouldBeNil) | ||||
| @@ -464,7 +458,27 @@ func Test_MapToObject(t *testing.T) { | |||||
| So(string(ret.Str), ShouldEqual, "1") | So(string(ret.Str), ShouldEqual, "1") | ||||
| }) | }) | ||||
| } | } | ||||
| func Test_JSON(t *testing.T) { | |||||
| type Base interface { | |||||
| Noop() | |||||
| } | |||||
| type St1 struct { | |||||
| Metadata `union:"St1"` | |||||
| Type string | |||||
| Val string | |||||
| } | |||||
| func (*St1) Noop() {} | |||||
| type St2 struct { | |||||
| Metadata `union:"St2"` | |||||
| Type string | |||||
| Val int | |||||
| } | |||||
| func (St2) Noop() {} | |||||
| func Test_ObjectToJSON2(t *testing.T) { | |||||
| Convey("NewType", t, func() { | Convey("NewType", t, func() { | ||||
| type Str string | type Str string | ||||
| @@ -484,4 +498,154 @@ func Test_JSON(t *testing.T) { | |||||
| So(err, ShouldBeNil) | So(err, ShouldBeNil) | ||||
| So(string(ret.Str), ShouldEqual, "1") | So(string(ret.Str), ShouldEqual, "1") | ||||
| }) | }) | ||||
| Convey("UnionType ExternallyTagged", t, func() { | |||||
| type Base interface{} | |||||
| type St1 struct { | |||||
| Val string | |||||
| } | |||||
| type St2 struct { | |||||
| Val int64 | |||||
| } | |||||
| type Outter struct { | |||||
| B []Base | |||||
| } | |||||
| union := types.NewTypeUnion[Base](St1{}, &St2{}) | |||||
| UseTypeUnionExternallyTagged(&union) | |||||
| val := Outter{B: []Base{St1{Val: "asd"}, &St2{Val: 123}}} | |||||
| data, err := ObjectToJSONEx(val) | |||||
| So(err, ShouldBeNil) | |||||
| ret, err := JSONToObjectEx[Outter](data) | |||||
| So(err, ShouldBeNil) | |||||
| So(ret, ShouldResemble, val) | |||||
| }) | |||||
| Convey("UnionType InternallyTagged", t, func() { | |||||
| type Base interface{} | |||||
| type St1 struct { | |||||
| Metadata `union:"St1"` | |||||
| Type string | |||||
| Val string | |||||
| } | |||||
| type St2 struct { | |||||
| Metadata `union:"St2"` | |||||
| Type string | |||||
| Val int64 | |||||
| } | |||||
| type Outter struct { | |||||
| B []Base | |||||
| } | |||||
| union := types.NewTypeUnion[Base](St1{}, &St2{}) | |||||
| UseTypeUnionInternallyTagged(&union, "Type") | |||||
| val := Outter{B: []Base{St1{Val: "asd", Type: "St1"}, &St2{Val: 123, Type: "St2"}}} | |||||
| data, err := ObjectToJSONEx(val) | |||||
| So(err, ShouldBeNil) | |||||
| ret, err := JSONToObjectEx[Outter](data) | |||||
| So(err, ShouldBeNil) | |||||
| So(ret, ShouldResemble, val) | |||||
| }) | |||||
| Convey("实参类型和目标类型本身就是UnionType ExternallyTagged", t, func() { | |||||
| type Base interface{} | |||||
| type St1 struct { | |||||
| Val string | |||||
| } | |||||
| union := types.NewTypeUnion[Base](St1{}) | |||||
| UseTypeUnionExternallyTagged(&union) | |||||
| var val Base = St1{Val: "asd"} | |||||
| data, err := ObjectToJSONEx(val) | |||||
| So(err, ShouldBeNil) | |||||
| ret, err := JSONToObjectEx[Base](data) | |||||
| So(err, ShouldBeNil) | |||||
| So(ret, ShouldResemble, val) | |||||
| }) | |||||
| Convey("实参类型和目标类型本身就是UnionType InternallyTagged", t, func() { | |||||
| type Base interface{} | |||||
| type St1 struct { | |||||
| Metadata `union:"St1"` | |||||
| Type string | |||||
| Val string | |||||
| } | |||||
| union := types.NewTypeUnion[Base](St1{}) | |||||
| UseTypeUnionInternallyTagged(&union, "Type") | |||||
| var val Base = St1{Val: "asd", Type: "St1"} | |||||
| data, err := ObjectToJSONEx(val) | |||||
| So(err, ShouldBeNil) | |||||
| ret, err := JSONToObjectEx[Base](data) | |||||
| So(err, ShouldBeNil) | |||||
| So(ret, ShouldResemble, val) | |||||
| }) | |||||
| Convey("UnionType带有函数 ExternallyTagged", t, func() { | |||||
| union := types.NewTypeUnion[Base](&St1{}, St2{}) | |||||
| UseTypeUnionExternallyTagged(&union) | |||||
| var val []Base = []Base{ | |||||
| &St1{Val: "asd", Type: "St1"}, | |||||
| St2{Val: 123, Type: "St2"}, | |||||
| } | |||||
| data, err := ObjectToJSONEx(val) | |||||
| So(err, ShouldBeNil) | |||||
| ret, err := JSONToObjectEx[[]Base](data) | |||||
| So(err, ShouldBeNil) | |||||
| So(ret, ShouldResemble, val) | |||||
| }) | |||||
| Convey("UnionType带有函数 InternallyTagged", t, func() { | |||||
| union := types.NewTypeUnion[Base](&St1{}, St2{}) | |||||
| UseTypeUnionInternallyTagged(&union, "Type") | |||||
| var val []Base = []Base{ | |||||
| &St1{Val: "asd", Type: "St1"}, | |||||
| St2{Val: 123, Type: "St2"}, | |||||
| } | |||||
| data, err := ObjectToJSONEx(val) | |||||
| So(err, ShouldBeNil) | |||||
| ret, err := JSONToObjectEx[[]Base](data) | |||||
| So(err, ShouldBeNil) | |||||
| So(ret, ShouldResemble, val) | |||||
| }) | |||||
| Convey("UnionType,但实际值为nil ExternallyTagged", t, func() { | |||||
| union := types.NewTypeUnion[Base](&St1{}, St2{}) | |||||
| UseTypeUnionExternallyTagged(&union) | |||||
| var val []Base = []Base{ | |||||
| nil, | |||||
| } | |||||
| data, err := ObjectToJSONEx(val) | |||||
| So(err, ShouldBeNil) | |||||
| ret, err := JSONToObjectEx[[]Base](data) | |||||
| So(err, ShouldBeNil) | |||||
| So(ret, ShouldResemble, val) | |||||
| }) | |||||
| Convey("UnionType,但实际值为nil InternallyTagged", t, func() { | |||||
| union := types.NewTypeUnion[Base](&St1{}, St2{}) | |||||
| UseTypeUnionInternallyTagged(&union, "Type") | |||||
| var val []Base = []Base{ | |||||
| nil, | |||||
| } | |||||
| data, err := ObjectToJSONEx(val) | |||||
| So(err, ShouldBeNil) | |||||
| ret, err := JSONToObjectEx[[]Base](data) | |||||
| So(err, ShouldBeNil) | |||||
| So(ret, ShouldResemble, val) | |||||
| }) | |||||
| } | } | ||||
| @@ -6,6 +6,8 @@ import ( | |||||
| "time" | "time" | ||||
| ) | ) | ||||
| type Metadata struct{} | |||||
| type TimestampSecond time.Time | type TimestampSecond time.Time | ||||
| func (t *TimestampSecond) MarshalJSON() ([]byte, error) { | func (t *TimestampSecond) MarshalJSON() ([]byte, error) { | ||||
| @@ -0,0 +1,336 @@ | |||||
| package serder | |||||
| import ( | |||||
| "fmt" | |||||
| "reflect" | |||||
| "unsafe" | |||||
| jsoniter "github.com/json-iterator/go" | |||||
| "github.com/modern-go/reflect2" | |||||
| "gitlink.org.cn/cloudream/common/pkgs/types" | |||||
| myreflect "gitlink.org.cn/cloudream/common/utils/reflect" | |||||
| ) | |||||
| type TypeUnionExternallyTagged struct { | |||||
| Union *types.TypeUnion | |||||
| TypeNameToType map[string]reflect.Type | |||||
| } | |||||
| // 遇到TypeUnion的基类(UnionType)的字段时,将其实际值的类型信息也编码到JSON中,反序列化时也会解析出类型信息,还原出真实的类型。 | |||||
| // Externally Tagged的格式是:{ "类型名": {...对象内容...} } | |||||
| // | |||||
| // 可以通过内嵌Metadata结构体,并在它身上增加"union"Tag来指定类型名称,如果没有指定,则默认使用系统类型名(包括包路径)。 | |||||
| func UseTypeUnionExternallyTagged(union *types.TypeUnion) *TypeUnionExternallyTagged { | |||||
| eu := &TypeUnionExternallyTagged{ | |||||
| Union: union, | |||||
| TypeNameToType: make(map[string]reflect.Type), | |||||
| } | |||||
| for _, eleType := range union.ElementTypes { | |||||
| eu.Add(eleType) | |||||
| } | |||||
| unionHandler.externallyTagged[union.UnionType] = eu | |||||
| return eu | |||||
| } | |||||
| func (u *TypeUnionExternallyTagged) Add(typ reflect.Type) error { | |||||
| err := u.Union.Add(typ) | |||||
| if err != nil { | |||||
| return nil | |||||
| } | |||||
| u.TypeNameToType[makeDerefFullTypeName(typ)] = typ | |||||
| return nil | |||||
| } | |||||
| type TypeUnionInternallyTagged struct { | |||||
| Union *types.TypeUnion | |||||
| TagField string | |||||
| TagToType map[string]reflect.Type | |||||
| } | |||||
| // 遇到TypeUnion的基类(UnionType)的字段时,将其实际值的类型信息也编码到JSON中,反序列化时也会解析出类型信息,还原出真实的类型。 | |||||
| // Internally Tagged的格式是:{ "类型字段": "类型名", ...对象内容...},JSON中的类型字段名需要指定。 | |||||
| // 注:对象定义需要包含类型字段,而且在序列化之前需要手动赋值,目前不支持自动设置。 | |||||
| // | |||||
| // 可以通过内嵌Metadata结构体,并在它身上增加"union"Tag来指定类型名称,如果没有指定,则默认使用系统类型名(包括包路径)。 | |||||
| func UseTypeUnionInternallyTagged(union *types.TypeUnion, tagField string) *TypeUnionInternallyTagged { | |||||
| iu := &TypeUnionInternallyTagged{ | |||||
| Union: union, | |||||
| TagField: tagField, | |||||
| TagToType: make(map[string]reflect.Type), | |||||
| } | |||||
| for _, eleType := range union.ElementTypes { | |||||
| iu.Add(eleType) | |||||
| } | |||||
| unionHandler.internallyTagged[union.UnionType] = iu | |||||
| return iu | |||||
| } | |||||
| func (u *TypeUnionInternallyTagged) Add(typ reflect.Type) error { | |||||
| err := u.Union.Add(typ) | |||||
| if err != nil { | |||||
| return nil | |||||
| } | |||||
| // 解引用直到得到结构体类型 | |||||
| structType := typ | |||||
| for structType.Kind() == reflect.Pointer { | |||||
| structType = structType.Elem() | |||||
| } | |||||
| // 要求内嵌Metadata结构体,那么结构体中的字段名就会是Metadata, | |||||
| field, ok := structType.FieldByName(myreflect.TypeNameOf[Metadata]()) | |||||
| if !ok { | |||||
| u.TagToType[makeDerefFullTypeName(structType)] = typ | |||||
| return nil | |||||
| } | |||||
| // 为防同名,检查类型是不是也是Metadata | |||||
| if field.Type != myreflect.TypeOf[Metadata]() { | |||||
| u.TagToType[makeDerefFullTypeName(structType)] = typ | |||||
| return nil | |||||
| } | |||||
| tag := field.Tag.Get("union") | |||||
| if tag == "" { | |||||
| u.TagToType[makeDerefFullTypeName(structType)] = typ | |||||
| return nil | |||||
| } | |||||
| u.TagToType[tag] = typ | |||||
| return nil | |||||
| } | |||||
| type UnionHandler struct { | |||||
| internallyTagged map[reflect.Type]*TypeUnionInternallyTagged | |||||
| externallyTagged map[reflect.Type]*TypeUnionExternallyTagged | |||||
| } | |||||
| func (h *UnionHandler) UpdateStructDescriptor(structDescriptor *jsoniter.StructDescriptor) { | |||||
| } | |||||
| func (h *UnionHandler) CreateMapKeyDecoder(typ reflect2.Type) jsoniter.ValDecoder { | |||||
| return nil | |||||
| } | |||||
| func (h *UnionHandler) CreateMapKeyEncoder(typ reflect2.Type) jsoniter.ValEncoder { | |||||
| return nil | |||||
| } | |||||
| func (h *UnionHandler) CreateDecoder(typ reflect2.Type) jsoniter.ValDecoder { | |||||
| typ1 := typ.Type1() | |||||
| if it, ok := h.internallyTagged[typ1]; ok { | |||||
| return &InternallyTaggedDecoder{ | |||||
| union: it, | |||||
| } | |||||
| } | |||||
| if et, ok := h.externallyTagged[typ1]; ok { | |||||
| return &ExternallyTaggedDecoder{ | |||||
| union: et, | |||||
| } | |||||
| } | |||||
| return nil | |||||
| } | |||||
| func (h *UnionHandler) CreateEncoder(typ reflect2.Type) jsoniter.ValEncoder { | |||||
| typ1 := typ.Type1() | |||||
| if it, ok := h.internallyTagged[typ1]; ok { | |||||
| return &InternallyTaggedEncoder{ | |||||
| union: it, | |||||
| } | |||||
| } | |||||
| if et, ok := h.externallyTagged[typ1]; ok { | |||||
| return &ExternallyTaggedEncoder{ | |||||
| union: et, | |||||
| } | |||||
| } | |||||
| return nil | |||||
| } | |||||
| func (h *UnionHandler) DecorateDecoder(typ reflect2.Type, decoder jsoniter.ValDecoder) jsoniter.ValDecoder { | |||||
| return decoder | |||||
| } | |||||
| func (h *UnionHandler) DecorateEncoder(typ reflect2.Type, encoder jsoniter.ValEncoder) jsoniter.ValEncoder { | |||||
| return encoder | |||||
| } | |||||
| // 以下Encoder/Decoder都是在传入类型/目标类型是TypeUnion的基类(UnionType)时使用 | |||||
| type InternallyTaggedEncoder struct { | |||||
| union *TypeUnionInternallyTagged | |||||
| } | |||||
| func (e *InternallyTaggedEncoder) IsEmpty(ptr unsafe.Pointer) bool { | |||||
| return false | |||||
| } | |||||
| func (e *InternallyTaggedEncoder) Encode(ptr unsafe.Pointer, stream *jsoniter.Stream) { | |||||
| var val any | |||||
| if e.union.Union.UnionType.NumMethod() == 0 { | |||||
| // 无方法的interface底层都是eface结构体,所以可以直接转*any | |||||
| val = *(*any)(ptr) | |||||
| } else { | |||||
| // 有方法的interface底层都是iface结构体,可以将其转成eface,转换后不损失类型信息 | |||||
| val = reflect2.IFaceToEFace(ptr) | |||||
| } | |||||
| // 可以考虑检查一下Type字段有没有赋值,没有赋值则将其赋值为union Tag指定的值 | |||||
| stream.WriteVal(val) | |||||
| } | |||||
| type InternallyTaggedDecoder struct { | |||||
| union *TypeUnionInternallyTagged | |||||
| } | |||||
| func (e *InternallyTaggedDecoder) Decode(ptr unsafe.Pointer, iter *jsoniter.Iterator) { | |||||
| nextTokenKind := iter.WhatIsNext() | |||||
| if nextTokenKind == jsoniter.NilValue { | |||||
| iter.Skip() | |||||
| return | |||||
| } | |||||
| raw := iter.ReadAny() | |||||
| if raw.LastError() != nil { | |||||
| iter.ReportError("decode TaggedUnionType", "getting object raw:"+raw.LastError().Error()) | |||||
| return | |||||
| } | |||||
| tagField := raw.Get(e.union.TagField) | |||||
| if tagField.LastError() != nil { | |||||
| iter.ReportError("decode TaggedUnionType", "getting type tag field:"+tagField.LastError().Error()) | |||||
| return | |||||
| } | |||||
| typeTag := tagField.ToString() | |||||
| if typeTag == "" { | |||||
| iter.ReportError("decode TaggedUnionType", "type tag is empty") | |||||
| return | |||||
| } | |||||
| typ, ok := e.union.TagToType[typeTag] | |||||
| if !ok { | |||||
| iter.ReportError("decode TaggedUnionType", fmt.Sprintf("unknow type tag %s in union %s", typeTag, e.union.Union.UnionType.Name())) | |||||
| return | |||||
| } | |||||
| // 如果目标类型已经是个指针类型*T,那么在New的时候就需要使用T, | |||||
| // 否则New出来的是会是**T,这将导致后续的反序列化出问题 | |||||
| if typ.Kind() == reflect.Pointer { | |||||
| val := reflect.New(typ.Elem()) | |||||
| raw.ToVal(val.Interface()) | |||||
| retVal := reflect.NewAt(e.union.Union.UnionType, ptr) | |||||
| retVal.Elem().Set(val) | |||||
| } else { | |||||
| val := reflect.New(typ) | |||||
| raw.ToVal(val.Interface()) | |||||
| retVal := reflect.NewAt(e.union.Union.UnionType, ptr) | |||||
| retVal.Elem().Set(val.Elem()) | |||||
| } | |||||
| } | |||||
| type ExternallyTaggedEncoder struct { | |||||
| union *TypeUnionExternallyTagged | |||||
| } | |||||
| func (e *ExternallyTaggedEncoder) IsEmpty(ptr unsafe.Pointer) bool { | |||||
| return false | |||||
| } | |||||
| func (e *ExternallyTaggedEncoder) Encode(ptr unsafe.Pointer, stream *jsoniter.Stream) { | |||||
| var val any | |||||
| if e.union.Union.UnionType.NumMethod() == 0 { | |||||
| // 无方法的interface底层都是eface结构体,所以可以直接转*any | |||||
| val = *(*any)(ptr) | |||||
| } else { | |||||
| // 有方法的interface底层都是iface结构体,可以将其转成eface,转换后不损失类型信息 | |||||
| val = reflect2.IFaceToEFace(ptr) | |||||
| } | |||||
| if val == nil { | |||||
| stream.WriteNil() | |||||
| return | |||||
| } | |||||
| stream.WriteObjectStart() | |||||
| valType := myreflect.TypeOfValue(val) | |||||
| if !e.union.Union.Include(valType) { | |||||
| stream.Error = fmt.Errorf("type %v is not in union %v", valType, e.union.Union.UnionType) | |||||
| return | |||||
| } | |||||
| stream.WriteObjectField(makeDerefFullTypeName(valType)) | |||||
| stream.WriteVal(val) | |||||
| stream.WriteObjectEnd() | |||||
| } | |||||
| type ExternallyTaggedDecoder struct { | |||||
| union *TypeUnionExternallyTagged | |||||
| } | |||||
| func (e *ExternallyTaggedDecoder) Decode(ptr unsafe.Pointer, iter *jsoniter.Iterator) { | |||||
| nextTkType := iter.WhatIsNext() | |||||
| if nextTkType == jsoniter.NilValue { | |||||
| iter.Skip() | |||||
| return | |||||
| } | |||||
| if nextTkType != jsoniter.ObjectValue { | |||||
| iter.ReportError("decode UnionType", fmt.Sprintf("unknow next token type %v", nextTkType)) | |||||
| return | |||||
| } | |||||
| typeStr := iter.ReadObject() | |||||
| if typeStr == "" { | |||||
| iter.ReportError("decode UnionType", "type string is empty") | |||||
| } | |||||
| typ, ok := e.union.TypeNameToType[typeStr] | |||||
| if !ok { | |||||
| iter.ReportError("decode UnionType", fmt.Sprintf("unknow type string %s in union %v", typeStr, e.union.Union.UnionType)) | |||||
| return | |||||
| } | |||||
| // 如果目标类型已经是个指针类型*T,那么在New的时候就需要使用T, | |||||
| // 否则New出来的是会是**T,这将导致后续的反序列化出问题 | |||||
| if typ.Kind() == reflect.Pointer { | |||||
| val := reflect.New(typ.Elem()) | |||||
| iter.ReadVal(val.Interface()) | |||||
| retVal := reflect.NewAt(e.union.Union.UnionType, ptr) | |||||
| retVal.Elem().Set(val) | |||||
| } else { | |||||
| val := reflect.New(typ) | |||||
| iter.ReadVal(val.Interface()) | |||||
| retVal := reflect.NewAt(e.union.Union.UnionType, ptr) | |||||
| retVal.Elem().Set(val.Elem()) | |||||
| } | |||||
| if iter.ReadObject() != "" { | |||||
| iter.ReportError("decode UnionType", "there should be only one fields in the json object") | |||||
| } | |||||
| } | |||||
| func makeDerefFullTypeName(typ reflect.Type) string { | |||||
| realType := typ | |||||
| for realType.Kind() == reflect.Pointer { | |||||
| realType = realType.Elem() | |||||
| } | |||||
| return fmt.Sprintf("%s.%s", realType.PkgPath(), realType.Name()) | |||||
| } | |||||