1 /+ 2 == serialization.json == 3 Copyright Alexey Drozhzhin aka Grim Maple 2024 4 Distributed under the Boost Software License, Version 1.0. 5 +/ 6 /++ 7 This is a JSON serializer for OpenD programming language. 8 9 $(PITFALL 10 JSON Serializer relies on `new` to create arrays and objects, so it's incompatible 11 with `@nogc` and `betterC` code. Limited compatibility might be provided if no 12 arrays / objects are used in serializable object 13 ) 14 15 ## Usage examples 16 To use this serializer, annotate any field you want serialized with [serializable]. 17 JSON serializator will attempt to automatically convert primitives to corresponding 18 JSON tupes, such as any of the number types to JSON Number, string to JSON String, 19 bool to JSON true/false. 20 21 --- 22 struct Foo 23 { 24 @serializable int bar; 25 @serializable string baz; 26 } 27 28 string test = serializeToJSONString(Foo()); 29 --- 30 31 By default, serializer will skip serializing fields that are `null`. 32 If you want to ensure that a ceratin field exists in the JSON object, use [jsonRequired] 33 attribute: 34 --- 35 struct Foo 36 { 37 @serializable object bar; 38 @serializable @jsonRequired object baz; 39 } 40 41 assert(serializeToJSONString(Foo()) == "{\"baz\": null}") 42 --- 43 44 Marking a field with [jsonRequired] will also result in an error if a required field was 45 missing when deserializing: 46 47 --- 48 struct Foo 49 { 50 @serializeable @jsonRequired object bar; 51 } 52 53 deserializeJSONFromString!Foo("{}"); // Error 54 --- 55 +/ 56 module odc.serialization.json; 57 58 import std.traits; 59 import std.conv : to; 60 61 import std.exception : assertThrown, assertNotThrown; 62 import std.json; 63 64 import d.serialization; 65 66 /** 67 * A UDA to mark a JSON field as required for deserialization 68 * 69 * When applied to a field, deserialization will throw if field is not found in json, 70 * and serialization will produce `null` for `null` fields 71 */ 72 struct jsonRequired { } 73 74 /** 75 * Serializes an object to a $(LREF JSONValue). To make this work, use $(LREF serializable) UDA on 76 * any fields that you want to be serializable. Automatically maps marked fields to 77 * corresponding JSON types. Any field not marked with $(LREF serializable) is not serialized. 78 */ 79 JSONValue serializeJSON(T)(auto ref T obj) @safe 80 { 81 static if(isPointer!T) 82 return serializeJSON!(PointerTarget!T)(*obj); 83 else 84 { 85 JSONValue ret; 86 foreach(alias prop; readableSerializables!T) 87 { 88 enum name = getSerializableName!prop; 89 auto value = __traits(child, obj, prop); 90 static if(isArray!(typeof(prop))) 91 { 92 if(value.length > 0) 93 ret[name] = serializeAutoObj(value); 94 else if(isJSONRequired!prop) 95 ret[name] = JSONValue(null); 96 } 97 else ret[name] = serializeAutoObj(value); 98 } 99 return ret; 100 } 101 } 102 /// 103 @safe unittest 104 { 105 struct Test 106 { 107 @serializable int test = 43; 108 @serializable string other = "Hello, world"; 109 110 @serializable int foo() { return inaccessible; } 111 @serializable void foo(int val) { inaccessible = val; } 112 private: 113 int inaccessible = 32; 114 } 115 116 auto val = serializeJSON(Test()); 117 assert(val["test"].get!int == 43); 118 assert(val["other"].get!string == "Hello, world"); 119 assert(val["foo"].get!int == 32); 120 } 121 122 /** 123 * Serialize `T` into a JSON string 124 */ 125 string serializeToJSONString(T)(auto ref T obj, in bool pretty = false) @safe 126 { 127 auto val = serializeJSON(obj); 128 return toJSON(val, pretty); 129 } 130 /// 131 @safe unittest 132 { 133 struct Test 134 { 135 @serializable int test = 43; 136 @serializable string other = "Hello, world"; 137 138 @serializable int foo() { return inaccessible; } 139 @serializable void foo(int val) { inaccessible = val; } 140 private: 141 int inaccessible = 32; 142 } 143 144 assert(serializeToJSONString(Test()) == `{"foo":32,"other":"Hello, world","test":43}`); 145 } 146 /** 147 * Deserializes a $(LREF JSONValue) to `T` 148 * 149 * Throws: $(LREF SerializationException) if fails to create an instance of any class 150 * $(LREF SerializationException) if a $(LREF jsonRequired) $(LREF serializable) is missing 151 */ 152 T deserializeJSON(T)(auto ref JSONValue root) @safe 153 { 154 import std.stdio : writeln; 155 static if(is(T == class) || isPointer!T) 156 { 157 if(root.isNull) 158 return null; 159 } 160 T ret; 161 static if(is(T == class)) 162 { 163 ret = new T(); 164 if(ret is null) 165 throw new SerializationException("Could not create an instance of " ~ fullyQualifiedName!T); 166 } 167 foreach(alias prop; writeableSerializables!T) 168 { 169 enum name = getSerializableName!prop; 170 static if(isJSONRequired!prop) 171 { 172 if((name in root) is null && isJSONRequired!prop) 173 throw new SerializationException("Missing required field \"" ~ name ~ "\" in JSON!"); 174 } 175 if(name in root) 176 { 177 static if(isFunction!prop) 178 __traits(child, ret, prop) = deserializeAutoObj!(Parameters!prop[0])(root[name]); 179 else 180 __traits(child, ret, prop) = deserializeAutoObj!(typeof(prop))(root[name]); 181 } 182 } 183 return ret; 184 } 185 /// 186 @safe unittest 187 { 188 immutable json = `{"a": 123, "b": "Hello"}`; 189 190 struct Test 191 { 192 @serializable int a; 193 @serializable string b; 194 } 195 196 immutable test = deserializeJSON!Test(parseJSON(json)); 197 assert(test.a == 123 && test.b == "Hello"); 198 } 199 200 /** 201 * Deserialize a JSON string into `T` 202 */ 203 T deserializeJSONFromString(T)(string json) @safe 204 { 205 return deserializeJSON!T(parseJSON(json)); 206 } 207 /// 208 @safe unittest 209 { 210 immutable json = `{"a": 123, "b": "Hello"}`; 211 212 struct Test 213 { 214 @serializable int a; 215 @serializable string b; 216 } 217 218 immutable test = deserializeJSONFromString!Test(json); 219 assert(test.a == 123 && test.b == "Hello"); 220 } 221 222 @safe unittest 223 { 224 immutable json = `{"a": 123}`; 225 struct A { @serializable("b") @jsonRequired int b; } 226 struct B { @serializable int a; } 227 228 auto res = parseJSON(json); 229 assertThrown(deserializeJSON!A(res)); 230 assertNotThrown(deserializeJSON!B(res)); 231 } 232 233 private @safe 234 { 235 JSONValue serializeAutoObj(T)(auto ref T obj) @trusted 236 { 237 static if(isJSONNumber!T || isJSONString!T || is(T == bool)) 238 return JSONValue(obj); 239 else static if(is(T == struct)) 240 return serializeJSON(obj); 241 else static if(is(T == class)) 242 return serializeJSON(obj); 243 else static if(isPointer!T && is(PointerTarget!T == struct)) 244 return obj is null ? JSONValue(null) : serializeJSON(obj); 245 else static if(isArray!T) 246 return serializeJSONArray(obj); 247 else static assert(false, "Cannot serialize type " ~ T.stringof); 248 249 } 250 251 JSONValue serializeJSONArray(T)(auto ref T obj) @trusted 252 { 253 JSONValue v = JSONValue(new JSONValue[0]); 254 foreach(i; obj) 255 v.array ~= serializeAutoObj(i); 256 return v; 257 } 258 259 T deserializeAutoObj(T)(auto ref JSONValue value) @trusted 260 { 261 static if(is(T == struct)) 262 return deserializeJSON!T(value); 263 else static if(isPointer!T && is(PointerTarget!T == struct)) 264 { 265 if(value.isNull) 266 return null; 267 alias underlying = PointerTarget!T; 268 underlying* ret = new underlying; 269 *ret = deserializeAutoObj!underlying(value); 270 return ret; 271 } 272 else static if(is(T == class)) 273 { 274 return deserializeJSON!T(value); 275 } 276 else static if(isJSONString!T) 277 return value.get!T; 278 else static if(isArray!T) 279 return deserializeJSONArray!T(value); 280 else return value.get!T; 281 } 282 283 T deserializeJSONArray(T)(auto ref JSONValue value) @trusted 284 { 285 T ret; 286 static if(!__traits(isStaticArray, T)) 287 ret = new T(value.arrayNoRef.length); 288 foreach(i, val; value.arrayNoRef) 289 ret[i] = deserializeAutoObj!(typeof(ret[0]))(val); 290 return ret; 291 } 292 293 template isJSONRequired(alias T) 294 { 295 enum bool isJSONRequired = getUDAs!(T, jsonRequired).length > 0; 296 } 297 298 template isJSONNumber(T) 299 { 300 enum bool isJSONNumber = __traits(isScalar, T) && !isPointer!T && !is(T == bool); 301 } 302 /// 303 unittest 304 { 305 assert(isJSONNumber!int); 306 assert(isJSONNumber!float); 307 assert(!isJSONNumber!bool); 308 assert(!isJSONNumber!string); 309 } 310 311 template isJSONString(T) 312 { 313 enum bool isJSONString = is(T == string) || is(T == wstring) || is(T == dstring); 314 } 315 /// 316 @safe unittest 317 { 318 assert(isJSONString!string && isJSONString!wstring && isJSONString!dstring); 319 } 320 } 321 // For UT purposes. Declaring those in a unittest causes frame pointer errors 322 version(unittest) 323 { 324 private struct TestStruct 325 { 326 @serializable int a; 327 @serializable string b; 328 329 @serializable void foo(int val) @safe { inaccessible = val; } 330 @serializable int foo() @safe const { return inaccessible; } 331 private: 332 int inaccessible; 333 } 334 335 private class Test 336 { 337 @serializable int a; 338 @serializable string b; 339 } 340 } 341 342 // Test case for deserialization with getters 343 @safe unittest 344 { 345 string json = `{"a": 123, "b": "Hello", "foo": 345}`; 346 auto t = deserializeJSON!TestStruct(parseJSON(json)); 347 assert(t.a == 123 && t.b == "Hello" && t.foo == 345); 348 } 349 350 // Test case for deserializing classes 351 @safe unittest 352 { 353 string json = `{"a": 123, "b": "Hello"}`; 354 auto t = deserializeJSON!Test(parseJSON(json)); 355 assert(t.a == 123 && t.b == "Hello"); 356 } 357 358 // Global unittest for everything 359 unittest 360 { 361 struct Other 362 { 363 @serializable 364 string name; 365 366 @serializable 367 int id; 368 } 369 370 static class TTT 371 { 372 @serializable string o = "o"; 373 } 374 375 struct Foo 376 { 377 // Works with or without brackets 378 @serializable int a = 123; 379 @serializable() double floating = 123; 380 @serializable int[3] arr = [1, 2, 3]; 381 @serializable string name = "Hello"; 382 @serializable("flag") bool check = true; 383 @serializable() Other object; 384 @serializable Other[3] arrayOfObjects; 385 @serializable Other* nullable = null; 386 @serializable Other* structField = new Other("t", 1); 387 @serializable Test classField = new Test(); 388 } 389 390 auto orig = Foo(); 391 auto val = serializeJSON(Foo()); 392 string res = toJSON(val); 393 auto back = deserializeJSON!Foo(parseJSON(res)); 394 assert(back.a == orig.a); 395 assert(back.floating == orig.floating); 396 assert(back.structField.id == orig.structField.id); 397 } 398 399 // Special tests to check compile-time messages 400 unittest 401 { 402 struct TooMany 403 { 404 @serializable @serializable int a; 405 } 406 407 struct NotSetter 408 { 409 @serializable void b(int a, int b); 410 } 411 412 TooMany a; 413 NotSetter b; 414 415 assert(!__traits(compiles, serializeJSON(a))); // Error: Only 1 UDA is allowed per property 416 assert(!__traits(compiles, serializeJSON(b))); // Error: not a getter or a setter 417 } 418 419 // Test for using return value 420 @safe unittest 421 { 422 struct A 423 { 424 @serializable int a; 425 } 426 427 A a = deserializeJSON!A(parseJSON("{\"a\": 123}")); 428 } 429 430 // Test for const and immutable objects 431 @safe unittest 432 { 433 struct A 434 { 435 @serializable int a = 12; 436 } 437 438 static class B 439 { 440 @serializable int a = 12; 441 } 442 443 struct C 444 { 445 @serializable int a() const { return _a; } 446 private int _a = 12; 447 } 448 449 immutable aa = A(); 450 const ab = A(); 451 452 immutable ba = new B(); 453 immutable bb = new B(); 454 455 immutable ca = C(); 456 457 immutable expected = `{"a":12}`; 458 459 assert(serializeToJSONString(aa) == expected); 460 assert(serializeToJSONString(ab) == expected); 461 462 assert(serializeToJSONString(ba) == expected); 463 assert(serializeToJSONString(bb) == expected); 464 465 assert(serializeToJSONString(C()) == expected); 466 assert(serializeToJSONString(ca) == expected); 467 } 468 469 // Unittest for virtual getters 470 @safe unittest 471 { 472 static class A 473 { 474 @serializable int b() @safe { return 0; } 475 } 476 477 static class B : A 478 { 479 override int b() @safe { return 1; } 480 } 481 482 B b = new B(); 483 484 assert(serializeToJSONString(cast(A)b) == `{"b":1}`); 485 }