1 /+ 2 == serialization == 3 Copyright Alexey Drozhzhin aka Grim Maple 2024 4 Distributed under the Boost Software License, Version 1.0. 5 +/ 6 /++ 7 This is a serialization module for OpenD programming language! 8 It contains an API for making serializers and includes JSON serializator. 9 10 The JSON serializator is implemented via `std.json`. 11 12 ## Usage examples 13 14 ### Basic usage 15 16 By-design, only fields marked as [serializable] would be visible to serializator 17 --- 18 struct Foo 19 { 20 @serializable int bar; 21 float bar; 22 } 23 --- 24 Using the code above, only `bar` would be "visible" for serialization. 25 Please not that `@serializable` can be applied to field, or getter/setter functions. 26 A getter function is a function that returns non-void and has no parameters. 27 A setter function is a function that returns void and has exactly 1 parameter. 28 $(NOTE 29 Only a single [serializable] UDA is allowed per member. Having more will result in 30 a compile-time error. 31 ) 32 33 Example usage with getter/setter functions: 34 --- 35 struct Foo 36 { 37 @serializable void foo(int bar) { } 38 @serialziable int foo() { } 39 } 40 --- 41 42 To control how serizliation is done, `@serializable` attribute has a constructor 43 that accepts a string, which is then used as a name for serializtion 44 --- 45 struct Foo 46 { 47 // This field will be serialized with the name "Bar" 48 @serializable("Bar") int baz; 49 } 50 --- 51 52 If serialization fails for any reason, [SerializationException] is used. 53 54 ### Advanced usage 55 56 A common desirable behavior is to serialize structs/classes that were not originally annotated 57 with [serializable]. For such use-case, it's advised to use getter/setter serialization to convert 58 such types to another type that is easily serializable: 59 60 --- 61 struct Foo 62 { 63 @serializable("entry") string serEntry() { return entry.path; } 64 @serializable("entry") void serEntry(string path) { entry = DirEntry(path); } 65 DirEntry entry; 66 } 67 --- 68 69 ### Writing serializers 70 71 To make a custom serializer, use any of those helper templates: 72 [serializableFields], [writeableSerializables], [readableSerializables]. 73 Usage should be quite obvious from their names! Here's a silly sample code that 74 serializes a custom type `T`: 75 --- 76 string serialize(T)(auto ref T t) 77 { 78 streing return; 79 foreach(alias prop; readableSerializables!T) 80 { 81 auto name = getSerializableName!prop; 82 auto val = __traits(child, obj, prop).to!string; 83 return ~= name ~ ":" ~ val ~ "\n"; 84 } 85 return return; 86 } 87 --- 88 89 Please refer to the provided JSON serialization module for a comprehensive example! 90 +/ 91 92 module odc.serialization; 93 94 import std.meta : Filter, AliasSeq; 95 import std.traits; 96 97 /** 98 * A UDA for marking fields as serializable. 99 * 100 * Use on any field to mark it as serializable. Only fields and getters/setters can 101 * be marked as `serializeable`. 102 */ 103 struct serializable 104 { 105 /** 106 * Constructs new `serializable` 107 * 108 * Params 109 * n = field name in json 110 */ 111 this(string n) @safe @nogc nothrow 112 { 113 name = n; 114 } 115 116 /** 117 * Controls the field name in serialized object. 118 * 119 * If set to "" (default), the field name is the same as in D code. 120 */ 121 private string name; 122 } 123 124 /** 125 * Retreive all fields of type `T` that are $(LREF serializable) 126 */ 127 template serializableFields(T) 128 { 129 alias serializableFields = getSymbolsByUDA!(T, serializable); 130 131 // Compile-time errors generation 132 static foreach(alias prop; getSymbolsByUDA!(T, serializable)) 133 { 134 static assert(getUDAs!(prop, serializable).length == 1, 135 "Only 1 `serializable` UDA is allowed per property. See field `" ~ fullyQualifiedName!prop ~ "`."); 136 static if(isFunction!prop) 137 { 138 static assert(isGetterFunction!(FunctionTypeOf!prop) || isSetterFunction!(FunctionTypeOf!prop), 139 "Function `" ~ fullyQualifiedName!prop ~ "` is not a getter or setter"); 140 } 141 } 142 } 143 unittest 144 { 145 struct A 146 { 147 @serializable int a; 148 @serializable int foo() { return 1; } 149 @serializable void bar(int a) { b = a; } 150 int b; 151 } 152 } 153 154 /** 155 * Is `T` marked as $(LREF serializable) 156 */ 157 template isSerializable(alias T) 158 { 159 enum bool isSerializable = getUDAs!(T, serializable).length == 1; 160 } 161 unittest 162 { 163 struct A 164 { 165 @serializable int a; 166 int b; 167 } 168 169 assert(isSerializable!(A.a)); 170 assert(!isSerializable!(A.b)); 171 } 172 173 /** 174 * Retreive all writeable serializables for `T`. This includes properties and setters. 175 */ 176 template writeableSerializables(alias T) 177 { 178 alias writeableSerializables = Filter!(isSerializableWriteable, serializableFields!T); 179 } 180 /// 181 @safe unittest 182 { 183 static struct A 184 { 185 @serializable void a(int value) { _a = value; } 186 @serializable int b; 187 private int _a; 188 } 189 } 190 /// 191 @safe unittest 192 { 193 struct A 194 { 195 @serializable int a; 196 @serializable void foo(int s); 197 } 198 } 199 200 /** 201 * Retreive all readable serializables for `T`. This includes properties and getters 202 */ 203 template readableSerializables(alias T) 204 { 205 alias readableSerializables = Filter!(isSerializableReadable, serializableFields!T); 206 } 207 /// 208 @safe unittest 209 { 210 static struct A 211 { 212 @serializable void a(int value) { _a = value; } 213 @serializable int b; 214 private int _a; 215 } 216 } 217 218 /** 219 * Retreive the name for this serializable 220 */ 221 template getSerializableName(alias T) if(isSerializable!T) 222 { 223 static if(is(getUDAs!(T, serializable)[0] == struct)) 224 enum string getSerializableName = __traits(identifier, T); 225 else 226 enum string getSerializableName = getUDAs!(T, serializable)[0].name == "" ? __traits(identifier, T) : getUDAs!(T, serializable)[0].name ; 227 } 228 /// 229 @safe @nogc unittest 230 { 231 struct A 232 { 233 @serializable int a; 234 @serializable() int b; 235 @serializable("test") int c; 236 } 237 238 assert(getSerializableName!(A.a) == "a"); 239 assert(getSerializableName!(A.b) == "b"); 240 assert(getSerializableName!(A.c) == "test"); 241 } 242 243 /** 244 * Is this $(LREF serializable) readable 245 */ 246 template isSerializableReadable(alias T) if(isSerializable!T) 247 { 248 static if (isFunction!T) 249 enum bool isSerializableReadable = isGetterFunction!T; 250 else 251 enum bool isSerializableReadable = true; 252 } 253 @safe @nogc unittest 254 { 255 struct A 256 { 257 @serializable void foo(int a) { } 258 @serializable int bar() { return 1; } 259 @serializable int a; 260 int b; 261 } 262 263 assert(isSerializableReadable!(A.a)); 264 assert(isSerializableReadable!(A.bar)); 265 assert(!isSerializableReadable!(A.foo)); 266 } 267 268 /** 269 * Is this $(LREF serializable) writeable 270 */ 271 template isSerializableWriteable(alias T) if(isSerializable!T) 272 { 273 static if(isFunction!T) 274 enum bool isSerializableWriteable = isSetterFunction!T; 275 else 276 enum bool isSerializableWriteable = true; 277 } 278 /// 279 @safe @nogc unittest 280 { 281 struct A 282 { 283 @serializable void foo(int a) { } 284 @serializable int a; 285 @serializable int bar() { return 1; } 286 } 287 288 assert(isSerializableWriteable!(A.foo)); 289 assert(isSerializableWriteable!(A.a)); 290 assert(!isSerializableWriteable!(A.bar)); 291 } 292 293 /** 294 * A base exception class for all serialization exceptions. 295 */ 296 class SerializationException : Exception 297 { 298 this(string message) @safe 299 { 300 super(message); 301 } 302 } 303 304 // Internal stuff 305 private 306 { 307 template isSetterFunction(alias T) 308 { 309 enum bool isSetterFunction = isFunction!T && ((Parameters!T).length == 1) && is(ReturnType!T == void); 310 } 311 /// 312 @safe @nogc unittest 313 { 314 void foo(int b) { } 315 int fee() { return 0; } 316 int bar(int b) { return b; } 317 void baz(int a, int b) { } 318 assert(isSetterFunction!(FunctionTypeOf!foo)); 319 assert(!isSetterFunction!(FunctionTypeOf!fee)); 320 assert(!isSetterFunction!(FunctionTypeOf!bar)); 321 assert(!isSetterFunction!(FunctionTypeOf!baz)); 322 } 323 324 template isGetterFunction(alias T) 325 { 326 enum bool isGetterFunction = isFunction!T && ((Parameters!T).length == 0) && !is(ReturnType!T == void); 327 } 328 /// 329 @safe @nogc unittest 330 { 331 void foo(int b) { } 332 int fee() { return 0; } 333 int bar(int b) { return b; } 334 void baz(int a, int b) { } 335 struct Test 336 { 337 int bar() { return 1; } 338 void baz() { } 339 } 340 assert(isGetterFunction!(FunctionTypeOf!fee)); 341 assert(isGetterFunction!(Test.bar)); 342 assert(!isGetterFunction!(Test.baz)); 343 assert(!isGetterFunction!(FunctionTypeOf!foo)); 344 assert(!isGetterFunction!(FunctionTypeOf!bar)); 345 assert(!isGetterFunction!(FunctionTypeOf!baz)); 346 } 347 }