├── 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 | --------------------------------------------------------------------------------