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