1
0

rjson.pas 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605
  1. {
  2. TRJ - JSON Simple Read and Write
  3. - v0.9.1
  4. - 2024-09-05 by gale
  5. - https://github.com/higale/RJSON
  6. }
  7. unit rjson;
  8. interface
  9. uses
  10. System.IOUtils, System.Classes, System.SysUtils, System.StrUtils, System.JSON,
  11. System.Generics.Collections;
  12. type
  13. TJObject = TJSONObject;
  14. TJArray = TJSONArray;
  15. TJValue = TJSONValue;
  16. TJString = TJSONString;
  17. TJNumber = TJSONNumber;
  18. TJBool = TJSONBool;
  19. TJNull = TJSONNull;
  20. TJVType = type of TJSONValue;
  21. { JSON root data interface }
  22. IRJRoot = interface
  23. function GetData: TJSONValue;
  24. procedure SetData(const AValue: TJSONValue);
  25. function ForceJV(AType: TJVType): TJSONValue;
  26. property Data: TJSONValue read GetData write SetData;
  27. end;
  28. { JSON root data interface class }
  29. TRJRoot = class(TInterfacedObject, IRJRoot)
  30. private
  31. FData: TJSONValue;
  32. function GetData: TJSONValue;
  33. procedure SetData(const AValue: TJSONValue);
  34. function ForceJV(AType: TJVType): TJSONValue;
  35. public
  36. constructor Create; overload;
  37. constructor Create(const AValue: TJSONValue); overload;
  38. destructor Destroy; override;
  39. end;
  40. TRJEnumerator = class;
  41. { TRJ is equivalent to TRecordJSON }
  42. TRJ = record
  43. private
  44. FRoot: IRJRoot;
  45. FPath: string;
  46. function LinkPath(const ALeft, ARight: string): string;
  47. function GetJSONValue: TJSONValue; inline;
  48. function GetItems(const APath: string): TRJ; overload;
  49. function GetItems(AIndex: Integer): TRJ; overload; inline;
  50. function GetPairs(AIndex: Integer): TRJ;
  51. procedure SetValue(const [ref] AValue: TRJ);
  52. procedure SetItems(const APath: string; const [ref] AValue: TRJ); overload;
  53. procedure SetItems(AIndex: Integer; const [ref] AValue: TRJ); overload; inline;
  54. function GetCount: Integer;
  55. function GetIndex: Integer;
  56. function GetKey: string;
  57. public
  58. function GetEnumerator(): TRJEnumerator;
  59. class operator Initialize(out Dest: TRJ);
  60. class operator Finalize(var Dest: TRJ);
  61. class operator Assign(var Dest: TRJ; const [ref] Src: TRJ);
  62. class operator Implicit(const Value: string): TRJ;
  63. class operator Implicit(const [ref] Value: TRJ): string;
  64. class operator Implicit(Value: Integer): TRJ;
  65. class operator Implicit(const [ref] Value: TRJ): Integer;
  66. class operator Implicit(Value: Int64): TRJ;
  67. class operator Implicit(const [ref] Value: TRJ): Int64;
  68. class operator Implicit(Value: Extended): TRJ;
  69. class operator Implicit(const [ref] Value: TRJ): Extended;
  70. class operator Implicit(Value: boolean): TRJ;
  71. class operator Implicit(const [ref] Value: TRJ): boolean;
  72. class operator Implicit(const Value: TJSONValue): TRJ;
  73. function ToStr(const ADefault: string = ''): string;
  74. function ToInt(ADefault: Integer = 0): Integer;
  75. function ToInt64(ADefault: Int64 = 0): Int64;
  76. function ToFloat(ADefault: Extended = 0.0): Extended;
  77. function ToBool(ADefault: boolean = False): boolean;
  78. property Items[const APath: string]: TRJ read GetItems write SetItems; default;
  79. property Items[AIndex: Integer]: TRJ read GetItems write SetItems; default;
  80. property Pairs[AIndex: Integer]: TRJ read GetPairs;
  81. property Count: Integer read GetCount;
  82. property Index: Integer read GetIndex;
  83. property Key: string read GetKey;
  84. property Root: IRJRoot read FRoot;
  85. property Path: string read FPath;
  86. function RootIs<T: TJSONValue>: boolean;
  87. function ValueIs<T: TJSONValue>: boolean;
  88. property JSONValue: TJSONValue read GetJSONValue;
  89. function CloneJSONValue: TJSONValue;
  90. procedure Reset;
  91. function Format(Indentation: Integer = 4): string;
  92. procedure ParseJSONValue(const AData: string; AUseBool: boolean = False; ARaiseExc: boolean = False);
  93. procedure LoadFromFile(const AFileName: string; AUseBool: boolean = False; ARaiseExc: boolean = False);
  94. procedure SaveToFile(const AFileName: string; AIndentation: Integer = 4; AWriteBOM: boolean = False; ATrailingLineBreak: boolean = False);
  95. end;
  96. { Iterators }
  97. TRJEnumerator = class
  98. private
  99. FPData: ^TRJ;
  100. FIndex: Integer;
  101. function GetCurrent: TRJ;
  102. public
  103. constructor Create(const [ref] AData: TRJ);
  104. function MoveNext: boolean;
  105. property Current: TRJ read GetCurrent;
  106. end;
  107. implementation
  108. { ============================================================================ }
  109. { TRJRoot }
  110. constructor TRJRoot.Create;
  111. begin
  112. inherited;
  113. FData := nil;
  114. end;
  115. constructor TRJRoot.Create(const AValue: TJSONValue);
  116. begin
  117. inherited Create;
  118. FData := AValue;
  119. end;
  120. destructor TRJRoot.Destroy;
  121. begin
  122. FData.Free;
  123. inherited;
  124. end;
  125. function TRJRoot.GetData: TJSONValue;
  126. begin
  127. Result := FData;
  128. end;
  129. procedure TRJRoot.SetData(const AValue: TJSONValue);
  130. begin
  131. FData := AValue;
  132. end;
  133. function TRJRoot.ForceJV(AType: TJVType): TJSONValue;
  134. begin
  135. if not(FData is AType) then
  136. begin
  137. FData.Free;
  138. FData := AType.Create;
  139. end;
  140. Result := FData;
  141. end;
  142. { TRJRoot }
  143. { ============================================================================ }
  144. { TJSONValueHelper }
  145. type
  146. TJSONValueHelper = class helper for TJSONValue
  147. private
  148. procedure ObjSetItem(const AName: string; const AValue: TJSONValue);
  149. procedure ArrFill<T: TJSONValue>(ACount: Integer);
  150. procedure ArrInsert(const AIndex: Integer; const AValue: TJSONValue);
  151. procedure ArrSetItem(AIndex: Integer; const AValue: TJSONValue);
  152. function ToType<T>(ADefault: T): T;
  153. function GetOrCreate<T: TJSONValue>(AName: string): T;
  154. procedure SetValue(const APath: string; const AValue: TJSONValue);
  155. end;
  156. procedure TJSONValueHelper.ObjSetItem(const AName: string; const AValue: TJSONValue);
  157. var
  158. pairTmp: TJSONPair;
  159. begin
  160. pairTmp := TJSONObject(self).Get(AName);
  161. if pairTmp = nil then
  162. TJSONObject(self).AddPair(AName, AValue)
  163. else
  164. pairTmp.JSONValue := AValue;
  165. end;
  166. procedure TJSONValueHelper.ArrFill<T>(ACount: Integer);
  167. begin
  168. for var j := TJSONArray(self).Count to ACount do
  169. TJSONArray(self).AddElement(T.Create);
  170. end;
  171. procedure TJSONValueHelper.ArrInsert(const AIndex: Integer; const AValue: TJSONValue);
  172. begin
  173. TJSONArray(self).AddElement(AValue);
  174. for var i := AIndex to TJSONArray(self).Count - 2 do
  175. TJSONArray(self).AddElement(TJSONArray(self).Remove(AIndex));
  176. end;
  177. procedure TJSONValueHelper.ArrSetItem(AIndex: Integer; const AValue: TJSONValue);
  178. begin
  179. ArrFill<TJSONNull>(AIndex - 1);
  180. if AIndex <= TJSONArray(self).Count - 1 then
  181. TJSONArray(self).Remove(AIndex).Free;
  182. ArrInsert(AIndex, AValue);
  183. end;
  184. procedure TJSONValueHelper.SetValue(const APath: string; const AValue: TJSONValue);
  185. var
  186. LParser: TJSONPathParser;
  187. preName: string;
  188. jv: TJSONValue;
  189. begin
  190. if APath.IsEmpty then
  191. raise Exception.Create('TJSONValueHelper.SetValue: path cannot be empty');
  192. jv := self;
  193. LParser := TJSONPathParser.Create(APath);
  194. LParser.NextToken;
  195. while true do
  196. begin
  197. preName := LParser.TokenName;
  198. LParser.NextToken;
  199. case LParser.Token of
  200. TJSONPathParser.TToken.Name:
  201. jv := jv.GetOrCreate<TJSONObject>(preName);
  202. TJSONPathParser.TToken.ArrayIndex:
  203. jv := jv.GetOrCreate<TJSONArray>(preName);
  204. TJSONPathParser.TToken.Eof:
  205. begin
  206. if jv is TJSONObject then
  207. jv.ObjSetItem(preName, AValue)
  208. else
  209. jv.ArrSetItem(preName.ToInteger, AValue);
  210. break;
  211. end;
  212. else
  213. raise Exception.Create('TJSONValueHelper.SetValue, LParser.Token Error!');
  214. end;
  215. end;
  216. end;
  217. function TJSONValueHelper.ToType<T>(ADefault: T): T;
  218. begin
  219. if self = nil then
  220. Exit(ADefault);
  221. try
  222. Result := AsType<T>;
  223. except
  224. Result := ADefault;
  225. end;
  226. end;
  227. function TJSONValueHelper.GetOrCreate<T>(AName: string): T;
  228. begin
  229. if self is TJSONObject then
  230. begin
  231. Result := T(TJSONObject(self).GetValue(AName));
  232. if not(Result is T) then
  233. begin
  234. Result := T.Create;
  235. ObjSetItem(AName, Result);
  236. end;
  237. end
  238. else if self is TJSONArray then
  239. begin
  240. ArrFill<TJSONNull>(AName.ToInteger);
  241. Result := T(TJSONArray(self).Items[AName.ToInteger]);
  242. if not(Result is T) then
  243. begin
  244. Result := T.Create;
  245. ArrSetItem(AName.ToInteger, Result);
  246. end;
  247. end
  248. else
  249. begin
  250. raise Exception.Create('GetOrCreate<T> Error, self must be TJO or TJA');
  251. end;
  252. end;
  253. { TJSONValueHelper }
  254. { ============================================================================ }
  255. { TRJEnumerator }
  256. constructor TRJEnumerator.Create(const [ref] AData: TRJ);
  257. begin
  258. inherited Create;
  259. FPData := @AData;
  260. FIndex := -1;
  261. end;
  262. function TRJEnumerator.GetCurrent: TRJ;
  263. var
  264. jvTmp: TJSONValue;
  265. begin
  266. Result.Reset;
  267. Result.FRoot := FPData^.FRoot;
  268. jvTmp := FPData^.GetJSONValue;
  269. if jvTmp is TJSONObject then
  270. begin
  271. if FPData^.FPath = '' then
  272. Result.FPath := TJSONObject(jvTmp).Pairs[FIndex].JsonString.Value
  273. else
  274. Result.FPath := FPData^.FPath + '.' + TJSONObject(jvTmp).Pairs[FIndex].JsonString.Value;
  275. end
  276. else if jvTmp is TJSONArray then
  277. begin
  278. Result.FPath := FPData^.FPath + '[' + FIndex.ToString + ']';
  279. end;
  280. end;
  281. function TRJEnumerator.MoveNext: boolean;
  282. begin
  283. Inc(FIndex);
  284. Exit(FIndex < FPData^.Count)
  285. end;
  286. { TRJEnumerator }
  287. { ============================================================================ }
  288. { TRJ }
  289. function TRJ.GetEnumerator(): TRJEnumerator;
  290. begin
  291. Result := TRJEnumerator.Create(self);
  292. end;
  293. class operator TRJ.Initialize(out Dest: TRJ);
  294. begin
  295. Dest.FRoot := TRJRoot.Create;
  296. Dest.FPath := '';
  297. end;
  298. class operator TRJ.Finalize(var Dest: TRJ);
  299. begin
  300. Dest.FRoot := nil;
  301. end;
  302. function TRJ.LinkPath(const ALeft, ARight: string): string;
  303. begin
  304. if ALeft.IsEmpty then
  305. Result := ARight
  306. else if ARight.IsEmpty then
  307. Result := ALeft
  308. else if ARight.StartsWith('[') then
  309. Result := ALeft + ARight
  310. else
  311. Result := ALeft + '.' + ARight;
  312. end;
  313. function TRJ.GetJSONValue: TJSONValue;
  314. begin
  315. Result := FRoot.Data.FindValue(FPath);
  316. end;
  317. function TRJ.CloneJSONValue: TJSONValue;
  318. begin
  319. Result := GetJSONValue;
  320. if Result <> nil then
  321. Result := Result.Clone as TJSONValue
  322. else
  323. Result := TJSONNull.Create;
  324. end;
  325. class operator TRJ.Assign(var Dest: TRJ; const [ref] Src: TRJ);
  326. begin
  327. if Dest.FPath.IsEmpty then
  328. begin
  329. Dest.FRoot := Src.FRoot;
  330. Dest.FPath := Src.FPath;
  331. end
  332. else
  333. begin
  334. Dest.SetValue(Src);
  335. end;
  336. end;
  337. class operator TRJ.Implicit(const Value: string): TRJ;
  338. begin
  339. Result.FRoot.Data := TJSONString.Create(Value);
  340. end;
  341. class operator TRJ.Implicit(const [ref] Value: TRJ): string;
  342. begin
  343. Result := Value.ToStr('');
  344. end;
  345. class operator TRJ.Implicit(Value: Integer): TRJ;
  346. begin
  347. Result.FRoot.Data := TJSONNumber.Create(Value);
  348. end;
  349. class operator TRJ.Implicit(const [ref] Value: TRJ): Integer;
  350. begin
  351. Result := Value.ToInt(0);
  352. end;
  353. class operator TRJ.Implicit(Value: Int64): TRJ;
  354. begin
  355. Result.FRoot.Data := TJSONNumber.Create(Value);
  356. end;
  357. class operator TRJ.Implicit(const [ref] Value: TRJ): Int64;
  358. begin
  359. Result := Value.ToInt64(0);
  360. end;
  361. class operator TRJ.Implicit(Value: Extended): TRJ;
  362. begin
  363. Result.FRoot.Data := TJSONNumber.Create(Value);
  364. end;
  365. class operator TRJ.Implicit(const [ref] Value: TRJ): Extended;
  366. begin
  367. Result := Value.ToFloat(0.0);
  368. end;
  369. class operator TRJ.Implicit(Value: boolean): TRJ;
  370. begin
  371. Result.FRoot.Data := TJSONBool.Create(Value);
  372. end;
  373. class operator TRJ.Implicit(const [ref] Value: TRJ): boolean;
  374. begin
  375. Result := Value.ToBool(False);
  376. end;
  377. class operator TRJ.Implicit(const Value: TJSONValue): TRJ;
  378. begin
  379. Result.FRoot.Data := Value;
  380. end;
  381. function TRJ.ToStr(const ADefault: string): string;
  382. begin
  383. Result := FRoot.Data.FindValue(FPath).ToType<string>(ADefault);
  384. end;
  385. function TRJ.ToInt(ADefault: Integer = 0): Integer;
  386. begin
  387. Result := FRoot.Data.FindValue(FPath).ToType<Integer>(ADefault);
  388. end;
  389. function TRJ.ToInt64(ADefault: Int64 = 0): Int64;
  390. begin
  391. Result := FRoot.Data.FindValue(FPath).ToType<Int64>(ADefault);
  392. end;
  393. function TRJ.ToFloat(ADefault: Extended = 0.0): Extended;
  394. begin
  395. Result := FRoot.Data.FindValue(FPath).ToType<Extended>(ADefault);
  396. end;
  397. function TRJ.ToBool(ADefault: boolean = False): boolean;
  398. begin
  399. Result := FRoot.Data.FindValue(FPath).ToType<boolean>(ADefault);
  400. end;
  401. function TRJ.GetItems(const APath: string): TRJ;
  402. begin
  403. Result.FRoot := FRoot;
  404. Result.FPath := LinkPath(FPath, APath);
  405. end;
  406. function TRJ.GetItems(AIndex: Integer): TRJ;
  407. begin
  408. Result := GetItems('[' + AIndex.ToString + ']');
  409. end;
  410. function TRJ.GetPairs(AIndex: Integer): TRJ;
  411. var
  412. jvTmp: TJSONValue;
  413. begin
  414. jvTmp := GetJSONValue;
  415. if (jvTmp is TJSONObject) then
  416. Result := GetItems(TJSONObject(jvTmp).Pairs[AIndex].JsonString.Value);
  417. end;
  418. procedure TRJ.SetValue(const [ref] AValue: TRJ);
  419. var
  420. LValue: TJSONValue;
  421. begin
  422. if FPath.IsEmpty then
  423. raise Exception.Create(' TRJ.SetValue: Path is empty');
  424. LValue := AValue.CloneJSONValue;
  425. try
  426. if FPath.StartsWith('[') then
  427. FRoot.ForceJV(TJSONArray).SetValue(FPath, LValue)
  428. else
  429. FRoot.ForceJV(TJSONObject).SetValue(FPath, LValue);
  430. except
  431. on E: Exception do
  432. begin
  433. LValue.Free;
  434. raise Exception.Create(E.message);
  435. end;
  436. end;
  437. end;
  438. procedure TRJ.SetItems(const APath: string; const [ref] AValue: TRJ);
  439. var
  440. tmp: TRJ;
  441. begin
  442. tmp.FRoot := FRoot;
  443. tmp.FPath := LinkPath(FPath, APath);
  444. tmp.SetValue(AValue)
  445. end;
  446. procedure TRJ.SetItems(AIndex: Integer; const [ref] AValue: TRJ);
  447. begin
  448. SetItems('[' + AIndex.ToString + ']', AValue);
  449. end;
  450. function TRJ.GetCount: Integer;
  451. var
  452. jvTemp: TJSONValue;
  453. begin
  454. jvTemp := GetJSONValue;
  455. if jvTemp is TJSONArray then
  456. Result := TJSONArray(jvTemp).Count
  457. else if jvTemp is TJSONObject then
  458. Result := TJSONObject(jvTemp).Count
  459. else
  460. Result := 0;
  461. end;
  462. function TRJ.GetIndex: Integer;
  463. var
  464. strTmp: string;
  465. begin
  466. Result := -1;
  467. strTmp := FPath.Substring(FPath.LastIndexOf('[') + 1);
  468. if strTmp.EndsWith(']') then
  469. Result := StrToIntDef(strTmp.TrimRight([']']), -1);
  470. end;
  471. function TRJ.GetKey: string;
  472. begin
  473. Result := FPath.Substring(FPath.LastIndexOf('.') + 1);
  474. if Result.EndsWith(']') then
  475. Result := '';
  476. end;
  477. function TRJ.RootIs<T>: boolean;
  478. begin
  479. Result := FRoot.Data is T;
  480. end;
  481. function TRJ.ValueIs<T>: boolean;
  482. begin
  483. Result := GetJSONValue is T;
  484. end;
  485. procedure TRJ.Reset;
  486. begin
  487. FRoot := TRJRoot.Create;
  488. FPath := '';
  489. end;
  490. function TRJ.Format(Indentation: Integer): string;
  491. var
  492. LValue: TJSONValue;
  493. begin
  494. Result := '';
  495. LValue := GetJSONValue;
  496. if LValue <> nil then
  497. begin
  498. if Indentation > 0 then
  499. Result := LValue.Format(Indentation)
  500. else
  501. Result := LValue.ToString;
  502. end;
  503. end;
  504. procedure TRJ.ParseJSONValue(const AData: string; AUseBool: boolean; ARaiseExc: boolean);
  505. begin
  506. Reset;
  507. FRoot.Data := TJSONValue.ParseJSONValue(AData, AUseBool, ARaiseExc);
  508. end;
  509. procedure TRJ.LoadFromFile(const AFileName: string; AUseBool: boolean; ARaiseExc: boolean);
  510. begin
  511. ParseJSONValue(TFile.ReadAllText(AFileName, TEncoding.UTF8), AUseBool, ARaiseExc);
  512. end;
  513. procedure TRJ.SaveToFile(const AFileName: string; AIndentation: Integer; AWriteBOM: boolean; ATrailingLineBreak: boolean);
  514. var
  515. strs: TStrings;
  516. begin
  517. strs := TStringList.Create;
  518. try
  519. strs.WriteBOM := AWriteBOM;
  520. strs.TrailingLineBreak := ATrailingLineBreak;
  521. strs.Text := Format(AIndentation);
  522. strs.SaveToFile(AFileName, TEncoding.UTF8);
  523. finally
  524. strs.Free;
  525. end;
  526. end;
  527. { TRJ }
  528. { ============================================================================ }
  529. end.