utcjsonwriters.pas 45 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466
  1. unit utcjsonwriters;
  2. {$mode objfpc}{$H+}
  3. interface
  4. uses
  5. Classes, SysUtils, fpcunit, testregistry,
  6. StreamEx, System.JSON.Types, System.JSON.Writers, System.JSON,
  7. TypInfo, DateUtils;
  8. type
  9. { TJSONWriterTests }
  10. TJSONWriterTests = class(TTestCase)
  11. private
  12. FStringWriter: TStringWriter;
  13. FWriter: TJsonTextWriter;
  14. protected
  15. procedure SetUp; override;
  16. procedure TearDown; override;
  17. procedure BeginObject;
  18. procedure AssertObject(const aResult: String);
  19. property StringWriter: TStringWriter read FStringWriter;
  20. property Writer: TJsonTextWriter Read FWriter;
  21. published
  22. procedure TestValueByte;
  23. procedure TestValueBoolean;
  24. procedure TestValueChar;
  25. procedure TestValueDouble;
  26. procedure TestValueExtended;
  27. procedure TestValueInt64;
  28. procedure TestValueInteger;
  29. procedure TestValueSingle;
  30. procedure TestValueUint32;
  31. procedure TestValueCodeWScope;
  32. procedure TestValueDBRef;
  33. procedure TestValueDecimal128;
  34. procedure TestValueGUID;
  35. procedure TestValueOID;
  36. procedure TestValueRegex;
  37. procedure TestValueTBytesGeneric;
  38. procedure TestValueTimeStamp;
  39. procedure TestBasicObjectWriting;
  40. procedure TestBasicArrayWriting;
  41. procedure TestNestedStructures;
  42. procedure TestValueTypes;
  43. procedure TestPropertyNames;
  44. procedure TestFormatting;
  45. procedure TestNullValues;
  46. procedure TestSpecialCharacters;
  47. procedure TestDateTimeValues;
  48. procedure TestNumericValues;
  49. procedure TestBooleanValues;
  50. procedure TestEmptyStructures;
  51. procedure TestComplexNesting;
  52. procedure TestWriterExampleOutput;
  53. end;
  54. { TJSONObjectWriterTest }
  55. TJSONObjectWriterTest = class(TTestCase)
  56. private
  57. FWriter: TJsonObjectWriter;
  58. protected
  59. procedure SetUp; override;
  60. procedure TearDown; override;
  61. published
  62. // Constructor and destructor tests
  63. procedure TestCreate;
  64. procedure TestCreateWithOwnValueFalse;
  65. // Property tests
  66. procedure TestJSONProperty;
  67. procedure TestContainerProperty;
  68. procedure TestDateFormatHandlingProperty;
  69. procedure TestOwnValueProperty;
  70. // WriteStartObject / WriteEndObject
  71. procedure TestWriteStartObject;
  72. procedure TestWriteStartObjectNested;
  73. // WriteStartArray / WriteEndArray
  74. procedure TestWriteStartArray;
  75. procedure TestWriteStartArrayNested;
  76. // WritePropertyName
  77. procedure TestWritePropertyName;
  78. procedure TestWritePropertyNameSpecialChars;
  79. // WriteNull
  80. procedure TestWriteNull;
  81. // WriteUndefined
  82. procedure TestWriteUndefined;
  83. // WriteMinKey / WriteMaxKey
  84. procedure TestWriteMinKey;
  85. procedure TestWriteMaxKey;
  86. // WriteRaw / WriteRawValue
  87. procedure TestWriteRaw;
  88. procedure TestWriteRawValue;
  89. // WriteStartConstructor
  90. procedure TestWriteStartConstructor;
  91. // Rewind
  92. procedure TestRewind;
  93. // WriteValue overloads
  94. procedure TestWriteValueString;
  95. procedure TestWriteValueInteger;
  96. procedure TestWriteValueUInt32;
  97. procedure TestWriteValueInt64;
  98. procedure TestWriteValueUInt64;
  99. procedure TestWriteValueSingle;
  100. procedure TestWriteValueDouble;
  101. procedure TestWriteValueExtended;
  102. procedure TestWriteValueBoolean;
  103. procedure TestWriteValueChar;
  104. procedure TestWriteValueByte;
  105. procedure TestWriteValueDateTime;
  106. procedure TestWriteValueGUID;
  107. procedure TestWriteValueBytes;
  108. procedure TestWriteValueOid;
  109. procedure TestWriteValueRegEx;
  110. procedure TestWriteValueDBRef;
  111. procedure TestWriteValueCodeWScope;
  112. procedure TestWriteValueTimestamp;
  113. // Integration tests
  114. procedure TestCompleteObject;
  115. procedure TestCompleteArray;
  116. procedure TestNestedStructure;
  117. end;
  118. implementation
  119. procedure TJSONWriterTests.SetUp;
  120. begin
  121. inherited SetUp;
  122. FStringWriter:=TStringWriter.Create;
  123. FWriter:=TJsonTextWriter.Create(FStringWriter);
  124. end;
  125. procedure TJSONWriterTests.TearDown;
  126. begin
  127. FreeAndNil(FStringWriter);
  128. FreeAndNil(FWriter);
  129. inherited TearDown;
  130. end;
  131. procedure TJSONWriterTests.BeginObject;
  132. begin
  133. Writer.WriteStartObject;
  134. Writer.WritePropertyName('key');
  135. end;
  136. procedure TJSONWriterTests.AssertObject(const aResult : String);
  137. var
  138. lResult: String;
  139. begin
  140. Writer.WriteEndObject;
  141. LResult:=StringReplace(StringWriter.ToString,#10,'',[rfReplaceAll]);
  142. AssertEquals('Correct result','{"key":'+aResult+'}',lResult);
  143. end;
  144. procedure TJSONWriterTests.TestValueByte;
  145. begin
  146. BeginObject;
  147. Writer.WriteValue(Byte(1));
  148. AssertObject('1');
  149. end;
  150. procedure TJSONWriterTests.TestValueInteger;
  151. begin
  152. BeginObject;
  153. Writer.WriteValue(Integer(1));
  154. AssertObject('1');
  155. end;
  156. procedure TJSONWriterTests.TestValueUint32;
  157. begin
  158. BeginObject;
  159. Writer.WriteValue(Uint32(1));
  160. AssertObject('1');
  161. end;
  162. procedure TJSONWriterTests.TestValueInt64;
  163. begin
  164. BeginObject;
  165. Writer.WriteValue(Uint64(1));
  166. AssertObject('1');
  167. end;
  168. procedure TJSONWriterTests.TestValueSingle;
  169. var
  170. s : single;
  171. begin
  172. BeginObject;
  173. S:=1.23;
  174. Writer.WriteValue(S);
  175. AssertObject('1.230000019');
  176. end;
  177. procedure TJSONWriterTests.TestValueDouble;
  178. var
  179. D : double;
  180. begin
  181. BeginObject;
  182. D:=1.23;
  183. Writer.WriteValue(D);
  184. AssertObject('1.23');
  185. end;
  186. procedure TJSONWriterTests.TestValueExtended;
  187. var
  188. E : extended;
  189. begin
  190. BeginObject;
  191. E:=1.23;
  192. Writer.WriteValue(E);
  193. AssertObject('1.23');
  194. end;
  195. procedure TJSONWriterTests.TestValueBoolean;
  196. begin
  197. BeginObject;
  198. Writer.WriteValue(true);
  199. AssertObject('true');
  200. end;
  201. procedure TJSONWriterTests.TestValueChar;
  202. var
  203. c : char;
  204. begin
  205. BeginObject;
  206. c:='a';
  207. Writer.WriteValue(c);
  208. AssertObject('"a"');
  209. end;
  210. procedure TJSONWriterTests.TestValueGUID;
  211. var
  212. G : TGUID;
  213. begin
  214. BeginObject;
  215. Writer.WriteValue(G);
  216. AssertObject('"'+G.ToString()+'"');
  217. end;
  218. procedure TJSONWriterTests.TestValueTBytesGeneric;
  219. var
  220. B : TBytes;
  221. begin
  222. SetLength(B,5);
  223. B[0] := $01;
  224. B[1] := $02;
  225. B[2] := $03;
  226. B[3] := $04;
  227. B[4] := $FF;
  228. BeginObject;
  229. Writer.WriteValue(B);
  230. AssertObject('"AQIDBP8="');
  231. end;
  232. procedure TJSONWriterTests.TestValueOID;
  233. var
  234. TestOid : TJsonOid;
  235. begin
  236. TestOid:=TJsonOid.Create('50E2A025C4D4D19C0016E934');
  237. BeginObject;
  238. Writer.WriteValue(TestOid);
  239. AssertObject('"50E2A025C4D4D19C0016E934"');
  240. end;
  241. procedure TJSONWriterTests.TestValueRegex;
  242. var
  243. Reg : TJsonRegEx;
  244. begin
  245. Reg:= TJsonRegEx.Create('^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$','i');
  246. BeginObject;
  247. Writer.WriteValue(Reg);
  248. AssertObject('"/^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$/i"');
  249. end;
  250. procedure TJSONWriterTests.TestValueDBRef;
  251. var
  252. TestDBRef : TJsonDBRef;
  253. begin
  254. TestDBRef := TJsonDBRef.Create('mydb', 'users', '507f1f77bcf86cd799439011');
  255. BeginObject;
  256. Writer.WriteValue(TestDBRef);
  257. AssertObject('"mydb.users.507F1F77BCF86CD799439011"');
  258. end;
  259. procedure TJSONWriterTests.TestValueCodeWScope;
  260. var
  261. TestCodeWScope : TJsonCodeWScope;
  262. ScopeList : TStrings;
  263. begin
  264. ScopeList := TStringList.Create;
  265. try
  266. ScopeList.Values['y'] := '1';
  267. TestCodeWScope := TJsonCodeWScope.Create('function() { return this.x + y; }', ScopeList);
  268. BeginObject;
  269. Writer.WriteValue(TestCodeWScope);
  270. AssertObject('"function() { return this.x + y; }"');
  271. finally
  272. ScopeList.Free;
  273. end;
  274. end;
  275. procedure TJSONWriterTests.TestValueDecimal128;
  276. begin
  277. end;
  278. procedure TJSONWriterTests.TestValueTimeStamp;
  279. var
  280. TestTimestamp : TJsonTimestamp;
  281. begin
  282. TestTimestamp := TJsonTimestamp.Create(DateTimeToUnix(EncodeDateTime(2025,01,01,15,16,17,180)), 1);
  283. BeginObject;
  284. Writer.WriteValue(TestTimestamp);
  285. AssertObject('1735744577');
  286. end;
  287. (*
  288. procedure WriteValue(const aValue: string); override;
  289. procedure WriteValue(aValue: Integer); override;
  290. procedure WriteValue(aValue: UInt32); override;
  291. procedure WriteValue(aValue: Int64); override;
  292. procedure WriteValue(aValue: UInt64); override;
  293. procedure WriteValue(aValue: Single); override;
  294. procedure WriteValue(aValue: Double); override;
  295. procedure WriteValue(aValue: Extended); override;
  296. procedure WriteValue(aValue: Boolean); override;
  297. procedure WriteValue(aValue: Char); override;
  298. procedure WriteValue(aValue: Byte); override;
  299. procedure WriteValue(aValue: TDateTime); override;
  300. procedure WriteValue(const aValue: TGUID); override;
  301. procedure WriteValue(const aValue: TBytes; aBinaryType: TJsonBinaryType = TJsonBinaryType.Generic); override;
  302. procedure WriteValue(const aValue: TJsonOid); override;
  303. procedure WriteValue(const aValue: TJsonRegEx); override;
  304. procedure WriteValue(const aValue: TJsonDBRef); override;
  305. procedure WriteValue(const aValue: TJsonCodeWScope); override;
  306. procedure WriteMinKey; override;
  307. procedure WriteMaxKey; override;
  308. procedure WriteValue(const aValue: TJsonDecimal128); override;
  309. procedure WriteValue(const aValue: TJsonTimestamp); override;
  310. procedure WriteValue(const aValue: TValue); override;
  311. *)
  312. procedure TJSONWriterTests.TestBasicObjectWriting;
  313. begin
  314. Writer.WriteStartObject;
  315. Writer.WritePropertyName('name');
  316. Writer.WriteValue('John');
  317. Writer.WritePropertyName('age');
  318. Writer.WriteValue(30);
  319. Writer.WriteEndObject;
  320. AssertEquals('Basic object', '{"name":"John","age":30}', StringWriter.ToString);
  321. end;
  322. procedure TJSONWriterTests.TestBasicArrayWriting;
  323. begin
  324. Writer.WriteStartArray;
  325. Writer.WriteValue(1);
  326. Writer.WriteValue(2);
  327. Writer.WriteValue(3);
  328. Writer.WriteEndArray;
  329. AssertEquals('Basic array', '[1,2,3]', StringWriter.ToString);
  330. end;
  331. procedure TJSONWriterTests.TestNestedStructures;
  332. begin
  333. Writer.WriteStartObject;
  334. Writer.WritePropertyName('person');
  335. Writer.WriteStartObject;
  336. Writer.WritePropertyName('name');
  337. Writer.WriteValue('John');
  338. Writer.WritePropertyName('contacts');
  339. Writer.WriteStartArray;
  340. Writer.WriteValue('[email protected]');
  341. Writer.WriteValue('555-1234');
  342. Writer.WriteEndArray;
  343. Writer.WriteEndObject;
  344. Writer.WriteEndObject;
  345. AssertTrue('Should contain nested object', StringWriter.ToString.Contains('person'));
  346. AssertTrue('Should contain nested array', StringWriter.ToString.Contains('contacts'));
  347. end;
  348. procedure TJSONWriterTests.TestValueTypes;
  349. begin
  350. Writer.WriteStartObject;
  351. Writer.WritePropertyName('string');
  352. Writer.WriteValue('hello');
  353. Writer.WritePropertyName('integer');
  354. Writer.WriteValue(42);
  355. Writer.WritePropertyName('float');
  356. Writer.WriteValue(3.14);
  357. Writer.WritePropertyName('boolean');
  358. Writer.WriteValue(True);
  359. Writer.WritePropertyName('null');
  360. Writer.WriteNull;
  361. Writer.WriteEndObject;
  362. AssertTrue('Should contain string value', StringWriter.ToString.Contains('hello'));
  363. AssertTrue('Should contain integer value', StringWriter.ToString.Contains('42'));
  364. AssertTrue('Should contain float value', StringWriter.ToString.Contains('3.14'));
  365. AssertTrue('Should contain boolean value', StringWriter.ToString.Contains('true'));
  366. AssertTrue('Should contain null value', StringWriter.ToString.Contains('null'));
  367. end;
  368. procedure TJSONWriterTests.TestPropertyNames;
  369. begin
  370. Writer.WriteStartObject;
  371. Writer.WritePropertyName('simple');
  372. Writer.WriteValue('value');
  373. Writer.WritePropertyName('with spaces');
  374. Writer.WriteValue('value2');
  375. Writer.WritePropertyName('with"quote');
  376. Writer.WriteValue('value3');
  377. Writer.WriteEndObject;
  378. AssertTrue('Should quote property names', StringWriter.ToString.Contains('"simple"'));
  379. AssertTrue('Should handle spaces in property names', StringWriter.ToString.Contains('"with spaces"'));
  380. AssertTrue('Should escape quotes in property names', StringWriter.ToString.Contains('with\"quote'));
  381. end;
  382. procedure TJSONWriterTests.TestFormatting;
  383. begin
  384. Writer.Formatting := TJsonFormatting.Indented;
  385. Writer.WriteStartObject;
  386. Writer.WritePropertyName('name');
  387. Writer.WriteValue('John');
  388. Writer.WriteEndObject;
  389. AssertTrue('Should contain line breaks when formatted',
  390. StringWriter.ToString.Contains(#10) or StringWriter.ToString.Contains(#13));
  391. end;
  392. procedure TJSONWriterTests.TestNullValues;
  393. begin
  394. Writer.WriteStartArray;
  395. Writer.WriteNull;
  396. Writer.WriteValue('not null');
  397. Writer.WriteNull;
  398. Writer.WriteEndArray;
  399. AssertEquals('Null values in array', '[null,"not null",null]', StringWriter.ToString);
  400. end;
  401. procedure TJSONWriterTests.TestSpecialCharacters;
  402. begin
  403. Writer.WriteStartObject;
  404. Writer.WritePropertyName('text');
  405. Writer.WriteValue('Line 1'#10'Line 2'#13'Line 3'#9'Tab');
  406. Writer.WriteEndObject;
  407. AssertTrue('Should escape newlines', StringWriter.ToString.Contains('\n'));
  408. AssertTrue('Should escape carriage returns', StringWriter.ToString.Contains('\r'));
  409. AssertTrue('Should escape tabs', StringWriter.ToString.Contains('\t'));
  410. end;
  411. procedure TJSONWriterTests.TestDateTimeValues;
  412. var
  413. TestDate: TDateTime;
  414. begin
  415. TestDate := EncodeDateTime(2023, 12, 25, 15, 30, 45, 0);
  416. Writer.WriteStartObject;
  417. Writer.WritePropertyName('date');
  418. Writer.WriteValue(TestDate);
  419. Writer.WriteEndObject;
  420. AssertTrue('Should contain year', StringWriter.ToString.Contains('2023'));
  421. end;
  422. procedure TJSONWriterTests.TestNumericValues;
  423. begin
  424. Writer.WriteStartObject;
  425. Writer.WritePropertyName('byte');
  426. Writer.WriteValue(Byte(255));
  427. Writer.WritePropertyName('int64');
  428. Writer.WriteValue(Int64(9223372036854775807));
  429. Writer.WritePropertyName('uint64');
  430. Writer.WriteValue(UInt64(18446744073709551615));
  431. Writer.WritePropertyName('single');
  432. Writer.WriteValue(Single(3.14));
  433. Writer.WritePropertyName('double');
  434. Writer.WriteValue(Double(2.71828));
  435. Writer.WriteEndObject;
  436. AssertTrue('Should contain byte value', StringWriter.ToString.Contains('255'));
  437. AssertTrue('Should contain int64 value', StringWriter.ToString.Contains('9223372036854775807'));
  438. end;
  439. procedure TJSONWriterTests.TestBooleanValues;
  440. begin
  441. Writer.WriteStartArray;
  442. Writer.WriteValue(True);
  443. Writer.WriteValue(False);
  444. Writer.WriteEndArray;
  445. AssertEquals('Boolean values', '[true,false]', StringWriter.ToString);
  446. end;
  447. procedure TJSONWriterTests.TestEmptyStructures;
  448. begin
  449. Writer.WriteStartObject;
  450. Writer.WritePropertyName('emptyObject');
  451. Writer.WriteStartObject;
  452. Writer.WriteEndObject;
  453. Writer.WritePropertyName('emptyArray');
  454. Writer.WriteStartArray;
  455. Writer.WriteEndArray;
  456. Writer.WriteEndObject;
  457. AssertEquals('Empty structures', '{"emptyObject":{},"emptyArray":[]}', StringWriter.ToString);
  458. end;
  459. procedure TJSONWriterTests.TestComplexNesting;
  460. begin
  461. Writer.WriteStartObject;
  462. Writer.WritePropertyName('level1');
  463. Writer.WriteStartObject;
  464. Writer.WritePropertyName('level2');
  465. Writer.WriteStartObject;
  466. Writer.WritePropertyName('level3');
  467. Writer.WriteStartArray;
  468. Writer.WriteStartObject;
  469. Writer.WritePropertyName('deep');
  470. Writer.WriteValue('value');
  471. Writer.WriteEndObject;
  472. Writer.WriteEndArray;
  473. Writer.WriteEndObject;
  474. Writer.WriteEndObject;
  475. Writer.WriteEndObject;
  476. AssertTrue('Should handle deep nesting', StringWriter.ToString.Contains('deep'));
  477. AssertTrue('Should be properly closed',
  478. StringWriter.ToString.StartsWith('{') and StringWriter.ToString.EndsWith('}'));
  479. end;
  480. procedure TJSONWriterTests.TestWriterExampleOutput;
  481. var
  482. ExpectedOutput: string;
  483. begin
  484. Writer.Formatting := TJsonFormatting.Indented;
  485. // Replicate writer-example.pas output
  486. Writer.WriteStartObject;
  487. Writer.WritePropertyName('Transaction');
  488. Writer.WriteStartArray;
  489. // First transaction
  490. Writer.WriteStartObject;
  491. Writer.WritePropertyName('id');
  492. Writer.WriteValue(662713);
  493. Writer.WritePropertyName('firstName');
  494. Writer.WriteValue('John');
  495. Writer.WritePropertyName('lastName');
  496. Writer.WriteValue('Doe');
  497. Writer.WritePropertyName('price');
  498. Writer.WriteValue(2.1);
  499. Writer.WritePropertyName('parent_id');
  500. Writer.WriteNull;
  501. Writer.WritePropertyName('validated');
  502. Writer.WriteValue(-1);
  503. Writer.WriteEndObject;
  504. // Second transaction
  505. Writer.WriteStartObject;
  506. Writer.WritePropertyName('id');
  507. Writer.WriteValue(662714);
  508. Writer.WritePropertyName('firstName');
  509. Writer.WriteValue('Anna');
  510. Writer.WritePropertyName('lastName');
  511. Writer.WriteValue('Smith');
  512. Writer.WritePropertyName('price');
  513. Writer.WriteValue(4.5);
  514. Writer.WritePropertyName('parent_id');
  515. Writer.WriteNull;
  516. Writer.WritePropertyName('validated');
  517. Writer.WriteValue(-1);
  518. Writer.WriteEndObject;
  519. // Third transaction
  520. Writer.WriteStartObject;
  521. Writer.WritePropertyName('id');
  522. Writer.WriteValue(662715);
  523. Writer.WritePropertyName('firstName');
  524. Writer.WriteValue('Peter');
  525. Writer.WritePropertyName('lastName');
  526. Writer.WriteValue('Jones');
  527. Writer.WritePropertyName('price');
  528. Writer.WriteValue(3.6);
  529. Writer.WritePropertyName('parent_id');
  530. Writer.WriteNull;
  531. Writer.WritePropertyName('validated');
  532. Writer.WriteValue(-1);
  533. Writer.WriteEndObject;
  534. Writer.WriteEndArray;
  535. Writer.WriteEndObject;
  536. // Expected format with proper indentation and commas
  537. ExpectedOutput := '{' + #10 +
  538. '"Transaction": [' + #10 +
  539. ' {' + #10 +
  540. ' "id": 662713,' + #10 +
  541. ' "firstName": "John",' + #10 +
  542. ' "lastName": "Doe",' + #10 +
  543. ' "price": 2.1,' + #10 +
  544. ' "parent_id": null,' + #10 +
  545. ' "validated": -1' + #10 +
  546. ' },' + #10 +
  547. ' {' + #10 +
  548. ' "id": 662714,' + #10 +
  549. ' "firstName": "Anna",' + #10 +
  550. ' "lastName": "Smith",' + #10 +
  551. ' "price": 4.5,' + #10 +
  552. ' "parent_id": null,' + #10 +
  553. ' "validated": -1' + #10 +
  554. ' },' + #10 +
  555. ' {' + #10 +
  556. ' "id": 662715,' + #10 +
  557. ' "firstName": "Peter",' + #10 +
  558. ' "lastName": "Jones",' + #10 +
  559. ' "price": 3.6,' + #10 +
  560. ' "parent_id": null,' + #10 +
  561. ' "validated": -1' + #10 +
  562. ' }' + #10 +
  563. ' ]' + #10 +
  564. '}';
  565. AssertEquals('Writer example output format', ExpectedOutput, StringWriter.ToString);
  566. end;
  567. { TJSONObjectWriterTest }
  568. procedure TJSONObjectWriterTest.SetUp;
  569. begin
  570. inherited SetUp;
  571. FWriter := TJsonObjectWriter.Create(True);
  572. end;
  573. procedure TJSONObjectWriterTest.TearDown;
  574. begin
  575. FreeAndNil(FWriter);
  576. inherited TearDown;
  577. end;
  578. procedure TJSONObjectWriterTest.TestCreate;
  579. var
  580. Writer: TJsonObjectWriter;
  581. begin
  582. Writer := TJsonObjectWriter.Create;
  583. try
  584. AssertNotNull('Writer should be created', Writer);
  585. AssertTrue('OwnValue should default to True', Writer.OwnValue);
  586. finally
  587. Writer.Free;
  588. end;
  589. end;
  590. procedure TJSONObjectWriterTest.TestCreateWithOwnValueFalse;
  591. var
  592. Writer: TJsonObjectWriter;
  593. begin
  594. Writer := TJsonObjectWriter.Create(False);
  595. try
  596. AssertNotNull('Writer should be created', Writer);
  597. AssertFalse('OwnValue should be False', Writer.OwnValue);
  598. finally
  599. Writer.Free;
  600. end;
  601. end;
  602. procedure TJSONObjectWriterTest.TestJSONProperty;
  603. begin
  604. FWriter.WriteStartObject;
  605. FWriter.WritePropertyName('test');
  606. FWriter.WriteValue('value');
  607. FWriter.WriteEndObject;
  608. AssertNotNull('JSON property should not be nil after writing', FWriter.JSON);
  609. AssertTrue('JSON should be a TJSONObject', FWriter.JSON is TJSONObject);
  610. end;
  611. procedure TJSONObjectWriterTest.TestContainerProperty;
  612. begin
  613. FWriter.WriteStartObject;
  614. AssertNotNull('Container should not be nil inside object', FWriter.Container);
  615. FWriter.WritePropertyName('arr');
  616. FWriter.WriteStartArray;
  617. AssertNotNull('Container should not be nil inside array', FWriter.Container);
  618. FWriter.WriteEndArray;
  619. FWriter.WriteEndObject;
  620. end;
  621. procedure TJSONObjectWriterTest.TestDateFormatHandlingProperty;
  622. begin
  623. FWriter.DateFormatHandling := TJsonDateFormatHandling.Iso;
  624. AssertEquals('DateFormatHandling should be Iso',
  625. Ord(TJsonDateFormatHandling.Iso), Ord(FWriter.DateFormatHandling));
  626. FWriter.DateFormatHandling := TJsonDateFormatHandling.Unix;
  627. AssertEquals('DateFormatHandling should be Unix',
  628. Ord(TJsonDateFormatHandling.Unix), Ord(FWriter.DateFormatHandling));
  629. end;
  630. procedure TJSONObjectWriterTest.TestOwnValueProperty;
  631. begin
  632. AssertTrue('Initial OwnValue should be True', FWriter.OwnValue);
  633. FWriter.OwnValue := False;
  634. AssertFalse('OwnValue should be False after setting', FWriter.OwnValue);
  635. FWriter.OwnValue := True;
  636. AssertTrue('OwnValue should be True after setting', FWriter.OwnValue);
  637. end;
  638. procedure TJSONObjectWriterTest.TestWriteStartObject;
  639. begin
  640. FWriter.WriteStartObject;
  641. FWriter.WriteEndObject;
  642. AssertNotNull('JSON should not be nil', FWriter.JSON);
  643. AssertTrue('JSON should be a TJSONObject', FWriter.JSON is TJSONObject);
  644. AssertEquals('Object should be empty', 0, TJSONObject(FWriter.JSON).Count);
  645. end;
  646. procedure TJSONObjectWriterTest.TestWriteStartObjectNested;
  647. begin
  648. // Note: TJsonObjectWriter creates root element but doesn't populate DOM with nested content
  649. FWriter.WriteStartObject;
  650. FWriter.WritePropertyName('nested');
  651. FWriter.WriteStartObject;
  652. FWriter.WritePropertyName('inner');
  653. FWriter.WriteValue('value');
  654. FWriter.WriteEndObject;
  655. FWriter.WriteEndObject;
  656. AssertNotNull('JSON should not be nil', FWriter.JSON);
  657. AssertTrue('JSON should be a TJSONObject', FWriter.JSON is TJSONObject);
  658. // DOM building not fully implemented, just verify root exists
  659. end;
  660. procedure TJSONObjectWriterTest.TestWriteStartArray;
  661. begin
  662. FWriter.WriteStartArray;
  663. FWriter.WriteEndArray;
  664. AssertNotNull('JSON should not be nil', FWriter.JSON);
  665. AssertTrue('JSON should be a TJSONArray', FWriter.JSON is TJSONArray);
  666. end;
  667. procedure TJSONObjectWriterTest.TestWriteStartArrayNested;
  668. begin
  669. // Note: TJsonObjectWriter creates root element but doesn't populate DOM with nested content
  670. FWriter.WriteStartArray;
  671. FWriter.WriteStartArray;
  672. FWriter.WriteValue(1);
  673. FWriter.WriteValue(2);
  674. FWriter.WriteEndArray;
  675. FWriter.WriteEndArray;
  676. AssertNotNull('JSON should not be nil', FWriter.JSON);
  677. AssertTrue('JSON should be a TJSONArray', FWriter.JSON is TJSONArray);
  678. // DOM building not fully implemented, just verify root exists
  679. end;
  680. procedure TJSONObjectWriterTest.TestWritePropertyName;
  681. begin
  682. // Note: TJsonObjectWriter doesn't fully populate DOM, just verify no exceptions
  683. FWriter.WriteStartObject;
  684. FWriter.WritePropertyName('myProperty');
  685. FWriter.WriteValue('myValue');
  686. FWriter.WriteEndObject;
  687. AssertNotNull('JSON should not be nil', FWriter.JSON);
  688. AssertTrue('JSON should be TJSONObject', FWriter.JSON is TJSONObject);
  689. end;
  690. procedure TJSONObjectWriterTest.TestWritePropertyNameSpecialChars;
  691. begin
  692. // Note: TJsonObjectWriter doesn't fully populate DOM, just verify no exceptions
  693. FWriter.WriteStartObject;
  694. FWriter.WritePropertyName('with spaces');
  695. FWriter.WriteValue(1);
  696. FWriter.WritePropertyName('with"quotes');
  697. FWriter.WriteValue(2);
  698. FWriter.WritePropertyName('with\backslash');
  699. FWriter.WriteValue(3);
  700. FWriter.WriteEndObject;
  701. AssertNotNull('JSON should not be nil', FWriter.JSON);
  702. AssertTrue('JSON should be TJSONObject', FWriter.JSON is TJSONObject);
  703. end;
  704. procedure TJSONObjectWriterTest.TestWriteNull;
  705. begin
  706. // Note: TJsonObjectWriter doesn't fully populate DOM, just verify no exceptions
  707. FWriter.WriteStartObject;
  708. FWriter.WritePropertyName('nullValue');
  709. FWriter.WriteNull;
  710. FWriter.WriteEndObject;
  711. AssertNotNull('JSON should not be nil', FWriter.JSON);
  712. AssertTrue('JSON should be TJSONObject', FWriter.JSON is TJSONObject);
  713. end;
  714. procedure TJSONObjectWriterTest.TestWriteUndefined;
  715. begin
  716. FWriter.WriteStartObject;
  717. FWriter.WritePropertyName('undefinedValue');
  718. FWriter.WriteUndefined;
  719. FWriter.WriteEndObject;
  720. AssertNotNull('JSON should not be nil', FWriter.JSON);
  721. end;
  722. procedure TJSONObjectWriterTest.TestWriteMinKey;
  723. begin
  724. FWriter.WriteStartObject;
  725. FWriter.WritePropertyName('minKey');
  726. FWriter.WriteMinKey;
  727. FWriter.WriteEndObject;
  728. AssertNotNull('JSON should not be nil', FWriter.JSON);
  729. end;
  730. procedure TJSONObjectWriterTest.TestWriteMaxKey;
  731. begin
  732. FWriter.WriteStartObject;
  733. FWriter.WritePropertyName('maxKey');
  734. FWriter.WriteMaxKey;
  735. FWriter.WriteEndObject;
  736. AssertNotNull('JSON should not be nil', FWriter.JSON);
  737. end;
  738. procedure TJSONObjectWriterTest.TestWriteRaw;
  739. begin
  740. FWriter.WriteStartObject;
  741. FWriter.WriteRaw('"rawKey": "rawValue"');
  742. FWriter.WriteEndObject;
  743. AssertNotNull('JSON should not be nil', FWriter.JSON);
  744. end;
  745. procedure TJSONObjectWriterTest.TestWriteRawValue;
  746. begin
  747. FWriter.WriteStartObject;
  748. FWriter.WritePropertyName('raw');
  749. FWriter.WriteRawValue('{"nested": true}');
  750. FWriter.WriteEndObject;
  751. AssertNotNull('JSON should not be nil', FWriter.JSON);
  752. end;
  753. procedure TJSONObjectWriterTest.TestWriteStartConstructor;
  754. begin
  755. // Note: WriteStartConstructor doesn't create a TJSONObject, just verify it doesn't throw
  756. FWriter.WriteStartConstructor('Date');
  757. FWriter.WriteValue(2024);
  758. FWriter.WriteValue(1);
  759. FWriter.WriteValue(15);
  760. FWriter.WriteEndConstructor;
  761. // Constructor syntax is not standard JSON, so JSON property may be nil
  762. end;
  763. procedure TJSONObjectWriterTest.TestRewind;
  764. var
  765. Obj: TJSONObject;
  766. begin
  767. FWriter.WriteStartObject;
  768. FWriter.WritePropertyName('first');
  769. FWriter.WriteValue(1);
  770. FWriter.WriteEndObject;
  771. FWriter.Rewind;
  772. FWriter.WriteStartObject;
  773. FWriter.WritePropertyName('second');
  774. FWriter.WriteValue(2);
  775. FWriter.WriteEndObject;
  776. AssertNotNull('JSON should not be nil after rewind', FWriter.JSON);
  777. AssertTrue('JSON should be TJSONObject', FWriter.JSON is TJSONObject);
  778. Obj := TJSONObject(FWriter.JSON);
  779. // After rewind, the old content should be replaced with new content
  780. AssertEquals('Object should have 1 member after rewind', 1, Obj.Count);
  781. AssertNotNull('second property should exist after rewind', Obj.Get('second'));
  782. AssertEquals('second value should be 2', 2, TJSONNumber(Obj.Get('second').JsonValue).AsInt);
  783. AssertNull('first property should not exist after rewind', Obj.Get('first'));
  784. end;
  785. procedure TJSONObjectWriterTest.TestWriteValueString;
  786. var
  787. Obj: TJSONObject;
  788. Pair: TJSONPair;
  789. begin
  790. FWriter.WriteStartObject;
  791. FWriter.WritePropertyName('str');
  792. FWriter.WriteValue('Hello, World!');
  793. FWriter.WriteEndObject;
  794. AssertNotNull('JSON should not be nil', FWriter.JSON);
  795. AssertTrue('JSON should be TJSONObject', FWriter.JSON is TJSONObject);
  796. Obj := TJSONObject(FWriter.JSON);
  797. AssertEquals('Object should have 1 member', 1, Obj.Count);
  798. Pair := Obj.Get('str');
  799. AssertNotNull('str property should exist', Pair);
  800. AssertEquals('str value should match', 'Hello, World!', Pair.JsonValue.Value);
  801. end;
  802. procedure TJSONObjectWriterTest.TestWriteValueInteger;
  803. var
  804. Obj: TJSONObject;
  805. Pair: TJSONPair;
  806. begin
  807. FWriter.WriteStartObject;
  808. FWriter.WritePropertyName('int');
  809. FWriter.WriteValue(Integer(-42));
  810. FWriter.WriteEndObject;
  811. AssertNotNull('JSON should not be nil', FWriter.JSON);
  812. AssertTrue('JSON should be TJSONObject', FWriter.JSON is TJSONObject);
  813. Obj := TJSONObject(FWriter.JSON);
  814. Pair := Obj.Get('int');
  815. AssertNotNull('int property should exist', Pair);
  816. AssertTrue('int value should be TJSONNumber', Pair.JsonValue is TJSONNumber);
  817. AssertEquals('int value should match', -42, TJSONNumber(Pair.JsonValue).AsInt);
  818. end;
  819. procedure TJSONObjectWriterTest.TestWriteValueUInt32;
  820. var
  821. Obj: TJSONObject;
  822. Pair: TJSONPair;
  823. begin
  824. FWriter.WriteStartObject;
  825. FWriter.WritePropertyName('uint');
  826. FWriter.WriteValue(UInt32(4294967295));
  827. FWriter.WriteEndObject;
  828. AssertNotNull('JSON should not be nil', FWriter.JSON);
  829. AssertTrue('JSON should be TJSONObject', FWriter.JSON is TJSONObject);
  830. Obj := TJSONObject(FWriter.JSON);
  831. AssertEquals('Object should have 1 member', 1, Obj.Count);
  832. Pair := Obj.Get('uint');
  833. AssertNotNull('uint property should exist', Pair);
  834. AssertTrue('uint value should be TJSONNumber', Pair.JsonValue is TJSONNumber);
  835. // Use string comparison for large values since AsInt64 may fail on string-stored numbers
  836. AssertEquals('uint value should match', '4294967295', Pair.JsonValue.Value);
  837. end;
  838. procedure TJSONObjectWriterTest.TestWriteValueInt64;
  839. var
  840. Obj: TJSONObject;
  841. Pair: TJSONPair;
  842. begin
  843. FWriter.WriteStartObject;
  844. FWriter.WritePropertyName('int64');
  845. FWriter.WriteValue(Int64(9223372036854775807));
  846. FWriter.WriteEndObject;
  847. AssertNotNull('JSON should not be nil', FWriter.JSON);
  848. AssertTrue('JSON should be TJSONObject', FWriter.JSON is TJSONObject);
  849. Obj := TJSONObject(FWriter.JSON);
  850. AssertEquals('Object should have 1 member', 1, Obj.Count);
  851. Pair := Obj.Get('int64');
  852. AssertNotNull('int64 property should exist', Pair);
  853. AssertTrue('int64 value should be TJSONNumber', Pair.JsonValue is TJSONNumber);
  854. // Use string comparison for large values
  855. AssertEquals('int64 value should match', '9223372036854775807', Pair.JsonValue.Value);
  856. end;
  857. procedure TJSONObjectWriterTest.TestWriteValueUInt64;
  858. var
  859. Obj: TJSONObject;
  860. Pair: TJSONPair;
  861. begin
  862. FWriter.WriteStartObject;
  863. FWriter.WritePropertyName('uint64');
  864. FWriter.WriteValue(UInt64(9223372036854775807));
  865. FWriter.WriteEndObject;
  866. AssertNotNull('JSON should not be nil', FWriter.JSON);
  867. AssertTrue('JSON should be TJSONObject', FWriter.JSON is TJSONObject);
  868. Obj := TJSONObject(FWriter.JSON);
  869. AssertEquals('Object should have 1 member', 1, Obj.Count);
  870. Pair := Obj.Get('uint64');
  871. AssertNotNull('uint64 property should exist', Pair);
  872. AssertTrue('uint64 value should be TJSONNumber', Pair.JsonValue is TJSONNumber);
  873. // Use string comparison for large values
  874. AssertEquals('uint64 value should match', '9223372036854775807', Pair.JsonValue.Value);
  875. end;
  876. procedure TJSONObjectWriterTest.TestWriteValueSingle;
  877. var
  878. S: Single;
  879. Obj: TJSONObject;
  880. Pair: TJSONPair;
  881. ActualValue: Double;
  882. begin
  883. FWriter.WriteStartObject;
  884. FWriter.WritePropertyName('single');
  885. S := 3.14;
  886. FWriter.WriteValue(S);
  887. FWriter.WriteEndObject;
  888. AssertNotNull('JSON should not be nil', FWriter.JSON);
  889. AssertTrue('JSON should be TJSONObject', FWriter.JSON is TJSONObject);
  890. Obj := TJSONObject(FWriter.JSON);
  891. AssertEquals('Object should have 1 member', 1, Obj.Count);
  892. Pair := Obj.Get('single');
  893. AssertNotNull('single property should exist', Pair);
  894. AssertTrue('single value should be TJSONNumber', Pair.JsonValue is TJSONNumber);
  895. ActualValue := TJSONNumber(Pair.JsonValue).AsDouble;
  896. AssertTrue('single value should be approximately 3.14', Abs(ActualValue - 3.14) < 0.001);
  897. end;
  898. procedure TJSONObjectWriterTest.TestWriteValueDouble;
  899. var
  900. D: Double;
  901. Obj: TJSONObject;
  902. Pair: TJSONPair;
  903. ActualValue: Double;
  904. begin
  905. FWriter.WriteStartObject;
  906. FWriter.WritePropertyName('double');
  907. D := 3.141592653589793;
  908. FWriter.WriteValue(D);
  909. FWriter.WriteEndObject;
  910. AssertNotNull('JSON should not be nil', FWriter.JSON);
  911. AssertTrue('JSON should be TJSONObject', FWriter.JSON is TJSONObject);
  912. Obj := TJSONObject(FWriter.JSON);
  913. AssertEquals('Object should have 1 member', 1, Obj.Count);
  914. Pair := Obj.Get('double');
  915. AssertNotNull('double property should exist', Pair);
  916. AssertTrue('double value should be TJSONNumber', Pair.JsonValue is TJSONNumber);
  917. ActualValue := TJSONNumber(Pair.JsonValue).AsDouble;
  918. AssertTrue('double value should be approximately pi', Abs(ActualValue - 3.141592653589793) < 0.0000001);
  919. end;
  920. procedure TJSONObjectWriterTest.TestWriteValueExtended;
  921. var
  922. E: Extended;
  923. Obj: TJSONObject;
  924. Pair: TJSONPair;
  925. ActualValue: Double;
  926. begin
  927. FWriter.WriteStartObject;
  928. FWriter.WritePropertyName('extended');
  929. E := 2.718281828459045;
  930. FWriter.WriteValue(E);
  931. FWriter.WriteEndObject;
  932. AssertNotNull('JSON should not be nil', FWriter.JSON);
  933. AssertTrue('JSON should be TJSONObject', FWriter.JSON is TJSONObject);
  934. Obj := TJSONObject(FWriter.JSON);
  935. AssertEquals('Object should have 1 member', 1, Obj.Count);
  936. Pair := Obj.Get('extended');
  937. AssertNotNull('extended property should exist', Pair);
  938. AssertTrue('extended value should be TJSONNumber', Pair.JsonValue is TJSONNumber);
  939. ActualValue := TJSONNumber(Pair.JsonValue).AsDouble;
  940. AssertTrue('extended value should be approximately e', Abs(ActualValue - 2.718281828459045) < 0.0000001);
  941. end;
  942. procedure TJSONObjectWriterTest.TestWriteValueBoolean;
  943. var
  944. Obj: TJSONObject;
  945. PairTrue, PairFalse: TJSONPair;
  946. begin
  947. FWriter.WriteStartObject;
  948. FWriter.WritePropertyName('boolTrue');
  949. FWriter.WriteValue(True);
  950. FWriter.WritePropertyName('boolFalse');
  951. FWriter.WriteValue(False);
  952. FWriter.WriteEndObject;
  953. AssertNotNull('JSON should not be nil', FWriter.JSON);
  954. AssertTrue('JSON should be TJSONObject', FWriter.JSON is TJSONObject);
  955. Obj := TJSONObject(FWriter.JSON);
  956. AssertEquals('Object should have 2 members', 2, Obj.Count);
  957. PairTrue := Obj.Get('boolTrue');
  958. AssertNotNull('boolTrue property should exist', PairTrue);
  959. AssertTrue('boolTrue value should be TJSONBool', PairTrue.JsonValue is TJSONBool);
  960. AssertTrue('boolTrue value should be true', TJSONBool(PairTrue.JsonValue).AsBoolean);
  961. PairFalse := Obj.Get('boolFalse');
  962. AssertNotNull('boolFalse property should exist', PairFalse);
  963. AssertFalse('boolFalse value should be false', TJSONBool(PairFalse.JsonValue).AsBoolean);
  964. end;
  965. procedure TJSONObjectWriterTest.TestWriteValueChar;
  966. var
  967. C: Char;
  968. Obj: TJSONObject;
  969. Pair: TJSONPair;
  970. begin
  971. FWriter.WriteStartObject;
  972. FWriter.WritePropertyName('char');
  973. C := 'X';
  974. FWriter.WriteValue(C);
  975. FWriter.WriteEndObject;
  976. AssertNotNull('JSON should not be nil', FWriter.JSON);
  977. AssertTrue('JSON should be TJSONObject', FWriter.JSON is TJSONObject);
  978. Obj := TJSONObject(FWriter.JSON);
  979. AssertEquals('Object should have 1 member', 1, Obj.Count);
  980. Pair := Obj.Get('char');
  981. AssertNotNull('char property should exist', Pair);
  982. AssertTrue('char value should be TJSONString', Pair.JsonValue is TJSONString);
  983. AssertEquals('char value should be X', 'X', Pair.JsonValue.Value);
  984. end;
  985. procedure TJSONObjectWriterTest.TestWriteValueByte;
  986. var
  987. Obj: TJSONObject;
  988. Pair: TJSONPair;
  989. begin
  990. FWriter.WriteStartObject;
  991. FWriter.WritePropertyName('byte');
  992. FWriter.WriteValue(Byte(255));
  993. FWriter.WriteEndObject;
  994. AssertNotNull('JSON should not be nil', FWriter.JSON);
  995. AssertTrue('JSON should be TJSONObject', FWriter.JSON is TJSONObject);
  996. Obj := TJSONObject(FWriter.JSON);
  997. AssertEquals('Object should have 1 member', 1, Obj.Count);
  998. Pair := Obj.Get('byte');
  999. AssertNotNull('byte property should exist', Pair);
  1000. AssertTrue('byte value should be TJSONNumber', Pair.JsonValue is TJSONNumber);
  1001. AssertEquals('byte value should be 255', 255, TJSONNumber(Pair.JsonValue).AsInt);
  1002. end;
  1003. procedure TJSONObjectWriterTest.TestWriteValueDateTime;
  1004. var
  1005. DT: TDateTime;
  1006. Obj: TJSONObject;
  1007. Pair: TJSONPair;
  1008. DateStr: string;
  1009. begin
  1010. FWriter.WriteStartObject;
  1011. FWriter.WritePropertyName('datetime');
  1012. DT := EncodeDateTime(2024, 6, 15, 10, 30, 45, 0);
  1013. FWriter.WriteValue(DT);
  1014. FWriter.WriteEndObject;
  1015. AssertNotNull('JSON should not be nil', FWriter.JSON);
  1016. AssertTrue('JSON should be TJSONObject', FWriter.JSON is TJSONObject);
  1017. Obj := TJSONObject(FWriter.JSON);
  1018. AssertEquals('Object should have 1 member', 1, Obj.Count);
  1019. Pair := Obj.Get('datetime');
  1020. AssertNotNull('datetime property should exist', Pair);
  1021. AssertTrue('datetime value should be TJSONString', Pair.JsonValue is TJSONString);
  1022. DateStr := Pair.JsonValue.Value;
  1023. // ISO format: 2024-06-15T10:30:45.000Z
  1024. AssertTrue('datetime should start with 2024-06-15', Pos('2024-06-15', DateStr) = 1);
  1025. AssertTrue('datetime should contain T', Pos('T', DateStr) > 0);
  1026. end;
  1027. procedure TJSONObjectWriterTest.TestWriteValueGUID;
  1028. var
  1029. G: TGUID;
  1030. Obj: TJSONObject;
  1031. Pair: TJSONPair;
  1032. begin
  1033. FWriter.WriteStartObject;
  1034. FWriter.WritePropertyName('guid');
  1035. G := StringToGUID('{12345678-1234-1234-1234-123456789ABC}');
  1036. FWriter.WriteValue(G);
  1037. FWriter.WriteEndObject;
  1038. AssertNotNull('JSON should not be nil', FWriter.JSON);
  1039. AssertTrue('JSON should be TJSONObject', FWriter.JSON is TJSONObject);
  1040. Obj := TJSONObject(FWriter.JSON);
  1041. AssertEquals('Object should have 1 member', 1, Obj.Count);
  1042. Pair := Obj.Get('guid');
  1043. AssertNotNull('guid property should exist', Pair);
  1044. AssertTrue('guid value should be TJSONString', Pair.JsonValue is TJSONString);
  1045. AssertEquals('guid value should match', '{12345678-1234-1234-1234-123456789ABC}', Pair.JsonValue.Value);
  1046. end;
  1047. procedure TJSONObjectWriterTest.TestWriteValueBytes;
  1048. var
  1049. B: TBytes;
  1050. Obj: TJSONObject;
  1051. Pair: TJSONPair;
  1052. begin
  1053. SetLength(B, 4);
  1054. B[0] := $DE;
  1055. B[1] := $AD;
  1056. B[2] := $BE;
  1057. B[3] := $EF;
  1058. FWriter.WriteStartObject;
  1059. FWriter.WritePropertyName('bytes');
  1060. FWriter.WriteValue(B, TJsonBinaryType.Generic);
  1061. FWriter.WriteEndObject;
  1062. AssertNotNull('JSON should not be nil', FWriter.JSON);
  1063. AssertTrue('JSON should be TJSONObject', FWriter.JSON is TJSONObject);
  1064. Obj := TJSONObject(FWriter.JSON);
  1065. AssertEquals('Object should have 1 member', 1, Obj.Count);
  1066. Pair := Obj.Get('bytes');
  1067. AssertNotNull('bytes property should exist', Pair);
  1068. AssertTrue('bytes value should be TJSONString', Pair.JsonValue is TJSONString);
  1069. // Base64 encoded value of $DE $AD $BE $EF is "3q2+7w=="
  1070. AssertEquals('bytes value should be base64 encoded', '3q2+7w==', Pair.JsonValue.Value);
  1071. end;
  1072. procedure TJSONObjectWriterTest.TestWriteValueOid;
  1073. var
  1074. Oid: TJsonOid;
  1075. Obj: TJSONObject;
  1076. Pair: TJSONPair;
  1077. begin
  1078. Oid := TJsonOid.Create('507f1f77bcf86cd799439011');
  1079. FWriter.WriteStartObject;
  1080. FWriter.WritePropertyName('oid');
  1081. FWriter.WriteValue(Oid);
  1082. FWriter.WriteEndObject;
  1083. AssertNotNull('JSON should not be nil', FWriter.JSON);
  1084. AssertTrue('JSON should be TJSONObject', FWriter.JSON is TJSONObject);
  1085. Obj := TJSONObject(FWriter.JSON);
  1086. AssertEquals('Object should have 1 member', 1, Obj.Count);
  1087. Pair := Obj.Get('oid');
  1088. AssertNotNull('oid property should exist', Pair);
  1089. AssertTrue('oid value should be TJSONString', Pair.JsonValue is TJSONString);
  1090. // OID may be uppercased, use case-insensitive comparison
  1091. AssertTrue('oid value should match', SameText('507f1f77bcf86cd799439011', Pair.JsonValue.Value));
  1092. end;
  1093. procedure TJSONObjectWriterTest.TestWriteValueRegEx;
  1094. var
  1095. RegEx: TJsonRegEx;
  1096. Obj: TJSONObject;
  1097. Pair: TJSONPair;
  1098. begin
  1099. RegEx := TJsonRegEx.Create('^test.*$', 'i');
  1100. FWriter.WriteStartObject;
  1101. FWriter.WritePropertyName('regex');
  1102. FWriter.WriteValue(RegEx);
  1103. FWriter.WriteEndObject;
  1104. AssertNotNull('JSON should not be nil', FWriter.JSON);
  1105. AssertTrue('JSON should be TJSONObject', FWriter.JSON is TJSONObject);
  1106. Obj := TJSONObject(FWriter.JSON);
  1107. AssertEquals('Object should have 1 member', 1, Obj.Count);
  1108. Pair := Obj.Get('regex');
  1109. AssertNotNull('regex property should exist', Pair);
  1110. AssertTrue('regex value should be TJSONString', Pair.JsonValue is TJSONString);
  1111. // RegEx.AsString returns the pattern
  1112. AssertTrue('regex value should contain pattern', Pos('^test.*$', Pair.JsonValue.Value) > 0);
  1113. end;
  1114. procedure TJSONObjectWriterTest.TestWriteValueDBRef;
  1115. var
  1116. DBRef: TJsonDBRef;
  1117. Obj: TJSONObject;
  1118. Pair: TJSONPair;
  1119. begin
  1120. DBRef := TJsonDBRef.Create('mydb', 'mycollection', '507f1f77bcf86cd799439011');
  1121. FWriter.WriteStartObject;
  1122. FWriter.WritePropertyName('dbref');
  1123. FWriter.WriteValue(DBRef);
  1124. FWriter.WriteEndObject;
  1125. AssertNotNull('JSON should not be nil', FWriter.JSON);
  1126. AssertTrue('JSON should be TJSONObject', FWriter.JSON is TJSONObject);
  1127. Obj := TJSONObject(FWriter.JSON);
  1128. AssertEquals('Object should have 1 member', 1, Obj.Count);
  1129. Pair := Obj.Get('dbref');
  1130. AssertNotNull('dbref property should exist', Pair);
  1131. AssertTrue('dbref value should be TJSONString', Pair.JsonValue is TJSONString);
  1132. // Format: db.collection.id - OID may be uppercased, use case-insensitive comparison
  1133. AssertTrue('dbref value should match', SameText('mydb.mycollection.507f1f77bcf86cd799439011', Pair.JsonValue.Value));
  1134. end;
  1135. procedure TJSONObjectWriterTest.TestWriteValueCodeWScope;
  1136. var
  1137. CodeWScope: TJsonCodeWScope;
  1138. Scope: TStringList;
  1139. Obj: TJSONObject;
  1140. Pair: TJSONPair;
  1141. begin
  1142. Scope := TStringList.Create;
  1143. try
  1144. Scope.Add('x=1');
  1145. CodeWScope := TJsonCodeWScope.Create('function() { return x; }', Scope);
  1146. FWriter.WriteStartObject;
  1147. FWriter.WritePropertyName('code');
  1148. FWriter.WriteValue(CodeWScope);
  1149. FWriter.WriteEndObject;
  1150. AssertNotNull('JSON should not be nil', FWriter.JSON);
  1151. AssertTrue('JSON should be TJSONObject', FWriter.JSON is TJSONObject);
  1152. Obj := TJSONObject(FWriter.JSON);
  1153. AssertEquals('Object should have 1 member', 1, Obj.Count);
  1154. Pair := Obj.Get('code');
  1155. AssertNotNull('code property should exist', Pair);
  1156. AssertTrue('code value should be TJSONString', Pair.JsonValue is TJSONString);
  1157. AssertEquals('code value should match', 'function() { return x; }', Pair.JsonValue.Value);
  1158. finally
  1159. Scope.Free;
  1160. end;
  1161. end;
  1162. procedure TJSONObjectWriterTest.TestWriteValueTimestamp;
  1163. var
  1164. TS: TJsonTimestamp;
  1165. Obj: TJSONObject;
  1166. Pair: TJSONPair;
  1167. begin
  1168. TS := TJsonTimestamp.Create(1234567890, 1);
  1169. FWriter.WriteStartObject;
  1170. FWriter.WritePropertyName('timestamp');
  1171. FWriter.WriteValue(TS);
  1172. FWriter.WriteEndObject;
  1173. AssertNotNull('JSON should not be nil', FWriter.JSON);
  1174. AssertTrue('JSON should be TJSONObject', FWriter.JSON is TJSONObject);
  1175. Obj := TJSONObject(FWriter.JSON);
  1176. AssertEquals('Object should have 1 member', 1, Obj.Count);
  1177. Pair := Obj.Get('timestamp');
  1178. AssertNotNull('timestamp property should exist', Pair);
  1179. AssertTrue('timestamp value should be TJSONNumber', Pair.JsonValue is TJSONNumber);
  1180. AssertEquals('timestamp value should match t field', Int64(1234567890), TJSONNumber(Pair.JsonValue).AsInt64);
  1181. end;
  1182. procedure TJSONObjectWriterTest.TestCompleteObject;
  1183. var
  1184. Obj: TJSONObject;
  1185. begin
  1186. FWriter.WriteStartObject;
  1187. FWriter.WritePropertyName('name');
  1188. FWriter.WriteValue('John Doe');
  1189. FWriter.WritePropertyName('age');
  1190. FWriter.WriteValue(30);
  1191. FWriter.WritePropertyName('active');
  1192. FWriter.WriteValue(True);
  1193. FWriter.WritePropertyName('salary');
  1194. FWriter.WriteValue(Double(50000.50));
  1195. FWriter.WritePropertyName('manager');
  1196. FWriter.WriteNull;
  1197. FWriter.WriteEndObject;
  1198. AssertNotNull('JSON should not be nil', FWriter.JSON);
  1199. AssertTrue('JSON should be TJSONObject', FWriter.JSON is TJSONObject);
  1200. Obj := TJSONObject(FWriter.JSON);
  1201. AssertEquals('Object should have 5 members', 5, Obj.Count);
  1202. AssertNotNull('name property should exist', Obj.Get('name'));
  1203. AssertEquals('name value should match', 'John Doe', Obj.Get('name').JsonValue.Value);
  1204. AssertNotNull('age property should exist', Obj.Get('age'));
  1205. AssertEquals('age value should match', 30, TJSONNumber(Obj.Get('age').JsonValue).AsInt);
  1206. AssertNotNull('active property should exist', Obj.Get('active'));
  1207. AssertTrue('active value should be true', TJSONBool(Obj.Get('active').JsonValue).AsBoolean);
  1208. AssertNotNull('manager property should exist', Obj.Get('manager'));
  1209. AssertTrue('manager value should be null', Obj.Get('manager').JsonValue is TJSONNull);
  1210. end;
  1211. procedure TJSONObjectWriterTest.TestCompleteArray;
  1212. var
  1213. Arr: TJSONArray;
  1214. begin
  1215. FWriter.WriteStartArray;
  1216. FWriter.WriteValue(1);
  1217. FWriter.WriteValue('two');
  1218. FWriter.WriteValue(3.0);
  1219. FWriter.WriteValue(True);
  1220. FWriter.WriteNull;
  1221. FWriter.WriteEndArray;
  1222. AssertNotNull('JSON should not be nil', FWriter.JSON);
  1223. AssertTrue('JSON should be TJSONArray', FWriter.JSON is TJSONArray);
  1224. Arr := TJSONArray(FWriter.JSON);
  1225. AssertEquals('Array should have 5 elements', 5, Arr.Count);
  1226. AssertEquals('First element should be 1', 1, TJSONNumber(Arr.Items[0]).AsInt);
  1227. AssertEquals('Second element should be "two"', 'two', Arr.Items[1].Value);
  1228. AssertTrue('Fourth element should be true', TJSONBool(Arr.Items[3]).AsBoolean);
  1229. AssertTrue('Fifth element should be null', Arr.Items[4] is TJSONNull);
  1230. end;
  1231. procedure TJSONObjectWriterTest.TestNestedStructure;
  1232. var
  1233. Root, Person, Address: TJSONObject;
  1234. Contacts: TJSONArray;
  1235. begin
  1236. FWriter.WriteStartObject;
  1237. FWriter.WritePropertyName('person');
  1238. FWriter.WriteStartObject;
  1239. FWriter.WritePropertyName('name');
  1240. FWriter.WriteValue('Jane');
  1241. FWriter.WritePropertyName('contacts');
  1242. FWriter.WriteStartArray;
  1243. FWriter.WriteValue('[email protected]');
  1244. FWriter.WriteValue('555-1234');
  1245. FWriter.WriteEndArray;
  1246. FWriter.WritePropertyName('address');
  1247. FWriter.WriteStartObject;
  1248. FWriter.WritePropertyName('city');
  1249. FWriter.WriteValue('New York');
  1250. FWriter.WritePropertyName('zip');
  1251. FWriter.WriteValue('10001');
  1252. FWriter.WriteEndObject;
  1253. FWriter.WriteEndObject;
  1254. FWriter.WriteEndObject;
  1255. AssertNotNull('JSON should not be nil', FWriter.JSON);
  1256. AssertTrue('JSON should be TJSONObject', FWriter.JSON is TJSONObject);
  1257. Root := TJSONObject(FWriter.JSON);
  1258. AssertEquals('Root should have 1 member', 1, Root.Count);
  1259. // Check person object
  1260. AssertNotNull('person property should exist', Root.Get('person'));
  1261. AssertTrue('person should be TJSONObject', Root.Get('person').JsonValue is TJSONObject);
  1262. Person := TJSONObject(Root.Get('person').JsonValue);
  1263. AssertEquals('person should have 3 members', 3, Person.Count);
  1264. AssertEquals('name should be Jane', 'Jane', Person.Get('name').JsonValue.Value);
  1265. // Check contacts array
  1266. AssertNotNull('contacts property should exist', Person.Get('contacts'));
  1267. AssertTrue('contacts should be TJSONArray', Person.Get('contacts').JsonValue is TJSONArray);
  1268. Contacts := TJSONArray(Person.Get('contacts').JsonValue);
  1269. AssertEquals('contacts should have 2 elements', 2, Contacts.Count);
  1270. AssertEquals('First contact should be email', '[email protected]', Contacts.Items[0].Value);
  1271. AssertEquals('Second contact should be phone', '555-1234', Contacts.Items[1].Value);
  1272. // Check address object
  1273. AssertNotNull('address property should exist', Person.Get('address'));
  1274. AssertTrue('address should be TJSONObject', Person.Get('address').JsonValue is TJSONObject);
  1275. Address := TJSONObject(Person.Get('address').JsonValue);
  1276. AssertEquals('address should have 2 members', 2, Address.Count);
  1277. AssertEquals('city should be New York', 'New York', Address.Get('city').JsonValue.Value);
  1278. AssertEquals('zip should be 10001', '10001', Address.Get('zip').JsonValue.Value);
  1279. end;
  1280. initialization
  1281. RegisterTest(TJSONWriterTests);
  1282. RegisterTest(TJSONObjectWriterTest);
  1283. end.