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_data) 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 : Nullable, Tuple, tuple; 295 import std.typecons : BitFlags; 296 297 static assert(Serializer.isSupportedValueType!string, "All serializers must support string values."); 298 static assert(Serializer.isSupportedValueType!(typeof(null)), "All serializers must support null values."); 299 300 alias TU = Unqual!T; 301 302 static if (is(TU == enum)) { 303 static if (hasAttributeL!(ByNameAttribute, ATTRIBUTES)) { 304 serializeImpl!(Serializer, Policy, string)(serializer, value.to!string()); 305 } else { 306 serializeImpl!(Serializer, Policy, OriginalType!TU)(serializer, cast(OriginalType!TU)value); 307 } 308 } else static if (Serializer.isSupportedValueType!TU) { 309 static if (is(TU == typeof(null))) serializer.writeValue!TU(null); 310 else serializer.writeValue!TU(value); 311 } else static if (/*isInstanceOf!(Tuple, TU)*/is(T == Tuple!TPS, TPS...)) { 312 static if (TU.Types.length == 1) { 313 serializeImpl!(Serializer, Policy, typeof(value[0]), ATTRIBUTES)(serializer, value[0]); 314 } else { 315 serializer.beginWriteArray!TU(value.length); 316 foreach (i, TV; T.Types) { 317 serializer.beginWriteArrayEntry!TV(i); 318 serializeImpl!(Serializer, Policy, TV, ATTRIBUTES)(serializer, value[i]); 319 serializer.endWriteArrayEntry!TV(i); 320 } 321 serializer.endWriteArray!TU(); 322 } 323 } else static if (isArray!TU) { 324 alias TV = typeof(value[0]); 325 serializer.beginWriteArray!TU(value.length); 326 foreach (i, ref el; value) { 327 serializer.beginWriteArrayEntry!TV(i); 328 serializeImpl!(Serializer, Policy, TV, ATTRIBUTES)(serializer, el); 329 serializer.endWriteArrayEntry!TV(i); 330 } 331 serializer.endWriteArray!TU(); 332 } else static if (isAssociativeArray!TU) { 333 alias TK = KeyType!TU; 334 alias TV = ValueType!TU; 335 static if (__traits(compiles, serializer.beginWriteDictionary!TU(0))) { 336 auto nfields = value.length; 337 serializer.beginWriteDictionary!TU(nfields); 338 } else { 339 serializer.beginWriteDictionary!TU(); 340 } 341 foreach (key, ref el; value) { 342 string keyname; 343 static if (is(TK : string)) keyname = key; 344 else static if (is(TK : real) || is(TK : long) || is(TK == enum)) keyname = key.to!string; 345 else static if (isStringSerializable!TK) keyname = key.toString(); 346 else static assert(false, "Associative array keys must be strings, numbers, enums, or have toString/fromString methods."); 347 serializer.beginWriteDictionaryEntry!TV(keyname); 348 serializeImpl!(Serializer, Policy, TV, ATTRIBUTES)(serializer, el); 349 serializer.endWriteDictionaryEntry!TV(keyname); 350 } 351 static if (__traits(compiles, serializer.endWriteDictionary!TU(0))) { 352 serializer.endWriteDictionary!TU(nfields); 353 } else { 354 serializer.endWriteDictionary!TU(); 355 } 356 } else static if (/*isInstanceOf!(Nullable, TU)*/is(T == Nullable!TPS, TPS...)) { 357 if (value.isNull()) serializeImpl!(Serializer, Policy, typeof(null))(serializer, null); 358 else serializeImpl!(Serializer, Policy, typeof(value.get()), ATTRIBUTES)(serializer, value.get()); 359 } else static if (is(T == BitFlags!E, E)) { 360 size_t cnt = 0; 361 foreach (v; EnumMembers!E) 362 if (value & v) 363 cnt++; 364 365 serializer.beginWriteArray!(E[])(cnt); 366 cnt = 0; 367 foreach (v; EnumMembers!E) 368 if (value & v) { 369 serializer.beginWriteArrayEntry!E(cnt); 370 serializeImpl!(Serializer, Policy, E, ATTRIBUTES)(serializer, v); 371 serializer.endWriteArrayEntry!E(cnt); 372 cnt++; 373 } 374 serializer.endWriteArray!(E[])(); 375 } else static if (isPolicySerializable!(Policy, TU)) { 376 alias CustomType = typeof(Policy!TU.toRepresentation(TU.init)); 377 serializeImpl!(Serializer, Policy, CustomType, ATTRIBUTES)(serializer, Policy!TU.toRepresentation(value)); 378 } else static if (isCustomSerializable!TU) { 379 alias CustomType = typeof(T.init.toRepresentation()); 380 serializeImpl!(Serializer, Policy, CustomType, ATTRIBUTES)(serializer, value.toRepresentation()); 381 } else static if (isISOExtStringSerializable!TU) { 382 serializer.writeValue(value.toISOExtString()); 383 } else static if (isStringSerializable!TU) { 384 serializer.writeValue(value.toString()); 385 } else static if (is(TU == struct) || is(TU == class)) { 386 static if (!hasSerializableFields!TU) 387 pragma(msg, "Serializing composite type "~T.stringof~" which has no serializable fields"); 388 static if (is(TU == class)) { 389 if (value is null) { 390 serializeImpl!(Serializer, Policy, typeof(null))(serializer, null); 391 return; 392 } 393 } 394 static if (hasAttributeL!(AsArrayAttribute, ATTRIBUTES)) { 395 enum nfields = getExpandedFieldCount!(TU, SerializableFields!TU); 396 serializer.beginWriteArray!TU(nfields); 397 foreach (mname; SerializableFields!TU) { 398 alias TMS = TypeTuple!(typeof(__traits(getMember, value, mname))); 399 foreach (j, TM; TMS) { 400 alias TA = TypeTuple!(__traits(getAttributes, TypeTuple!(__traits(getMember, T, mname))[j])); 401 serializer.beginWriteArrayEntry!TM(j); 402 serializeImpl!(Serializer, Policy, TM, TA)(serializer, tuple(__traits(getMember, value, mname))[j]); 403 serializer.endWriteArrayEntry!TM(j); 404 } 405 } 406 serializer.endWriteArray!TU(); 407 } else { 408 static if (__traits(compiles, serializer.beginWriteDictionary!TU(0))) { 409 enum nfields = getExpandedFieldCount!(TU, SerializableFields!TU); 410 serializer.beginWriteDictionary!TU(nfields); 411 } else { 412 serializer.beginWriteDictionary!TU(); 413 } 414 foreach (mname; SerializableFields!TU) { 415 alias TM = TypeTuple!(typeof(__traits(getMember, value, mname))); 416 static if (TM.length == 1) { 417 alias TA = TypeTuple!(__traits(getAttributes, __traits(getMember, T, mname))); 418 enum name = getAttribute!(TU, mname, NameAttribute)(NameAttribute(underscoreStrip(mname))).name; 419 auto vt = __traits(getMember, value, mname); 420 serializer.beginWriteDictionaryEntry!(typeof(vt))(name); 421 serializeImpl!(Serializer, Policy, typeof(vt), TA)(serializer, vt); 422 serializer.endWriteDictionaryEntry!(typeof(vt))(name); 423 } else { 424 alias TA = TypeTuple!(); // FIXME: support attributes for tuples somehow 425 enum name = underscoreStrip(mname); 426 auto vt = tuple(__traits(getMember, value, mname)); 427 serializer.beginWriteDictionaryEntry!(typeof(vt))(name); 428 serializeImpl!(Serializer, Policy, typeof(vt), TA)(serializer, vt); 429 serializer.endWriteDictionaryEntry!(typeof(vt))(name); 430 } 431 } 432 static if (__traits(compiles, serializer.endWriteDictionary!TU(0))) { 433 serializer.endWriteDictionary!TU(nfields); 434 } else { 435 serializer.endWriteDictionary!TU(); 436 } 437 } 438 } else static if (isPointer!TU) { 439 if (value is null) { 440 serializer.writeValue(null); 441 return; 442 } 443 serializeImpl!(Serializer, Policy, PointerTarget!TU)(serializer, *value); 444 } else static if (is(TU == bool) || is(TU : real) || is(TU : long)) { 445 serializeImpl!(Serializer, Policy, string)(serializer, to!string(value)); 446 } else static assert(false, "Unsupported serialization type: " ~ T.stringof); 447 } 448 449 450 private T deserializeImpl(T, alias Policy, Serializer, ATTRIBUTES...)(ref Serializer deserializer) 451 { 452 import std.typecons : Nullable; 453 import std.typecons : BitFlags; 454 455 static assert(Serializer.isSupportedValueType!string, "All serializers must support string values."); 456 static assert(Serializer.isSupportedValueType!(typeof(null)), "All serializers must support null values."); 457 458 static if (is(T == enum)) { 459 static if (hasAttributeL!(ByNameAttribute, ATTRIBUTES)) { 460 return deserializeImpl!(string, Policy, Serializer)(deserializer).to!T(); 461 } else { 462 return cast(T)deserializeImpl!(OriginalType!T, Policy, Serializer)(deserializer); 463 } 464 } else static if (Serializer.isSupportedValueType!T) { 465 return deserializer.readValue!T(); 466 } else static if (isStaticArray!T) { 467 alias TV = typeof(T.init[0]); 468 T ret; 469 size_t i = 0; 470 deserializer.readArray!T((sz) { assert(sz == 0 || sz == T.length); }, { 471 assert(i < T.length); 472 ret[i++] = deserializeImpl!(TV, Policy, Serializer, ATTRIBUTES)(deserializer); 473 }); 474 return ret; 475 } else static if (isDynamicArray!T) { 476 alias TV = typeof(T.init[0]); 477 //auto ret = appender!T(); 478 T ret; // Cannot use appender because of DMD BUG 10690/10859/11357 479 deserializer.readArray!T((sz) { ret.reserve(sz); }, () { 480 ret ~= deserializeImpl!(TV, Policy, Serializer, ATTRIBUTES)(deserializer); 481 }); 482 return ret;//cast(T)ret.data; 483 } else static if (isAssociativeArray!T) { 484 alias TK = KeyType!T; 485 alias TV = ValueType!T; 486 T ret; 487 deserializer.readDictionary!T((name) { 488 TK key; 489 static if (is(TK == string)) key = name; 490 else static if (is(TK : real) || is(TK : long) || is(TK == enum)) key = name.to!TK; 491 else static if (isStringSerializable!TK) key = TK.fromString(name); 492 else static assert(false, "Associative array keys must be strings, numbers, enums, or have toString/fromString methods."); 493 ret[key] = deserializeImpl!(TV, Policy, Serializer, ATTRIBUTES)(deserializer); 494 }); 495 return ret; 496 } else static if (isInstanceOf!(Nullable, T)) { 497 if (deserializer.tryReadNull()) return T.init; 498 return T(deserializeImpl!(typeof(T.init.get()), Policy, Serializer, ATTRIBUTES)(deserializer)); 499 } else static if (is(T == BitFlags!E, E)) { 500 T ret; 501 deserializer.readArray!(E[])((sz) {}, { 502 ret |= deserializeImpl!(E, Policy, Serializer, ATTRIBUTES)(deserializer); 503 }); 504 return ret; 505 } else static if (isPolicySerializable!(Policy, T)) { 506 alias CustomType = typeof(Policy!T.toRepresentation(T.init)); 507 return Policy!T.fromRepresentation(deserializeImpl!(CustomType, Policy, Serializer, ATTRIBUTES)(deserializer)); 508 } else static if (isCustomSerializable!T) { 509 alias CustomType = typeof(T.init.toRepresentation()); 510 return T.fromRepresentation(deserializeImpl!(CustomType, Policy, Serializer, ATTRIBUTES)(deserializer)); 511 } else static if (isISOExtStringSerializable!T) { 512 return T.fromISOExtString(deserializer.readValue!string()); 513 } else static if (isStringSerializable!T) { 514 return T.fromString(deserializer.readValue!string()); 515 } else static if (is(T == struct) || is(T == class)) { 516 static if (is(T == class)) { 517 if (deserializer.tryReadNull()) return null; 518 } 519 520 bool[__traits(allMembers, T).length] set; 521 string name; 522 T ret; 523 static if (is(T == class)) ret = new T; 524 525 static if (hasAttributeL!(AsArrayAttribute, ATTRIBUTES)) { 526 size_t idx = 0; 527 deserializer.readArray!T((sz){}, { 528 static if (hasSerializableFields!T) { 529 switch (idx++) { 530 default: break; 531 foreach (i, mname; SerializableFields!T) { 532 alias TM = typeof(__traits(getMember, ret, mname)); 533 alias TA = TypeTuple!(__traits(getAttributes, __traits(getMember, ret, mname))); 534 case i: 535 static if (hasAttribute!(OptionalAttribute, __traits(getMember, T, mname))) 536 if (deserializer.tryReadNull()) return; 537 set[i] = true; 538 __traits(getMember, ret, mname) = deserializeImpl!(TM, Serializer, TA)(deserializer); 539 break; 540 } 541 } 542 } else { 543 pragma(msg, "Deserializing composite type "~T.stringof~" which has no serializable fields."); 544 } 545 }); 546 } else { 547 deserializer.readDictionary!T((name) { 548 static if (hasSerializableFields!T) { 549 switch (name) { 550 default: break; 551 foreach (i, mname; SerializableFields!T) { 552 alias TM = typeof(__traits(getMember, ret, mname)); 553 alias TA = TypeTuple!(__traits(getAttributes, __traits(getMember, ret, mname))); 554 enum fname = getAttribute!(T, mname, NameAttribute)(NameAttribute(underscoreStrip(mname))).name; 555 case fname: 556 static if (hasAttribute!(OptionalAttribute, __traits(getMember, T, mname))) 557 if (deserializer.tryReadNull()) return; 558 set[i] = true; 559 __traits(getMember, ret, mname) = deserializeImpl!(TM, Policy, Serializer, TA)(deserializer); 560 break; 561 } 562 } 563 } else { 564 pragma(msg, "Deserializing composite type "~T.stringof~" which has no serializable fields."); 565 } 566 }); 567 } 568 foreach (i, mname; SerializableFields!T) 569 static if (!hasAttribute!(OptionalAttribute, __traits(getMember, T, mname))) 570 enforce(set[i], "Missing non-optional field '"~mname~"' of type '"~T.stringof~"'."); 571 return ret; 572 } else static if (isPointer!T) { 573 if (deserializer.tryReadNull()) return null; 574 alias PT = PointerTarget!T; 575 auto ret = new PT; 576 *ret = deserializeImpl!(PT, Policy, Serializer)(deserializer); 577 return ret; 578 } else static if (is(T == bool) || is(T : real) || is(T : long)) { 579 return to!T(deserializeImpl!(string, Policy, Serializer)(deserializer)); 580 } else static assert(false, "Unsupported serialization type: " ~ T.stringof); 581 } 582 583 584 /** 585 Attribute for overriding the field name during (de-)serialization. 586 */ 587 NameAttribute name(string name) 588 { 589 return NameAttribute(name); 590 } 591 /// 592 unittest { 593 struct Test { 594 @name("screen-size") int screenSize; 595 } 596 } 597 598 599 /** 600 Attribute marking a field as optional during deserialization. 601 */ 602 @property OptionalAttribute optional() 603 { 604 return OptionalAttribute(); 605 } 606 /// 607 unittest { 608 struct Test { 609 // does not need to be present during deserialization 610 @optional int screenSize = 100; 611 } 612 } 613 614 615 /** 616 Attribute for marking non-serialized fields. 617 */ 618 @property IgnoreAttribute ignore() 619 { 620 return IgnoreAttribute(); 621 } 622 /// 623 unittest { 624 struct Test { 625 // is neither serialized not deserialized 626 @ignore int screenSize; 627 } 628 } 629 630 631 /** 632 Attribute for forcing serialization of enum fields by name instead of by value. 633 */ 634 @property ByNameAttribute byName() 635 { 636 return ByNameAttribute(); 637 } 638 /// 639 unittest { 640 enum Color { 641 red, 642 green, 643 blue 644 } 645 646 struct Test { 647 // serialized as an int (e.g. 1 for Color.green) 648 Color color; 649 // serialized as a string (e.g. "green" for Color.green) 650 @byName Color namedColor; 651 // serialized as array of ints 652 Color[] colorArray; 653 // serialized as array of strings 654 @byName Color[] namedColorArray; 655 } 656 } 657 658 659 /** 660 Attribute for representing a struct/class as an array instead of an object. 661 662 Usually structs and class objects are serialized as dictionaries mapping 663 from field name to value. Using this attribute, they will be serialized 664 as a flat array instead. Note that changing the layout will make any 665 already serialized data mismatch when this attribute is used. 666 */ 667 @property AsArrayAttribute asArray() 668 { 669 return AsArrayAttribute(); 670 } 671 /// 672 unittest { 673 struct Fields { 674 int f1; 675 string f2; 676 double f3; 677 } 678 679 struct Test { 680 // serialized as name:value pairs ["f1": int, "f2": string, "f3": double] 681 Fields object; 682 // serialized as a sequential list of values [int, string, double] 683 @asArray Fields array; 684 } 685 686 import dub.internal.vibecompat.data.json; 687 static assert(is(typeof(serializeToJson(Test())))); 688 } 689 690 691 /// 692 enum FieldExistence 693 { 694 missing, 695 exists, 696 defer 697 } 698 699 /// User defined attribute (not intended for direct use) 700 struct NameAttribute { string name; } 701 /// ditto 702 struct OptionalAttribute {} 703 /// ditto 704 struct IgnoreAttribute {} 705 /// ditto 706 struct ByNameAttribute {} 707 /// ditto 708 struct AsArrayAttribute {} 709 710 /** 711 Checks if a given type has a custom serialization representation. 712 713 A class or struct type is custom serializable if it defines a pair of 714 `toRepresentation`/`fromRepresentation` methods. Any class or 715 struct type that has this trait will be serialized by using the return 716 value of it's `toRepresentation` method instead of the original value. 717 718 This trait has precedence over `isISOExtStringSerializable` and 719 `isStringSerializable`. 720 */ 721 template isCustomSerializable(T) 722 { 723 enum bool isCustomSerializable = is(typeof(T.init.toRepresentation())) && is(typeof(T.fromRepresentation(T.init.toRepresentation())) == T); 724 } 725 /// 726 unittest { 727 // represented as a single uint when serialized 728 static struct S { 729 ushort x, y; 730 731 uint toRepresentation() const { return x + (y << 16); } 732 static S fromRepresentation(uint i) { return S(i & 0xFFFF, i >> 16); } 733 } 734 735 static assert(isCustomSerializable!S); 736 } 737 738 739 /** 740 Checks if a given type has an ISO extended string serialization representation. 741 742 A class or struct type is ISO extended string serializable if it defines a 743 pair of `toISOExtString`/`fromISOExtString` methods. Any class or 744 struct type that has this trait will be serialized by using the return 745 value of it's `toISOExtString` method instead of the original value. 746 747 This is mainly useful for supporting serialization of the the date/time 748 types in `std.datetime`. 749 750 This trait has precedence over `isStringSerializable`. 751 */ 752 template isISOExtStringSerializable(T) 753 { 754 enum bool isISOExtStringSerializable = is(typeof(T.init.toISOExtString()) == string) && is(typeof(T.fromISOExtString("")) == T); 755 } 756 /// 757 unittest { 758 import std.datetime; 759 760 static assert(isISOExtStringSerializable!DateTime); 761 static assert(isISOExtStringSerializable!SysTime); 762 763 // represented as an ISO extended string when serialized 764 static struct S { 765 // dummy example implementations 766 string toISOExtString() const { return ""; } 767 static S fromISOExtString(string s) { return S.init; } 768 } 769 770 static assert(isISOExtStringSerializable!S); 771 } 772 773 774 /** 775 Checks if a given type has a string serialization representation. 776 777 A class or struct type is string serializable if it defines a pair of 778 `toString`/`fromString` methods. Any class or struct type that 779 has this trait will be serialized by using the return value of it's 780 `toString` method instead of the original value. 781 */ 782 template isStringSerializable(T) 783 { 784 enum bool isStringSerializable = is(typeof(T.init.toString()) == string) && is(typeof(T.fromString("")) == T); 785 } 786 /// 787 unittest { 788 import std.conv; 789 790 // represented as the boxed value when serialized 791 static struct Box(T) { 792 T value; 793 } 794 795 template BoxPol(S) 796 { 797 auto toRepresentation(S s) { 798 return s.value; 799 } 800 801 S fromRepresentation(typeof(S.init.value) v) { 802 return S(v); 803 } 804 } 805 static assert(isPolicySerializable!(BoxPol, Box!int)); 806 } 807 808 private template DefaultPolicy(T) 809 { 810 } 811 812 /** 813 Checks if a given policy supports custom serialization for a given type. 814 815 A class or struct type is custom serializable according to a policy if 816 the policy defines a pair of `toRepresentation`/`fromRepresentation` 817 functions. Any class or struct type that has this trait for the policy supplied to 818 `serializeWithPolicy` will be serialized by using the return value of the 819 policy `toRepresentation` function instead of the original value. 820 821 This trait has precedence over `isCustomSerializable`, 822 `isISOExtStringSerializable` and `isStringSerializable`. 823 824 See_Also: `vibe.data.serialization.serializeWithPolicy` 825 */ 826 template isPolicySerializable(alias Policy, T) 827 { 828 enum bool isPolicySerializable = is(typeof(Policy!T.toRepresentation(T.init))) && 829 is(typeof(Policy!T.fromRepresentation(Policy!T.toRepresentation(T.init))) == T); 830 } 831 /// 832 unittest { 833 import std.conv; 834 835 // represented as a string when serialized 836 static struct S { 837 int value; 838 839 // dummy example implementations 840 string toString() const { return value.to!string(); } 841 static S fromString(string s) { return S(s.to!int()); } 842 } 843 844 static assert(isStringSerializable!S); 845 } 846 847 /** 848 Chains serialization policy. 849 850 Constructs a serialization policy that given a type `T` will apply the 851 first compatible policy `toRepresentation` and `fromRepresentation` 852 functions. Policies are evaluated left-to-right according to 853 `isPolicySerializable`. 854 855 See_Also: `vibe.data.serialization.serializeWithPolicy` 856 */ 857 template ChainedPolicy(alias Primary, Fallbacks...) 858 { 859 static if (Fallbacks.length == 0) { 860 alias ChainedPolicy = Primary; 861 } else { 862 alias ChainedPolicy = ChainedPolicy!(ChainedPolicyImpl!(Primary, Fallbacks[0]), Fallbacks[1..$]); 863 } 864 } 865 /// 866 unittest { 867 import std.conv; 868 869 // To be represented as the boxed value when serialized 870 static struct Box(T) { 871 T value; 872 } 873 // Also to berepresented as the boxed value when serialized, but has 874 // a different way to access the value. 875 static struct Box2(T) { 876 private T v; 877 ref T get() { 878 return v; 879 } 880 } 881 template BoxPol(S) 882 { 883 auto toRepresentation(S s) { 884 return s.value; 885 } 886 887 S fromRepresentation(typeof(toRepresentation(S.init)) v) { 888 return S(v); 889 } 890 } 891 template Box2Pol(S) 892 { 893 auto toRepresentation(S s) { 894 return s.get(); 895 } 896 897 S fromRepresentation(typeof(toRepresentation(S.init)) v) { 898 S s; 899 s.get() = v; 900 return s; 901 } 902 } 903 alias ChainPol = ChainedPolicy!(BoxPol, Box2Pol); 904 static assert(!isPolicySerializable!(BoxPol, Box2!int)); 905 static assert(!isPolicySerializable!(Box2Pol, Box!int)); 906 static assert(isPolicySerializable!(ChainPol, Box!int)); 907 static assert(isPolicySerializable!(ChainPol, Box2!int)); 908 } 909 910 private template ChainedPolicyImpl(alias Primary, alias Fallback) 911 { 912 template Pol(T) 913 { 914 static if (isPolicySerializable!(Primary, T)) { 915 alias toRepresentation = Primary!T.toRepresentation; 916 alias fromRepresentation = Primary!T.fromRepresentation; 917 } else { 918 alias toRepresentation = Fallback!T.toRepresentation; 919 alias fromRepresentation = Fallback!T.fromRepresentation; 920 } 921 } 922 alias ChainedPolicyImpl = Pol; 923 } 924 925 private template hasAttribute(T, alias decl) { enum hasAttribute = findFirstUDA!(T, decl).found; } 926 927 unittest { 928 @asArray int i1; 929 static assert(hasAttribute!(AsArrayAttribute, i1)); 930 int i2; 931 static assert(!hasAttribute!(AsArrayAttribute, i2)); 932 } 933 934 private template hasAttributeL(T, ATTRIBUTES...) { 935 static if (ATTRIBUTES.length == 1) { 936 enum hasAttributeL = is(typeof(ATTRIBUTES[0]) == T); 937 } else static if (ATTRIBUTES.length > 1) { 938 enum hasAttributeL = hasAttributeL!(T, ATTRIBUTES[0 .. $/2]) || hasAttributeL!(T, ATTRIBUTES[$/2 .. $]); 939 } else { 940 enum hasAttributeL = false; 941 } 942 } 943 944 unittest { 945 static assert(hasAttributeL!(AsArrayAttribute, byName, asArray)); 946 static assert(!hasAttributeL!(AsArrayAttribute, byName)); 947 } 948 949 private static T getAttribute(TT, string mname, T)(T default_value) 950 { 951 enum val = findFirstUDA!(T, __traits(getMember, TT, mname)); 952 static if (val.found) return val.value; 953 else return default_value; 954 } 955 956 private string underscoreStrip(string field_name) 957 { 958 if( field_name.length < 1 || field_name[$-1] != '_' ) return field_name; 959 else return field_name[0 .. $-1]; 960 } 961 962 963 private template hasSerializableFields(T, size_t idx = 0) 964 { 965 enum hasSerializableFields = SerializableFields!(T).length > 0; 966 /*static if (idx < __traits(allMembers, T).length) { 967 enum mname = __traits(allMembers, T)[idx]; 968 static if (!isRWPlainField!(T, mname) && !isRWField!(T, mname)) enum hasSerializableFields = hasSerializableFields!(T, idx+1); 969 else static if (hasAttribute!(IgnoreAttribute, __traits(getMember, T, mname))) enum hasSerializableFields = hasSerializableFields!(T, idx+1); 970 else enum hasSerializableFields = true; 971 } else enum hasSerializableFields = false;*/ 972 } 973 974 private template SerializableFields(COMPOSITE) 975 { 976 alias SerializableFields = FilterSerializableFields!(COMPOSITE, __traits(allMembers, COMPOSITE)); 977 } 978 979 private template FilterSerializableFields(COMPOSITE, FIELDS...) 980 { 981 static if (FIELDS.length > 1) { 982 alias FilterSerializableFields = TypeTuple!( 983 FilterSerializableFields!(COMPOSITE, FIELDS[0 .. $/2]), 984 FilterSerializableFields!(COMPOSITE, FIELDS[$/2 .. $])); 985 } else static if (FIELDS.length == 1) { 986 alias T = COMPOSITE; 987 enum mname = FIELDS[0]; 988 static if (isRWPlainField!(T, mname) || isRWField!(T, mname)) { 989 alias Tup = TypeTuple!(__traits(getMember, COMPOSITE, FIELDS[0])); 990 static if (Tup.length != 1) { 991 alias FilterSerializableFields = TypeTuple!(mname); 992 } else { 993 static if (!hasAttribute!(IgnoreAttribute, __traits(getMember, T, mname))) 994 alias FilterSerializableFields = TypeTuple!(mname); 995 else alias FilterSerializableFields = TypeTuple!(); 996 } 997 } else alias FilterSerializableFields = TypeTuple!(); 998 } else alias FilterSerializableFields = TypeTuple!(); 999 } 1000 1001 private size_t getExpandedFieldCount(T, FIELDS...)() 1002 { 1003 size_t ret = 0; 1004 foreach (F; FIELDS) ret += TypeTuple!(__traits(getMember, T, F)).length; 1005 return ret; 1006 } 1007 1008 /******************************************************************************/ 1009 /* General serialization unit testing */ 1010 /******************************************************************************/ 1011 1012 version (unittest) { 1013 private struct TestSerializer { 1014 import std.array, std.conv, std.string; 1015 1016 string result; 1017 1018 enum isSupportedValueType(T) = is(T == string) || is(T == typeof(null)) || is(T == float) || is (T == int); 1019 1020 string getSerializedResult() { return result; } 1021 void beginWriteDictionary(T)() { result ~= "D("~T.mangleof~"){"; } 1022 void endWriteDictionary(T)() { result ~= "}D("~T.mangleof~")"; } 1023 void beginWriteDictionaryEntry(T)(string name) { result ~= "DE("~T.mangleof~","~name~")("; } 1024 void endWriteDictionaryEntry(T)(string name) { result ~= ")DE("~T.mangleof~","~name~")"; } 1025 void beginWriteArray(T)(size_t length) { result ~= "A("~T.mangleof~")["~length.to!string~"]["; } 1026 void endWriteArray(T)() { result ~= "]A("~T.mangleof~")"; } 1027 void beginWriteArrayEntry(T)(size_t i) { result ~= "AE("~T.mangleof~","~i.to!string~")("; } 1028 void endWriteArrayEntry(T)(size_t i) { result ~= ")AE("~T.mangleof~","~i.to!string~")"; } 1029 void writeValue(T)(T value) { 1030 if (is(T == typeof(null))) result ~= "null"; 1031 else { 1032 assert(isSupportedValueType!T); 1033 result ~= "V("~T.mangleof~")("~value.to!string~")"; 1034 } 1035 } 1036 1037 // deserialization 1038 void readDictionary(T)(scope void delegate(string) entry_callback) 1039 { 1040 skip("D("~T.mangleof~"){"); 1041 while (result.startsWith("DE(")) { 1042 result = result[3 .. $]; 1043 auto idx = result.indexOf(','); 1044 auto idx2 = result.indexOf(")("); 1045 assert(idx > 0 && idx2 > idx); 1046 auto t = result[0 .. idx]; 1047 auto n = result[idx+1 .. idx2]; 1048 result = result[idx2+2 .. $]; 1049 entry_callback(n); 1050 skip(")DE("~t~","~n~")"); 1051 } 1052 skip("}D("~T.mangleof~")"); 1053 } 1054 1055 void readArray(T)(scope void delegate(size_t) size_callback, scope void delegate() entry_callback) 1056 { 1057 skip("A("~T.mangleof~")["); 1058 auto bidx = result.indexOf("]["); 1059 assert(bidx > 0); 1060 auto cnt = result[0 .. bidx].to!size_t; 1061 result = result[bidx+2 .. $]; 1062 1063 size_t i = 0; 1064 while (result.startsWith("AE(")) { 1065 result = result[3 .. $]; 1066 auto idx = result.indexOf(','); 1067 auto idx2 = result.indexOf(")("); 1068 assert(idx > 0 && idx2 > idx); 1069 auto t = result[0 .. idx]; 1070 auto n = result[idx+1 .. idx2]; 1071 result = result[idx2+2 .. $]; 1072 assert(n == i.to!string); 1073 entry_callback(); 1074 skip(")AE("~t~","~n~")"); 1075 i++; 1076 } 1077 skip("]A("~T.mangleof~")"); 1078 1079 assert(i == cnt); 1080 } 1081 1082 T readValue(T)() 1083 { 1084 skip("V("~T.mangleof~")("); 1085 auto idx = result.indexOf(')'); 1086 assert(idx >= 0); 1087 auto ret = result[0 .. idx].to!T; 1088 result = result[idx+1 .. $]; 1089 return ret; 1090 } 1091 1092 void skip(string prefix) 1093 { 1094 assert(result.startsWith(prefix), result); 1095 result = result[prefix.length .. $]; 1096 } 1097 1098 bool tryReadNull() 1099 { 1100 if (result.startsWith("null")) { 1101 result = result[4 .. $]; 1102 return true; 1103 } else return false; 1104 } 1105 } 1106 } 1107 1108 unittest { // basic serialization behavior 1109 import std.typecons : Nullable; 1110 1111 static void test(T)(T value, string expected) { 1112 assert(serialize!TestSerializer(value) == expected, serialize!TestSerializer(value)); 1113 static if (isPointer!T) { 1114 if (value) assert(*deserialize!(TestSerializer, T)(expected) == *value); 1115 else assert(deserialize!(TestSerializer, T)(expected) is null); 1116 } else static if (is(T == Nullable!U, U)) { 1117 if (value.isNull()) assert(deserialize!(TestSerializer, T)(expected).isNull); 1118 else assert(deserialize!(TestSerializer, T)(expected) == value); 1119 } else assert(deserialize!(TestSerializer, T)(expected) == value); 1120 } 1121 1122 test("hello", "V(Aya)(hello)"); 1123 test(12, "V(i)(12)"); 1124 test(12.0, "V(Aya)(12)"); 1125 test(12.0f, "V(f)(12)"); 1126 assert(serialize!TestSerializer(null) == "null"); 1127 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)"); 1128 string mangleOfAA = (string[string]).mangleof; 1129 test(["hello": "world"], "D(" ~ mangleOfAA ~ "){DE(Aya,hello)(V(Aya)(world))DE(Aya,hello)}D(" ~ mangleOfAA ~ ")"); 1130 test(cast(int*)null, "null"); 1131 int i = 42; 1132 test(&i, "V(i)(42)"); 1133 Nullable!int j; 1134 test(j, "null"); 1135 j = 42; 1136 test(j, "V(i)(42)"); 1137 } 1138 1139 unittest { // basic user defined types 1140 static struct S { string f; } 1141 enum Sm = S.mangleof; 1142 auto s = S("hello"); 1143 enum s_ser = "D("~Sm~"){DE(Aya,f)(V(Aya)(hello))DE(Aya,f)}D("~Sm~")"; 1144 assert(serialize!TestSerializer(s) == s_ser, serialize!TestSerializer(s)); 1145 assert(deserialize!(TestSerializer, S)(s_ser) == s); 1146 1147 static class C { string f; } 1148 enum Cm = C.mangleof; 1149 C c; 1150 assert(serialize!TestSerializer(c) == "null"); 1151 c = new C; 1152 c.f = "hello"; 1153 enum c_ser = "D("~Cm~"){DE(Aya,f)(V(Aya)(hello))DE(Aya,f)}D("~Cm~")"; 1154 assert(serialize!TestSerializer(c) == c_ser); 1155 assert(deserialize!(TestSerializer, C)(c_ser).f == c.f); 1156 1157 enum E { hello, world } 1158 assert(serialize!TestSerializer(E.hello) == "V(i)(0)"); 1159 assert(serialize!TestSerializer(E.world) == "V(i)(1)"); 1160 } 1161 1162 unittest { // tuple serialization 1163 import std.typecons : Tuple; 1164 1165 static struct S(T...) { T f; } 1166 enum Sm = S!(int, string).mangleof; 1167 enum Tum = Tuple!(int, string).mangleof; 1168 auto s = S!(int, string)(42, "hello"); 1169 assert(serialize!TestSerializer(s) == 1170 "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~")"); 1171 1172 static struct T { @asArray S!(int, string) g; } 1173 enum Tm = T.mangleof; 1174 auto t = T(s); 1175 assert(serialize!TestSerializer(t) == 1176 "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~")"); 1177 } 1178 1179 unittest { // testing the various UDAs 1180 enum E { hello, world } 1181 enum Em = E.mangleof; 1182 static struct S { 1183 @byName E e; 1184 @ignore int i; 1185 @optional float f; 1186 } 1187 enum Sm = S.mangleof; 1188 auto s = S(E.world, 42, 1.0f); 1189 assert(serialize!TestSerializer(s) == 1190 "D("~Sm~"){DE("~Em~",e)(V(Aya)(world))DE("~Em~",e)DE(f,f)(V(f)(1))DE(f,f)}D("~Sm~")"); 1191 } 1192 1193 unittest { // custom serialization support 1194 // iso-ext 1195 import std.datetime; 1196 auto t = TimeOfDay(6, 31, 23); 1197 assert(serialize!TestSerializer(t) == "V(Aya)(06:31:23)"); 1198 auto d = Date(1964, 1, 23); 1199 assert(serialize!TestSerializer(d) == "V(Aya)(1964-01-23)"); 1200 auto dt = DateTime(d, t); 1201 assert(serialize!TestSerializer(dt) == "V(Aya)(1964-01-23T06:31:23)"); 1202 auto st = SysTime(dt, UTC()); 1203 assert(serialize!TestSerializer(st) == "V(Aya)(1964-01-23T06:31:23Z)"); 1204 1205 // string 1206 struct S1 { int i; string toString() const { return "hello"; } static S1 fromString(string) { return S1.init; } } 1207 struct S2 { int i; string toString() const { return "hello"; } } 1208 enum S2m = S2.mangleof; 1209 struct S3 { int i; static S3 fromString(string) { return S3.init; } } 1210 enum S3m = S3.mangleof; 1211 assert(serialize!TestSerializer(S1.init) == "V(Aya)(hello)"); 1212 assert(serialize!TestSerializer(S2.init) == "D("~S2m~"){DE(i,i)(V(i)(0))DE(i,i)}D("~S2m~")"); 1213 assert(serialize!TestSerializer(S3.init) == "D("~S3m~"){DE(i,i)(V(i)(0))DE(i,i)}D("~S3m~")"); 1214 1215 // custom 1216 struct C1 { int i; float toRepresentation() const { return 1.0f; } static C1 fromRepresentation(float f) { return C1.init; } } 1217 struct C2 { int i; float toRepresentation() const { return 1.0f; } } 1218 enum C2m = C2.mangleof; 1219 struct C3 { int i; static C3 fromRepresentation(float f) { return C3.init; } } 1220 enum C3m = C3.mangleof; 1221 assert(serialize!TestSerializer(C1.init) == "V(f)(1)"); 1222 assert(serialize!TestSerializer(C2.init) == "D("~C2m~"){DE(i,i)(V(i)(0))DE(i,i)}D("~C2m~")"); 1223 assert(serialize!TestSerializer(C3.init) == "D("~C3m~"){DE(i,i)(V(i)(0))DE(i,i)}D("~C3m~")"); 1224 } 1225 1226 unittest // Testing corner case: member function returning by ref 1227 { 1228 import dub.internal.vibecompat.data.json; 1229 1230 static struct S 1231 { 1232 int i; 1233 ref int foo() { return i; } 1234 } 1235 1236 static assert(__traits(compiles, { S().serializeToJson(); })); 1237 static assert(__traits(compiles, { Json().deserializeJson!S(); })); 1238 1239 auto s = S(1); 1240 assert(s.serializeToJson().deserializeJson!S() == s); 1241 } 1242 1243 unittest // Testing corner case: Variadic template constructors and methods 1244 { 1245 import dub.internal.vibecompat.data.json; 1246 1247 static struct S 1248 { 1249 int i; 1250 this(Args...)(Args args) {} 1251 int foo(Args...)(Args args) { return i; } 1252 ref int bar(Args...)(Args args) { return i; } 1253 } 1254 1255 static assert(__traits(compiles, { S().serializeToJson(); })); 1256 static assert(__traits(compiles, { Json().deserializeJson!S(); })); 1257 1258 auto s = S(1); 1259 assert(s.serializeToJson().deserializeJson!S() == s); 1260 } 1261 1262 unittest // Make sure serializing through properties still works 1263 { 1264 import dub.internal.vibecompat.data.json; 1265 1266 static struct S 1267 { 1268 public int i; 1269 private int privateJ; 1270 1271 @property int j() { return privateJ; } 1272 @property void j(int j) { privateJ = j; } 1273 } 1274 1275 auto s = S(1, 2); 1276 assert(s.serializeToJson().deserializeJson!S() == s); 1277 } 1278 1279 unittest { // test BitFlags serialization 1280 import std.typecons : BitFlags; 1281 1282 enum Flag { 1283 a = 1<<0, 1284 b = 1<<1, 1285 c = 1<<2 1286 } 1287 enum Flagm = Flag.mangleof; 1288 1289 alias Flags = BitFlags!Flag; 1290 enum Flagsm = Flags.mangleof; 1291 1292 enum Fi_ser = "A(A"~Flagm~")[0][]A(A"~Flagm~")"; 1293 assert(serialize!TestSerializer(Flags.init) == Fi_ser); 1294 1295 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~")"; 1296 assert(serialize!TestSerializer(Flags(Flag.a, Flag.c)) == Fac_ser); 1297 1298 struct S { @byName Flags f; } 1299 enum Sm = S.mangleof; 1300 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~")"; 1301 1302 assert(serialize!TestSerializer(S(Flags(Flag.a, Flag.c))) == Sac_ser); 1303 1304 assert(deserialize!(TestSerializer, Flags)(Fi_ser) == Flags.init); 1305 assert(deserialize!(TestSerializer, Flags)(Fac_ser) == Flags(Flag.a, Flag.c)); 1306 assert(deserialize!(TestSerializer, S)(Sac_ser) == S(Flags(Flag.a, Flag.c))); 1307 }