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.