restored DynamicModel types cache
This commit is contained in:
		
							parent
							
								
									856cc604a7
								
							
						
					
					
						commit
						58dab5bf70
					
				|  | @ -13,6 +13,7 @@ import ( | |||
| 	"path/filepath" | ||||
| 	"reflect" | ||||
| 	"slices" | ||||
| 	"sort" | ||||
| 	"strings" | ||||
| 	"time" | ||||
| 
 | ||||
|  | @ -1020,9 +1021,19 @@ func structConstructorUnmarshal(vm *goja.Runtime, call goja.ConstructorCall, ins | |||
| 	return instanceValue | ||||
| } | ||||
| 
 | ||||
| var cachedDynamicModelStructs = store.New[string, reflect.Type](nil) | ||||
| 
 | ||||
| // newDynamicModel creates a new dynamic struct with fields based
 | ||||
| // on the specified "shape".
 | ||||
| //
 | ||||
| // The "shape" values are used as defaults and could be of type:
 | ||||
| // - int (ex. 0)
 | ||||
| // - float (ex. -0)
 | ||||
| // - string (ex. "")
 | ||||
| // - bool (ex. false)
 | ||||
| // - slice (ex. [])
 | ||||
| // - map (ex. map[string]any{})
 | ||||
| //
 | ||||
| // Example:
 | ||||
| //
 | ||||
| //	m := newDynamicModel(map[string]any{
 | ||||
|  | @ -1030,37 +1041,21 @@ func structConstructorUnmarshal(vm *goja.Runtime, call goja.ConstructorCall, ins | |||
| //		"total": 0,
 | ||||
| //	})
 | ||||
| func newDynamicModel(shape map[string]any) any { | ||||
| 	modelType := getDynamicModelStruct(shape) | ||||
| 	info := make([]*shapeFieldInfo, 0, len(shape)) | ||||
| 
 | ||||
| 	rvShapeValues := make([]reflect.Value, len(modelType.shapeValues)) | ||||
| 	for i, v := range modelType.shapeValues { | ||||
| 		rvShapeValues[i] = reflect.ValueOf(v) | ||||
| 	var hash strings.Builder | ||||
| 
 | ||||
| 	sortedKeys := make([]string, 0, len(shape)) | ||||
| 	for k := range shape { | ||||
| 		sortedKeys = append(sortedKeys, k) | ||||
| 	} | ||||
| 	sort.Strings(sortedKeys) | ||||
| 
 | ||||
| 	elem := reflect.New(modelType.structType).Elem() | ||||
| 
 | ||||
| 	for i, v := range rvShapeValues { | ||||
| 		elem.Field(i).Set(v) | ||||
| 	} | ||||
| 
 | ||||
| 	return elem.Addr().Interface() | ||||
| } | ||||
| 
 | ||||
| type dynamicModelType struct { | ||||
| 	structType  reflect.Type | ||||
| 	shapeValues []any | ||||
| } | ||||
| 
 | ||||
| func getDynamicModelStruct(shape map[string]any) *dynamicModelType { | ||||
| 	result := new(dynamicModelType) | ||||
| 	result.shapeValues = make([]any, 0, len(shape)) | ||||
| 
 | ||||
| 	structFields := make([]reflect.StructField, 0, len(shape)) | ||||
| 
 | ||||
| 	for k, v := range shape { | ||||
| 	for _, k := range sortedKeys { | ||||
| 		v := shape[k] | ||||
| 		vt := reflect.TypeOf(v) | ||||
| 
 | ||||
| 		switch kind := vt.Kind(); kind { | ||||
| 		switch vt.Kind() { | ||||
| 		case reflect.Map: | ||||
| 			raw, _ := json.Marshal(v) | ||||
| 			newV := types.JSONMap[any]{} | ||||
|  | @ -1075,16 +1070,40 @@ func getDynamicModelStruct(shape map[string]any) *dynamicModelType { | |||
| 			vt = reflect.TypeOf(newV) | ||||
| 		} | ||||
| 
 | ||||
| 		result.shapeValues = append(result.shapeValues, v) | ||||
| 		hash.WriteString(k) | ||||
| 		hash.WriteString(":") | ||||
| 		hash.WriteString(vt.String()) // it doesn't guarantee to be unique across all types but it should be fine with the primitive types DynamicModel is used
 | ||||
| 		hash.WriteString("|") | ||||
| 
 | ||||
| 		structFields = append(structFields, reflect.StructField{ | ||||
| 			Name: inflector.UcFirst(k), // ensures that the field is exportable
 | ||||
| 			Type: vt, | ||||
| 			Tag:  reflect.StructTag(`db:"` + k + `" json:"` + k + `" form:"` + k + `"`), | ||||
| 		}) | ||||
| 		info = append(info, &shapeFieldInfo{key: k, value: v, valueType: vt}) | ||||
| 	} | ||||
| 
 | ||||
| 	result.structType = reflect.StructOf(structFields) | ||||
| 	st := cachedDynamicModelStructs.GetOrSet(hash.String(), func() reflect.Type { | ||||
| 		structFields := make([]reflect.StructField, len(info)) | ||||
| 
 | ||||
| 	return result | ||||
| 		for i, item := range info { | ||||
| 			structFields[i] = reflect.StructField{ | ||||
| 				Name: inflector.UcFirst(item.key), // ensures that the field is exportable
 | ||||
| 				Type: item.valueType, | ||||
| 				Tag:  reflect.StructTag(`db:"` + item.key + `" json:"` + item.key + `" form:"` + item.key + `"`), | ||||
| 			} | ||||
| 		} | ||||
| 
 | ||||
| 		return reflect.StructOf(structFields) | ||||
| 	}) | ||||
| 
 | ||||
| 	elem := reflect.New(st).Elem() | ||||
| 
 | ||||
| 	// load default values into the new model
 | ||||
| 	for i, item := range info { | ||||
| 		elem.Field(i).Set(reflect.ValueOf(item.value)) | ||||
| 	} | ||||
| 
 | ||||
| 	return elem.Addr().Interface() | ||||
| } | ||||
| 
 | ||||
| type shapeFieldInfo struct { | ||||
| 	value     any | ||||
| 	valueType reflect.Type | ||||
| 	key       string | ||||
| } | ||||
|  |  | |||
|  | @ -1137,7 +1137,6 @@ func TestLoadingDynamicModel(t *testing.T) { | |||
| 	} | ||||
| } | ||||
| 
 | ||||
| // @todo revert the reflect caching and check other types
 | ||||
| func TestDynamicModelMapFieldCaching(t *testing.T) { | ||||
| 	app, _ := tests.NewTestApp() | ||||
| 	defer app.Cleanup() | ||||
|  | @ -1149,24 +1148,44 @@ func TestDynamicModelMapFieldCaching(t *testing.T) { | |||
| 
 | ||||
| 	_, err := vm.RunString(` | ||||
| 		let m1 = new DynamicModel({ | ||||
| 			int: 0, | ||||
| 			float: -0, | ||||
| 			text: "", | ||||
| 			bool: false, | ||||
| 			obj: {}, | ||||
| 			arr: [], | ||||
| 		}) | ||||
| 
 | ||||
| 		let m2 = new DynamicModel({ | ||||
| 			int: 0, | ||||
| 			float: -0, | ||||
| 			text: "", | ||||
| 			bool: false, | ||||
| 			obj: {}, | ||||
| 			arr: [], | ||||
| 		}) | ||||
| 
 | ||||
| 		m1.int = 1 | ||||
| 		m1.float = 1.5 | ||||
| 		m1.text = "a" | ||||
| 		m1.bool = true | ||||
| 		m1.obj.set("a", 1) | ||||
| 		m1.arr.push(1) | ||||
| 
 | ||||
| 		m2.int = 2 | ||||
| 		m2.float = 2.5 | ||||
| 		m2.text = "b" | ||||
| 		m2.bool = false | ||||
| 		m2.obj.set("b", 1) | ||||
| 		m2.arr.push(2) | ||||
| 
 | ||||
| 		let m1Expected = '{"obj":{"a":1}}'; | ||||
| 		let m1Expected = '{"arr":[1],"bool":true,"float":1.5,"int":1,"obj":{"a":1},"text":"a"}'; | ||||
| 		let m1Serialized = JSON.stringify(m1); | ||||
| 		if (m1Serialized != m1Expected) { | ||||
| 			throw new Error("Expected m1 \n" + m1Expected + "\ngot\n" + m1Serialized); | ||||
| 		} | ||||
| 
 | ||||
| 		let m2Expected = '{"obj":{"b":1}}'; | ||||
| 		let m2Expected = '{"arr":[2],"bool":false,"float":2.5,"int":2,"obj":{"b":1},"text":"b"}'; | ||||
| 		let m2Serialized = JSON.stringify(m2); | ||||
| 		if (m2Serialized != m2Expected) { | ||||
| 			throw new Error("Expected m2 \n" + m2Expected + "\ngot\n" + m2Serialized); | ||||
|  |  | |||
		Loading…
	
		Reference in New Issue