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 }