1 module served.lsp.jsonops; 2 3 import std.json : StdJSONValue = JSONValue; 4 5 public import mir.algebraic_alias.json : JsonValue = JsonAlgebraic, StringMap; 6 7 deprecated("use serializeJson") StdJSONValue toJSON(T)(T value) 8 { 9 import std.json : parseJSON; 10 11 return parseJSON(serializeJson(value)); 12 } 13 14 deprecated("use deserializeJson") T fromJSON(T)(StdJSONValue value) 15 { 16 return deserializeJson!T(value.toString); 17 } 18 19 /++ 20 JSON serialization function. 21 +/ 22 string serializeJson(T)(auto ref T value) 23 { 24 import mir.ser.json : serializeJson; 25 26 static if (is(T == RootJsonToken)) 27 return value.json; 28 else 29 return serializeJson!T(value); 30 } 31 32 T deserializeJson(T)(scope const(char)[] text) 33 { 34 import mir.deser.json : deserializeJson; 35 36 static if (is(T == RootJsonToken)) 37 return T(text.idup); 38 else 39 return deserializeJson!T(text); 40 } 41 42 /// Type that can be used to pass JSON as-is, without (de)serialization. 43 /// Only works at root level, e.g. can't be nested inside (de)serialized types. 44 struct RootJsonToken 45 { 46 string json; 47 48 void[1] unserializable; 49 } 50 51 unittest 52 { 53 string[] tests = [ 54 `{"hello":"world"}`, 55 `{"a":[1,"world",false,null]}`, 56 `null`, 57 `5`, 58 `"ok"`, 59 `["ok",[[[[[false]]]]]]`, 60 `true`, 61 ]; 62 63 foreach (v; tests) 64 { 65 assert(v.deserializeJson!JsonValue.serializeJson == v); 66 } 67 } 68 69 T jsonValueTo(T)(scope JsonValue value) 70 { 71 return value.serializeJson.deserializeJson!T; 72 } 73 74 JsonValue toJsonValue(StdJSONValue value) 75 { 76 import std.json : JSONType; 77 78 final switch (value.type) 79 { 80 case JSONType.object: 81 StringMap!JsonValue ret; 82 foreach (key, val; value.object) 83 ret[key] = val.toJsonValue; 84 return JsonValue(ret); 85 case JSONType.array: 86 JsonValue[] ret = new JsonValue[value.array.length]; 87 foreach (i, val; value.array) 88 ret[i] = val.toJsonValue; 89 return JsonValue(ret); 90 case JSONType.true_: 91 return JsonValue(true); 92 case JSONType.false_: 93 return JsonValue(false); 94 case JSONType.null_: 95 return JsonValue(null); 96 case JSONType..string: 97 return JsonValue(value.str); 98 case JSONType.float_: 99 return JsonValue(value.floating); 100 case JSONType.integer: 101 return JsonValue(value.integer); 102 case JSONType.uinteger: 103 return JsonValue(value.uinteger); 104 } 105 } 106 107 StdJSONValue toStdJSONValue(JsonValue value) 108 { 109 final switch (value.kind) 110 { 111 case JsonValue.Kind.object: 112 auto obj = value.get!(StringMap!JsonValue); 113 StdJSONValue[string] ret; 114 foreach (kv; obj.byKeyValue) 115 ret[kv.key] = kv.value.toStdJSONValue; 116 return StdJSONValue(ret); 117 case JsonValue.Kind.array: 118 auto arr = value.get!(JsonValue[]); 119 StdJSONValue[] ret = new StdJSONValue[arr.length]; 120 foreach (i, val; arr) 121 ret[i] = val.toStdJSONValue; 122 return StdJSONValue(ret); 123 case JsonValue.Kind.boolean: 124 return StdJSONValue(value.get!bool); 125 case JsonValue.Kind.null_: 126 return StdJSONValue(null); 127 case JsonValue.Kind..string: 128 return StdJSONValue(value.get!string); 129 case JsonValue.Kind.float_: 130 return StdJSONValue(value.get!double); 131 case JsonValue.Kind.integer: 132 return StdJSONValue(value.get!long); 133 } 134 } 135 136 JsonValue toJsonValue(T)(auto ref T value) 137 { 138 return value.serializeJson.deserializeJson!JsonValue; 139 } 140 141 private inout(char)[] skipString(ref scope return inout(char)[] jsonObject) 142 in(jsonObject.length) 143 in(jsonObject.ptr[0] == '"') 144 { 145 auto start = jsonObject.ptr; 146 jsonObject = jsonObject.ptr[1 .. jsonObject.length]; 147 int escape; 148 while (jsonObject.length) 149 { 150 char c = jsonObject.ptr[0]; 151 if (escape) 152 { 153 if (c == 'u') 154 escape += 4; 155 escape--; 156 } 157 else 158 { 159 if (c == '"') 160 break; 161 else if (c == '\\') 162 escape = 1; 163 } 164 jsonObject = jsonObject.ptr[1 .. jsonObject.length]; 165 } 166 if (!jsonObject.length || jsonObject.ptr[0] != '"') 167 throw new Exception("malformed JSON string"); 168 jsonObject = jsonObject.ptr[1 .. jsonObject.length]; 169 return start[0 .. jsonObject.ptr - start]; 170 } 171 172 private inout(char)[] skipNumber(ref scope return inout(char)[] jsonObject) 173 in(jsonObject.length) 174 { 175 import std.ascii : isDigit; 176 177 auto start = jsonObject.ptr; 178 if (jsonObject.ptr[0] == '-') 179 { 180 jsonObject = jsonObject.ptr[1 .. jsonObject.length]; 181 if (!jsonObject.length) 182 throw new Exception("malformed JSON number"); 183 } 184 185 while (jsonObject.length && jsonObject.ptr[0].isDigit) 186 jsonObject = jsonObject.ptr[1 .. jsonObject.length]; 187 188 if (jsonObject.length && jsonObject.ptr[0] == '.') 189 { 190 jsonObject = jsonObject.ptr[1 .. jsonObject.length]; 191 if (!jsonObject.length) 192 throw new Exception("malformed JSON number"); 193 194 while (jsonObject.length && jsonObject.ptr[0].isDigit) 195 jsonObject = jsonObject.ptr[1 .. jsonObject.length]; 196 } 197 198 if (jsonObject.length && (jsonObject.ptr[0] == 'e' || jsonObject.ptr[0] == 'E')) 199 { 200 jsonObject = jsonObject.ptr[1 .. jsonObject.length]; 201 if (!jsonObject.length) 202 throw new Exception("malformed JSON number"); 203 204 if (jsonObject.ptr[0] == '-' || jsonObject.ptr[0] == '+') 205 { 206 jsonObject = jsonObject.ptr[1 .. jsonObject.length]; 207 if (!jsonObject.length) 208 throw new Exception("malformed JSON number"); 209 } 210 211 while (jsonObject.length && jsonObject.ptr[0].isDigit) 212 jsonObject = jsonObject.ptr[1 .. jsonObject.length]; 213 } 214 215 return start[0 .. jsonObject.ptr - start]; 216 } 217 218 private inout(char)[] skipLiteral(ref scope return inout(char)[] jsonObject, string literal) 219 { 220 if (jsonObject.length < literal.length 221 || jsonObject.ptr[0 .. literal.length] != literal) 222 throw new Exception("expected literal '" ~ literal 223 ~ "', but got '" ~ jsonObject.idup ~ "'"); 224 225 auto ret = jsonObject.ptr[0 .. literal.length]; 226 jsonObject = jsonObject.ptr[literal.length .. jsonObject.length]; 227 return ret; 228 } 229 230 // skips until matching level of start/end tokens - skips strings 231 private inout(char)[] skipByPairs(char start, char end, string name)(ref scope return inout(char)[] jsonObject) 232 in(jsonObject.length) 233 in(jsonObject.ptr[0] == start) 234 { 235 auto startPtr = jsonObject.ptr; 236 int depth = 0; 237 Loop: do 238 { 239 switch (jsonObject.ptr[0]) 240 { 241 case start: 242 depth++; 243 break; 244 case end: 245 depth--; 246 break; 247 case '"': 248 jsonObject.skipString(); 249 continue Loop; 250 default: 251 break; 252 } 253 jsonObject = jsonObject.ptr[1 .. jsonObject.length]; 254 } while (depth && jsonObject.length); 255 256 if (depth != 0) 257 throw new Exception("malformed JSON " ~ name); 258 return startPtr[0 .. jsonObject.ptr - startPtr]; 259 } 260 261 private inout(char)[] skipObject(ref scope return inout(char)[] jsonObject) 262 { 263 return jsonObject.skipByPairs!('{', '}', "object"); 264 } 265 266 private inout(char)[] skipArray(ref scope return inout(char)[] jsonObject) 267 { 268 return jsonObject.skipByPairs!('[', ']', "array"); 269 } 270 271 private inout(char)[] skipValue(ref scope return inout(char)[] jsonObject) 272 { 273 if (!jsonObject.length) 274 return null; 275 switch (jsonObject.ptr[0]) 276 { 277 case '"': // string 278 return jsonObject.skipString(); 279 case '-': // number 280 case '0': .. case '9': // number 281 return jsonObject.skipNumber(); 282 case 't': // true 283 return jsonObject.skipLiteral("true"); 284 case 'f': // false 285 return jsonObject.skipLiteral("false"); 286 case 'n': // null 287 return jsonObject.skipLiteral("null"); 288 case '{': // object 289 return jsonObject.skipObject(); 290 case '[': // array 291 return jsonObject.skipArray(); 292 default: 293 return null; 294 } 295 } 296 297 private void skipWhite(ref scope return inout(char)[] jsonObject) 298 { 299 import std.string : stripLeft; 300 301 jsonObject = jsonObject.stripLeft; 302 } 303 304 /// Split the json object into keys, store the slices from the input string 305 /// into a newly created struct which contains all the keys from the `fields` 306 /// parameter. 307 /// 308 /// If a paremeter ends with _, the last _ will be removed in the string check, 309 /// so that D reserved keywords can be used. 310 template parseKeySlices(fields...) 311 { 312 auto parseKeySlices(T)(scope return T[] jsonObject) 313 if (is(immutable T == immutable char)) 314 in (jsonObject.looksLikeJsonObject) 315 { 316 import std.string : representation; 317 import std.algorithm : canFind; 318 319 mixin(`struct Ret { 320 union { 321 T[][fields.length] _arr; 322 struct { 323 `, (){ 324 string fieldsStr; 325 foreach (string field; fields) 326 fieldsStr ~= "T[] " ~ field ~ ";\n"; 327 return fieldsStr; 328 }(), ` 329 } 330 } 331 }`); 332 333 Ret ret; 334 335 jsonObject = jsonObject[1 .. $ - 1]; 336 jsonObject.skipWhite(); 337 while (jsonObject.length) 338 { 339 auto key = jsonObject.skipValue(); 340 if (key.length < 2 || key.ptr[0] != '"' || key.ptr[key.length - 1] != '"') 341 throw new Exception("malformed JSON object key"); 342 jsonObject.skipWhite(); 343 if (!jsonObject.length || jsonObject.ptr[0] != ':') 344 throw new Exception("malformed JSON"); 345 jsonObject = jsonObject.ptr[1 .. jsonObject.length]; 346 jsonObject.skipWhite(); 347 auto value = jsonObject.skipValue(); 348 jsonObject.skipWhite(); 349 350 if (jsonObject.length) 351 { 352 if (jsonObject.ptr[0] != ',') 353 throw new Exception("malformed JSON"); 354 jsonObject = jsonObject.ptr[1 .. jsonObject.length]; 355 jsonObject.skipWhite(); 356 } 357 358 RawKeySwitch: switch (key) 359 { 360 static foreach (string field; fields) 361 { 362 case '"' ~ (field[$ - 1] == '_' ? field[0 .. $ - 1] : field) ~ '"': 363 mixin("ret.", field, " = value;"); 364 break RawKeySwitch; 365 } 366 default: 367 if (key.representation.canFind('\\')) 368 { 369 // wtf escaped key 370 DeserializedSwitch: switch (key.deserializeJson!string) 371 { 372 static foreach (string field; fields) 373 { 374 case (field[$ - 1] == '_' ? field[0 .. $ - 1] : field): 375 mixin("ret.", field, " = value;"); 376 break DeserializedSwitch; 377 } 378 default: 379 break; // not part of wanted keys 380 } 381 } 382 break; // not part of wanted keys 383 } 384 } 385 386 return ret; 387 } 388 } 389 390 /// 391 unittest 392 { 393 string json = `{ 394 "hello": "ther\"e", 395 "foo": {"ok":"cool"} , 396 "bar": [1, 2.0,3], 397 "extra": { 398 "f": [ 399 1 , { 400 "a": "b", 401 "c": false 402 }, { 403 "f": [1, { 404 "a": "b", 405 "c": false 406 }], 407 "a": 10000 408 } 409 ], 410 "a": 10000 411 }, 412 "yes": true, 413 "no": false, 414 "opt": null 415 }`; 416 auto parts = json.parseKeySlices!("hello", "foo", "bar", "extra", "yes", "opt", "notinjson"); 417 assert(!parts.notinjson.length); 418 assert(parts.hello is json[13 .. 22]); 419 assert(parts.foo is json[33 .. 46]); 420 assert(parts.bar is json[58 .. 68]); 421 assert(parts.extra is json[81 .. 246]); 422 assert(parts.yes is json[257 .. 261]); 423 assert(parts.opt is json[287 .. 291]); 424 } 425 426 void visitJsonArray(alias fn, T)(scope T[] jsonArray) 427 if (is(immutable T == immutable char)) 428 in (jsonArray.length) 429 in (jsonArray[0] == '[') 430 in (jsonArray[$ - 1] == ']') 431 { 432 jsonArray = jsonArray[1 .. $ - 1]; 433 jsonArray.skipWhite(); 434 while (jsonArray.length) 435 { 436 auto value = jsonArray.skipValue(); 437 fn(value); 438 jsonArray.skipWhite(); 439 if (jsonArray.length) 440 { 441 if (jsonArray.ptr[0] != ',') 442 throw new Exception("malformed JSON array"); 443 jsonArray = jsonArray.ptr[1 .. jsonArray.length]; 444 jsonArray.skipWhite(); 445 } 446 } 447 } 448 449 unittest 450 { 451 string json = `["the[r\"e", 452 {"ok":"cool"} , 453 [1, 2.0,3], 454 { 455 "f": [ 456 1 , { 457 "a": "b", 458 "c": false 459 }, { 460 "f": [1, { 461 "a": "b", 462 "c": false 463 }], 464 "a": 10000 465 } 466 ], 467 "a": 10000 468 }, 469 true, 470 false, 471 null 472 ]`; 473 string[] expected = [ 474 json[1 .. 11], 475 json[15 .. 28], 476 json[33 .. 43], 477 json[47 .. 212], 478 json[216 .. 220], 479 json[224 .. 229], 480 json[233 .. 237], 481 ]; 482 int i; 483 json.visitJsonArray!((item) { 484 assert(expected[i++] is item); 485 }); 486 assert(i == expected.length); 487 } 488 489 bool looksLikeJsonObject(scope const(char)[] jsonString) 490 { 491 return jsonString.length >= 2 492 && jsonString.ptr[0] == '{' 493 && jsonString.ptr[jsonString.length - 1] == '}'; 494 } 495 496 bool looksLikeJsonArray(scope const(char)[] jsonString) 497 { 498 return jsonString.length >= 2 499 && jsonString.ptr[0] == '[' 500 && jsonString.ptr[jsonString.length - 1] == ']'; 501 } 502 503 bool isValidJsonStringContent(scope const(char)[] jsonString) 504 { 505 foreach (char c; jsonString) 506 if (c == '"' || c == '\\' || c < 0x20) 507 return false; 508 return true; 509 } 510 511 bool isEmptyJsonObject(scope const(char)[] jsonString) 512 { 513 import std.ascii : isWhite; 514 515 static immutable string expected = "{}"; 516 int i = 0; 517 foreach (char c; jsonString) 518 { 519 if (c.isWhite) 520 continue; 521 522 if (i >= expected.length || c != expected[i]) 523 return false; 524 i++; 525 } 526 return i == expected.length; 527 } 528 529 unittest 530 { 531 assert(isEmptyJsonObject(`{}`)); 532 assert(isEmptyJsonObject(` { } `)); 533 assert(isEmptyJsonObject(` {} `)); 534 assert(isEmptyJsonObject(`{ }`)); 535 assert(!isEmptyJsonObject(``)); 536 assert(!isEmptyJsonObject(`{`)); 537 assert(!isEmptyJsonObject(`}`)); 538 assert(!isEmptyJsonObject(`}{`)); 539 assert(!isEmptyJsonObject(`{]`)); 540 } 541 542 const(inout(char))[] escapeJsonStringContent(scope return inout(char)[] str) 543 { 544 import std.ascii : hexDigits; 545 import std.array; 546 547 if (isValidJsonStringContent(str)) 548 return str; 549 550 auto ret = appender!string; 551 ret.reserve(str.length); 552 foreach (char c; str) 553 { 554 if (c == '"' || c == '\\') 555 { 556 ret.put('\\'); 557 ret.put(c); 558 } 559 else if (c < 0x20) 560 { 561 if (c < 0x10) 562 ret.put("\\u000"); 563 else 564 ret.put("\\u001"); 565 ret.put(hexDigits[c & 0xF]); 566 } 567 else 568 { 569 ret.put(c); 570 } 571 } 572 return ret.data; 573 } 574 575 unittest 576 { 577 string test = "hello"; 578 assert(test.escapeJsonStringContent is test); 579 test = `hel"lo`; 580 assert(test.escapeJsonStringContent == `hel\"lo`); 581 582 string[] expected = [ 583 `\u0000`, 584 `\u0001`, 585 `\u0002`, 586 `\u0003`, 587 `\u0004`, 588 `\u0005`, 589 `\u0006`, 590 `\u0007`, 591 `\u0008`, 592 `\u0009`, 593 `\u000A`, 594 `\u000B`, 595 `\u000C`, 596 `\u000D`, 597 `\u000E`, 598 `\u000F`, 599 `\u0010`, 600 `\u0011`, 601 `\u0012`, 602 `\u0013`, 603 `\u0014`, 604 `\u0015`, 605 `\u0016`, 606 `\u0017`, 607 `\u0018`, 608 `\u0019`, 609 `\u001A`, 610 `\u001B`, 611 `\u001C`, 612 `\u001D`, 613 `\u001E`, 614 `\u001F`, 615 ]; 616 foreach (i; 0 .. 0x1F) 617 { 618 char[] testStr = [cast(char)i]; 619 assert(testStr.escapeJsonStringContent == expected[i]); 620 } 621 }