A user-friendly JSON library for Free Pascal with automatic reference counting (ARC) support. This library provides an intuitive API for working with JSON data while ensuring proper memory management through interfaces.
Note
This library was extracted from TidyKit-FP. It contains only the JSON functionality from the original TidyKit library.
- Interface-based design for automatic memory management
- Comprehensive JSON parsing and generation
- Support for all JSON data types (objects, arrays, strings, numbers, booleans, null)
- Pretty printing and compact output options
- Full Unicode support with proper escape sequence handling
- Error handling with descriptive messages
- Easy-to-use factory methods
- Compatible with Free Pascal 3.2.2
- Thoroughly tested with 41 comprehensive test cases (including stress, fuzz, and RFC-compliance tests)
SimpleJSON-FP strictly adheres to the JSON standards defined in:
- ECMA-404 (The JSON Data Interchange Syntax)
- RFC 8259 (The JavaScript Object Notation (JSON) Data Interchange Format)
- No leading zeros (except for 0)
- Decimal point must be followed by at least one digit
- Scientific notation supported (e.g., 1.23e2, 1.23e-2)
- No octal or hexadecimal notation
- No special values (NaN, Infinity)
- Proper handling of integer range limits
- Proper handling of decimal points and trailing zeros
- Double quotes required
- Full escape sequence support:
- Control characters (\b, \f, \n, \r, \t)
- Unicode escapes (\uXXXX)
- Quotation mark (")
- Reverse solidus (\)
- Solidus (/)
- Proper handling of surrogate pairs for characters outside BMP
- No unescaped control characters (0x00 through 0x1F)
- Objects: { }
- Arrays: [ ]
- Name separator: :
- Value separator: ,
- No trailing commas
- No comments allowed
- Full UTF-16 support
- Proper surrogate pair handling
- \u escape sequence support for all Unicode characters
- Validation of surrogate pairs
- Proper encoding of control characters
- Property order preservation in objects (not required by standard)
- Pretty printing with configurable indentation
- Compact output option
- Type-safe access through interfaces
- Memory safety through ARC
- Detailed error messages for parsing failures
- Numbers stored as Double (64-bit) for maximum precision
- Strict validation of number formats during parsing
- Improved scanner diagnostics and error messages for malformed numbers (position information)
- Singleton implementation for null values
- Interface-based memory management
- Lazy parsing of JSON strings
- Ordered property storage using TDictionary and TList
- Comprehensive error checking during parsing and type conversion
To use SimpleJSON-FP in your project:
- Add the SimpleJSON-FP source directory to your project's search path
- Add the following unit to your uses clause:
uses SimpleJSON; // All JSON functionality
That's it! The SimpleJSON unit provides everything you need for working with JSON, including:
- All JSON interfaces (IJSONValue, IJSONObject, IJSONArray)
- The TJSON factory class for creating and parsing JSON
- Exception handling through EJSONException
var
Person: IJSONObject;
Address: IJSONObject;
Hobbies: IJSONArray;
begin
// Create a person object
Person := TJSON.Obj;
Person.Add('name', 'John Smith');
Person.Add('age', 30);
Person.Add('isActive', True);
// Create and add an address object
Address := TJSON.Obj;
Address.Add('street', '123 Main St');
Address.Add('city', 'Springfield');
Address.Add('zipCode', '12345');
Person.Add('address', Address);
// Create and add a hobbies array
Hobbies := TJSON.Arr;
Hobbies.Add('reading');
Hobbies.Add('cycling');
Hobbies.Add('swimming');
Person.Add('hobbies', Hobbies);
// Convert to JSON string (pretty-printed)
WriteLn(Person.ToString(True));
end;var
JSON: string;
Value: IJSONValue;
Person: IJSONObject;
begin
JSON := '{"name":"Jane Doe","age":25,"skills":["Pascal","Python"]}';
// Parse JSON string
Value := TJSON.Parse(JSON);
Person := Value.AsObject;
// Access values
WriteLn('Name: ', Person['name'].AsString);
WriteLn('Age: ', Person['age'].AsInteger);
WriteLn('First Skill: ', Person['skills'].AsArray[0].AsString);
end;var
Success: Boolean;
Value: IJSONValue;
begin
// Using TryParse
Success := TJSON.TryParse('{invalid json}', Value);
if not Success then
WriteLn('Failed to parse JSON');
// Using exception handling
try
Value := TJSON.Parse('[1,2,]'); // Trailing comma
except
on E: EJSONException do
WriteLn('Error: ', E.Message);
end;
end;TJSON.Obj: Create an empty JSON objectTJSON.Arr: Create an empty JSON arrayTJSON.Str(Value: string): Create a JSON string valueTJSON.Num(Value: Double): Create a JSON number valueTJSON.Int(Value: Integer): Create a JSON integer valueTJSON.Bool(Value: Boolean): Create a JSON boolean valueTJSON.Null: Create a JSON null valueTJSON.Parse(JSON: string): Parse a JSON stringTJSON.TryParse(JSON: string; out Value: IJSONValue): Try to parse a JSON stringTJSON.PrettyPrint(JSON: string): Format JSON with indentationTJSON.Compact(JSON: string): Format JSON without whitespace
Base interface for all JSON values:
IJSONValue = interface
function GetAsString: string;
function GetAsNumber: Double;
function GetAsInteger: Integer;
function GetAsBoolean: Boolean;
function GetAsObject: IJSONObject;
function GetAsArray: IJSONArray;
function IsString: Boolean;
function IsNumber: Boolean;
function IsBoolean: Boolean;
function IsObject: Boolean;
function IsArray: Boolean;
function IsNull: Boolean;
function ToString(Pretty: Boolean = False): string;
property AsString: string read GetAsString;
property AsNumber: Double read GetAsNumber;
property AsInteger: Integer read GetAsInteger;
property AsBoolean: Boolean read GetAsBoolean;
property AsObject: IJSONObject read GetAsObject;
property AsArray: IJSONArray read GetAsArray;
end;Interface for JSON objects:
IJSONObject = interface(IJSONValue)
function GetValue(const Name: string): IJSONValue;
procedure SetValue(const Name: string; Value: IJSONValue);
function GetCount: Integer;
function GetNames: TStringArray;
procedure Add(const Name: string; Value: IJSONValue); overload;
procedure Add(const Name: string; const Value: string); overload;
procedure Add(const Name: string; Value: Integer); overload;
procedure Add(const Name: string; Value: Double); overload;
procedure Add(const Name: string; Value: Boolean); overload;
procedure Remove(const Name: string);
function Contains(const Name: string): Boolean;
property Values[const Name: string]: IJSONValue read GetValue write SetValue; default;
property Count: Integer read GetCount;
property Names: TStringArray read GetNames; // Returns keys in insertion order
end;ExtractValue(Obj: IJSONObject; const Name: string): IJSONValue is a small helper that
returns the value associated with Name and removes the key from Obj.
Because values are returned as IJSONValue (interfaces), no manual freeing is
required — the returned reference keeps the value alive.
Example:
var
Val: IJSONValue;
begin
Val := ExtractValue(Obj, 'key'); // Obj no longer contains 'key'
if Assigned(Val) then
WriteLn(Val.AsString);
end;Interface for JSON arrays:
IJSONArray = interface(IJSONValue)
function GetItem(Index: Integer): IJSONValue;
procedure SetItem(Index: Integer; Value: IJSONValue);
function GetCount: Integer;
procedure Add(Value: IJSONValue); overload;
procedure Add(const Value: string); overload;
procedure Add(Value: Integer); overload;
procedure Add(Value: Double); overload;
procedure Add(Value: Boolean); overload;
procedure Delete(Index: Integer);
procedure Clear;
property Items[Index: Integer]: IJSONValue read GetItem write SetItem; default;
property Count: Integer read GetCount;
end;The library includes 41 comprehensive test cases that verify its functionality:
- Test01_CreateEmptyObject: Creating and verifying empty JSON objects
- Test02_CreateEmptyArray: Creating and verifying empty JSON arrays
- Test03_CreateString: String value creation and verification
- Test04_CreateNumber: Numeric value handling
- Test05_CreateBoolean: Boolean value handling
- Test06_CreateNull: Null value handling
- Test07_ObjectAddAndGet: Object property manipulation
- Test08_ArrayAddAndGet: Array element manipulation
- Test09_ParseSimpleObject: Basic object parsing
- Test10_ParseSimpleArray: Basic array parsing
- Test11_ParseComplexObject: Nested object parsing
- Test12_ParseComplexArray: Nested array parsing
- Test13_ParseInvalidJSON: Error handling for invalid JSON
- Test14_PrettyPrint: JSON formatting with indentation
- Test15_Compact: JSON compression
- Test16_UnicodeString: Unicode character handling
- Test17_EscapeSequences: Special character escape sequences
- Test18_StrictNumberFormat: Strict JSON number format compliance
- Test19_IntFactory: Integer factory method behavior
- Test20_TryParseSuccess:
TryParsesuccess cases for all types - Test21_PropertyNameEscaping: Property names with special characters roundtrip
- Test22_ObjectContainsAndModify: Object contains, set and nil handling
- Test23_ArrayModification: Array set, clear and roundtrip behavior
- Test24_KeyOrderPreservation: Object key insertion order preservation
- Test25_SurrogatePairs: Surrogate pair and emoji handling
- Test26_InvalidPlusSign: Ensures leading
+is rejected in numbers - Test27_LeadingPlusExponent: Ensures
1e+10is accepted (plus in exponent valid) - Test28_SingleQuoteString: Verifies single quoted strings are rejected
- Test29_TrailingGarbage: Verifies trailing content after JSON text is rejected
- Test30_BOMHandling: Tests UTF-8 BOM handling (accept/reject)
- Test31_RejectComments: Ensures comments are rejected (strict RFC parsing)
- Test32_MaxNestingDepth: Deep nesting stress tests
- Test33_LongString: Very long string roundtrip including special characters
- Test34_LargeArray: Large arrays and diagnostics on failure
- Test35_RoundtripWriterParser: Writer+Parser roundtrip consistency (compact & pretty)
- Test36_DuplicateKeys: Handling of duplicate keys (last-in-wins semantics)
- Test37_SolidusEscape: Solidus escape behavior (/ vs /)
- Test38_UnicodeHexCase: Unicode hex case-insensitivity \u00ff == \u00FF
- Test39_BigExponents: Large/small exponent parsing and roundtrip validation
- Test40_FuzzBasic: Fuzz testing to validate no crashes on malformed input
- Test41_SingleZeroNumber: Scanning and parsing single '0' token, including arrays/objects
- Validates correct number formats (integers, decimals, scientific notation)
- Verifies rejection of invalid formats (leading zeros, trailing decimals)
- Tests number range and precision handling
-
Use Interface References: Always use interface types (IJSONValue, IJSONObject, IJSONArray) instead of concrete classes to ensure proper memory management.
-
Error Handling: Use TryParse when you want to handle parsing errors gracefully, or wrap Parse calls in try-except blocks.
-
Type Checking: Always check value types before conversion:
if Value.IsObject then // Use Value.AsObject else if Value.IsArray then // Use Value.AsArray
-
Memory Management: Let the interface references handle memory management. Don't try to manually free JSON values.
-
String Formatting: Use ToString(True) for human-readable output and ToString(False) for compact storage.
See the examples/json_example.pas file for a complete working example that demonstrates:
- Creating JSON objects and arrays
- Parsing JSON strings
- Modifying JSON data
- Error handling
- Pretty printing and compact output
Contributions are welcome! Please feel free to submit a Pull Request on the SimpleJSON-FP repository.
This library is licensed under the MIT License - see the LICENSE file for details.