├── .gitignore ├── README.md ├── djson.pas └── test ├── PjtTestJSON.dpr ├── PjtTestJSON.dproj ├── PjtTestJSON.res ├── Testdjson.pas └── Win32 └── Debug ├── dunit.ini ├── test1.json ├── test2.json ├── test3.json ├── test4.json ├── test5.json ├── test6.json ├── test7.json └── test8.json /.gitignore: -------------------------------------------------------------------------------- 1 | *.dcu 2 | *.~*~ 3 | *.local 4 | *.identcache 5 | __history 6 | *.drc 7 | *.map 8 | *.exe 9 | *.dll 10 | bin/* 11 | PjtTestJSON.stat 12 | bugreport.txt 13 | PjtTestJSON.mes 14 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # JSON parser for Delphi 2 | 3 | ## Why? 4 | Didn't like the other Delphi JSON parsers out there. 5 | They seemed too complicated for the simple task i had for JSON. 6 | 7 | So this is my go at it. 8 | 9 | This version is only tested on Delphi XE 3, Delphi XE 6 (Android) and Delphi 10 but should work for all Delphi versions that support generics and TStringHelper. 10 | 11 | ### What is missing 12 | - Exception if the JSON text is not valid. 13 | - Convert to string 14 | - Serialization and deserialization from/to objects. 15 | 16 | ## Examples 17 | 18 | Just include the djson.pas file in your uses list for this to work. 19 | 20 | ### Example 1 - User 21 | 22 | #### JSON 23 | ```json 24 | { 25 | "username": "thomas", 26 | "name": "Thomas", 27 | "photos": [ 28 | { 29 | "title": "Photo 1", 30 | "urls": { 31 | "small": "http://example.com/photo1_small.jpg", 32 | "large": "http://example.com/photo1_large.jpg" 33 | } 34 | }, 35 | { 36 | "title": "Photo 2", 37 | "urls": { 38 | "small": "http://example.com/photo2_small.jpg", 39 | "large": "http://example.com/photo2_large.jpg" 40 | } 41 | } 42 | ], 43 | "int_list": [ 44 | 1, 45 | 2, 46 | 3 47 | ] 48 | } 49 | ``` 50 | 51 | #### Delphi 52 | ```delphi 53 | var 54 | user: TdJSON; 55 | photo: TdJSON; 56 | i: TdJSON; 57 | begin 58 | user := TdJSON.parse({JSON_TEXT}); 59 | try 60 | writeln('Username: '+ user['username'].AsString); 61 | writeln('Name: '+ user['name'].AsString); 62 | // Photos 63 | for photo in user['photos'] do 64 | begin 65 | writeln('Title: ' + photo['title'].AsString); 66 | writeln('Small url: ' + photo['urls']['small'].AsString); 67 | writeln('Large url: ' + photo['urls']['large'].AsString); 68 | end; 69 | 70 | // Int list 71 | for i in user['int_list'] do 72 | begin 73 | writeln(i.AsInteger); 74 | end; 75 | finally 76 | user.free; 77 | end; 78 | end; 79 | ``` 80 | 81 | ### Example 2 - User list 82 | #### JSON 83 | ```json 84 | [ 85 | { 86 | "username": "thomas", 87 | "name": "Thomas" 88 | }, 89 | { 90 | "username": "kurt", 91 | "name": "Kurt" 92 | }, 93 | { 94 | "username": "bent", 95 | "name": null 96 | } 97 | ] 98 | ``` 99 | 100 | #### Delphi 101 | ```delphi 102 | var 103 | users: TdJSON; 104 | user: TdJSON; 105 | begin 106 | users := TdJSON.Parse({JSON_TEXT}); 107 | try 108 | for user in users do 109 | begin 110 | writeln(user['username'].AsString); 111 | if (not user['name'].IsNull) then 112 | writeln(user['name'].AsString); 113 | end; 114 | finally 115 | users.Free; 116 | end; 117 | end; 118 | ``` 119 | 120 | # LICENSE 121 | The MIT License (MIT) 122 | 123 | Copyright (c) 2018 Thomas Erlang 124 | 125 | Permission is hereby granted, free of charge, to any person obtaining a copy 126 | of this software and associated documentation files (the "Software"), to deal 127 | in the Software without restriction, including without limitation the rights 128 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 129 | copies of the Software, and to permit persons to whom the Software is 130 | furnished to do so, subject to the following conditions: 131 | 132 | The above copyright notice and this permission notice shall be included in 133 | all copies or substantial portions of the Software. 134 | 135 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 136 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 137 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 138 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 139 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 140 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 141 | THE SOFTWARE. 142 | -------------------------------------------------------------------------------- /djson.pas: -------------------------------------------------------------------------------- 1 | { 2 | The MIT License (MIT) 3 | 4 | Copyright (c) 2018 Thomas Erlang 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in 14 | all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | THE SOFTWARE. 23 | } 24 | // Version 0.3 25 | unit djson; 26 | 27 | interface 28 | 29 | uses System.SysUtils, System.Classes, System.Variants, generics.collections; 30 | 31 | type 32 | TdJSON = class; 33 | 34 | TdJSONItems = class(TDictionary) 35 | public 36 | destructor Destroy; override; 37 | end; 38 | 39 | TdJSONListItems = class(TList) 40 | public 41 | destructor Destroy; override; 42 | end; 43 | 44 | TdJSON = class(TObject) 45 | private 46 | FParent: TdJSON; 47 | FIsList: boolean; 48 | FIsKeyValue: boolean; 49 | FIsDict: boolean; 50 | FValue: Variant; 51 | FItems: TdJSONItems; 52 | FListItems: TdJSONListItems; 53 | function GetJSONByNameOrIndex(const AData: variant): TdJSON; 54 | function GetString: string; 55 | function GetInteger: integer; 56 | function GetBoolean: boolean; 57 | function GetInt64: int64; 58 | function GetDouble: double; 59 | function GetDateTime: TDateTime; 60 | function GetIsNull: boolean; 61 | protected 62 | function jsonString(FancyFormat: Boolean; Iteration: Integer; SpaceChar: String): String; 63 | public 64 | constructor Create(AParent: TdJSON = nil); 65 | destructor Destroy; override; 66 | function GetEnumerator: TList.TEnumerator; 67 | class function Parse(const AJSON: string): TdJSON; 68 | class function ParseFile(const APath: string): TdJSON; 69 | 70 | function AsJSONString(FancyFormat: Boolean = true; SpaceChar: String = #09): String; 71 | function AsXMLString(Name : string; Indent : integer): String; 72 | 73 | property Parent: TdJSON read FParent; 74 | property IsList: boolean read FIsList; 75 | property IsDict: boolean read FIsDict; 76 | property IsNull: boolean read GetIsNull; 77 | property Items: TdJSONItems read FItems; 78 | property ListItems: TdJSONListItems read FListItems; 79 | property Value: Variant read FValue; 80 | property AsString: string read GetString; 81 | property AsInteger: integer read GetInteger; 82 | property AsBoolean: boolean read GetBoolean; 83 | property AsInt64: int64 read GetInt64; 84 | property AsDouble: double read GetDouble; 85 | property AsDateTime: TDateTime read GetDateTime; 86 | property JSONByNameOrIndex[const AData: variant]: TdJSON read GetJSONByNameOrIndex; default; 87 | property _[const AData: variant]: TdJSON read GetJSONByNameOrIndex; 88 | end; 89 | 90 | EJSONUnknownFieldOrIndex = class(Exception); 91 | EJSONParseError = class(Exception); 92 | 93 | var 94 | DJSONFormatSettings: TFormatSettings; 95 | 96 | implementation 97 | 98 | uses 99 | XSBuiltIns 100 | {$IFDEF MSWINDOWS}, Windows{$ENDIF}; 101 | 102 | { TdJSON } 103 | 104 | function TdJSON.AsJSONString(FancyFormat: Boolean; SpaceChar: String): String; 105 | begin 106 | Result := jsonString(FancyFormat, 0, SpaceChar); 107 | end; 108 | 109 | function TdJSON.AsXMLString(Name : string; Indent: integer): String; 110 | var 111 | sub: TPair; 112 | CrLfStr : string; 113 | iIndentBy, i : integer; 114 | 115 | function StrToXML(Str: string): string; 116 | Var SB : TStringBuilder; 117 | begin 118 | 119 | try 120 | SB := TStringBuilder.Create; 121 | SB.Append(str); 122 | 123 | SB.Replace('&', '&'); 124 | SB.Replace('<', '<'); 125 | SB.Replace('>', '>'); 126 | SB.Replace('"', '"'); 127 | SB.Replace('''', '''); 128 | SB.Replace(#10, ' '); 129 | SB.Replace(#13, ' '); 130 | 131 | result := SB.ToString; 132 | finally 133 | FreeAndNil(SB); 134 | end; 135 | 136 | end; 137 | 138 | begin 139 | 140 | iIndentBy := 2; 141 | CrLfStr := #13#10; 142 | 143 | if FIsList then 144 | begin 145 | Result := StringOfChar(#32, Indent) + '<' + name + '>'; 146 | for i := 0 to FListItems.Count - 1 do 147 | Result := Result + CrLfStr + FListItems[i].AsXMLString('Node' + IntToStr(i), Indent + iIndentBy); 148 | Result := Result + CrLfStr + StringOfChar(#32, Indent) + ''; 149 | end 150 | else if FIsDict then 151 | begin 152 | Result := StringOfChar(#32, Indent) + '<' + name + '>'; 153 | for sub in Items do 154 | Result := Result + CrLfStr + sub.Value.AsXMLString(sub.Key, Indent + iIndentBy); 155 | Result := Result + CrLfStr + StringOfChar(#32, Indent) + ''; 156 | end 157 | else 158 | begin 159 | case VarType(FValue) of 160 | varNull: Result := 'null'; 161 | varInteger, varDouble: Result := VarToStr(FValue); 162 | varBoolean: Result := AnsiLowerCase(VarToStr(FValue)); 163 | varString, varUString: Result := VarToStr(FValue); 164 | else Result := 'ERROR'; 165 | end; 166 | if Result = '' then 167 | Result := StringOfChar(#32, Indent) + '<' + name + '/>' 168 | else 169 | Result := StringOfChar(#32, Indent) + '<' + name + '>' + StrToXML(Result) + ''; 170 | end 171 | 172 | end; 173 | 174 | constructor TdJSON.Create(AParent: TdJSON); 175 | begin 176 | FParent := AParent; 177 | FValue := Unassigned; 178 | end; 179 | 180 | destructor TdJSON.Destroy; 181 | begin 182 | if assigned(FListItems) then 183 | FListItems.free; 184 | if assigned(FItems) then 185 | FItems.Free; 186 | inherited; 187 | end; 188 | 189 | function TdJSON.GetBoolean: boolean; 190 | begin 191 | result := VarAsType(FValue, varBoolean); 192 | end; 193 | 194 | function TdJSON.GetDateTime: TDateTime; 195 | // TODO: Make a better date/time parser 196 | var 197 | d: string; 198 | begin 199 | d := VarToStr(FValue); 200 | if length(d) = 10 then // date 201 | result := StrToDate(d, DJSONFormatSettings) 202 | else if length(d) = 8 then // time 203 | result := StrToTime(d, DJSONFormatSettings) 204 | else 205 | with TXSDateTime.Create() do 206 | try 207 | XSToNative(d); 208 | Result := AsDateTime; 209 | finally 210 | Free(); 211 | end; 212 | end; 213 | 214 | function TdJSON.GetDouble: double; 215 | begin 216 | result := VarAsType(FValue, varDouble); 217 | end; 218 | 219 | function TdJSON.GetEnumerator: TList.TEnumerator; 220 | begin 221 | result := FListItems.GetEnumerator; 222 | end; 223 | 224 | function TdJSON.GetInt64: int64; 225 | begin 226 | result := VarAsType(FValue, varInt64); 227 | end; 228 | 229 | function TdJSON.GetInteger: integer; 230 | begin 231 | result := VarAsType(FValue, varInteger); 232 | end; 233 | 234 | function TdJSON.GetIsNull: boolean; 235 | begin 236 | result := Value = null; 237 | end; 238 | 239 | function TdJSON.GetJSONByNameOrIndex(const AData: variant): TdJSON; 240 | var 241 | i: integer; 242 | begin 243 | case VarType(AData) and VarTypeMask of 244 | varString, varUString, varWord, varLongWord: 245 | begin 246 | i := Pos('|', AData); 247 | if i = 0 then 248 | if not FItems.TryGetValue(AData, result) then 249 | raise EJSONUnknownFieldOrIndex.Create(format('Unknown field: %s', [AData])) 250 | else 251 | exit; 252 | 253 | if not FItems.TryGetValue(Copy(AData, 1, i - 1), result) then 254 | raise EJSONUnknownFieldOrIndex.Create(format('Unknown field: %s', [AData])) 255 | else 256 | begin 257 | Result := result.GetJSONByNameOrIndex(Copy(AData, i + 1, Length(aData) - i)); 258 | exit; 259 | end; 260 | end; 261 | varInteger, varInt64, varSmallint, varShortInt, varByte: 262 | begin 263 | if (FListItems.Count - 1) >= AData then 264 | begin 265 | result := FListItems.items[AData]; 266 | exit; 267 | end 268 | else 269 | raise EJSONUnknownFieldOrIndex.Create(format('Unknown index: %d', [AData])); 270 | end; 271 | end; 272 | raise EJSONUnknownFieldOrIndex.Create(format('Unknown field variant type: %s.', [VarTypeAsText(AData)])); 273 | end; 274 | 275 | function TdJSON.GetString: string; 276 | begin 277 | if VarIsType(FValue, varNull) then 278 | result := '' 279 | else 280 | result := VarToStr(FValue); 281 | end; 282 | 283 | function TdJSON.jsonString(FancyFormat: Boolean; Iteration: Integer; SpaceChar: String): String; 284 | 285 | function EscapeStr(AString: string): string; 286 | var 287 | i: Integer; 288 | begin 289 | Result := AString; 290 | for i := Length(AString) downto 1 do 291 | begin 292 | case AString[i] of 293 | '"', '\', '/', #08, #12, #10, #13, #09: 294 | begin 295 | Insert('\', Result, i); 296 | end; 297 | end; 298 | end; 299 | end; 300 | var 301 | sub: TPair; 302 | entry: TdJSON; 303 | tab, lb: string; 304 | i: Integer; 305 | begin 306 | if FancyFormat then 307 | begin 308 | for i := 0 to Iteration - 1 do 309 | tab := tab + SpaceChar; 310 | lb := #13#10; 311 | end 312 | else 313 | begin 314 | tab := ''; 315 | lb := ''; 316 | SpaceChar := ''; 317 | end; 318 | 319 | if FIsList then 320 | begin 321 | Result := '[' + lb; 322 | for entry in FListItems do 323 | begin 324 | Result := Result + tab + SpaceChar + entry.jsonString(FancyFormat, Iteration + 1, SpaceChar); 325 | Result := Result + ',' + lb; 326 | end; 327 | if Result[Length(Result) - length(lb)] = ',' then 328 | Delete(Result, Length(Result) - Length(lb), 1); 329 | Result := Result + tab + ']'; 330 | end 331 | else if FIsDict then 332 | begin 333 | Result := '{' + lb; 334 | for sub in Items do 335 | begin 336 | Result := Result + tab + SpaceChar + '"' + sub.Key + '"'; 337 | if FancyFormat then 338 | Result := Result + ': ' 339 | else 340 | Result := Result + ':'; 341 | Result := Result + sub.Value.jsonString(FancyFormat, Iteration + 1, SpaceChar); 342 | Result := Result + ',' + lb; 343 | end; 344 | if Result[Length(Result) - Length(lb)] = ',' then 345 | Delete(Result, Length(Result) - Length(lb), 1); 346 | Result := Result + tab + '}'; 347 | end 348 | else 349 | begin 350 | case VarType(FValue) of 351 | varNull: Result := 'null'; 352 | varInteger, varDouble: Result := VarToStr(FValue); 353 | varBoolean: Result := AnsiLowerCase(VarToStr(FValue)); 354 | varString, varUString: Result := '"' + EscapeStr(VarToStr(FValue)) + '"'; 355 | else Result := 'ERROR'; 356 | end; 357 | end 358 | end; 359 | 360 | class function TdJSON.Parse(const AJSON: string): TdJSON; 361 | var 362 | a, prevA: char; 363 | index, tag: integer; 364 | inString, escaped: boolean; 365 | temp: variant; 366 | obj: TdJSON; 367 | 368 | function getValue: variant; 369 | var 370 | prev, prevPrev: char; 371 | ubuf, i, skip: integer; 372 | s: string; 373 | resultS: string; 374 | FS: TFormatSettings; 375 | begin 376 | s := trim(AJSON.Substring(tag-1, index-tag)); 377 | result := unassigned; 378 | if s = '' then 379 | exit; 380 | 381 | if s.Chars[0] <> '"' then 382 | begin 383 | FS.ThousandSeparator := ','; 384 | FS.DecimalSeparator := '.'; 385 | if s = 'null' then 386 | exit(null) 387 | else if s = 'false' then 388 | exit(false) 389 | else if s = 'true' then 390 | exit(true); 391 | exit(StrToFloat(s, FS)); 392 | end; 393 | 394 | if s = '""' then 395 | exit(''); 396 | resultS := ''; 397 | prev := #0; 398 | prevPrev := #0; 399 | skip := 0; 400 | for i := 0 to s.Length - 1 do 401 | begin 402 | if skip > 0 then 403 | begin 404 | dec(skip); 405 | Continue; 406 | end; 407 | try 408 | if (prev = '\') and (prevPrev <> '\') then 409 | begin 410 | case s.chars[i] of 411 | '\', '/', '"': resultS := resultS + s.chars[i]; 412 | 'u': 413 | begin 414 | if not TryStrToInt('$' + s.Substring(i+1, 4), ubuf) then 415 | raise EJSONParseError.Create(format('Invalid unicode \u%s', [s.Substring(i+1, 4)])); 416 | resultS := resultS + WideChar(ubuf); 417 | skip := 4; 418 | Continue; 419 | end; 420 | 'b': resultS := resultS + #8; 421 | 'n': resultS := resultS + #10; 422 | 'r': resultS := resultS + #13; 423 | 't': resultS := resultS + #9; 424 | 'f': resultS := resultS + #12; 425 | end; 426 | end 427 | else 428 | case s.chars[i] of 429 | '\', '"': continue; 430 | else 431 | resultS := resultS + s.chars[i]; 432 | end; 433 | finally 434 | if (prev = '\') and (prevPrev = '\') then 435 | prevPrev := #0 436 | else 437 | prevPrev := prev; 438 | prev := s.chars[i]; 439 | end; 440 | end; 441 | if resultS <> '' then 442 | result := resultS; 443 | end; 444 | 445 | procedure SetValue; 446 | begin 447 | obj.FValue := getValue; 448 | end; 449 | 450 | procedure AddSingleValue; 451 | begin 452 | temp := getValue(); 453 | if not VarIsEmpty(temp) then 454 | begin 455 | obj := TdJSON.Create(obj); 456 | obj.FValue := temp; 457 | obj.parent.ListItems.Add(obj); 458 | obj := obj.Parent; 459 | end; 460 | end; 461 | 462 | begin 463 | result := nil; 464 | index := 0; 465 | tag := 0; 466 | prevA := ' '; 467 | inString := false; 468 | escaped := false; 469 | obj := nil; 470 | for a in AJSON do 471 | begin 472 | inc(index); 473 | if tag = 0 then 474 | tag := index; 475 | escaped := (prevA = '\') and (not escaped); 476 | if (a = '"') and (not escaped) then 477 | inString := not inString; 478 | prevA := a; 479 | if inString or (CharInSet(a, [#9, #10, #13, ' '])) then 480 | continue; 481 | case a of 482 | '{': 483 | begin 484 | if not assigned(obj) or not obj.FIsKeyValue then 485 | obj := TdJSON.Create(obj); 486 | obj.FIsKeyValue := false; 487 | obj.FIsDict := true; 488 | obj.FItems := TdJSONItems.Create; 489 | if not assigned(result) then 490 | begin 491 | result := obj; 492 | end; 493 | if assigned(obj.parent) and obj.parent.IsList then 494 | begin 495 | obj.Parent.ListItems.Add(obj); 496 | end; 497 | tag := 0; 498 | end; 499 | '}': 500 | begin 501 | if not obj.IsDict then 502 | begin 503 | SetValue(); 504 | obj := obj.Parent; 505 | end; 506 | obj := obj.Parent; 507 | tag := 0; 508 | end; 509 | '[': 510 | begin 511 | if not assigned(obj) or not obj.FIsKeyValue then 512 | obj := TdJSON.Create(obj); 513 | obj.FIsKeyValue := false; 514 | obj.FIsList := true; 515 | obj.FListItems := TdJSONListItems.Create; 516 | if not assigned(result) then 517 | begin 518 | result := obj; 519 | end; 520 | if assigned(obj.parent) and obj.parent.IsList then 521 | begin 522 | obj.Parent.ListItems.Add(obj); 523 | end; 524 | tag := 0; 525 | end; 526 | ']': 527 | begin 528 | if not obj.IsList and not obj.IsDict then 529 | begin 530 | SetValue(); 531 | obj.Parent.ListItems.Add(obj); 532 | end 533 | else if obj.IsList then 534 | begin 535 | AddSingleValue(); 536 | end; 537 | obj := obj.Parent; 538 | tag := 0; 539 | end; 540 | ':': 541 | begin 542 | obj := TdJSON.Create(obj); 543 | obj.FIsKeyValue := true; 544 | obj.Parent.Items.Add(getValue(), obj); 545 | tag := 0; 546 | end; 547 | ',': 548 | begin 549 | if not obj.IsList and not obj.IsDict then 550 | begin 551 | SetValue(); 552 | obj := obj.Parent; 553 | end 554 | else if obj.IsList then 555 | begin 556 | AddSingleValue(); 557 | end; 558 | tag := 0; 559 | end; 560 | end; 561 | end; 562 | end; 563 | 564 | class function TdJSON.ParseFile(const APath: string): TdJSON; 565 | var 566 | f: TextFile; 567 | r, t: string; 568 | begin 569 | t := ''; 570 | AssignFile(f, APath); 571 | try 572 | Reset(f); 573 | while not Eof(f) do 574 | begin 575 | ReadLn(f, t); 576 | r := r+t; 577 | end; 578 | finally 579 | CloseFile(f); 580 | end; 581 | Result := Parse(r); 582 | end; 583 | 584 | { TJSONItems } 585 | 586 | destructor TdJSONItems.Destroy; 587 | var 588 | item: TdJSON; 589 | begin 590 | for item in self.Values do 591 | item.Free; 592 | inherited; 593 | end; 594 | 595 | { TJSONListItem } 596 | 597 | destructor TdJSONListItems.Destroy; 598 | var 599 | item: TdJSON; 600 | begin 601 | for item in self do 602 | item.Free; 603 | inherited; 604 | end; 605 | 606 | initialization 607 | 608 | DJSONFormatSettings := TFormatsettings.Create; 609 | with DJSONFormatSettings do 610 | begin 611 | DateSeparator := '-'; 612 | TimeSeparator := ':'; 613 | ShortDateFormat := 'yyyy-mm-dd'; 614 | LongDateFormat := 'yyyy-mm-dd'; 615 | ShortTimeFormat := 'hh:nn:ss'; 616 | LongTimeFormat := 'hh:nn:ss'; 617 | end; 618 | 619 | end. 620 | -------------------------------------------------------------------------------- /test/PjtTestJSON.dpr: -------------------------------------------------------------------------------- 1 | program PjtTestJSON; 2 | { 3 | 4 | Delphi DUnit Test Project 5 | ------------------------- 6 | This project contains the DUnit test framework and the GUI/Console test runners. 7 | Add "CONSOLE_TESTRUNNER" to the conditional defines entry in the project options 8 | to use the console test runner. Otherwise the GUI test runner will be used by 9 | default. 10 | 11 | } 12 | 13 | {$IFDEF CONSOLE_TESTRUNNER} 14 | {$APPTYPE CONSOLE} 15 | {$ENDIF} 16 | 17 | uses 18 | DUnitTestRunner, 19 | Testdjson in 'Testdjson.pas', 20 | djson in '..\djson.pas'; 21 | 22 | {$R *.RES} 23 | 24 | begin 25 | ReportMemoryLeaksOnShutdown := True; 26 | DUnitTestRunner.RunRegisteredTests; 27 | end. 28 | 29 | -------------------------------------------------------------------------------- /test/PjtTestJSON.dproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | {DD6EC364-429A-4F09-B1EB-9E63B4F570DB} 4 | 14.4 5 | None 6 | True 7 | Debug 8 | Win32 9 | 1 10 | Console 11 | PjtTestJSON.dpr 12 | 13 | 14 | true 15 | 16 | 17 | true 18 | Base 19 | true 20 | 21 | 22 | true 23 | Base 24 | true 25 | 26 | 27 | true 28 | Base 29 | true 30 | 31 | 32 | true 33 | Base 34 | true 35 | 36 | 37 | true 38 | Cfg_1 39 | true 40 | true 41 | 42 | 43 | true 44 | Base 45 | true 46 | 47 | 48 | System;Xml;Data;Datasnap;Web;Soap;Vcl;Vcl.Imaging;Vcl.Touch;Vcl.Samples;Vcl.Shell;$(DCC_Namespace) 49 | _CONSOLE_TESTRUNNER;$(DCC_Define) 50 | $(BDS)\Source\DUnit\src;$(DCC_UnitSearchPath) 51 | . 52 | .\$(Platform)\$(Config) 53 | false 54 | false 55 | false 56 | false 57 | false 58 | 59 | 60 | bindcompfmx;DBXSqliteDriver;fmx;rtl;dbrtl;DbxClientDriver;IndySystem;bindcomp;inetdb;DBXInterBaseDriver;DataSnapClient;DataSnapCommon;DataSnapServer;DataSnapProviderClient;xmlrtl;ibxpress;DbxCommonDriver;IndyProtocols;dbxcds;DBXMySQLDriver;bindengine;soaprtl;bindcompdbx;DBXOracleDriver;CustomIPTransport;dsnap;IndyIPServer;DBXInformixDriver;fmxase;IndyCore;IndyIPCommon;DBXFirebirdDriver;inet;fmxobj;inetdbxpress;DBXSybaseASADriver;fmxdae;dbexpress;DataSnapIndy10ServerTransport;IndyIPClient;$(DCC_UsePackage) 61 | 62 | 63 | fs17;frx17;bindcompfmx;dac170;DBXSqliteDriver;vcldbx;fmx;rtl;dbrtl;DbxClientDriver;IndySystem;TeeDB;bindcomp;inetdb;vclib;inetdbbde;DBXInterBaseDriver;DataSnapClient;DataSnapCommon;DBXOdbcDriver;DataSnapServer;Tee;DataSnapProviderClient;xmlrtl;svnui;ibxpress;DbxCommonDriver;DBXSybaseASEDriver;vclimg;IndyProtocols;dbxcds;DBXMySQLDriver;DatasnapConnectorsFreePascal;MetropolisUILiveTile;vclactnband;bindengine;vcldb;soaprtl;bindcompdbx;vcldsnap;bindcompvcl;FMXTee;TeeUI;dacvcl170;vclie;vcltouch;DBXDb2Driver;websnap;DBXOracleDriver;CustomIPTransport;vclribbon;VclSmp;dsnap;IndyIPServer;DBXInformixDriver;Intraweb;fmxase;vcl;IndyCore;DataSnapConnectors;unidac170;IndyIPCommon;CloudService;DBXMSSQLDriver;dsnapcon;frxDB17;DBXFirebirdDriver;fsDB17;inet;fmxobj;FmxTeeUI;CodeSiteExpressPkg;unidacvcl170;vclx;frxe17;inetdbxpress;webdsnap;svn;DBXSybaseASADriver;fmxdae;bdertl;crcontrols170;dbexpress;adortl;DataSnapIndy10ServerTransport;IndyIPClient;$(DCC_UsePackage) 64 | 1033 65 | CompanyName=;FileDescription=;FileVersion=1.0.0.0;InternalName=;LegalCopyright=;LegalTrademarks=;OriginalFilename=;ProductName=;ProductVersion=1.0.0.0;Comments= 66 | Winapi;System.Win;Data.Win;Datasnap.Win;Web.Win;Soap.Win;Xml.Win;Bde;$(DCC_Namespace) 67 | 68 | 69 | bindcompfmx;DBXSqliteDriver;fmx;rtl;dbrtl;DbxClientDriver;IndySystem;TeeDB;bindcomp;inetdb;vclib;DBXInterBaseDriver;DataSnapClient;DataSnapCommon;DBXOdbcDriver;DataSnapServer;Tee;DataSnapProviderClient;xmlrtl;ibxpress;DbxCommonDriver;DBXSybaseASEDriver;vclimg;IndyProtocols;dbxcds;DBXMySQLDriver;DatasnapConnectorsFreePascal;vclactnband;bindengine;vcldb;soaprtl;bindcompdbx;vcldsnap;bindcompvcl;TeeUI;vclie;vcltouch;DBXDb2Driver;websnap;DBXOracleDriver;CustomIPTransport;VclSmp;dsnap;IndyIPServer;DBXInformixDriver;fmxase;vcl;IndyCore;DataSnapConnectors;IndyIPCommon;DBXMSSQLDriver;dsnapcon;DBXFirebirdDriver;inet;fmxobj;vclx;inetdbxpress;webdsnap;DBXSybaseASADriver;fmxdae;dbexpress;adortl;DataSnapIndy10ServerTransport;IndyIPClient;$(DCC_UsePackage) 70 | 71 | 72 | DEBUG;$(DCC_Define) 73 | true 74 | false 75 | true 76 | true 77 | true 78 | 79 | 80 | 1033 81 | None 82 | 3 83 | false 84 | 85 | 86 | false 87 | RELEASE;$(DCC_Define) 88 | 0 89 | false 90 | 91 | 92 | 93 | MainSource 94 | 95 | 96 | 97 | 98 | Cfg_2 99 | Base 100 | 101 | 102 | Base 103 | 104 | 105 | Cfg_1 106 | Base 107 | 108 | 109 | 110 | Delphi.Personality.12 111 | 112 | 113 | 114 | 115 | False 116 | False 117 | 1 118 | 0 119 | 0 120 | 0 121 | False 122 | False 123 | False 124 | False 125 | False 126 | 1030 127 | 1252 128 | 129 | 130 | 131 | 132 | 1.0.0.0 133 | 134 | 135 | 136 | 137 | 138 | 1.0.0.0 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | PjtTestJSON.dpr 151 | 152 | 153 | Microsoft Office 2000 Sample Automation Server Wrapper Components 154 | Microsoft Office XP Sample Automation Server Wrapper Components 155 | 156 | 157 | 158 | 159 | False 160 | True 161 | False 162 | 163 | 164 | DUnit / Delphi Win32 165 | GUI 166 | 167 | 168 | 169 | 170 | 12 171 | 172 | 173 | 174 | 175 | -------------------------------------------------------------------------------- /test/PjtTestJSON.res: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thomaserlang/delphi-json/15300c5fe3c17789efe09a1efcb8b18d69ac4ec9/test/PjtTestJSON.res -------------------------------------------------------------------------------- /test/Testdjson.pas: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thomaserlang/delphi-json/15300c5fe3c17789efe09a1efcb8b18d69ac4ec9/test/Testdjson.pas -------------------------------------------------------------------------------- /test/Win32/Debug/dunit.ini: -------------------------------------------------------------------------------- 1 | [GUITestRunner Config] 2 | AutoSave=1 3 | Left=909 4 | Top=360 5 | Width=819 6 | Height=618 7 | Maximized=0 8 | UseRegistry=0 9 | ResultsPanel.Height=261 10 | ErrorMessage.Height=75 11 | ErrorMessage.Visible=1 12 | FailureList.ColumnWidth[0]=120 13 | FailureList.ColumnWidth[1]=150 14 | FailureList.ColumnWidth[2]=369 15 | FailureList.ColumnWidth[3]=152 16 | HideTestNodesOnOpen=0 17 | BreakOnFailures=0 18 | FailOnNoChecksExecuted=0 19 | FailOnMemoryLeaked=0 20 | IgnoreSetUpTearDownLeaks=0 21 | ReportMemoryLeakTypes=0 22 | SelectTestedNode=1 23 | WarnOnFailTestOverride=0 24 | PopupX=350 25 | PopupY=30 26 | 27 | [Tests.PjtTestJSON.exe.TestTJSON] 28 | 29 | [Tests.PjtTestJSON.exe.TestTdJSON] 30 | 31 | -------------------------------------------------------------------------------- /test/Win32/Debug/test1.json: -------------------------------------------------------------------------------- 1 | { 2 | "username": "thomas", 3 | "name": "Thomas", 4 | "photos": [ 5 | { 6 | "title": "Photo 1", 7 | "urls": { 8 | "small": "http://example.com/photo1_small.jpg", 9 | "large": "http://example.com/photo1_large.jpg" 10 | } 11 | }, 12 | { 13 | "title": "Photo 2", 14 | "urls": { 15 | "small": "http://example.com/photo2_small.jpg", 16 | "large": "http://example.com/photo2_large.jpg" 17 | } 18 | } 19 | ], 20 | "a_number": 123123, 21 | "escape_text": "Some \"test\" \\\\ \\u00e6=\u00e6", 22 | "escape_path": "C:\\test\\test.txt", 23 | "int_list": [ 24 | 1, 25 | 2, 26 | 3 27 | ], 28 | "str_list": [ 29 | "1", 30 | "2", 31 | "3" 32 | ], 33 | "nullvalue": null, 34 | "null_list": [null], 35 | "emptyList": [], 36 | "emptyStringList": [""], 37 | "list_in_list": [ 38 | [1,2], 39 | [3,4] 40 | ], 41 | "bool_true": true, 42 | "bool_false": false, 43 | "double": "1.337" 44 | } -------------------------------------------------------------------------------- /test/Win32/Debug/test2.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "username": "thomas", 4 | "name": "Thomas" 5 | }, 6 | { 7 | "username": "kurt", 8 | "name": "Kurt" 9 | }, 10 | { 11 | "username": "bent", 12 | "name": "Bent" 13 | } 14 | ] -------------------------------------------------------------------------------- /test/Win32/Debug/test3.json: -------------------------------------------------------------------------------- 1 | [ 2 | [ 3 | [ 4 | "list in a list in a list" 5 | ] 6 | ] 7 | ] -------------------------------------------------------------------------------- /test/Win32/Debug/test4.json: -------------------------------------------------------------------------------- 1 | {"empty": []} -------------------------------------------------------------------------------- /test/Win32/Debug/test5.json: -------------------------------------------------------------------------------- 1 | [] -------------------------------------------------------------------------------- /test/Win32/Debug/test6.json: -------------------------------------------------------------------------------- 1 | {"page":1,"results":[{"adult":false,"backdrop_path":"/281QVUuSYKBgtdxmr5iAM7YDUZw.jpg","id":262543,"original_title":"Automata","release_date":"2014-10-09","poster_path":"/wwo81W8PxHREEprnrJRww471hWm.jpg","popularity":6.6273989934368,"title":"Automata","video":false,"vote_average":5.6,"vote_count":160}],"total_pages":1,"total_results":4} 2 | -------------------------------------------------------------------------------- /test/Win32/Debug/test7.json: -------------------------------------------------------------------------------- 1 | {"QueryResponse":{"Item":[{"ItemGroupDetail":{},"Name":"Advance"}]}} -------------------------------------------------------------------------------- /test/Win32/Debug/test8.json: -------------------------------------------------------------------------------- 1 | { 2 | "results": {} 3 | } --------------------------------------------------------------------------------