Skip to content

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:

pascal
// Activate this directive to use Serialization with Delphi-Neon library
{$DEFINE DELPHI_NEON}

3. Add Neon Units to Uses Clause ​

pascal
uses
  Neon.Core.Types,
  Neon.Core.Nullables,
  Neon.Core.Attributes,
  Instant.Neon.Serializers;

Basic Serialization ​

Serialize Object to JSON ​

pascal
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 ​

pascal
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 ​

pascal
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 ​

pascal
TContact = class(TInstantObject)
  {$IFDEF DELPHI_NEON}[NeonInclude, NeonProperty('Category')]{$ENDIF}
  _Category: TInstantReference;
end;

This produces:

json
{
  "Category": {
    "ReferenceObject": "CAT001"
  }
}

Instead of:

json
{
  "_Category": {
    "ReferenceObject": "CAT001"
  }
}

NeonIgnore - Skip Property ​

pascal
TContact = class(TInstantObject)
private
  {$IFDEF DELPHI_NEON}[NeonIgnore]{$ENDIF}
  _InternalData: TInstantString;  // Won't be serialized
end;

Advanced Examples ​

Complete Model Class with Neon Attributes ​

pascal
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 ​

pascal
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 ​

pascal
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 ​

pascal
// 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 ​

pascal
// 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 ​

pascal
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 ​

pascal
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 ​

pascal
TContact = class(TInstantObject)
  {$IFDEF DELPHI_NEON}[NeonInclude, NeonProperty('created_at')]{$ENDIF}
  _CreatedAt: TInstantDateTime;
end;

// Configure date format globally
NeonConfig.SetUseUTCDate(True);

Best Practices ​

  1. Use Conditional Compilation - Always wrap Neon attributes in {$IFDEF DELPHI_NEON} to maintain compatibility
  2. Ignore Published Properties - Use [NeonIgnore] on published properties to avoid duplication
  3. Include Private Fields - Use [NeonInclude] on private attribute fields (_Name, _Email, etc.)
  4. Custom Names - Use [NeonProperty] for clean JSON property names
  5. Test Serialization - Verify round-trip serialization (Object → JSON → Object) preserves data
  6. Handle Nulls - Use Nullable types for optional fields
  7. 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 ​

Released under Mozilla License, Version 2.0.