1 /** 2 Generic serialization framework. 3 4 This module provides general means for implementing (de-)serialization with 5 a standardized behavior. 6 7 Supported_types: 8 The following rules are applied in order when serializing or 9 deserializing a certain type: 10 11 $(OL 12 $(LI An `enum` type is serialized as its raw value, except if 13 `@byName` is used, in which case the name of the enum value 14 is serialized.) 15 $(LI Any type that is specifically supported by the serializer 16 is directly serialized. For example, the BSON serializer 17 supports `BsonObjectID` directly.) 18 $(LI Arrays and tuples (`std.typecons.Tuple`) are serialized 19 using the array serialization functions where each element is 20 serialized again according to these rules.) 21 $(LI Associative arrays are serialized similar to arrays. The key 22 type of the AA must satisfy the `isStringSerializable` trait 23 and will always be serialized as a string.) 24 $(LI Any `Nullable!T` will be serialized as either `null`, or 25 as the contained value (subject to these rules again).) 26 $(LI Any `BitFlags!T` value will be serialized as `T[]`) 27 $(LI Types satisfying the `isPolicySerializable` trait for the 28 supplied `Policy` will be serialized as the value returned 29 by the policy `toRepresentation` function (again subject to 30 these rules).) 31 $(LI Types satisfying the `isCustomSerializable` trait will be 32 serialized as the value returned by their `toRepresentation` 33 method (again subject to these rules).) 34 $(LI Types satisfying the `isISOExtStringSerializable` trait will be 35 serialized as a string, as returned by their `toISOExtString` 36 method. This causes types such as `SysTime` to be serialized 37 as strings.) 38 $(LI Types satisfying the `isStringSerializable` trait will be 39 serialized as a string, as returned by their `toString` 40 method.) 41 $(LI Struct and class types by default will be serialized as 42 associative arrays, where the key is the name of the 43 corresponding field (can be overridden using the `@name` 44 attribute). If the struct/class is annotated with `@asArray`, 45 it will instead be serialized as a flat array of values in the 46 order of declaration. Null class references will be serialized 47 as `null`.) 48 $(LI Pointer types will be serialized as either `null`, or as 49 the value they point to.) 50 $(LI Built-in integers and floating point values, as well as 51 boolean values will be converted to strings, if the serializer 52 doesn't support them directly.) 53 ) 54 55 Note that no aliasing detection is performed, so that pointers, class 56 references and arrays referencing the same memory will be serialized 57 as multiple copies. When in turn deserializing the data, they will also 58 end up as separate copies in memory. 59 60 Serializer_implementation: 61 Serializers are implemented in terms of a struct with template methods that 62 get called by the serialization framework: 63 64 --- 65 struct ExampleSerializer { 66 enum isSupportedValueType(T) = is(T == string) || is(T == typeof(null)); 67 68 // serialization 69 auto getSerializedResult(); 70 void beginWriteDictionary(T)(); 71 void endWriteDictionary(T)(); 72 void beginWriteDictionaryEntry(T)(string name); 73 void endWriteDictionaryEntry(T)(string name); 74 void beginWriteArray(T)(size_t length); 75 void endWriteArray(T)(); 76 void beginWriteArrayEntry(T)(size_t index); 77 void endWriteArrayEntry(T)(size_t index); 78 void writeValue(T)(T value); 79 80 // deserialization 81 void readDictionary(T)(scope void delegate(string) entry_callback); 82 void readArray(T)(scope void delegate(size_t) size_callback, scope void delegate() entry_callback); 83 T readValue(T)(); 84 bool tryReadNull(); 85 } 86 --- 87 88 Copyright: © 2013-2014 rejectedsoftware e.K. 89 License: Subject to the terms of the MIT license, as written in the included LICENSE.txt file. 90 Authors: Sönke Ludwig 91 */ 92 module dub.internal.vibecompat.data.serialization; 93 94 version (Have_vibe_d) public import vibe.data.serialization; 95 else: 96 97 import dub.internal.vibecompat.data.utils; 98 99 import std.array : Appender, appender; 100 import std.conv : to; 101 import std.exception : enforce; 102 import std.traits; 103 import std.typetuple; 104 105 106 /** 107 Serializes a value with the given serializer. 108 109 The serializer must have a value result for the first form 110 to work. Otherwise, use the range based form. 111 112 See_Also: `vibe.data.json.JsonSerializer`, `vibe.data.json.JsonStringSerializer`, `vibe.data.bson.BsonSerializer` 113 */ 114 auto serialize(Serializer, T, ARGS...)(T value, ARGS args) 115 { 116 auto serializer = Serializer(args); 117 serialize(serializer, value); 118 return serializer.getSerializedResult(); 119 } 120 /// ditto 121 void serialize(Serializer, T)(ref Serializer serializer, T value) 122 { 123 serializeImpl!(Serializer, DefaultPolicy, T)(serializer, value); 124 } 125 126 /** Note that there is a convenience function `vibe.data.json.serializeToJson` 127 that can be used instead of manually invoking `serialize`. 128 */ 129 unittest { 130 import dub.internal.vibecompat.data.json; 131 132 struct Test { 133 int value; 134 string text; 135 } 136 137 Test test; 138 test.value = 12; 139 test.text = "Hello"; 140 141 Json serialized = serialize!JsonSerializer(test); 142 assert(serialized.value.get!int == 12); 143 assert(serialized.text.get!string == "Hello"); 144 } 145 146 unittest { 147 import dub.internal.vibecompat.data.json; 148 149 // Make sure that immutable(char[]) works just like string 150 // (i.e., immutable(char)[]). 151 immutable key = "answer"; 152 auto ints = [key: 42]; 153 auto serialized = serialize!JsonSerializer(ints); 154 assert(serialized[key].get!int == 42); 155 } 156 157 /** 158 Serializes a value with the given serializer, representing values according to `Policy` when possible. 159 160 The serializer must have a value result for the first form 161 to work. Otherwise, use the range based form. 162 163 See_Also: `vibe.data.json.JsonSerializer`, `vibe.data.json.JsonStringSerializer`, `vibe.data.bson.BsonSerializer` 164 */ 165 auto serializeWithPolicy(Serializer, alias Policy, T, ARGS...)(T value, ARGS args) 166 { 167 auto serializer = Serializer(args); 168 serializeWithPolicy!(Serializer, Policy)(serializer, value); 169 return serializer.getSerializedResult(); 170 } 171 /// ditto 172 void serializeWithPolicy(Serializer, alias Policy, T)(ref Serializer serializer, T value) 173 { 174 serializeImpl!(Serializer, Policy, T)(serializer, value); 175 } 176 /// 177 version (unittest) 178 { 179 template SizePol(T) 180 { 181 import std.conv; 182 import std.array; 183 184 string toRepresentation(T value) { 185 return to!string(value.x) ~ "x" ~ to!string(value.y); 186 } 187 188 T fromRepresentation(string value) { 189 string[] fields = value.split('x'); 190 alias fieldT = typeof(T.x); 191 auto x = to!fieldT(fields[0]); 192 auto y = to!fieldT(fields[1]); 193 return T(x, y); 194 } 195 } 196 } 197 198 /// 199 unittest { 200 import dub.internal.vibecompat.data.json; 201 202 static struct SizeI { 203 int x; 204 int y; 205 } 206 SizeI sizeI = SizeI(1,2); 207 Json serializedI = serializeWithPolicy!(JsonSerializer, SizePol)(sizeI); 208 assert(serializedI.get!string == "1x2"); 209 210 static struct SizeF { 211 float x; 212 float y; 213 } 214 SizeF sizeF = SizeF(0.1f,0.2f); 215 Json serializedF = serializeWithPolicy!(JsonSerializer, SizePol)(sizeF); 216 assert(serializedF.get!string == "0.1x0.2"); 217 } 218 219 220 /** 221 Deserializes and returns a serialized value. 222 223 serialized_data can be either an input range or a value containing 224 the serialized data, depending on the type of serializer used. 225 226 See_Also: `vibe.data.json.JsonSerializer`, `vibe.data.json.JsonStringSerializer`, `vibe.data.bson.BsonSerializer` 227 */ 228 T deserialize(Serializer, T, ARGS...)(ARGS args) 229 { 230 auto deserializer = Serializer(args); 231 return deserializeImpl!(T, DefaultPolicy, Serializer)(deserializer); 232 } 233 234 /** Note that there is a convenience function `vibe.data.json.deserializeJson` 235 that can be used instead of manually invoking `deserialize`. 236 */ 237 unittest { 238 import dub.internal.vibecompat.data.json; 239 240 struct Test { 241 int value; 242 string text; 243 } 244 245 Json serialized = Json.emptyObject; 246 serialized.value = 12; 247 serialized.text = "Hello"; 248 249 Test test = deserialize!(JsonSerializer, Test)(serialized); 250 assert(test.value == 12); 251 assert(test.text == "Hello"); 252 } 253 254 /** 255 Deserializes and returns a serialized value, interpreting values according to `Policy` when possible. 256 257 serialized_data can be either an input range or a value containing 258 the serialized data, depending on the type of serializer used. 259 260 See_Also: `vibe.data.json.JsonSerializer`, `vibe.data.json.JsonStringSerializer`, `vibe.data.bson.BsonSerializer` 261 */ 262 T deserializeWithPolicy(Serializer, alias Policy, T, ARGS...)(ARGS args) 263 { 264 auto deserializer = Serializer(args); 265 return deserializeImpl!(T, Policy, Serializer)(deserializer); 266 } 267 268 /// 269 unittest { 270 import dub.internal.vibecompat.data.json; 271 272 static struct SizeI { 273 int x; 274 int y; 275 } 276 277 Json serializedI = "1x2"; 278 SizeI sizeI = deserializeWithPolicy!(JsonSerializer, SizePol, SizeI)(serializedI); 279 assert(sizeI.x == 1); 280 assert(sizeI.y == 2); 281 282 static struct SizeF { 283 float x; 284 float y; 285 } 286 Json serializedF = "0.1x0.2"; 287 SizeF sizeF = deserializeWithPolicy!(JsonSerializer, SizePol, SizeF)(serializedF); 288 assert(sizeF.x == 0.1f); 289 assert(sizeF.y == 0.2f); 290 } 291 292 private void serializeImpl(Serializer, alias Policy, T, ATTRIBUTES...)(ref Serializer serializer, T value) 293 { 294 import std.typecons : BitFlags, Nullable, Tuple, tuple; 295 296 static assert(Serializer.isSupportedValueType!string, "All serializers must support string values."); 297 static assert(Serializer.isSupportedValueType!(typeof(null)), "All serializers must support null values."); 298 299 alias TU = Unqual!T; 300 301 static if (is(TU == enum)) { 302 static if (hasAttributeL!(ByNameAttribute, ATTRIBUTES)) { 303 serializeImpl!(Serializer, Policy, string)(serializer, value.to!string()); 304 } else { 305 serializeImpl!(Serializer, Policy, OriginalType!TU)(serializer, cast(OriginalType!TU)value); 306 } 307 } else static if (Serializer.isSupportedValueType!TU) { 308 static if (is(TU == typeof(null))) serializer.writeValue!TU(null); 309 else serializer.writeValue!TU(value); 310 } else static if (/*isInstanceOf!(Tuple, TU)*/is(T == Tuple!TPS, TPS...)) { 311 static if (TU.Types.length == 1) { 312 serializeImpl!(Serializer, Policy, typeof(value[0]), ATTRIBUTES)(serializer, value[0]); 313 } else { 314 serializer.beginWriteArray!TU(value.length); 315 foreach (i, TV; T.Types) { 316 serializer.beginWriteArrayEntry!TV(i); 317 serializeImpl!(Serializer, Policy, TV, ATTRIBUTES)(serializer, value[i]); 318 serializer.endWriteArrayEntry!TV(i); 319 } 320 serializer.endWriteArray!TU(); 321 } 322 } else static if (isArray!TU) { 323 alias TV = typeof(value[0]); 324 serializer.beginWriteArray!TU(value.length); 325 foreach (i, ref el; value) { 326 serializer.beginWriteArrayEntry!TV(i); 327 serializeImpl!(Serializer, Policy, TV, ATTRIBUTES)(serializer, el); 328 serializer.endWriteArrayEntry!TV(i); 329 } 330 serializer.endWriteArray!TU(); 331 } else static if (isAssociativeArray!TU) { 332 alias TK = KeyType!TU; 333 alias TV = ValueType!TU; 334 static if (__traits(compiles, serializer.beginWriteDictionary!TU(0))) { 335 auto nfields = value.length; 336 serializer.beginWriteDictionary!TU(nfields); 337 } else { 338 serializer.beginWriteDictionary!TU(); 339 } 340 foreach (key, ref el; value) { 341 string keyname; 342 static if (is(TK : string)) keyname = key; 343 else static if (is(TK : real) || is(TK : long) || is(TK == enum)) keyname = key.to!string; 344 else static if (isStringSerializable!TK) keyname = key.toString(); 345 else static assert(false, "Associative array keys must be strings, numbers, enums, or have toString/fromString methods."); 346 serializer.beginWriteDictionaryEntry!TV(keyname); 347 serializeImpl!(Serializer, Policy, TV, ATTRIBUTES)(serializer, el); 348 serializer.endWriteDictionaryEntry!TV(keyname); 349 } 350 static if (__traits(compiles, serializer.endWriteDictionary!TU(0))) { 351 serializer.endWriteDictionary!TU(nfields); 352 } else { 353 serializer.endWriteDictionary!TU(); 354 } 355 } else static if (/*isInstanceOf!(Nullable, TU)*/is(T == Nullable!TPS, TPS...)) { 356 if (value.isNull()) serializeImpl!(Serializer, Policy, typeof(null))(serializer, null); 357 else serializeImpl!(Serializer, Policy, typeof(value.get()), ATTRIBUTES)(serializer, value.get()); 358 } else static if (is(T == BitFlags!E, E)) { 359 size_t cnt = 0; 360 foreach (v; EnumMembers!E) 361 if (value & v) 362 cnt++; 363 364 serializer.beginWriteArray!(E[])(cnt); 365 cnt = 0; 366 foreach (v; EnumMembers!E) 367 if (value & v) { 368 serializer.beginWriteArrayEntry!E(cnt); 369 serializeImpl!(Serializer, Policy, E, ATTRIBUTES)(serializer, v); 370 serializer.endWriteArrayEntry!E(cnt); 371 cnt++; 372 } 373 serializer.endWriteArray!(E[])(); 374 } else static if (isPolicySerializable!(Policy, TU)) { 375 alias CustomType = typeof(Policy!TU.toRepresentation(TU.init)); 376 serializeImpl!(Serializer, Policy, CustomType, ATTRIBUTES)(serializer, Policy!TU.toRepresentation(value)); 377 } else static if (isCustomSerializable!TU) { 378 alias CustomType = typeof(T.init.toRepresentation()); 379 serializeImpl!(Serializer, Policy, CustomType, ATTRIBUTES)(serializer, value.toRepresentation()); 380 } else static if (isISOExtStringSerializable!TU) { 381 serializer.writeValue(value.toISOExtString()); 382 } else static if (isStringSerializable!TU) { 383 serializer.writeValue(value.toString()); 384 } else static if (is(TU == struct) || is(TU == class)) { 385 static if (!hasSerializableFields!TU) 386 pragma(msg, "Serializing composite type "~T.stringof~" which has no serializable fields"); 387 static if (is(TU == class)) { 388 if (value is null) { 389 serializeImpl!(Serializer, Policy, typeof(null))(serializer, null); 390 return; 391 } 392 } 393 static if (hasAttributeL!(AsArrayAttribute, ATTRIBUTES)) { 394 enum nfields = getExpandedFieldCount!(TU, SerializableFields!TU); 395 serializer.beginWriteArray!TU(nfields); 396 foreach (mname; SerializableFields!TU) { 397 alias TMS = TypeTuple!(typeof(__traits(getMember, value, mname))); 398 foreach (j, TM; TMS) { 399 alias TA = TypeTuple!(__traits(getAttributes, TypeTuple!(__traits(getMember, T, mname))[j])); 400 serializer.beginWriteArrayEntry!TM(j); 401 serializeImpl!(Serializer, Policy, TM, TA)(serializer, tuple(__traits(getMember, value, mname))[j]); 402 serializer.endWriteArrayEntry!TM(j); 403 } 404 } 405 serializer.endWriteArray!TU(); 406 } else { 407 static if (__traits(compiles, serializer.beginWriteDictionary!TU(0))) { 408 enum nfields = getExpandedFieldCount!(TU, SerializableFields!TU); 409 serializer.beginWriteDictionary!TU(nfields); 410 } else { 411 serializer.beginWriteDictionary!TU(); 412 } 413 foreach (mname; SerializableFields!TU) { 414 alias TM = TypeTuple!(typeof(__traits(getMember, value, mname))); 415 static if (TM.length == 1) { 416 alias TA = TypeTuple!(__traits(getAttributes, __traits(getMember, T, mname))); 417 enum name = getAttribute!(TU, mname, NameAttribute)(NameAttribute(underscoreStrip(mname))).name; 418 auto vt = __traits(getMember, value, mname); 419 serializer.beginWriteDictionaryEntry!(typeof(vt))(name); 420 serializeImpl!(Serializer, Policy, typeof(vt), TA)(serializer, vt); 421 serializer.endWriteDictionaryEntry!(typeof(vt))(name); 422 } else { 423 alias TA = TypeTuple!(); // FIXME: support attributes for tuples somehow 424 enum name = underscoreStrip(mname); 425 auto vt = tuple(__traits(getMember, value, mname)); 426 serializer.beginWriteDictionaryEntry!(typeof(vt))(name); 427 serializeImpl!(Serializer, Policy, typeof(vt), TA)(serializer, vt); 428 serializer.endWriteDictionaryEntry!(typeof(vt))(name); 429 } 430 } 431 static if (__traits(compiles, serializer.endWriteDictionary!TU(0))) { 432 serializer.endWriteDictionary!TU(nfields); 433 } else { 434 serializer.endWriteDictionary!TU(); 435 } 436 } 437 } else static if (isPointer!TU) { 438 if (value is null) { 439 serializer.writeValue(null); 440 return; 441 } 442 serializeImpl!(Serializer, Policy, PointerTarget!TU)(serializer, *value); 443 } else static if (is(TU == bool) || is(TU : real) || is(TU : long)) { 444 serializeImpl!(Serializer, Policy, string)(serializer, to!string(value)); 445 } else static assert(false, "Unsupported serialization type: " ~ T.stringof); 446 } 447 448 449 private T deserializeImpl(T, alias Policy, Serializer, ATTRIBUTES...)(ref Serializer deserializer) 450 { 451 import std.typecons : BitFlags, Nullable; 452 453 static assert(Serializer.isSupportedValueType!string, "All serializers must support string values."); 454 static assert(Serializer.isSupportedValueType!(typeof(null)), "All serializers must support null values."); 455 456 static if (is(T == enum)) { 457 static if (hasAttributeL!(ByNameAttribute, ATTRIBUTES)) { 458 return deserializeImpl!(string, Policy, Serializer)(deserializer).to!T(); 459 } else { 460 return cast(T)deserializeImpl!(OriginalType!T, Policy, Serializer)(deserializer); 461 } 462 } else static if (Serializer.isSupportedValueType!T) { 463 return deserializer.readValue!T(); 464 } else static if (isStaticArray!T) { 465 alias TV = typeof(T.init[0]); 466 T ret; 467 size_t i = 0; 468 deserializer.readArray!T((sz) { assert(sz == 0 || sz == T.length); }, { 469 assert(i < T.length); 470 ret[i++] = deserializeImpl!(TV, Policy, Serializer, ATTRIBUTES)(deserializer); 471 }); 472 return ret; 473 } else static if (isDynamicArray!T) { 474 alias TV = typeof(T.init[0]); 475 //auto ret = appender!T(); 476 T ret; // Cannot use appender because of DMD BUG 10690/10859/11357 477 deserializer.readArray!T((sz) { ret.reserve(sz); }, () { 478 ret ~= deserializeImpl!(TV, Policy, Serializer, ATTRIBUTES)(deserializer); 479 }); 480 return ret;//cast(T)ret.data; 481 } else static if (isAssociativeArray!T) { 482 alias TK = KeyType!T; 483 alias TV = ValueType!T; 484 T ret; 485 deserializer.readDictionary!T((name) { 486 TK key; 487 static if (is(TK == string)) key = name; 488 else static if (is(TK : real) || is(TK : long) || is(TK == enum)) key = name.to!TK; 489 else static if (isStringSerializable!TK) key = TK.fromString(name); 490 else static assert(false, "Associative array keys must be strings, numbers, enums, or have toString/fromString methods."); 491 ret[key] = deserializeImpl!(TV, Policy, Serializer, ATTRIBUTES)(deserializer); 492 }); 493 return ret; 494 } else static if (isInstanceOf!(Nullable, T)) { 495 if (deserializer.tryReadNull()) return T.init; 496 return T(deserializeImpl!(typeof(T.init.get()), Policy, Serializer, ATTRIBUTES)(deserializer)); 497 } else static if (is(T == BitFlags!E, E)) { 498 T ret; 499 deserializer.readArray!(E[])((sz) {}, { 500 ret |= deserializeImpl!(E, Policy, Serializer, ATTRIBUTES)(deserializer); 501 }); 502 return ret; 503 } else static if (isPolicySerializable!(Policy, T)) { 504 alias CustomType = typeof(Policy!T.toRepresentation(T.init)); 505 return Policy!T.fromRepresentation(deserializeImpl!(CustomType, Policy, Serializer, ATTRIBUTES)(deserializer)); 506 } else static if (isCustomSerializable!T) { 507 alias CustomType = typeof(T.init.toRepresentation()); 508 return T.fromRepresentation(deserializeImpl!(CustomType, Policy, Serializer, ATTRIBUTES)(deserializer)); 509 } else static if (isISOExtStringSerializable!T) { 510 return T.fromISOExtString(deserializer.readValue!string()); 511 } else static if (isStringSerializable!T) { 512 return T.fromString(deserializer.readValue!string()); 513 } else static if (is(T == struct) || is(T == class)) { 514 static if (is(T == class)) { 515 if (deserializer.tryReadNull()) return null; 516 } 517 518 bool[__traits(allMembers, T).length] set; 519 string name; 520 T ret; 521 static if (is(T == class)) ret = new T; 522 523 static if (hasAttributeL!(AsArrayAttribute, ATTRIBUTES)) { 524 size_t idx = 0; 525 deserializer.readArray!T((sz){}, { 526 static if (hasSerializableFields!T) { 527 switch (idx++) { 528 default: break; 529 foreach (i, mname; SerializableFields!T) { 530 alias TM = typeof(__traits(getMember, ret, mname)); 531 alias TA = TypeTuple!(__traits(getAttributes, __traits(getMember, ret, mname))); 532 case i: 533 static if (hasAttribute!(OptionalAttribute, __traits(getMember, T, mname))) 534 if (deserializer.tryReadNull()) return; 535 set[i] = true; 536 __traits(getMember, ret, mname) = deserializeImpl!(TM, Serializer, TA)(deserializer); 537 break; 538 } 539 } 540 } else { 541 pragma(msg, "Deserializing composite type "~T.stringof~" which has no serializable fields."); 542 } 543 }); 544 } else { 545 deserializer.readDictionary!T((name) { 546 static if (hasSerializableFields!T) { 547 switch (name) { 548 default: break; 549 foreach (i, mname; SerializableFields!T) { 550 alias TM = typeof(__traits(getMember, ret, mname)); 551 alias TA = TypeTuple!(__traits(getAttributes, __traits(getMember, ret, mname))); 552 enum fname = getAttribute!(T, mname, NameAttribute)(NameAttribute(underscoreStrip(mname))).name; 553 case fname: 554 static if (hasAttribute!(OptionalAttribute, __traits(getMember, T, mname))) 555 if (deserializer.tryReadNull()) return; 556 set[i] = true; 557 __traits(getMember, ret, mname) = deserializeImpl!(TM, Policy, Serializer, TA)(deserializer); 558 break; 559 } 560 } 561 } else { 562 pragma(msg, "Deserializing composite type "~T.stringof~" which has no serializable fields."); 563 } 564 }); 565 } 566 foreach (i, mname; SerializableFields!T) 567 static if (!hasAttribute!(OptionalAttribute, __traits(getMember, T, mname))) 568 enforce(set[i], "Missing non-optional field '"~mname~"' of type '"~T.stringof~"'."); 569 return ret; 570 } else static if (isPointer!T) { 571 if (deserializer.tryReadNull()) return null; 572 alias PT = PointerTarget!T; 573 auto ret = new PT; 574 *ret = deserializeImpl!(PT, Policy, Serializer)(deserializer); 575 return ret; 576 } else static if (is(T == bool) || is(T : real) || is(T : long)) { 577 return to!T(deserializeImpl!(string, Policy, Serializer)(deserializer)); 578 } else static assert(false, "Unsupported serialization type: " ~ T.stringof); 579 } 580 581 582 /** 583 Attribute for overriding the field name during (de-)serialization. 584 */ 585 NameAttribute name(string name) 586 { 587 return NameAttribute(name); 588 } 589 /// 590 unittest { 591 struct Test { 592 @name("screen-size") int screenSize; 593 } 594 } 595 596 597 /** 598 Attribute marking a field as optional during deserialization. 599 */ 600 @property OptionalAttribute optional() 601 { 602 return OptionalAttribute(); 603 } 604 /// 605 unittest { 606 struct Test { 607 // does not need to be present during deserialization 608 @optional int screenSize = 100; 609 } 610 } 611 612 613 /** 614 Attribute for marking non-serialized fields. 615 */ 616 @property IgnoreAttribute ignore() 617 { 618 return IgnoreAttribute(); 619 } 620 /// 621 unittest { 622 struct Test { 623 // is neither serialized not deserialized 624 @ignore int screenSize; 625 } 626 } 627 628 629 /** 630 Attribute for forcing serialization of enum fields by name instead of by value. 631 */ 632 @property ByNameAttribute byName() 633 { 634 return ByNameAttribute(); 635 } 636 /// 637 unittest { 638 enum Color { 639 red, 640 green, 641 blue 642 } 643 644 struct Test { 645 // serialized as an int (e.g. 1 for Color.green) 646 Color color; 647 // serialized as a string (e.g. "green" for Color.green) 648 @byName Color namedColor; 649 // serialized as array of ints 650 Color[] colorArray; 651 // serialized as array of strings 652 @byName Color[] namedColorArray; 653 } 654 } 655 656 657 /** 658 Attribute for representing a struct/class as an array instead of an object. 659 660 Usually structs and class objects are serialized as dictionaries mapping 661 from field name to value. Using this attribute, they will be serialized 662 as a flat array instead. Note that changing the layout will make any 663 already serialized data mismatch when this attribute is used. 664 */ 665 @property AsArrayAttribute asArray() 666 { 667 return AsArrayAttribute(); 668 } 669 /// 670 unittest { 671 struct Fields { 672 int f1; 673 string f2; 674 double f3; 675 } 676 677 struct Test { 678 // serialized as name:value pairs ["f1": int, "f2": string, "f3": double] 679 Fields object; 680 // serialized as a sequential list of values [int, string, double] 681 @asArray Fields array; 682 } 683 684 import dub.internal.vibecompat.data.json; 685 static assert(is(typeof(serializeToJson(Test())))); 686 } 687 688 689 /// 690 enum FieldExistence 691 { 692 missing, 693 exists, 694 defer 695 } 696 697 /// User defined attribute (not intended for direct use) 698 struct NameAttribute { string name; } 699 /// ditto 700 struct OptionalAttribute {} 701 /// ditto 702 struct IgnoreAttribute {} 703 /// ditto 704 struct ByNameAttribute {} 705 /// ditto 706 struct AsArrayAttribute {} 707 708 /** 709 Checks if a given type has a custom serialization representation. 710 711 A class or struct type is custom serializable if it defines a pair of 712 `toRepresentation`/`fromRepresentation` methods. Any class or 713 struct type that has this trait will be serialized by using the return 714 value of it's `toRepresentation` method instead of the original value. 715 716 This trait has precedence over `isISOExtStringSerializable` and 717 `isStringSerializable`. 718 */ 719 template isCustomSerializable(T) 720 { 721 enum bool isCustomSerializable = is(typeof(T.init.toRepresentation())) && is(typeof(T.fromRepresentation(T.init.toRepresentation())) == T); 722 } 723 /// 724 unittest { 725 // represented as a single uint when serialized 726 static struct S { 727 ushort x, y; 728 729 uint toRepresentation() const { return x + (y << 16); } 730 static S fromRepresentation(uint i) { return S(i & 0xFFFF, i >> 16); } 731 } 732 733 static assert(isCustomSerializable!S); 734 } 735 736 737 /** 738 Checks if a given type has an ISO extended string serialization representation. 739 740 A class or struct type is ISO extended string serializable if it defines a 741 pair of `toISOExtString`/`fromISOExtString` methods. Any class or 742 struct type that has this trait will be serialized by using the return 743 value of it's `toISOExtString` method instead of the original value. 744 745 This is mainly useful for supporting serialization of the the date/time 746 types in `std.datetime`. 747 748 This trait has precedence over `isStringSerializable`. 749 */ 750 template isISOExtStringSerializable(T) 751 { 752 enum bool isISOExtStringSerializable = is(typeof(T.init.toISOExtString()) == string) && is(typeof(T.fromISOExtString("")) == T); 753 } 754 /// 755 unittest { 756 import std.datetime; 757 758 static assert(isISOExtStringSerializable!DateTime); 759 static assert(isISOExtStringSerializable!SysTime); 760 761 // represented as an ISO extended string when serialized 762 static struct S { 763 // dummy example implementations 764 string toISOExtString() const { return ""; } 765 static S fromISOExtString(string s) { return S.init; } 766 } 767 768 static assert(isISOExtStringSerializable!S); 769 } 770 771 772 /** 773 Checks if a given type has a string serialization representation. 774 775 A class or struct type is string serializable if it defines a pair of 776 `toString`/`fromString` methods. Any class or struct type that 777 has this trait will be serialized by using the return value of it's 778 `toString` method instead of the original value. 779 */ 780 template isStringSerializable(T) 781 { 782 enum bool isStringSerializable = is(typeof(T.init.toString()) == string) && is(typeof(T.fromString("")) == T); 783 } 784 /// 785 unittest { 786 import std.conv; 787 788 // represented as the boxed value when serialized 789 static struct Box(T) { 790 T value; 791 } 792 793 template BoxPol(S) 794 { 795 auto toRepresentation(S s) { 796 return s.value; 797 } 798 799 S fromRepresentation(typeof(S.init.value) v) { 800 return S(v); 801 } 802 } 803 static assert(isPolicySerializable!(BoxPol, Box!int)); 804 } 805 806 private template DefaultPolicy(T) 807 { 808 } 809 810 /** 811 Checks if a given policy supports custom serialization for a given type. 812 813 A class or struct type is custom serializable according to a policy if 814 the policy defines a pair of `toRepresentation`/`fromRepresentation` 815 functions. Any class or struct type that has this trait for the policy supplied to 816 `serializeWithPolicy` will be serialized by using the return value of the 817 policy `toRepresentation` function instead of the original value. 818 819 This trait has precedence over `isCustomSerializable`, 820 `isISOExtStringSerializable` and `isStringSerializable`. 821 822 See_Also: `vibe.data.serialization.serializeWithPolicy` 823 */ 824 template isPolicySerializable(alias Policy, T) 825 { 826 enum bool isPolicySerializable = is(typeof(Policy!T.toRepresentation(T.init))) && 827 is(typeof(Policy!T.fromRepresentation(Policy!T.toRepresentation(T.init))) == T); 828 } 829 /// 830 unittest { 831 import std.conv; 832 833 // represented as a string when serialized 834 static struct S { 835 int value; 836 837 // dummy example implementations 838 string toString() const { return value.to!string(); } 839 static S fromString(string s) { return S(s.to!int()); } 840 } 841 842 static assert(isStringSerializable!S); 843 } 844 845 /** 846 Chains serialization policy. 847 848 Constructs a serialization policy that given a type `T` will apply the 849 first compatible policy `toRepresentation` and `fromRepresentation` 850 functions. Policies are evaluated left-to-right according to 851 `isPolicySerializable`. 852 853 See_Also: `vibe.data.serialization.serializeWithPolicy` 854 */ 855 template ChainedPolicy(alias Primary, Fallbacks...) 856 { 857 static if (Fallbacks.length == 0) { 858 alias ChainedPolicy = Primary; 859 } else { 860 alias ChainedPolicy = ChainedPolicy!(ChainedPolicyImpl!(Primary, Fallbacks[0]), Fallbacks[1..$]); 861 } 862 } 863 /// 864 unittest { 865 import std.conv; 866 867 // To be represented as the boxed value when serialized 868 static struct Box(T) { 869 T value; 870 } 871 // Also to berepresented as the boxed value when serialized, but has 872 // a different way to access the value. 873 static struct Box2(T) { 874 private T v; 875 ref T get() { 876 return v; 877 } 878 } 879 template BoxPol(S) 880 { 881 auto toRepresentation(S s) { 882 return s.value; 883 } 884 885 S fromRepresentation(typeof(toRepresentation(S.init)) v) { 886 return S(v); 887 } 888 } 889 template Box2Pol(S) 890 { 891 auto toRepresentation(S s) { 892 return s.get(); 893 } 894 895 S fromRepresentation(typeof(toRepresentation(S.init)) v) { 896 S s; 897 s.get() = v; 898 return s; 899 } 900 } 901 alias ChainPol = ChainedPolicy!(BoxPol, Box2Pol); 902 static assert(!isPolicySerializable!(BoxPol, Box2!int)); 903 static assert(!isPolicySerializable!(Box2Pol, Box!int)); 904 static assert(isPolicySerializable!(ChainPol, Box!int)); 905 static assert(isPolicySerializable!(ChainPol, Box2!int)); 906 } 907 908 private template ChainedPolicyImpl(alias Primary, alias Fallback) 909 { 910 template Pol(T) 911 { 912 static if (isPolicySerializable!(Primary, T)) { 913 alias toRepresentation = Primary!T.toRepresentation; 914 alias fromRepresentation = Primary!T.fromRepresentation; 915 } else { 916 alias toRepresentation = Fallback!T.toRepresentation; 917 alias fromRepresentation = Fallback!T.fromRepresentation; 918 } 919 } 920 alias ChainedPolicyImpl = Pol; 921 } 922 923 private template hasAttribute(T, alias decl) { enum hasAttribute = findFirstUDA!(T, decl).found; } 924 925 unittest { 926 @asArray int i1; 927 static assert(hasAttribute!(AsArrayAttribute, i1)); 928 int i2; 929 static assert(!hasAttribute!(AsArrayAttribute, i2)); 930 } 931 932 private template hasAttributeL(T, ATTRIBUTES...) { 933 static if (ATTRIBUTES.length == 1) { 934 enum hasAttributeL = is(typeof(ATTRIBUTES[0]) == T); 935 } else static if (ATTRIBUTES.length > 1) { 936 enum hasAttributeL = hasAttributeL!(T, ATTRIBUTES[0 .. $/2]) || hasAttributeL!(T, ATTRIBUTES[$/2 .. $]); 937 } else { 938 enum hasAttributeL = false; 939 } 940 } 941 942 unittest { 943 static assert(hasAttributeL!(AsArrayAttribute, byName, asArray)); 944 static assert(!hasAttributeL!(AsArrayAttribute, byName)); 945 } 946 947 private static T getAttribute(TT, string mname, T)(T default_value) 948 { 949 enum val = findFirstUDA!(T, __traits(getMember, TT, mname)); 950 static if (val.found) return val.value; 951 else return default_value; 952 } 953 954 private string underscoreStrip(string field_name) 955 { 956 if( field_name.length < 1 || field_name[$-1] != '_' ) return field_name; 957 else return field_name[0 .. $-1]; 958 } 959 960 961 private template hasSerializableFields(T, size_t idx = 0) 962 { 963 enum hasSerializableFields = SerializableFields!(T).length > 0; 964 /*static if (idx < __traits(allMembers, T).length) { 965 enum mname = __traits(allMembers, T)[idx]; 966 static if (!isRWPlainField!(T, mname) && !isRWField!(T, mname)) enum hasSerializableFields = hasSerializableFields!(T, idx+1); 967 else static if (hasAttribute!(IgnoreAttribute, __traits(getMember, T, mname))) enum hasSerializableFields = hasSerializableFields!(T, idx+1); 968 else enum hasSerializableFields = true; 969 } else enum hasSerializableFields = false;*/ 970 } 971 972 private template SerializableFields(COMPOSITE) 973 { 974 alias SerializableFields = FilterSerializableFields!(COMPOSITE, __traits(allMembers, COMPOSITE)); 975 } 976 977 private template FilterSerializableFields(COMPOSITE, FIELDS...) 978 { 979 static if (FIELDS.length > 1) { 980 alias FilterSerializableFields = TypeTuple!( 981 FilterSerializableFields!(COMPOSITE, FIELDS[0 .. $/2]), 982 FilterSerializableFields!(COMPOSITE, FIELDS[$/2 .. $])); 983 } else static if (FIELDS.length == 1) { 984 alias T = COMPOSITE; 985 enum mname = FIELDS[0]; 986 static if (isRWPlainField!(T, mname) || isRWField!(T, mname)) { 987 alias Tup = TypeTuple!(__traits(getMember, COMPOSITE, FIELDS[0])); 988 static if (Tup.length != 1) { 989 alias FilterSerializableFields = TypeTuple!(mname); 990 } else { 991 static if (!hasAttribute!(IgnoreAttribute, __traits(getMember, T, mname))) 992 alias FilterSerializableFields = TypeTuple!(mname); 993 else alias FilterSerializableFields = TypeTuple!(); 994 } 995 } else alias FilterSerializableFields = TypeTuple!(); 996 } else alias FilterSerializableFields = TypeTuple!(); 997 } 998 999 private size_t getExpandedFieldCount(T, FIELDS...)() 1000 { 1001 size_t ret = 0; 1002 foreach (F; FIELDS) ret += TypeTuple!(__traits(getMember, T, F)).length; 1003 return ret; 1004 } 1005 1006 /******************************************************************************/ 1007 /* General serialization unit testing */ 1008 /******************************************************************************/ 1009 1010 version (unittest) { 1011 private struct TestSerializer { 1012 import std.array, std.conv, std..string; 1013 1014 string result; 1015 1016 enum isSupportedValueType(T) = is(T == string) || is(T == typeof(null)) || is(T == float) || is (T == int); 1017 1018 string getSerializedResult() { return result; } 1019 void beginWriteDictionary(T)() { result ~= "D("~T.mangleof~"){"; } 1020 void endWriteDictionary(T)() { result ~= "}D("~T.mangleof~")"; } 1021 void beginWriteDictionaryEntry(T)(string name) { result ~= "DE("~T.mangleof~","~name~")("; } 1022 void endWriteDictionaryEntry(T)(string name) { result ~= ")DE("~T.mangleof~","~name~")"; } 1023 void beginWriteArray(T)(size_t length) { result ~= "A("~T.mangleof~")["~length.to!string~"]["; } 1024 void endWriteArray(T)() { result ~= "]A("~T.mangleof~")"; } 1025 void beginWriteArrayEntry(T)(size_t i) { result ~= "AE("~T.mangleof~","~i.to!string~")("; } 1026 void endWriteArrayEntry(T)(size_t i) { result ~= ")AE("~T.mangleof~","~i.to!string~")"; } 1027 void writeValue(T)(T value) { 1028 if (is(T == typeof(null))) result ~= "null"; 1029 else { 1030 assert(isSupportedValueType!T); 1031 result ~= "V("~T.mangleof~")("~value.to!string~")"; 1032 } 1033 } 1034 1035 // deserialization 1036 void readDictionary(T)(scope void delegate(string) entry_callback) 1037 { 1038 skip("D("~T.mangleof~"){"); 1039 while (result.startsWith("DE(")) { 1040 result = result[3 .. $]; 1041 auto idx = result.indexOf(','); 1042 auto idx2 = result.indexOf(")("); 1043 assert(idx > 0 && idx2 > idx); 1044 auto t = result[0 .. idx]; 1045 auto n = result[idx+1 .. idx2]; 1046 result = result[idx2+2 .. $]; 1047 entry_callback(n); 1048 skip(")DE("~t~","~n~")"); 1049 } 1050 skip("}D("~T.mangleof~")"); 1051 } 1052 1053 void readArray(T)(scope void delegate(size_t) size_callback, scope void delegate() entry_callback) 1054 { 1055 skip("A("~T.mangleof~")["); 1056 auto bidx = result.indexOf("]["); 1057 assert(bidx > 0); 1058 auto cnt = result[0 .. bidx].to!size_t; 1059 result = result[bidx+2 .. $]; 1060 1061 size_t i = 0; 1062 while (result.startsWith("AE(")) { 1063 result = result[3 .. $]; 1064 auto idx = result.indexOf(','); 1065 auto idx2 = result.indexOf(")("); 1066 assert(idx > 0 && idx2 > idx); 1067 auto t = result[0 .. idx]; 1068 auto n = result[idx+1 .. idx2]; 1069 result = result[idx2+2 .. $]; 1070 assert(n == i.to!string); 1071 entry_callback(); 1072 skip(")AE("~t~","~n~")"); 1073 i++; 1074 } 1075 skip("]A("~T.mangleof~")"); 1076 1077 assert(i == cnt); 1078 } 1079 1080 T readValue(T)() 1081 { 1082 skip("V("~T.mangleof~")("); 1083 auto idx = result.indexOf(')'); 1084 assert(idx >= 0); 1085 auto ret = result[0 .. idx].to!T; 1086 result = result[idx+1 .. $]; 1087 return ret; 1088 } 1089 1090 void skip(string prefix) 1091 { 1092 assert(result.startsWith(prefix), result); 1093 result = result[prefix.length .. $]; 1094 } 1095 1096 bool tryReadNull() 1097 { 1098 if (result.startsWith("null")) { 1099 result = result[4 .. $]; 1100 return true; 1101 } else return false; 1102 } 1103 } 1104 } 1105 1106 unittest { // basic serialization behavior 1107 import std.typecons : Nullable; 1108 1109 static void test(T)(T value, string expected) { 1110 assert(serialize!TestSerializer(value) == expected, serialize!TestSerializer(value)); 1111 static if (isPointer!T) { 1112 if (value) assert(*deserialize!(TestSerializer, T)(expected) == *value); 1113 else assert(deserialize!(TestSerializer, T)(expected) is null); 1114 } else static if (is(T == Nullable!U, U)) { 1115 if (value.isNull()) assert(deserialize!(TestSerializer, T)(expected).isNull); 1116 else assert(deserialize!(TestSerializer, T)(expected) == value); 1117 } else assert(deserialize!(TestSerializer, T)(expected) == value); 1118 } 1119 1120 test("hello", "V(Aya)(hello)"); 1121 test(12, "V(i)(12)"); 1122 test(12.0, "V(Aya)(12)"); 1123 test(12.0f, "V(f)(12)"); 1124 assert(serialize!TestSerializer(null) == "null"); 1125 test(["hello", "world"], "A(AAya)[2][AE(Aya,0)(V(Aya)(hello))AE(Aya,0)AE(Aya,1)(V(Aya)(world))AE(Aya,1)]A(AAya)"); 1126 test(["hello": "world"], "D(HAyaAya){DE(Aya,hello)(V(Aya)(world))DE(Aya,hello)}D(HAyaAya)"); 1127 test(cast(int*)null, "null"); 1128 int i = 42; 1129 test(&i, "V(i)(42)"); 1130 Nullable!int j; 1131 test(j, "null"); 1132 j = 42; 1133 test(j, "V(i)(42)"); 1134 } 1135 1136 unittest { // basic user defined types 1137 static struct S { string f; } 1138 enum Sm = S.mangleof; 1139 auto s = S("hello"); 1140 enum s_ser = "D("~Sm~"){DE(Aya,f)(V(Aya)(hello))DE(Aya,f)}D("~Sm~")"; 1141 assert(serialize!TestSerializer(s) == s_ser, serialize!TestSerializer(s)); 1142 assert(deserialize!(TestSerializer, S)(s_ser) == s); 1143 1144 static class C { string f; } 1145 enum Cm = C.mangleof; 1146 C c; 1147 assert(serialize!TestSerializer(c) == "null"); 1148 c = new C; 1149 c.f = "hello"; 1150 enum c_ser = "D("~Cm~"){DE(Aya,f)(V(Aya)(hello))DE(Aya,f)}D("~Cm~")"; 1151 assert(serialize!TestSerializer(c) == c_ser); 1152 assert(deserialize!(TestSerializer, C)(c_ser).f == c.f); 1153 1154 enum E { hello, world } 1155 assert(serialize!TestSerializer(E.hello) == "V(i)(0)"); 1156 assert(serialize!TestSerializer(E.world) == "V(i)(1)"); 1157 } 1158 1159 unittest { // tuple serialization 1160 import std.typecons : Tuple; 1161 1162 static struct S(T...) { T f; } 1163 enum Sm = S!(int, string).mangleof; 1164 enum Tum = Tuple!(int, string).mangleof; 1165 auto s = S!(int, string)(42, "hello"); 1166 assert(serialize!TestSerializer(s) == 1167 "D("~Sm~"){DE("~Tum~",f)(A("~Tum~")[2][AE(i,0)(V(i)(42))AE(i,0)AE(Aya,1)(V(Aya)(hello))AE(Aya,1)]A("~Tum~"))DE("~Tum~",f)}D("~Sm~")"); 1168 1169 static struct T { @asArray S!(int, string) g; } 1170 enum Tm = T.mangleof; 1171 auto t = T(s); 1172 assert(serialize!TestSerializer(t) == 1173 "D("~Tm~"){DE("~Sm~",g)(A("~Sm~")[2][AE(i,0)(V(i)(42))AE(i,0)AE(Aya,1)(V(Aya)(hello))AE(Aya,1)]A("~Sm~"))DE("~Sm~",g)}D("~Tm~")"); 1174 } 1175 1176 unittest { // testing the various UDAs 1177 enum E { hello, world } 1178 enum Em = E.mangleof; 1179 static struct S { 1180 @byName E e; 1181 @ignore int i; 1182 @optional float f; 1183 } 1184 enum Sm = S.mangleof; 1185 auto s = S(E.world, 42, 1.0f); 1186 assert(serialize!TestSerializer(s) == 1187 "D("~Sm~"){DE("~Em~",e)(V(Aya)(world))DE("~Em~",e)DE(f,f)(V(f)(1))DE(f,f)}D("~Sm~")"); 1188 } 1189 1190 unittest { // custom serialization support 1191 // iso-ext 1192 import std.datetime; 1193 auto t = TimeOfDay(6, 31, 23); 1194 assert(serialize!TestSerializer(t) == "V(Aya)(06:31:23)"); 1195 auto d = Date(1964, 1, 23); 1196 assert(serialize!TestSerializer(d) == "V(Aya)(1964-01-23)"); 1197 auto dt = DateTime(d, t); 1198 assert(serialize!TestSerializer(dt) == "V(Aya)(1964-01-23T06:31:23)"); 1199 auto st = SysTime(dt, UTC()); 1200 assert(serialize!TestSerializer(st) == "V(Aya)(1964-01-23T06:31:23Z)"); 1201 1202 // string 1203 struct S1 { int i; string toString() const { return "hello"; } static S1 fromString(string) { return S1.init; } } 1204 struct S2 { int i; string toString() const { return "hello"; } } 1205 enum S2m = S2.mangleof; 1206 struct S3 { int i; static S3 fromString(string) { return S3.init; } } 1207 enum S3m = S3.mangleof; 1208 assert(serialize!TestSerializer(S1.init) == "V(Aya)(hello)"); 1209 assert(serialize!TestSerializer(S2.init) == "D("~S2m~"){DE(i,i)(V(i)(0))DE(i,i)}D("~S2m~")"); 1210 assert(serialize!TestSerializer(S3.init) == "D("~S3m~"){DE(i,i)(V(i)(0))DE(i,i)}D("~S3m~")"); 1211 1212 // custom 1213 struct C1 { int i; float toRepresentation() const { return 1.0f; } static C1 fromRepresentation(float f) { return C1.init; } } 1214 struct C2 { int i; float toRepresentation() const { return 1.0f; } } 1215 enum C2m = C2.mangleof; 1216 struct C3 { int i; static C3 fromRepresentation(float f) { return C3.init; } } 1217 enum C3m = C3.mangleof; 1218 assert(serialize!TestSerializer(C1.init) == "V(f)(1)"); 1219 assert(serialize!TestSerializer(C2.init) == "D("~C2m~"){DE(i,i)(V(i)(0))DE(i,i)}D("~C2m~")"); 1220 assert(serialize!TestSerializer(C3.init) == "D("~C3m~"){DE(i,i)(V(i)(0))DE(i,i)}D("~C3m~")"); 1221 } 1222 1223 unittest // Testing corner case: member function returning by ref 1224 { 1225 import dub.internal.vibecompat.data.json; 1226 1227 static struct S 1228 { 1229 int i; 1230 ref int foo() { return i; } 1231 } 1232 1233 static assert(__traits(compiles, { S().serializeToJson(); })); 1234 static assert(__traits(compiles, { Json().deserializeJson!S(); })); 1235 1236 auto s = S(1); 1237 assert(s.serializeToJson().deserializeJson!S() == s); 1238 } 1239 1240 unittest // Testing corner case: Variadic template constructors and methods 1241 { 1242 import dub.internal.vibecompat.data.json; 1243 1244 static struct S 1245 { 1246 int i; 1247 this(Args...)(Args args) {} 1248 int foo(Args...)(Args args) { return i; } 1249 ref int bar(Args...)(Args args) { return i; } 1250 } 1251 1252 static assert(__traits(compiles, { S().serializeToJson(); })); 1253 static assert(__traits(compiles, { Json().deserializeJson!S(); })); 1254 1255 auto s = S(1); 1256 assert(s.serializeToJson().deserializeJson!S() == s); 1257 } 1258 1259 unittest // Make sure serializing through properties still works 1260 { 1261 import dub.internal.vibecompat.data.json; 1262 1263 static struct S 1264 { 1265 public int i; 1266 private int privateJ; 1267 1268 @property int j() { return privateJ; } 1269 @property void j(int j) { privateJ = j; } 1270 } 1271 1272 auto s = S(1, 2); 1273 assert(s.serializeToJson().deserializeJson!S() == s); 1274 } 1275 1276 unittest { // test BitFlags serialization 1277 import std.typecons : BitFlags; 1278 1279 enum Flag { 1280 a = 1<<0, 1281 b = 1<<1, 1282 c = 1<<2 1283 } 1284 enum Flagm = Flag.mangleof; 1285 1286 alias Flags = BitFlags!Flag; 1287 enum Flagsm = Flags.mangleof; 1288 1289 enum Fi_ser = "A(A"~Flagm~")[0][]A(A"~Flagm~")"; 1290 assert(serialize!TestSerializer(Flags.init) == Fi_ser); 1291 1292 enum Fac_ser = "A(A"~Flagm~")[2][AE("~Flagm~",0)(V(i)(1))AE("~Flagm~",0)AE("~Flagm~",1)(V(i)(4))AE("~Flagm~",1)]A(A"~Flagm~")"; 1293 assert(serialize!TestSerializer(Flags(Flag.a, Flag.c)) == Fac_ser); 1294 1295 struct S { @byName Flags f; } 1296 enum Sm = S.mangleof; 1297 enum Sac_ser = "D("~Sm~"){DE("~Flagsm~",f)(A(A"~Flagm~")[2][AE("~Flagm~",0)(V(Aya)(a))AE("~Flagm~",0)AE("~Flagm~",1)(V(Aya)(c))AE("~Flagm~",1)]A(A"~Flagm~"))DE("~Flagsm~",f)}D("~Sm~")"; 1298 1299 assert(serialize!TestSerializer(S(Flags(Flag.a, Flag.c))) == Sac_ser); 1300 1301 assert(deserialize!(TestSerializer, Flags)(Fi_ser) == Flags.init); 1302 assert(deserialize!(TestSerializer, Flags)(Fac_ser) == Flags(Flag.a, Flag.c)); 1303 assert(deserialize!(TestSerializer, S)(Sac_ser) == S(Flags(Flag.a, Flag.c))); 1304 }