| @@ -0,0 +1,117 @@ | |||||
| package models | |||||
| import ( | |||||
| myreflect "gitlink.org.cn/cloudream/common/utils/reflect" | |||||
| "gitlink.org.cn/cloudream/common/utils/serder" | |||||
| ) | |||||
| const ( | |||||
| JobTypeNormal = "Normal" | |||||
| JobTypeResource = "Resource" | |||||
| FileInfoTypePackage = "Package" | |||||
| FileInfoTypeLocalFile = "LocalFile" | |||||
| FileInfoTypeResource = "Resource" | |||||
| FileInfoTypeImage = "Image" | |||||
| ) | |||||
| type JobSetInfo struct { | |||||
| Jobs []JobInfo `json:"jobs"` | |||||
| } | |||||
| type JobInfo interface{} | |||||
| var JobInfoTypeUnion = serder.NewTypeUnion[JobInfo]("type", | |||||
| serder.NewStringTypeResolver(). | |||||
| Add(JobTypeNormal, myreflect.TypeOf[NormalJobInfo]()). | |||||
| Add(JobTypeResource, myreflect.TypeOf[ResourceJobInfo]()), | |||||
| ) | |||||
| type NormalJobInfo struct { | |||||
| LocalJobID string `json:"localJobID"` | |||||
| Type string `json:"type"` | |||||
| Files JobFilesInfo `json:"files"` | |||||
| Runtime JobRuntimeInfo `json:"runtime"` | |||||
| Resources JobResourcesInfo `json:"resources"` | |||||
| } | |||||
| type ResourceJobInfo struct { | |||||
| LocalJobID string `json:"localJobID"` | |||||
| Type string `json:"type"` | |||||
| TargetLocalJobID string `json:"targetLocalJobID"` | |||||
| } | |||||
| type JobFilesInfo struct { | |||||
| Dateset FileInfo `json:"dataset"` | |||||
| Code FileInfo `json:"code"` | |||||
| Image FileInfo `json:"image"` | |||||
| } | |||||
| type FileInfo interface{} | |||||
| var FileInfoTypeUnion = serder.NewTypeUnion[JobInfo]("type", | |||||
| serder.NewStringTypeResolver(). | |||||
| Add(FileInfoTypePackage, myreflect.TypeOf[PackageFileInfo]()). | |||||
| Add(FileInfoTypeLocalFile, myreflect.TypeOf[LocalFileInfo]()). | |||||
| Add(FileInfoTypeResource, myreflect.TypeOf[ResourceFileInfo]()). | |||||
| Add(FileInfoTypeImage, myreflect.TypeOf[ImageFileInfo]()), | |||||
| ) | |||||
| type PackageFileInfo struct { | |||||
| Type string `json:"type"` | |||||
| PackageID int64 `json:"packageID"` | |||||
| } | |||||
| type LocalFileInfo struct { | |||||
| Type string `json:"type"` | |||||
| LocalPath string `json:"localPath"` | |||||
| } | |||||
| type ResourceFileInfo struct { | |||||
| Type string `json:"type"` | |||||
| ResourceLocalJobID string `json:"resourceLocalJobID"` | |||||
| } | |||||
| type ImageFileInfo struct { | |||||
| Type string `json:"type"` | |||||
| ImageID string `json:"imageID"` | |||||
| } | |||||
| type JobRuntimeInfo struct { | |||||
| Command string `json:"command"` | |||||
| Envs []EnvVar `json:"envs"` | |||||
| } | |||||
| type EnvVar struct { | |||||
| Var string `json:"var"` | |||||
| Value string `json:"value"` | |||||
| } | |||||
| type JobResourcesInfo struct { | |||||
| CPU float64 `json:"cpu"` | |||||
| GPU float64 `json:"gpu"` | |||||
| NPU float64 `json:"npu"` | |||||
| MLU float64 `json:"mlu"` | |||||
| Storage int64 `json:"storage"` | |||||
| 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, serder.MapToObjectOption{ | |||||
| UnionTypes: []serder.UnionTypeInfo{ | |||||
| JobInfoTypeUnion, | |||||
| FileInfoTypeUnion, | |||||
| }, | |||||
| }) | |||||
| if err != nil { | |||||
| return nil, err | |||||
| } | |||||
| return &ret, nil | |||||
| } | |||||
| @@ -1,5 +1,10 @@ | |||||
| package models | package models | ||||
| import ( | |||||
| myreflect "gitlink.org.cn/cloudream/common/utils/reflect" | |||||
| "gitlink.org.cn/cloudream/common/utils/serder" | |||||
| ) | |||||
| const ( | const ( | ||||
| ResourceTypeCPU = "CPU" | ResourceTypeCPU = "CPU" | ||||
| ResourceTypeNPU = "NPU" | ResourceTypeNPU = "NPU" | ||||
| @@ -20,6 +25,16 @@ type ResourceDataConst interface { | |||||
| ResourceData | CPUResourceData | NPUResourceData | GPUResourceData | MLUResourceData | StorageResourceData | MemoryResourceData | ResourceData | CPUResourceData | NPUResourceData | GPUResourceData | MLUResourceData | StorageResourceData | MemoryResourceData | ||||
| } | } | ||||
| var ResourceDataTypeUnion = serder.NewTypeUnion[ResourceData]("name", | |||||
| serder.NewStringTypeResolver(). | |||||
| Add(ResourceTypeCPU, myreflect.TypeOf[CPUResourceData]()). | |||||
| Add(ResourceTypeNPU, myreflect.TypeOf[NPUResourceData]()). | |||||
| Add(ResourceTypeGPU, myreflect.TypeOf[GPUResourceData]()). | |||||
| Add(ResourceTypeMLU, myreflect.TypeOf[MLUResourceData]()). | |||||
| Add(ResourceTypeStorage, myreflect.TypeOf[StorageResourceData]()). | |||||
| Add(ResourceTypeMemory, myreflect.TypeOf[MemoryResourceData]()), | |||||
| ) | |||||
| type DetailType[T any] struct { | type DetailType[T any] struct { | ||||
| Unit string `json:"unit"` | Unit string `json:"unit"` | ||||
| Value T `json:"value"` | Value T `json:"value"` | ||||
| @@ -51,42 +51,26 @@ func MakeMessage(body MessageBodyTypes) Message { | |||||
| return msg | return msg | ||||
| } | } | ||||
| type typeSet struct { | |||||
| TopType myreflect.Type | |||||
| ElementTypes serder.TypeNameResolver | |||||
| } | |||||
| var typeSets map[myreflect.Type]typeSet = make(map[reflect.Type]typeSet) | |||||
| var messageTypeSet *typeSet | |||||
| var unionTypes map[myreflect.Type]serder.UnionTypeInfo = make(map[reflect.Type]serder.UnionTypeInfo) | |||||
| var messageTypeUnionEles *serder.TypeNameResolver | |||||
| // 所有新定义的Message都需要在init中调用此函数 | // 所有新定义的Message都需要在init中调用此函数 | ||||
| func RegisterMessage[T any]() { | func RegisterMessage[T any]() { | ||||
| messageTypeSet.ElementTypes.Register(myreflect.TypeOf[T]()) | |||||
| messageTypeUnionEles.Register(myreflect.TypeOf[T]()) | |||||
| } | } | ||||
| // 如果对一个类型T调用了此函数,那么在序列化结构体中包含的T类型字段时, | |||||
| // 会将字段值的实际类型保存在序列化后的结果中(作为一个字段@type), | |||||
| // 在序列化结构体中包含的UnionType类型字段时,会将字段值的实际类型保存在序列化后的结果中。 | |||||
| // 在反序列化时,会根据类型信息重建原本的字段值。 | // 在反序列化时,会根据类型信息重建原本的字段值。 | ||||
| // | // | ||||
| // 只会处理types指定的类型。 | |||||
| func RegisterTypeSet[T any](types ...myreflect.Type) *typeSet { | |||||
| set := typeSet{ | |||||
| TopType: myreflect.TypeOf[T](), | |||||
| ElementTypes: serder.NewTypeNameResolver(true), | |||||
| } | |||||
| // 注:不是采用在序列化后的数据中增加TypeFieldName指名的字段数据,因此会无视UnionTypeInfo中的这个字段的设定 | |||||
| func RegisterUnionType(set serder.UnionTypeInfo) { | |||||
| unionTypes[set.UnionType] = set | |||||
| for _, t := range types { | |||||
| set.ElementTypes.Register(t) | |||||
| } | |||||
| typeSets[set.TopType] = set | |||||
| jsoniter.RegisterTypeEncoderFunc(myreflect.TypeOf[T]().String(), | |||||
| jsoniter.RegisterTypeEncoderFunc(set.UnionType.String(), | |||||
| func(ptr unsafe.Pointer, stream *jsoniter.Stream) { | func(ptr unsafe.Pointer, stream *jsoniter.Stream) { | ||||
| val := *((*T)(ptr)) | |||||
| var ifVal any = val | |||||
| if ifVal != nil { | |||||
| // 此处无法变成*UnionType,只能强转为*any | |||||
| val := *(*any)(ptr) | |||||
| if val != nil { | |||||
| stream.WriteArrayStart() | stream.WriteArrayStart() | ||||
| typeStr, err := set.ElementTypes.TypeToString(myreflect.TypeOfValue(val)) | typeStr, err := set.ElementTypes.TypeToString(myreflect.TypeOfValue(val)) | ||||
| if err != nil { | if err != nil { | ||||
| @@ -105,15 +89,16 @@ func RegisterTypeSet[T any](types ...myreflect.Type) *typeSet { | |||||
| return false | return false | ||||
| }) | }) | ||||
| jsoniter.RegisterTypeDecoderFunc(myreflect.TypeOf[T]().String(), | |||||
| jsoniter.RegisterTypeDecoderFunc(set.UnionType.String(), | |||||
| func(ptr unsafe.Pointer, iter *jsoniter.Iterator) { | func(ptr unsafe.Pointer, iter *jsoniter.Iterator) { | ||||
| vp := (*T)(ptr) | |||||
| // 此处无法变成*UnionType,只能强转为*any | |||||
| vp := (*any)(ptr) | |||||
| nextTkType := iter.WhatIsNext() | nextTkType := iter.WhatIsNext() | ||||
| if nextTkType == jsoniter.NilValue { | if nextTkType == jsoniter.NilValue { | ||||
| iter.ReadNil() | iter.ReadNil() | ||||
| var zero T | |||||
| *vp = zero | |||||
| *vp = nil | |||||
| } else if nextTkType == jsoniter.ArrayValue { | } else if nextTkType == jsoniter.ArrayValue { | ||||
| iter.ReadArray() | iter.ReadArray() | ||||
| typeStr := iter.ReadString() | typeStr := iter.ReadString() | ||||
| @@ -127,7 +112,7 @@ func RegisterTypeSet[T any](types ...myreflect.Type) *typeSet { | |||||
| val := reflect.New(typ) | val := reflect.New(typ) | ||||
| iter.ReadVal(val.Interface()) | iter.ReadVal(val.Interface()) | ||||
| *vp = val.Elem().Interface().(T) | |||||
| *vp = val.Elem().Interface() | |||||
| iter.ReadArray() | iter.ReadArray() | ||||
| } else { | } else { | ||||
| @@ -135,7 +120,84 @@ func RegisterTypeSet[T any](types ...myreflect.Type) *typeSet { | |||||
| return | return | ||||
| } | } | ||||
| }) | }) | ||||
| } | |||||
| // 如果对一个类型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) | |||||
| } | |||||
| /* | |||||
| 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 | return &set | ||||
| } | } | ||||
| @@ -163,5 +225,6 @@ func Deserialize(data []byte) (*Message, error) { | |||||
| } | } | ||||
| func init() { | func init() { | ||||
| messageTypeSet = RegisterTypeSet[MessageBodyTypes]() | |||||
| messageTypeUnionEles = serder.NewTypeNameResolver(true) | |||||
| RegisterUnionType(serder.NewTypeUnion[MessageBodyTypes]("", messageTypeUnionEles)) | |||||
| } | } | ||||
| @@ -131,7 +131,9 @@ func TestMessage(t *testing.T) { | |||||
| }) | }) | ||||
| Convey("使用TypeSet类型,但字段值为nil", t, func() { | Convey("使用TypeSet类型,但字段值为nil", t, func() { | ||||
| type MyTypeSet interface{} | |||||
| type MyTypeSet interface { | |||||
| Test() | |||||
| } | |||||
| type Body struct { | type Body struct { | ||||
| Value MyTypeSet | Value MyTypeSet | ||||
| @@ -7,7 +7,7 @@ import ( | |||||
| myreflect "gitlink.org.cn/cloudream/common/utils/reflect" | myreflect "gitlink.org.cn/cloudream/common/utils/reflect" | ||||
| ) | ) | ||||
| type Converter func(srcType reflect.Type, dstType reflect.Type, data interface{}) (interface{}, error) | |||||
| type Converter func(from reflect.Value, to reflect.Value) (interface{}, error) | |||||
| type AnyToAnyOption struct { | type AnyToAnyOption struct { | ||||
| NoFromAny bool // 不判断目的字段是否实现了FromAny接口 | NoFromAny bool // 不判断目的字段是否实现了FromAny接口 | ||||
| @@ -6,6 +6,8 @@ import ( | |||||
| "io" | "io" | ||||
| "reflect" | "reflect" | ||||
| "strings" | "strings" | ||||
| myreflect "gitlink.org.cn/cloudream/common/utils/reflect" | |||||
| ) | ) | ||||
| func ObjectToJSON(obj any) ([]byte, error) { | func ObjectToJSON(obj any) ([]byte, error) { | ||||
| @@ -47,13 +49,68 @@ type TypeResolver interface { | |||||
| StringToType(typeStr string) (reflect.Type, error) | StringToType(typeStr string) (reflect.Type, error) | ||||
| } | } | ||||
| type TypedSerderOption struct { | |||||
| TypeResolver TypeResolver | |||||
| type UnionTypeInfo struct { | |||||
| UnionType reflect.Type | |||||
| TypeFieldName string | TypeFieldName string | ||||
| ElementTypes TypeResolver | |||||
| } | |||||
| func NewTypeUnion[TU any](typeField string, eleTypes TypeResolver) UnionTypeInfo { | |||||
| return UnionTypeInfo{ | |||||
| UnionType: myreflect.TypeOf[TU](), | |||||
| TypeFieldName: typeField, | |||||
| ElementTypes: eleTypes, | |||||
| } | |||||
| } | } | ||||
| func MapToObject(m map[string]any, obj any) error { | |||||
| return AnyToAny(m, obj) | |||||
| type MapToObjectOption struct { | |||||
| UnionTypes []UnionTypeInfo // 转换过程中遇到这些类型时,会依据指定的字段的值,来决定转换后的实际类型 | |||||
| } | |||||
| func MapToObject(m map[string]any, obj any, opt ...MapToObjectOption) error { | |||||
| var op MapToObjectOption | |||||
| if len(opt) > 0 { | |||||
| op = opt[0] | |||||
| } | |||||
| unionTypeMapping := make(map[reflect.Type]*UnionTypeInfo) | |||||
| for _, u := range op.UnionTypes { | |||||
| unionTypeMapping[u.UnionType] = &u | |||||
| } | |||||
| convs := []Converter{ | |||||
| func(from reflect.Value, to reflect.Value) (interface{}, error) { | |||||
| info, ok := unionTypeMapping[to.Type()] | |||||
| if !ok { | |||||
| return from.Interface(), nil | |||||
| } | |||||
| mp := from.Interface().(map[string]any) | |||||
| tag, ok := mp[info.TypeFieldName] | |||||
| if !ok { | |||||
| return nil, fmt.Errorf("converting to %v: no tag field %s in map", to.Type(), info.TypeFieldName) | |||||
| } | |||||
| tagStr, ok := tag.(string) | |||||
| if !ok { | |||||
| return nil, fmt.Errorf("converting to %v: tag field %s value is %v, which is not a string", to.Type(), info.TypeFieldName, tag) | |||||
| } | |||||
| eleType, err := info.ElementTypes.StringToType(tagStr) | |||||
| if err != nil { | |||||
| return nil, fmt.Errorf("converting to %v: %w", to.Type(), err) | |||||
| } | |||||
| to.Set(reflect.Indirect(reflect.New(eleType))) | |||||
| return from.Interface(), nil | |||||
| }, | |||||
| } | |||||
| return AnyToAny(m, obj, AnyToAnyOption{ | |||||
| Converters: convs, | |||||
| }) | |||||
| } | } | ||||
| func ObjectToMap(obj any) (map[string]any, error) { | func ObjectToMap(obj any) (map[string]any, error) { | ||||
| @@ -132,51 +189,3 @@ func contains(arr []string, ele string, startIndex int) bool { | |||||
| return false | return false | ||||
| } | } | ||||
| func TypedMapToObject(m map[string]any, opt TypedSerderOption) (any, error) { | |||||
| typeVal, ok := m[opt.TypeFieldName] | |||||
| if !ok { | |||||
| return nil, fmt.Errorf("no type field in the map") | |||||
| } | |||||
| typeStr, ok := typeVal.(string) | |||||
| if !ok { | |||||
| return nil, fmt.Errorf("type is not a string") | |||||
| } | |||||
| typ, err := opt.TypeResolver.StringToType(typeStr) | |||||
| if err != nil { | |||||
| return nil, fmt.Errorf("get type from string failed, err: %w", err) | |||||
| } | |||||
| val := reflect.New(typ) | |||||
| valPtr := val.Interface() | |||||
| err = AnyToAny(m, valPtr) | |||||
| if err != nil { | |||||
| return nil, err | |||||
| } | |||||
| return val.Elem().Interface(), nil | |||||
| } | |||||
| func ObjectToTypedMap(obj any, opt TypedSerderOption) (map[string]any, error) { | |||||
| var mp map[string]any | |||||
| err := AnyToAny(obj, &mp) | |||||
| if err != nil { | |||||
| return nil, err | |||||
| } | |||||
| _, ok := mp[opt.TypeFieldName] | |||||
| if ok { | |||||
| return nil, fmt.Errorf("object has the same field as the type field") | |||||
| } | |||||
| mp[opt.TypeFieldName], err = opt.TypeResolver.TypeToString(reflect.TypeOf(obj)) | |||||
| if err != nil { | |||||
| return nil, fmt.Errorf("get string from type failed, err: %w", err) | |||||
| } | |||||
| return mp, nil | |||||
| } | |||||
| @@ -179,9 +179,9 @@ func Test_AnyToAny(t *testing.T) { | |||||
| st2 := Struct2{} | st2 := Struct2{} | ||||
| err := AnyToAny(st1, &st2, AnyToAnyOption{ | err := AnyToAny(st1, &st2, AnyToAnyOption{ | ||||
| Converters: []Converter{func(srcType reflect.Type, dstType reflect.Type, data interface{}) (interface{}, error) { | |||||
| if srcType == myreflect.TypeOf[Struct1]() && dstType == myreflect.TypeOf[Struct2]() { | |||||
| s1 := data.(Struct1) | |||||
| Converters: []Converter{func(from reflect.Value, to reflect.Value) (interface{}, error) { | |||||
| if from.Type() == myreflect.TypeOf[Struct1]() && to.Type() == myreflect.TypeOf[Struct2]() { | |||||
| s1 := from.Interface().(Struct1) | |||||
| return Struct2{ | return Struct2{ | ||||
| Value: "@" + s1.Value, | Value: "@" + s1.Value, | ||||
| }, nil | }, nil | ||||
| @@ -196,44 +196,6 @@ func Test_AnyToAny(t *testing.T) { | |||||
| }) | }) | ||||
| } | } | ||||
| func Test_TypedMapToObject(t *testing.T) { | |||||
| type Struct struct { | |||||
| A string `json:"a"` | |||||
| B int `json:"b"` | |||||
| C int64 `json:"c,string"` | |||||
| } | |||||
| nameResovler := NewTypeNameResolver(true) | |||||
| nameResovler.Register(myreflect.TypeOf[Struct]()) | |||||
| Convey("结构体", t, func() { | |||||
| st := Struct{ | |||||
| A: "a", | |||||
| B: 1, | |||||
| C: 2, | |||||
| } | |||||
| mp, err := ObjectToTypedMap(st, TypedSerderOption{ | |||||
| TypeResolver: &nameResovler, | |||||
| TypeFieldName: "@type", | |||||
| }) | |||||
| So(err, ShouldBeNil) | |||||
| st2Ptr, err := TypedMapToObject(mp, TypedSerderOption{ | |||||
| TypeResolver: &nameResovler, | |||||
| TypeFieldName: "@type", | |||||
| }) | |||||
| So(err, ShouldBeNil) | |||||
| st2, ok := st2Ptr.(Struct) | |||||
| So(ok, ShouldBeTrue) | |||||
| So(st2, ShouldHaveSameTypeAs, st) | |||||
| So(st2, ShouldResemble, st) | |||||
| }) | |||||
| } | |||||
| func Test_MapToObject(t *testing.T) { | func Test_MapToObject(t *testing.T) { | ||||
| type Base struct { | type Base struct { | ||||
| Int int | Int int | ||||
| @@ -394,4 +356,53 @@ func Test_MapToObject(t *testing.T) { | |||||
| So(string(mpRetJson), ShouldEqualJSON, string(exceptMapJson)) | So(string(mpRetJson), ShouldEqualJSON, string(exceptMapJson)) | ||||
| }) | }) | ||||
| Convey("包含UnionType", t, func() { | |||||
| type UnionType interface{} | |||||
| type EleType1 struct { | |||||
| Value1 string `json:"value1"` | |||||
| } | |||||
| type EleType2 struct { | |||||
| Value2 int `json:"value2"` | |||||
| } | |||||
| type St struct { | |||||
| Us []UnionType `json:"us"` | |||||
| } | |||||
| mp := map[string]any{ | |||||
| "us": []map[string]any{ | |||||
| { | |||||
| "type": "1", | |||||
| "value1": "1", | |||||
| }, | |||||
| { | |||||
| "type": "2", | |||||
| "value2": 2, | |||||
| }, | |||||
| }, | |||||
| } | |||||
| var ret St | |||||
| err := MapToObject(mp, &ret, MapToObjectOption{ | |||||
| UnionTypes: []UnionTypeInfo{ | |||||
| { | |||||
| UnionType: myreflect.TypeOf[UnionType](), | |||||
| TypeFieldName: "type", | |||||
| ElementTypes: NewStringTypeResolver(). | |||||
| Add("1", myreflect.TypeOf[EleType1]()). | |||||
| Add("2", myreflect.TypeOf[EleType2]()), | |||||
| }, | |||||
| }, | |||||
| }) | |||||
| So(err, ShouldBeNil) | |||||
| So(ret.Us, ShouldResemble, []UnionType{ | |||||
| EleType1{Value1: "1"}, | |||||
| EleType2{Value2: 2}, | |||||
| }) | |||||
| }) | |||||
| } | } | ||||
| @@ -0,0 +1,43 @@ | |||||
| package serder | |||||
| import ( | |||||
| "fmt" | |||||
| "reflect" | |||||
| ) | |||||
| type StringTypeResolver struct { | |||||
| strToType map[string]reflect.Type | |||||
| typeToStr map[reflect.Type]string | |||||
| } | |||||
| func NewStringTypeResolver() *StringTypeResolver { | |||||
| return &StringTypeResolver{ | |||||
| strToType: make(map[string]reflect.Type), | |||||
| typeToStr: make(map[reflect.Type]string), | |||||
| } | |||||
| } | |||||
| func (r *StringTypeResolver) Add(str string, typ reflect.Type) *StringTypeResolver { | |||||
| r.strToType[str] = typ | |||||
| r.typeToStr[typ] = str | |||||
| return r | |||||
| } | |||||
| func (r *StringTypeResolver) TypeToString(typ reflect.Type) (string, error) { | |||||
| var typeStr string | |||||
| var ok bool | |||||
| if typeStr, ok = r.typeToStr[typ]; !ok { | |||||
| return "", fmt.Errorf("type %s is not registered before", typ) | |||||
| } | |||||
| return typeStr, nil | |||||
| } | |||||
| func (r *StringTypeResolver) StringToType(typeStr string) (reflect.Type, error) { | |||||
| typ, ok := r.strToType[typeStr] | |||||
| if !ok { | |||||
| return nil, fmt.Errorf("unknow type string %s", typeStr) | |||||
| } | |||||
| return typ, nil | |||||
| } | |||||
| @@ -10,8 +10,8 @@ type TypeNameResolver struct { | |||||
| types map[string]reflect.Type | types map[string]reflect.Type | ||||
| } | } | ||||
| func NewTypeNameResolver(includePackagePath bool) TypeNameResolver { | |||||
| return TypeNameResolver{ | |||||
| func NewTypeNameResolver(includePackagePath bool) *TypeNameResolver { | |||||
| return &TypeNameResolver{ | |||||
| includePackagePath: includePackagePath, | includePackagePath: includePackagePath, | ||||
| types: make(map[string]reflect.Type), | types: make(map[string]reflect.Type), | ||||
| } | } | ||||