├── README
├── README.md
├── FluentJSONTest.dpr
├── FluentJSONTest.dproj
└── VSoft.Fluent.JSON.pas
/README:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Fluent JSON for Delphi
2 |
3 | This is just a simple fluent api for generating JSON. It doesn't serialize objects, or parse json.
4 |
5 |
--------------------------------------------------------------------------------
/FluentJSONTest.dpr:
--------------------------------------------------------------------------------
1 | program FluentJSONTest;
2 |
3 | {$APPTYPE CONSOLE}
4 |
5 | uses
6 | SysUtils,
7 | VSoft.Fluent.JSON in 'VSoft.Fluent.JSON.pas';
8 |
9 | procedure DoTest;
10 | var
11 | builder : IFluentJSONBuilder;
12 | begin
13 | builder := TFluentJSON.CreateJSONBuilder;
14 | builder.AddObject()
15 | .AddString('name1','value1\sdfgsdf')
16 | .AddString('name2','value2')
17 | .AddNumber('name3',1234)
18 | .AddNumber('name3',1234.5678)
19 | .AddObject('child')
20 | .AddString('name4','value4')
21 | .AddNumber('name5',5678)
22 | .Up
23 | .AddString('Another','fgdfgdf')
24 | .AddArray('Array')
25 | .AddString('element1')
26 | .AddNumber(123456789);
27 | Writeln(builder.ToString);
28 | Readln;
29 | end;
30 |
31 | begin
32 | try
33 | DoTest;
34 | except
35 | on E: Exception do
36 | Writeln(E.ClassName, ': ', E.Message);
37 | end;
38 | end.
39 |
--------------------------------------------------------------------------------
/FluentJSONTest.dproj:
--------------------------------------------------------------------------------
1 |
2 |
3 | {690E19FD-7488-4FFA-B0A6-AA40706C2FCA}
4 | 12.0
5 | FluentJSONTest.dpr
6 | Debug
7 | DCC32
8 |
9 |
10 | true
11 |
12 |
13 | true
14 | Base
15 | true
16 |
17 |
18 | true
19 | Base
20 | true
21 |
22 |
23 | WinTypes=Windows;WinProcs=Windows;DbiTypes=BDE;DbiProcs=BDE;DbiErrs=BDE;$(DCC_UnitAlias)
24 | FluentJSONTest.exe
25 | 00400000
26 | x86
27 |
28 |
29 | false
30 | RELEASE;$(DCC_Define)
31 | 0
32 | false
33 |
34 |
35 | 3
36 | DEBUG;madExcept;$(DCC_Define)
37 |
38 |
39 |
40 | MainSource
41 |
42 |
43 |
44 | Base
45 |
46 |
47 | Cfg_2
48 | Base
49 |
50 |
51 | Cfg_1
52 | Base
53 |
54 |
55 |
56 |
57 | Delphi.Personality.12
58 |
59 |
60 |
61 |
62 | FluentJSONTest.dpr
63 |
64 |
65 | False
66 | True
67 | False
68 |
69 |
70 | False
71 | False
72 | 1
73 | 0
74 | 0
75 | 0
76 | False
77 | False
78 | False
79 | False
80 | False
81 | 3081
82 | 1252
83 |
84 |
85 |
86 |
87 | 1.0.0.0
88 |
89 |
90 |
91 |
92 |
93 | 1.0.0.0
94 |
95 |
96 |
97 | Embarcadero DBExpress DataSnap Server Components
98 |
99 |
100 |
101 | 12
102 |
103 |
104 |
--------------------------------------------------------------------------------
/VSoft.Fluent.JSON.pas:
--------------------------------------------------------------------------------
1 | {***************************************************************************}
2 | { }
3 | { VSoft.Fluent.JSON }
4 | { }
5 | { Copyright (C) 2011 Vincent Parrett }
6 | { }
7 | { http://www.finalbuilder.com }
8 | { }
9 | { }
10 | {***************************************************************************}
11 | { }
12 | { Licensed under the Apache License, Version 2.0 (the "License"); }
13 | { you may not use this file except in compliance with the License. }
14 | { You may obtain a copy of the License at }
15 | { }
16 | { http://www.apache.org/licenses/LICENSE-2.0 }
17 | { }
18 | { Unless required by applicable law or agreed to in writing, software }
19 | { distributed under the License is distributed on an "AS IS" BASIS, }
20 | { WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. }
21 | { See the License for the specific language governing permissions and }
22 | { limitations under the License. }
23 | { }
24 | {***************************************************************************}
25 |
26 | unit VSoft.Fluent.JSON;
27 |
28 | interface
29 |
30 | type
31 | IFluentJSONBuilder = interface
32 | ['{9574F82E-B81D-49B9-AA04-60EB87C60E8B}']
33 | function AddObject : IFluentJSONBuilder;overload;
34 | function AddObject(const name : string) : IFluentJSONBuilder;overload;
35 | function AddNull(const name : string) : IFluentJSONBuilder;
36 | function AddString(const name : string; const value : string) : IFluentJSONBuilder;overload;
37 | function AddString(const value : string) : IFluentJSONBuilder;overload;
38 | function AddNumber(const name : string; const value : integer) : IFluentJSONBuilder;overload;
39 | function AddNumber(const name : string; const value : Double) : IFluentJSONBuilder;overload;
40 | function AddNumber(const value : integer) : IFluentJSONBuilder;overload;
41 | function AddNumber(const value : Double; const formatStr : string) : IFluentJSONBuilder;overload;
42 | function AddArray(const name : string) : IFluentJSONBuilder;overload;
43 | function AddArray : IFluentJSONBuilder;overload;
44 | function Up : IFluentJSONBuilder;
45 | function Mark : IFluentJSONBuilder;
46 | function Return : IFluentJSONBuilder;
47 | function ToString : string;
48 | end;
49 |
50 | //factory class
51 | TFluentJSON = class
52 | class function CreateJSONBuilder : IFluentJSONBuilder;
53 | end;
54 |
55 | implementation
56 |
57 | uses
58 | Generics.Collections,
59 | SysUtils;
60 |
61 | type
62 | TJSONElementType = (etObject,etArray,etString,etInteger,etDouble,etBoolean,etNull);
63 |
64 |
65 | TJSONElement = class
66 | public
67 | Parent : TJSONElement;
68 | ElementType : TJSONElementType;
69 | Members : TList;
70 | Name : string;
71 | FormatStr: string;
72 | StringValue : string;
73 | Value: record
74 | case TJSONElementType of
75 | etBoolean: (BoolValue: boolean);
76 | etDouble: (DoubleValue: double);
77 | etInteger: (IntegerValue: Int64);
78 | end;
79 |
80 | function GetIndentLevel : integer;
81 | function GetIndentString : string;
82 | constructor Create(const AElementType : TJSONElementType; const formatString : string = '');
83 | destructor Destroy;override;
84 | function JSONEscapeString(const value : string) : string;
85 | function ToString : string;override;
86 | end;
87 |
88 |
89 | TFluentJSONBuilder = class(TInterfacedObject,IFluentJSONBuilder)
90 | private
91 | FObjects : TList;
92 | FStack : TStack;
93 | FCurrentElement : TJSONElement;
94 | FMarkedObjects : TList;
95 | protected
96 | function AddObject : IFluentJSONBuilder;overload;
97 | function AddObject(const name : string) : IFluentJSONBuilder;overload;
98 | function AddNull(const name : string) : IFluentJSONBuilder;
99 | function AddString(const name : string; const value : string) : IFluentJSONBuilder;overload;
100 | function AddString(const value : string) : IFluentJSONBuilder;overload;
101 | function AddNumber(const name : string; const value : integer) : IFluentJSONBuilder;overload;
102 | function AddNumber(const name : string; const value : Double) : IFluentJSONBuilder;overload;
103 | function AddNumber(const value : integer) : IFluentJSONBuilder;overload;
104 | function AddNumber(const value : Double; const formatStr : string) : IFluentJSONBuilder;overload;
105 | function AddArray(const name : string) : IFluentJSONBuilder;overload;
106 | function AddArray : IFluentJSONBuilder;overload;
107 | function Up : IFluentJSONBuilder;
108 | function Mark : IFluentJSONBuilder;
109 | function Return : IFluentJSONBuilder;
110 | function ToString : string;override;
111 | public
112 | constructor Create;
113 | destructor Destroy;override;
114 | end;
115 |
116 | { TFluentJSON }
117 |
118 | class function TFluentJSON.CreateJSONBuilder: IFluentJSONBuilder;
119 | begin
120 | result := TFluentJSONBuilder.Create;
121 | end;
122 |
123 | { TFluentJSONBuilder }
124 |
125 | function TFluentJSONBuilder.AddArray(const name: string): IFluentJSONBuilder;
126 | var
127 | newElement : TJSONElement;
128 | begin
129 | Assert(FCurrentElement <> nil);
130 | Assert(FCurrentElement.ElementType in [etObject,etArray]);
131 | newElement := TJSONElement.Create(etArray);
132 | newElement.Name := name;
133 | newElement.Parent := FCurrentElement;
134 | if FCurrentElement <> nil then
135 | begin
136 | FCurrentElement.Members.Add(newElement);
137 | FStack.Push(FCurrentElement);
138 | end;
139 | FCurrentElement := newElement;
140 | result := Self;
141 | end;
142 |
143 |
144 |
145 | function TFluentJSONBuilder.AddNumber(const name: string; const value: Double): IFluentJSONBuilder;
146 | var
147 | newElement : TJSONElement;
148 | begin
149 | Assert(FCurrentElement <> nil);
150 | Assert(FCurrentElement.ElementType in [etObject,etArray]);
151 | newElement := TJSONElement.Create(etDouble);
152 | newElement.Name := name;
153 | newElement.Value.DoubleValue := value;
154 | newElement.Parent := FCurrentElement;
155 | if FCurrentElement <> nil then
156 | FCurrentElement.Members.Add(newElement);
157 | result := Self;
158 | end;
159 |
160 | function TFluentJSONBuilder.AddObject: IFluentJSONBuilder;
161 | begin
162 | result := AddObject('');
163 | end;
164 |
165 | function TFluentJSONBuilder.AddNumber(const name : string; const value: Integer): IFluentJSONBuilder;
166 | var
167 | newElement : TJSONElement;
168 | begin
169 | Assert(FCurrentElement <> nil);
170 | Assert(FCurrentElement.ElementType in [etObject,etArray]);
171 | newElement := TJSONElement.Create(etInteger);
172 | newElement.Name := name;
173 | newElement.Value.IntegerValue := value;
174 | newElement.Parent := FCurrentElement;
175 | if FCurrentElement <> nil then
176 | FCurrentElement.Members.Add(newElement);
177 | result := Self;
178 | end;
179 |
180 | function TFluentJSONBuilder.AddNumber(const value: integer): IFluentJSONBuilder;
181 | var
182 | newElement : TJSONElement;
183 | begin
184 | Assert(FCurrentElement <> nil);
185 | Assert(FCurrentElement.ElementType in [etArray]);
186 | newElement := TJSONElement.Create(etInteger);
187 | newElement.Name := '';
188 | newElement.Value.IntegerValue := value;
189 | newElement.Parent := FCurrentElement;
190 | if FCurrentElement <> nil then
191 | FCurrentElement.Members.Add(newElement);
192 | result := Self;
193 | end;
194 |
195 | function TFluentJSONBuilder.AddArray: IFluentJSONBuilder;
196 | var
197 | newElement : TJSONElement;
198 | begin
199 | Assert(FCurrentElement <> nil);
200 | Assert(FCurrentElement.ElementType in [etArray]);
201 | newElement := TJSONElement.Create(etArray);
202 | newElement.Name := '';
203 | newElement.Parent := FCurrentElement;
204 | if FCurrentElement <> nil then
205 | begin
206 | FCurrentElement.Members.Add(newElement);
207 | FStack.Push(FCurrentElement);
208 | end;
209 | FCurrentElement := newElement;
210 | result := Self;
211 | end;
212 |
213 | function TFluentJSONBuilder.AddNull(const name: string): IFluentJSONBuilder;
214 | var
215 | newElement : TJSONElement;
216 | begin
217 | Assert(FCurrentElement <> nil);
218 | Assert(FCurrentElement.ElementType in [etObject,etArray]);
219 | newElement := TJSONElement.Create(etNull);
220 | newElement.Name := name;
221 | newElement.Parent := FCurrentElement;
222 | if FCurrentElement <> nil then
223 | FCurrentElement.Members.Add(newElement);
224 | result := Self;
225 | end;
226 |
227 | function TFluentJSONBuilder.AddNumber(const value: Double; const formatStr: string): IFluentJSONBuilder;
228 | var
229 | newElement : TJSONElement;
230 | begin
231 | Assert(FCurrentElement <> nil);
232 | Assert(FCurrentElement.ElementType in [etArray]);
233 | newElement := TJSONElement.Create(etInteger);
234 | newElement.Name := '';
235 | newElement.Value.DoubleValue := value;
236 | newElement.Parent := FCurrentElement;
237 | if FCurrentElement <> nil then
238 | FCurrentElement.Members.Add(newElement);
239 | result := Self;
240 | end;
241 |
242 | function TFluentJSONBuilder.AddObject(const name: string): IFluentJSONBuilder;
243 | var
244 | newElement : TJSONElement;
245 | begin
246 | newElement := TJSONElement.Create(etObject);
247 | newElement.Parent := FCurrentElement;
248 | newElement.Name := name;
249 | if FCurrentElement = nil then
250 | FObjects.Add(newElement)
251 | else
252 | begin
253 | FCurrentElement.Members.Add(newElement);
254 | FStack.Push(FCurrentElement);
255 | end;
256 | FCurrentElement := newElement;
257 | result := Self;
258 | end;
259 |
260 | function TFluentJSONBuilder.AddString(const value: string): IFluentJSONBuilder;
261 | var
262 | newElement : TJSONElement;
263 | begin
264 | Assert(FCurrentElement <> nil);
265 | Assert(FCurrentElement.ElementType in [etArray]);
266 | newElement := TJSONElement.Create(etString);
267 | newElement.Name := '';
268 | newElement.StringValue := value;
269 | newElement.Parent := FCurrentElement;
270 | if FCurrentElement <> nil then
271 | FCurrentElement.Members.Add(newElement);
272 | result := Self;
273 | end;
274 |
275 | function TFluentJSONBuilder.AddString(const name, value: string): IFluentJSONBuilder;
276 | var
277 | newElement : TJSONElement;
278 | begin
279 | Assert(FCurrentElement <> nil);
280 | Assert(FCurrentElement.ElementType in [etObject,etArray]);
281 | newElement := TJSONElement.Create(etString);
282 | newElement.Name := name;
283 | newElement.StringValue := value;
284 | newElement.Parent := FCurrentElement;
285 | if FCurrentElement <> nil then
286 | FCurrentElement.Members.Add(newElement);
287 | result := Self;
288 | end;
289 |
290 | constructor TFluentJSONBuilder.Create;
291 | begin
292 | FObjects := TList.Create;
293 | FStack := TStack.Create;
294 | FMarkedObjects := TList.Create;
295 | FCurrentElement := nil;
296 | end;
297 |
298 | destructor TFluentJSONBuilder.Destroy;
299 | var
300 | element : TJSONElement;
301 | begin
302 | for element in FObjects do
303 | begin
304 | element.Free;
305 | end;
306 | FObjects.Free;
307 | FStack.Free;
308 | inherited;
309 | end;
310 |
311 | function TFluentJSONBuilder.Mark: IFluentJSONBuilder;
312 | begin
313 | result := Self;
314 | Assert(FCurrentElement <> nil);
315 | FMarkedObjects.Add(FCurrentElement);
316 | end;
317 |
318 | function TFluentJSONBuilder.Return: IFluentJSONBuilder;
319 | begin
320 | result := Self;
321 | Assert(FMarkedObjects.Count > 0);
322 | FCurrentElement := FMarkedObjects.Last;
323 | end;
324 |
325 | function TFluentJSONBuilder.ToString: string;
326 | var
327 | element : TJSONElement;
328 | begin
329 | for element in FObjects do
330 | result := result + element.ToString;
331 | end;
332 |
333 | function TFluentJSONBuilder.Up: IFluentJSONBuilder;
334 | begin
335 | if FStack.Count > 0 then
336 | FCurrentElement := FStack.Pop
337 | else
338 | FCurrentElement := nil;
339 | result := Self;
340 |
341 | end;
342 |
343 | { TJSONElement }
344 |
345 | constructor TJSONElement.Create(const AElementType: TJSONElementType; const formatString : string = '');
346 | begin
347 | ElementType := AElementType;
348 | if ElementType in [etObject,etArray] then
349 | Members := TList.Create
350 | else
351 | Members := nil;
352 | formatStr := formatString;
353 | end;
354 |
355 | destructor TJSONElement.Destroy;
356 | var
357 | element : TJSONElement;
358 | begin
359 | if Members <> nil then
360 | begin
361 | for element in Members do
362 | begin
363 | element.Free;
364 | end;
365 | Members.Free;
366 | end;
367 | inherited;
368 | end;
369 |
370 | function TJSONElement.GetIndentLevel: integer;
371 | var
372 | parentElement : TJSONElement;
373 | begin
374 | result := 0;
375 | parentElement := Self.Parent;
376 | while parentElement <> nil do
377 | begin
378 | Inc(result);
379 | parentElement := parentElement.Parent;
380 | end;
381 | end;
382 |
383 | function TJSONElement.GetIndentString: string;
384 | var
385 | count : integer;
386 | begin
387 | count := GetIndentLevel * 2;
388 | if count > 0 then
389 | result := StringOfChar(' ',count);
390 | end;
391 |
392 | function TJSONElement.JSONEscapeString(const value: string): string;
393 | var
394 | c : Char;
395 | i : integer;
396 | count : integer;
397 | begin
398 | result := '';
399 | count := Length(value);
400 | for i := 1 to count do
401 | begin
402 | c := value[i];
403 | case c of
404 | '"' : result := result + '\"';
405 | '\' : result := result + '\\';
406 | '/' : result := result + '\/';
407 | #8 : result := result + '\b';
408 | #9 : result := result + '\t';
409 | #10 : result := result + '\n';
410 | #12 : result := result + '\f';
411 | #13 : result := result + '\r';
412 | else
413 | //TODO : Deal with unicode characters properly!
414 | result := result + c;
415 | end;
416 | end;
417 | end;
418 |
419 | function TJSONElement.ToString: string;
420 | var
421 | member : TJSONElement;
422 | i : integer;
423 | sIndent : string;
424 | begin
425 | sIndent := GetIndentString;
426 | result := '';
427 | case ElementType of
428 | etObject:
429 | begin
430 | if Parent <> nil then
431 | result := sIndent + '"' + JSONEscapeString(Self.Name) + '":';
432 |
433 | result := result + '{';
434 | if Members.Count > 0 then
435 | begin
436 | result := result + #13#10;
437 | for i := 0 to Self.Members.Count - 1 do
438 | begin
439 | member := Self.Members[i];
440 | result := result + member.ToString;
441 | if i < Self.Members.Count - 1 then
442 | result := result + ',';
443 | result := result + #13#10;
444 | end;
445 | end;
446 | result := result + sIndent + '}';
447 | end;
448 | etArray:
449 | begin
450 | if Parent <> nil then
451 | begin
452 | case parent.ElementType of
453 | etObject: result := result + sIndent + '"' + JSONEscapeString(Self.Name) + '":[';
454 | etArray: result := result + sIndent + '[';
455 | end;
456 | end;
457 | if Members.Count > 0 then
458 | begin
459 | for i := 0 to Members.Count - 1 do
460 | begin
461 | member := Members[i];
462 | if i > 0 then
463 | result := result + ',' ;
464 | result := result + member.ToString;
465 | end;
466 | end;
467 | result := result + ']';
468 | end;
469 | etString:
470 | begin
471 | if ((Self.Parent <> nil) and (Self.Parent.ElementType = etArray)) or (Self.Name = '') then
472 | result := '"' + JSONEscapeString(Self.StringValue) + '"'
473 | else
474 | result := sIndent + '"' + JSONEscapeString(Self.Name) + '":"' + JSONEscapeString(Self.StringValue) + '"';
475 | end;
476 | etInteger:
477 | begin
478 | if ((Self.Parent <> nil) and (Self.Parent.ElementType = etArray)) or (Self.Name = '') then
479 | result := IntToStr(Self.Value.IntegerValue)
480 | else
481 | result := sIndent + '"' + JSONEscapeString(Self.Name) + '":' + IntToStr(Self.Value.IntegerValue);
482 | end;
483 | etDouble:
484 | begin
485 | if ((Self.Parent <> nil) and (Self.Parent.ElementType = etArray)) or (Self.Name = '') then
486 | result := FloatToStr(Self.Value.DoubleValue)
487 | else
488 | result := sIndent + '"' + JSONEscapeString(Self.Name) + '":' + FloatToStr(Self.Value.DoubleValue);
489 | end;
490 | etBoolean:
491 | begin
492 | if ((Self.Parent <> nil) and (Self.Parent.ElementType = etArray)) or (Self.Name = '') then
493 | result := LowerCase(BoolToStr(Self.Value.BoolValue,true))
494 | else
495 | result := sIndent + '"' + JSONEscapeString(Self.Name) + '":' + LowerCase(BoolToStr(Self.Value.BoolValue,true));
496 | end;
497 | etNull :
498 | begin
499 | if ((Self.Parent <> nil) and (Self.Parent.ElementType = etArray)) or (Self.Name = '') then
500 | result := 'null'
501 | else
502 | result := sIndent + '"' + JSONEscapeString(Self.Name) + '":null';
503 | end;
504 | end;
505 | end;
506 |
507 | end.
508 |
--------------------------------------------------------------------------------