Skip to content

Instantly share code, notes, and snippets.

@Cretezy
Created April 15, 2025 08:39
Show Gist options
  • Save Cretezy/ae9d66c5ddd5efc582feec9824ed84c8 to your computer and use it in GitHub Desktop.
Save Cretezy/ae9d66c5ddd5efc582feec9824ed84c8 to your computer and use it in GitHub Desktop.
class SerializerTest:
const _NATIVE_SERIALIZABLE_TYPES = [
TYPE_VECTOR2,
TYPE_VECTOR2I,
TYPE_VECTOR3,
TYPE_VECTOR3I,
TYPE_VECTOR4,
TYPE_VECTOR4I,
TYPE_RECT2,
TYPE_RECT2I,
TYPE_BASIS,
TYPE_TRANSFORM2D,
TYPE_TRANSFORM3D,
TYPE_PLANE,
TYPE_QUATERNION,
TYPE_AABB,
TYPE_PROJECTION,
TYPE_COLOR,
TYPE_PACKED_BYTE_ARRAY, # Could be more efficient (base64)
TYPE_PACKED_INT32_ARRAY,
TYPE_PACKED_INT64_ARRAY,
TYPE_PACKED_FLOAT32_ARRAY,
TYPE_PACKED_FLOAT64_ARRAY,
TYPE_PACKED_STRING_ARRAY,
TYPE_PACKED_VECTOR2_ARRAY,
TYPE_PACKED_VECTOR3_ARRAY,
TYPE_PACKED_COLOR_ARRAY,
TYPE_PACKED_VECTOR4_ARRAY,
]
static var type_field := "._type"
static var _object_prefix := "Object_"
static var _registry: Dictionary[String, ObjectRegistryEntry]
static func register(script: Script, name: StringName = "") -> void:
var script_name := _get_script_name(script, name)
assert(script_name, "Script must have name\n" + script.source_code)
var entry := ObjectRegistryEntry.new()
entry.script_type = script
entry.type = _object_prefix + script_name
_registry[entry.type] = entry
static func serialize(value: Variant) -> Variant:
match typeof(value):
TYPE_OBJECT:
var name: StringName = value.get_script().get_global_name()
var object_entry := _get_entry(name, value.get_script())
if !object_entry:
assert(false, "Could not find type (%s) in registry\n%s" % [name if name else "no name", value.get_script().source_code]);
return object_entry.serialize(value)
TYPE_ARRAY:
return value.map(SerializerTest.serialize)
TYPE_DICTIONARY:
var result := {}
for i: Variant in value:
result[i] = SerializerTest.serialize(value[i])
return result
if _NATIVE_SERIALIZABLE_TYPES.has(typeof(value)):
return {
SerializerTest.type_field: type_string(typeof(value)),
"_": JSON.from_native(value)["args"]
}
return value;
static func deserialize(value: Variant) -> Variant:
match typeof(value):
TYPE_DICTIONARY:
if value.has(type_field):
var type: String = value.get(type_field)
if type.begins_with(_object_prefix):
var entry := _get_entry(type)
if !entry:
assert(false, "Could not find type (%s) in registry" % type);
return entry.deserialize(value)
elif _NATIVE_SERIALIZABLE_TYPES.any(
func(native_type: Variant.Type) -> bool: return type_string(native_type) == type
):
return JSON.to_native({
"type": type,
"args": value["_"]
})
var result := {}
for i: Variant in value:
result[i] = SerializerTest.deserialize(value[i])
return result
TYPE_ARRAY:
return value.map(SerializerTest.deserialize)
return value
static func _get_script_name(script: Script, name: StringName = "") -> StringName:
if name:
return name
if script.resource_name:
return script.resource_name
if script.get_global_name():
return script.get_global_name()
return ""
static func _get_entry(name: StringName = "", script: Script = null) -> ObjectRegistryEntry:
if name:
var entry: ObjectRegistryEntry = _registry.get(name)
if entry:
return entry
if script:
for i: String in _registry:
var entry: ObjectRegistryEntry = _registry.get(i)
if entry:
if script == entry.script_type:
return entry
return null
class ObjectRegistryEntry:
var type: String
var script_type: Script
func serialize(value: Variant) -> Variant:
var result := {
SerializerTest.type_field: type
}
for property: Dictionary in value.get_property_list():
if property.usage & PROPERTY_USAGE_SCRIPT_VARIABLE:
result[property.name] = SerializerTest.serialize(value.get(property.name))
return result
func deserialize(value: Variant) -> Variant:
var instance: Variant = script_type.new()
for key: String in value:
if key == SerializerTest.type_field:
continue
var key_value: Variant = SerializerTest.deserialize(value[key])
match typeof(key_value):
TYPE_DICTIONARY:
instance[key].assign(key_value)
TYPE_ARRAY:
instance[key].assign(key_value)
_:
instance[key] = key_value
return instance
# Example
class Data:
# Custom handling
var nested: Data
var array: Array
var array_nested: Array[Data]
var dictionary: Dictionary
var dictionary_nested: Dictionary[String, Data]
# JSON types
var string: String
var boolean: bool
var integer: int
var floating_point: float
# Godot types
var vector2: Vector2
var vector2i: Vector2i
var vector3: Vector3
var vector3i: Vector3i
var vector4: Vector4
var vector4i: Vector4i
var rect2: Rect2
var rect2i: Rect2i
var transform2d: Transform2D
var transform3d: Transform3D
var basis: Basis
var plane: Plane
var quaternion: Quaternion
var aabb: AABB
var projection: Projection
var color: Color
var packed_byte_array: PackedByteArray
var packed_int32_array: PackedInt32Array
var packed_int64_array: PackedInt64Array
var packed_float32_array: PackedFloat32Array
var packed_float64_array: PackedFloat64Array
var packed_string_array: PackedStringArray
var packed_vector2_array: PackedVector2Array
var packed_vector3_array: PackedVector3Array
var packed_vector4_array: PackedVector4Array
var packed_color_array: PackedColorArray
func _build_data(string: String) -> Data:
var data := Data.new()
data.string = string
return data
# Tests
func _init() -> void:
# Register objects
SerializerTest.register(Data, "Data")
# Build test data
var data := Data.new()
data.nested = _build_data("nested")
data.array = [1, "a", Vector2(1,2)]
data.array_nested = [_build_data("array nested")]
data.dictionary = { "int": 1, "vector": Vector2(1, 2) }
data.dictionary_nested = { "data": _build_data("dictionary nested") }
data.string = "a"
data.boolean = true
data.integer = 1
data.floating_point = 2.3
data.vector2 = Vector2(1.2, 3.4)
data.vector2i = Vector2i(1, 2)
data.vector3 = Vector3(1.2, 3.4, 5.6)
data.vector3i = Vector3(1, 2, 3)
data.vector4 = Vector4(1.2, 3.4, 5.6, 7.8)
data.vector4i = Vector4i(1, 2, 3, 4)
data.rect2 = Rect2(1.2, 3.4, 5.6, 7.8)
data.rect2i = Rect2(1, 2, 3, 4)
data.transform2d = Transform2D(Vector2(1, 2), Vector2(3, 4), Vector2(5, 6))
data.transform3d = Transform3D(Vector3(1, 2, 3), Vector3(4, 5, 6), Vector3(7, 8, 9), Vector3(10, 11, 12))
data.basis = Basis(Vector3(1, 2, 3), Vector3(4, 5, 6), Vector3(7, 8, 9))
data.plane = Plane(Vector3(1, 2, 3), Vector3(4, 5, 6), Vector3(7, 8, 9))
data.quaternion = Quaternion(Vector3(1, 2, 3), 2)
data.aabb = AABB(Vector3(1, 2, 3), Vector3(4, 5, 6))
data.projection = Projection(Vector4(1, 2, 3, 4), Vector4(1, 2, 3, 4), Vector4(1, 2, 3, 4), Vector4(1, 2, 3, 4))
data.color = Color(0.1, 0.2, 0.3, 0.4)
data.packed_byte_array = PackedByteArray([1, 2, 3])
data.packed_int32_array = PackedInt32Array([1, 2, 3])
data.packed_int64_array = PackedInt64Array([1, 2, 3])
data.packed_float32_array = PackedFloat32Array([1.2, 2.3])
data.packed_float64_array = PackedFloat64Array([1.2, 2.3])
data.packed_string_array = PackedStringArray(["a", "b"])
data.packed_vector2_array = PackedVector2Array([Vector2(1, 2), Vector2(3, 4)])
data.packed_vector3_array = PackedVector3Array([Vector3(1, 2, 3), Vector3(4, 5, 6)])
data.packed_vector4_array = PackedVector4Array([Vector4(1, 2, 3, 4), Vector4(5, 6, 7, 8)])
data.packed_color_array = PackedColorArray([Color(0.1, 0.2, 0.3, 0.4), Color(0.5, 0.6, 0.7, 0.8)])
# Serialize
var serialized: Variant = SerializerTest.serialize(data)
print(JSON.stringify(serialized, " "))
# Verify
var deserialized: Data = SerializerTest.deserialize(serialized)
assert(data.nested.string == deserialized.nested.string, "nested different")
assert(data.array == deserialized.array, "array different")
assert(data.array_nested[0].string == deserialized.array_nested[0].string, "array nested different")
assert(data.dictionary == deserialized.dictionary, "dictionary different")
assert(data.dictionary_nested["data"].string == deserialized.dictionary_nested["data"].string, "dictionary nested different")
assert(data.string == deserialized.string, "string different")
assert(data.integer == deserialized.integer, "integer different")
assert(data.floating_point == deserialized.floating_point, "floating_point different")
assert(data.vector2 == deserialized.vector2, "vector2 different")
assert(data.vector2i == deserialized.vector2i, "vector2i different")
assert(data.vector3 == deserialized.vector3, "vector3 different")
assert(data.vector3i == deserialized.vector3i, "vector3i different")
assert(data.vector4 == deserialized.vector4, "vector4 different")
assert(data.vector4i == deserialized.vector4i, "vector4i different")
assert(data.rect2 == deserialized.rect2, "rect2 different")
assert(data.rect2i == deserialized.rect2i, "rect2i different")
assert(data.transform2d == deserialized.transform2d, "transform2d different")
assert(data.transform3d == deserialized.transform3d, "transform3d different")
assert(data.basis == deserialized.basis, "basis different")
assert(data.plane == deserialized.plane, "plane different")
assert(data.quaternion == deserialized.quaternion, "quaternion different")
assert(data.aabb == deserialized.aabb, "aabb different")
assert(data.projection == deserialized.projection, "projection different")
assert(data.color == deserialized.color, "color different")
assert(data.packed_byte_array == deserialized.packed_byte_array, "packed_byte_array different")
assert(data.packed_int32_array == deserialized.packed_int32_array , "packed_int32_array different")
assert(data.packed_int64_array == deserialized.packed_int64_array, "packed_int64_array different")
assert(data.packed_float32_array == deserialized.packed_float32_array, "packed_float32_array different")
assert(data.packed_float64_array == deserialized.packed_float64_array, "packed_float64_array different")
assert(data.packed_string_array == deserialized.packed_string_array, "packed_string_array different")
assert(data.packed_vector2_array == deserialized.packed_vector2_array, "packed_vector2_array different")
assert(data.packed_vector3_array == deserialized.packed_vector3_array, "packed_vector3_array different")
assert(data.packed_vector4_array == deserialized.packed_vector4_array, "packed_vector4_array different")
assert(data.packed_color_array == deserialized.packed_color_array, "packed_color_array different")
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment