146 lines
3.9 KiB
GDScript3
146 lines
3.9 KiB
GDScript3
|
extends Reference
|
||
|
class_name NakamaSerializer
|
||
|
|
||
|
static func serialize(p_obj : Object) -> Dictionary:
|
||
|
var out = {}
|
||
|
var schema = p_obj.get("_SCHEMA")
|
||
|
if schema == null:
|
||
|
return {} # No schema defined
|
||
|
for k in schema:
|
||
|
var prop = schema[k]
|
||
|
var val = p_obj.get(prop["name"])
|
||
|
if val == null:
|
||
|
continue
|
||
|
var type = prop["type"]
|
||
|
var content = prop.get("content", TYPE_NIL)
|
||
|
if typeof(content) == TYPE_STRING:
|
||
|
content = TYPE_OBJECT
|
||
|
var val_type = typeof(val)
|
||
|
match val_type:
|
||
|
TYPE_OBJECT: # Simple objects
|
||
|
out[k] = serialize(val)
|
||
|
TYPE_ARRAY: # Array of objects
|
||
|
var arr = []
|
||
|
for e in val:
|
||
|
if typeof(e) != TYPE_OBJECT:
|
||
|
continue
|
||
|
arr.append(serialize(e))
|
||
|
out[k] = arr
|
||
|
TYPE_INT_ARRAY, TYPE_STRING_ARRAY: # Array of ints, bools, or strings
|
||
|
var arr = []
|
||
|
for e in val:
|
||
|
if content == TYPE_BOOL:
|
||
|
e = bool(e)
|
||
|
if typeof(e) != content:
|
||
|
continue
|
||
|
arr.append(e)
|
||
|
out[k] = arr
|
||
|
TYPE_DICTIONARY: # Maps
|
||
|
var dict = {}
|
||
|
if content == TYPE_OBJECT: # Map of objects
|
||
|
for l in val:
|
||
|
if val_type != TYPE_OBJECT:
|
||
|
continue
|
||
|
dict[l] = serialize(val)
|
||
|
else: # Map of simple types
|
||
|
for l in val:
|
||
|
if val_type != content:
|
||
|
continue
|
||
|
dict[l] = val
|
||
|
_:
|
||
|
out[k] = val
|
||
|
return out
|
||
|
|
||
|
static func deserialize(p_ns : GDScript, p_cls_name : String, p_dict : Dictionary) -> Object:
|
||
|
var cls : GDScript = p_ns.get(p_cls_name)
|
||
|
var schema = cls.get("_SCHEMA")
|
||
|
if schema == null:
|
||
|
return NakamaException.new() # No schema defined
|
||
|
var obj = cls.new()
|
||
|
for k in schema:
|
||
|
var prop = schema[k]
|
||
|
var pname = prop["name"]
|
||
|
var type = prop["type"]
|
||
|
var required = prop["required"]
|
||
|
var content = prop.get("content", TYPE_NIL)
|
||
|
var type_cmp = type
|
||
|
if typeof(type) == TYPE_STRING: # A class
|
||
|
type_cmp = TYPE_DICTIONARY
|
||
|
if type_cmp == TYPE_STRING_ARRAY or type_cmp == TYPE_INT_ARRAY: # A specialized array
|
||
|
type_cmp = TYPE_ARRAY
|
||
|
|
||
|
var content_cmp = content
|
||
|
if typeof(content) == TYPE_STRING: # A dictionary or array of classes
|
||
|
content_cmp = TYPE_DICTIONARY
|
||
|
|
||
|
var val = p_dict.get(k, null)
|
||
|
|
||
|
# Ints might and up being recognized as floats. Change that if needed
|
||
|
if typeof(val) == TYPE_REAL and type_cmp == TYPE_INT:
|
||
|
val = int(val)
|
||
|
|
||
|
if typeof(val) == type_cmp:
|
||
|
if typeof(type) == TYPE_STRING:
|
||
|
obj.set(pname, deserialize(p_ns, type, val))
|
||
|
elif type_cmp == TYPE_DICTIONARY:
|
||
|
var v = {}
|
||
|
for l in val:
|
||
|
if typeof(content) == TYPE_STRING:
|
||
|
v[l] = deserialize(p_ns, content, val[l])
|
||
|
elif content == TYPE_INT:
|
||
|
v[l] = int(val[l])
|
||
|
elif content == TYPE_BOOL:
|
||
|
v[l] = bool(val[l])
|
||
|
else:
|
||
|
v[l] = str(val[l])
|
||
|
obj.set(pname, v)
|
||
|
elif type_cmp == TYPE_ARRAY:
|
||
|
var v
|
||
|
match content:
|
||
|
TYPE_INT, TYPE_BOOL: v = PoolIntArray()
|
||
|
TYPE_STRING: v = PoolStringArray()
|
||
|
_: v = Array()
|
||
|
for e in val:
|
||
|
if typeof(content) == TYPE_STRING:
|
||
|
v.append(deserialize(p_ns, content, e))
|
||
|
elif content == TYPE_INT:
|
||
|
v.append(int(e))
|
||
|
elif content == TYPE_BOOL:
|
||
|
v.append(bool(e))
|
||
|
else:
|
||
|
v.append(str(e))
|
||
|
obj.set(pname, v)
|
||
|
else:
|
||
|
obj.set(pname, val)
|
||
|
elif required:
|
||
|
obj._ex = NakamaException.new("ERROR [%s]: Missing or invalid required prop %s = %s:\n\t%s" % [p_cls_name, prop, p_dict.get(k), p_dict])
|
||
|
return obj
|
||
|
return obj
|
||
|
|
||
|
|
||
|
###
|
||
|
# Compatibility with Godot 3.1 which does not expose String.http_escape
|
||
|
###
|
||
|
const HEX = ["0","1","2","3","4","5","6","7","8","9","A","B","C","D","E","F"]
|
||
|
|
||
|
static func escape_http(p_str : String) -> String:
|
||
|
var out : String = ""
|
||
|
for o in p_str:
|
||
|
if (o == '.' or o == '-' or o == '_' or o == '~' or
|
||
|
(o >= 'a' and o <= 'z') or
|
||
|
(o >= 'A' and o <= 'Z') or
|
||
|
(o >= '0' and o <= '9')):
|
||
|
out += o
|
||
|
else:
|
||
|
for b in o.to_utf8():
|
||
|
out += "%%%s" % to_hex(b)
|
||
|
return out
|
||
|
|
||
|
static func to_hex(p_val : int) -> String:
|
||
|
var v := p_val
|
||
|
var o := ""
|
||
|
while v != 0:
|
||
|
o = HEX[v % 16] + o
|
||
|
v /= 16
|
||
|
return o
|