JSON Serialization with Neon
InstantObjects integrates with the powerful delphi-neon library to provide advanced JSON serialization and deserialization capabilities. This enables modern REST API integration, flexible data exchange, and enhanced JSON storage options.
Overview
The Neon integration allows you to:
- Serialize InstantObjects business objects to JSON
- Deserialize JSON back to InstantObjects
- Customize JSON output with attributes
- Integrate with REST frameworks (MARS, WiRL)
- Control property visibility and naming
- Support complex nested structures
Installation and Configuration
1. Install delphi-neon Library
Download and install delphi-neon from: https://github.com/paolo-rossi/delphi-neon
Add the library path to your Delphi Library Path.
2. Enable DELPHI_NEON Directive
In InstantDefines.inc, enable the Neon support:
// Activate this directive to use Serialization with Delphi-Neon library
{$DEFINE DELPHI_NEON}3. Add Neon Units to Uses Clause
uses
Neon.Core.Types,
Neon.Core.Nullables,
Neon.Core.Attributes,
Instant.Neon.Serializers;Basic Serialization
Serialize Object to JSON
uses
Neon.Core.Persistence,
Neon.Core.Persistence.JSON;
var
Contact: TContact;
JSONValue: TJSONValue;
JSONString: string;
begin
Contact := TContact.Retrieve('CONT001');
try
// Serialize to TJSONValue
JSONValue := TNeon.ObjectToJSON(Contact);
try
// Convert to string
JSONString := TNeon.Print(JSONValue, True); // True = pretty print
Memo1.Lines.Text := JSONString;
finally
JSONValue.Free;
end;
finally
Contact.Free;
end;
end;Deserialize JSON to Object
var
Contact: TContact;
JSONString: string;
begin
JSONString := '{"_Name":"John Smith","_Email":"john@example.com"}';
Contact := TContact.Create;
try
TNeon.JSONToObject(Contact, JSONString);
// Object is now populated
ShowMessage(Contact.Name); // "John Smith"
// Save to database
Contact.Store;
finally
Contact.Free;
end;
end;Neon Attributes for InstantObjects
Use Neon attributes to control JSON serialization behavior:
NeonInclude - Include Property
TContact = class(TInstantObject)
{$IFDEF DELPHI_NEON}[NeonInclude]{$ENDIF}
_Category: TInstantReference;
published
{$IFDEF DELPHI_NEON}[NeonIgnore]{$ENDIF} // Don't serialize getter
property Category: TCategory read GetCategory write SetCategory;
end;NeonProperty - Custom JSON Name
TContact = class(TInstantObject)
{$IFDEF DELPHI_NEON}[NeonInclude, NeonProperty('Category')]{$ENDIF}
_Category: TInstantReference;
end;This produces:
{
"Category": {
"ReferenceObject": "CAT001"
}
}Instead of:
{
"_Category": {
"ReferenceObject": "CAT001"
}
}NeonIgnore - Skip Property
TContact = class(TInstantObject)
private
{$IFDEF DELPHI_NEON}[NeonIgnore]{$ENDIF}
_InternalData: TInstantString; // Won't be serialized
end;Advanced Examples
Complete Model Class with Neon Attributes
TContact = class(TInstantObject)
{IOMETADATA stored 'CONTACTS';
Name: String(50);
Email: String(100);
Category: Reference(TCategory);
Phones: Parts(TPhone);}
{$IFDEF DELPHI_NEON}[NeonInclude, NeonProperty('name')]{$ENDIF}
_Name: TInstantString;
{$IFDEF DELPHI_NEON}[NeonInclude, NeonProperty('email')]{$ENDIF}
_Email: TInstantString;
{$IFDEF DELPHI_NEON}[NeonInclude, NeonProperty('category')]{$ENDIF}
_Category: TInstantReference;
{$IFDEF DELPHI_NEON}[NeonInclude, NeonProperty('phones')]{$ENDIF}
_Phones: TInstantParts;
published
{$IFDEF DELPHI_NEON}[NeonIgnore]{$ENDIF}
property Name: string read GetName write SetName;
{$IFDEF DELPHI_NEON}[NeonIgnore]{$ENDIF}
property Email: string read GetEmail write SetEmail;
{$IFDEF DELPHI_NEON}[NeonIgnore]{$ENDIF}
property Category: TCategory read GetCategory write SetCategory;
{$IFDEF DELPHI_NEON}[NeonIgnore]{$ENDIF}
property Phones: TInstantParts read GetPhones;
end;Serializing Collections
var
Contacts: TObjectList<TContact>;
JSONArray: TJSONArray;
JSONString: string;
begin
Contacts := TObjectList<TContact>.Create(False);
try
// Retrieve all contacts
TInstantSelector.RetrieveObjects<TContact>(Contacts, 'SELECT * FROM TContact');
// Serialize list
JSONArray := TNeon.ObjectToJSON(Contacts) as TJSONArray;
try
JSONString := TNeon.Print(JSONArray, True);
Memo1.Lines.Text := JSONString;
finally
JSONArray.Free;
end;
finally
Contacts.Free;
end;
end;Custom Serialization Configuration
var
Contact: TContact;
NeonConfig: INeonConfiguration;
JSONValue: TJSONValue;
begin
Contact := TContact.Retrieve('CONT001');
try
// Configure Neon serialization
NeonConfig := TNeonConfiguration.Default;
NeonConfig.SetMembers([TNeonMembers.Standard, TNeonMembers.Fields]);
NeonConfig.SetMemberCase(TNeonCase.LowerCase);
NeonConfig.SetVisibility([mvPublic, mvPublished]);
// Serialize with custom configuration
JSONValue := TNeon.ObjectToJSON(Contact, NeonConfig);
try
ShowMessage(TNeon.Print(JSONValue, True));
finally
JSONValue.Free;
end;
finally
Contact.Free;
end;
end;REST Integration
MARS Curiosity Example
// Resource method in MARS REST server
[GET, Path('/contacts/{id}')]
function GetContact([PathParam] id: string): TContact;
begin
Result := TContact.Retrieve(id);
// MARS automatically serializes TContact to JSON using Neon
end;
[POST, Path('/contacts'), Consumes(TMediaType.APPLICATION_JSON)]
function CreateContact([BodyParam] AContact: TContact): TContact;
begin
AContact.Store; // Save to database
Result := AContact;
end;See MARS Curiosity REST Server for complete MARS examples.
WiRL Example
// Resource method in WiRL REST server
[GET, Path('/contacts/{id}')]
function GetContact([PathParam] id: string): TContact;
begin
Result := TContact.Retrieve(id);
// WiRL automatically serializes using Neon
end;See WiRL REST Server for complete WiRL examples.
Handling Special Cases
Nullable Values
uses Neon.Core.Nullables;
TContact = class(TInstantObject)
{$IFDEF DELPHI_NEON}[NeonInclude]{$ENDIF}
_BirthDate: TInstantDateTime;
function GetBirthDateNullable: NullableDateTime;
procedure SetBirthDateNullable(const Value: NullableDateTime);
end;
function TContact.GetBirthDateNullable: NullableDateTime;
begin
if _BirthDate.Value = 0 then
Result := NullableDateTime.Null
else
Result := _BirthDate.Value;
end;Enum Serialization
type
TPhoneType = (ptHome, ptMobile, ptOffice);
TPhone = class(TInstantObject)
{$IFDEF DELPHI_NEON}[NeonInclude, NeonEnumeration(TNeonEnumEncoding.String)]{$ENDIF}
_PhoneType: Integer; // Stores enum as integer
end;Date/Time Formatting
TContact = class(TInstantObject)
{$IFDEF DELPHI_NEON}[NeonInclude, NeonProperty('created_at')]{$ENDIF}
_CreatedAt: TInstantDateTime;
end;
// Configure date format globally
NeonConfig.SetUseUTCDate(True);Best Practices
- Use Conditional Compilation - Always wrap Neon attributes in
{$IFDEF DELPHI_NEON}to maintain compatibility - Ignore Published Properties - Use
[NeonIgnore]on published properties to avoid duplication - Include Private Fields - Use
[NeonInclude]on private attribute fields (_Name,_Email, etc.) - Custom Names - Use
[NeonProperty]for clean JSON property names - Test Serialization - Verify round-trip serialization (Object → JSON → Object) preserves data
- Handle Nulls - Use Nullable types for optional fields
- Version Your API - Consider JSON schema versioning for public APIs
Troubleshooting
Problem: Properties appear twice in JSON
- Solution: Add
[NeonIgnore]to published properties, only include private fields
Problem: References serialize with full object instead of ID
- Solution: This is correct behavior for Part attributes; use Reference for ID-only
Problem: Circular references cause infinite loop
- Solution: Use
[NeonIgnore]on back-references or configure max depth
Problem: Dates serialize as numbers
- Solution: Configure
NeonConfig.SetUseUTCDate(True)for ISO 8601 format
Performance Considerations
- Lazy Loading - References and Parts load on demand; control depth with configuration
- Large Collections - Consider pagination for large datasets
- Memory - Deserializing large JSON creates many temporary objects
- Caching - Cache frequently serialized objects to improve performance
See Also
- JSON Broker - JSON file-based storage
- WiRL REST Server - Building REST APIs with WiRL
- MARS Curiosity REST Server - Building REST APIs with MARS
- delphi-neon Documentation - Complete Neon reference
