1 /** 2 Inochi2D SerDe Interface 3 4 Copyright: 5 Copyright © 2020-2026, Inochi2D Project 6 7 License: 8 $(LINK2 https://github.com/Inochi2D/inochi2d/blob/main/LICENSE, BSD 2-clause License) 9 10 Authors: 11 Luna Nielsen 12 */ 13 module inochi2d.core.serde; 14 import inochi2d.core.math; 15 import inochi2d.core; 16 import numem.core.traits; 17 import nulib.string; 18 import numath; 19 20 public import inp.format; 21 22 // 23 // INTERFACES 24 // 25 26 /** 27 Whether type T can be serialized. 28 */ 29 enum isSerializable(T) = 30 is(T : ISerializable) || 31 is(typeof((ref DataNode obj) { T.init.onSerialize(obj); })) || 32 is(typeof((ref DataNode obj) { T.init.serialize(obj); })); 33 34 /** 35 Interface for classes that can be serialized to JSON with custom code 36 */ 37 interface ISerializable { 38 39 /** 40 Custom serializer function 41 */ 42 void onSerialize(ref DataNode object); 43 } 44 45 /** 46 Interface for classes that can be deserialized to JSON with custom code 47 */ 48 interface IDeserializable(ST) { 49 50 /** 51 Custom deserializer function 52 */ 53 void onDeserialize(ref DataNode object, ref ST state) @nogc; 54 } 55 56 /** 57 Whether type T can be deserialized. 58 */ 59 enum isDeserializable(T, ST) = 60 is(T : IDeserializable!ST) || 61 is(typeof((ref DataNode obj, ref ST state) { T a; a.onDeserialize(obj, state); })) || 62 is(typeof((ref DataNode obj, ref ST state) { T a; onDeserialize(a, obj, state); })); 63 64 // 65 // IMPLEMENTATION 66 // 67 68 /** 69 Helper which deserializes to an internal intermediate value before 70 returning it. 71 */ 72 pragma(inline, true) 73 T deserialize(T, ST)(ref DataNode data, ref ST state) { 74 import numem : nogc_new; 75 76 static if (is(T == class) && is(typeof((ref T a) { a = new T; }))) 77 T tmp = nogc_new!T; 78 else 79 T tmp; 80 81 data.deserialize(tmp, state); 82 return tmp; 83 } 84 85 pragma(inline, true) 86 void deserialize(T, ST)(ref DataNode data, ref T destination, ref ST state) @nogc { 87 import inochi2d.core.math; 88 import nulib.collections : MapImpl, VectorImpl; 89 import nulib.string; 90 import numem; 91 92 static if (is(T == DataNode)) { 93 destination = data; 94 } else static if (isDeserializable!(T, ST)) { 95 static if (is(T == class) && is(typeof((ref T a) { a = new T; }))) { 96 if (!destination) 97 destination = nogc_new!T; 98 } 99 100 static if (is(typeof((ref DataNode obj, ref ST state) { T a; a.deserialize(obj, state); }))) 101 destination.deserialize(data, state); 102 else static if (is(typeof((ref DataNode obj) { T a; a.onDeserialize(obj); }))) 103 destination.onDeserialize(data); 104 else static if (is(typeof((ref DataNode obj, ref ST state) { T a; a.onDeserialize(obj, state); }))) 105 destination.onDeserialize(data, state); 106 else 107 static assert(0, "Can't deserialize " ~ T.stringof); 108 109 } else static if (is(T : string) || is(T : nstring)) { 110 if (!data.isNull) 111 destination = cast(T)data.text; 112 } else static if (is(T == bool)) { 113 destination = data.boolean; 114 } else static if (__traits(isFloating, T)) { 115 destination = cast(T)data.tryCoerce!float; 116 } else static if (__traits(isIntegral, T)) { 117 static if (__traits(isUnsigned, T)) 118 destination = cast(T)data.tryCoerce!ulong; 119 else 120 destination = cast(T)data.tryCoerce!long; 121 } else static if (__traits(isStaticArray, T)) { 122 if (!data.isArray) 123 return; 124 125 foreach (i, ref value; data.array) { 126 if (i >= T.length) 127 break; 128 129 destination[i] = value.deserialize!(ElementType!T, ST)(state); 130 } 131 } else static if (is(T == VectorImpl!(VT, Args), VT, Args...)) { 132 if (!data.isArray) 133 return; 134 135 destination.resize(data.length); 136 foreach (i, ref value; data.array) { 137 destination[i] = value.deserialize!(VT, ST)(state); 138 } 139 } else static if (is(T == U[], U)) { 140 if (!data.isArray) 141 return; 142 143 destination = destination.nu_resize(data.length); 144 foreach (i, ref value; data.array) { 145 destination[i] = value.deserialize!(U, ST)(state); 146 } 147 } else static if (is(T == MapImpl!(string, VT, Args), VT, Args...)) { 148 if (!data.isObject) 149 return; 150 151 foreach (key, ref value; data.object) { 152 destination[key] = value.deserialize!(VT, ST)(state); 153 } 154 } else { 155 destination = data.tryCoerce!T; 156 } 157 } 158 159 /** 160 Serializes a given type 161 */ 162 DataNode serialize(T)(auto ref T toSerialize) @nogc { 163 import std.traits : isAggregateType, isAssociativeArray; 164 import std.range : ElementType; 165 import std.traits : KeyType, ValueType; 166 import nulib.collections; 167 168 enum VType = dataNodeTypeOf!T; 169 170 static if (is(T == DataNode)) { 171 return toSerialize; 172 } else static if (VType == DataNodeType.undefined) { 173 return DataNode.init; 174 } else static if (isSerializable!T) { 175 static if (is(typeof((ref DataNode object) { T.init.serialize(object); }))) { 176 DataNode result; 177 toSerialize.serialize(result); 178 return result; 179 } else static if (is(T : ISerializable)) { 180 DataNode result; 181 toSerialize.onSerialize(result); 182 return result; 183 } else static if (is(typeof((ref DataNode object) { T.init.onSerialize(object); }))) { 184 DataNode result; 185 toSerialize.onSerialize(result); 186 return result; 187 } else 188 static assert(0, T.stringof ~ " does not support serialization."); 189 } else static if (VType == DataNodeType.object_) { 190 static if (is(T == MapImpl!(string, VT, Args), VT, Args...)) { 191 192 enum DataNodeType VDType = dataNodeTypeOf!VT; 193 static if (VDType != DataNodeType.undefined) { 194 DataNode obj = DataNode.createObject(); 195 foreach (kv; toSerialize.byKeyValue) { 196 obj[kv.key] = kv.value.serialize(); 197 } 198 return obj; 199 } else { 200 return DataNode.createObject(); 201 } 202 203 } else static if (is(T == VectorImpl!(VT, Args), VT, Args...)) { 204 205 enum EVType = dataNodeTypeOf!VT; 206 static if (EVType != DataNodeType.undefined) { 207 DataNode arr = DataNode.createArray(); 208 foreach (ref element; toSerialize) { 209 arr.array ~= serialize(element); 210 } 211 return arr; 212 } else { 213 return DataNode.createArray(); 214 } 215 } else static if (isAggregateType!T) { 216 217 DataNode obj; 218 toSerialize.onSerialize(obj); 219 return obj; 220 } else { 221 return DataNode.createObject(); 222 } 223 } else static if (VType == DataNodeType.array_) { 224 225 enum EVType = dataNodeTypeOf!(ElementType!T); 226 static if (EVType != DataNodeType.undefined) { 227 DataNode arr = DataNode.createArray(); 228 foreach (ref element; toSerialize) { 229 arr.array ~= serialize(element); 230 } 231 return arr; 232 } else { 233 return DataNode.createArray(); 234 } 235 } else static if (__traits(isFloating, T)) { 236 return DataNode(isFinite(toSerialize) ? toSerialize : 0); 237 } else { 238 return DataNode(toSerialize); 239 } 240 } 241 242 /** 243 Attempts to get a value from a JSON object by its key and type. 244 */ 245 T tryGet(T, ST)(auto ref DataNode data, ref ST state, T defaultValue = T.init) { 246 static if (__traits(isScalar, T)) { 247 static if (__traits(isFloating, T)) 248 defaultValue = 0.0; 249 250 return data.isNumber ? data.tryCoerce!T : defaultValue; 251 } 252 if (data.type != dataNodeTypeOf!T) 253 return defaultValue; 254 255 return data.deserialize!T(state); 256 } 257 258 /** 259 Attempts to get a value from a JSON object by its key and type. 260 */ 261 T tryGet(T, ST)(ref DataNode object, ref ST state, string key, T defaultValue = T.init) { 262 if (key !in object) 263 return defaultValue; 264 265 return object[key].deserialize!(T, ST)(state); 266 } 267 268 /** 269 Attempts to get a value from a JSON object by its key and type. 270 */ 271 T tryGet(T, ST)(ref DataNode object, ref ST state, string key) 272 if (is(T == nstring)) { 273 if (key !in object) 274 return nstring.init; 275 276 return nstring(object[key].text()); 277 } 278 279 /** 280 Attempts to get a value from a JSON object by its key and type. 281 */ 282 void tryGetRef(T, ST)(ref DataNode object, ref ST state, ref T dst, string key) 283 if (__traits(isFloating, T)) { 284 if (key !in object) { 285 dst = 0.0; 286 return; 287 } 288 289 object[key].deserialize!T(dst, state); 290 } 291 292 /** 293 Attempts to get a value from a JSON object by its key and type. 294 */ 295 void tryGetRef(T, ST)(ref DataNode object, ref ST state, ref T dst, string key, T defaultValue = T.init) { 296 if (key !in object) { 297 dst = __rvalue(defaultValue); 298 return; 299 } 300 301 object[key].deserialize!(T, ST)(dst, state); 302 } 303 304 /** 305 Attempts to get a value from a JSON array by its index and type. 306 */ 307 void tryGetRef(T, ST)(ref DataNode object, ref ST state, ref T dst, size_t i) 308 if (__traits(isFloating, T)) { 309 if (i >= object.length) { 310 dst = 0.0; 311 return; 312 } 313 314 object[i].deserialize!T(dst, state); 315 } 316 317 /** 318 Attempts to get a value from a JSON array by its index and type. 319 */ 320 void tryGetRef(T, ST)(ref DataNode object, ref ST state, ref T dst, size_t i, T defaultValue = T.init) { 321 if (i >= object.length) { 322 dst = __rvalue(defaultValue); 323 return; 324 } 325 326 object[i].deserialize!(T, ST)(dst, state); 327 }