Skip to content

NeonInclude

Paolo Rossi edited this page Feb 11, 2024 · 1 revision

NeonInclude Attribute

The NeonInclude attribute helps you to define fields inclusion rules. To fully understand it let’s have a look at the attribute’s definition in the Neon.Core.Attributes.pas unit:

/// <summary>
///   The IncludeIf enum values define when to include the field or property.
/// </summary>
IncludeIf = (
  /// <summary>
  ///   Include the member if it's not nil
  /// </summary>
  NotNull,
  /// <summary>
  ///   Include the member if the value it's not empty
  /// </summary>
  NotEmpty,
  /// <summary>
  ///   Include the member if it's value it's not the default value
  /// </summary>
  NotDefault,
  /// <summary>
  ///   Include the member always
  /// </summary>
  Always,
  /// <summary>
  ///   Include the member based on the result of the function specified as string
  ///   (default function is ShouldInclude)
  /// </summary>
  CustomFunction);

TIncludeValue = record
  Present: Boolean;
  Value: IncludeIf;
  IncludeFunction: string;
end;

/// <summary>
///   The Neon annotation NeonInclude tells Neon to include the property (or field)
///   based on the value of the enumeration Include
/// </summary>
NeonIncludeAttribute = class(TCustomAttribute)
private
  FIncludeValue: TIncludeValue;
public
  constructor Create(AIncludeValue: IncludeIf = IncludeIf.Always; const AIncludeFunction: string = 'ShouldInclude');
  property IncludeValue: TIncludeValue read FIncludeValue write FIncludeValue;
end;

As you can see the attribute can have 2 parameters: the first parameter controls when to include the field or property:

  1. NotNull Tells Neon to include the value only if it’s a non nil value. It’s useful for objects and nullable values.
  2. NotEmpty Tells Neon to include the value only if it’s not Empty. Useful on arrays, lists, generic collections, objects, records, strings, etc…
  3. NotDefault Tells Neon to include the value only if it has a non default value, for example 0 for an integer field.
  4. Always Tells Neon to always include the value. It’s the default setting.
  5. CustomFunction Tells Neon to include the value based on the result (True or False) of a custom function.

The second parameter lets you specify the name of the custom function and it has meaning only if the first parameter is CustomFunction

Using the NeonInclude attribute

Always including a value

The simplest way to use the NeonInclude attribute is to use it without additional parameters or specifying IncludeIf.Always as value for the first parameter . Let’s say you have a private field that you want to include in the resulting JSON:

TPerson = record
private
  InternalField1: Integer;
  InternalField2: Integer;
	[NeonInclude]
  InternalField3: Integer;  
public
  Name: string;
	Age: Integer;
end;

If you serialize the struct the resulting JSON will be:

{
  "InternalField3": 55,
  "Name": "Paolo",
  "Age": 50
}

This is the most simple use of the NeonInclude attribute, now if you want more flexibility you must start using the parameter IncludeIf.

Including a value only if it’s not nil

If you don’t want to serialize object reference that are not assigned, you have to use IncludeIf.NotNull as a first parameter. This setting applies to object references and nullable values.

TPerson = class
  private
    FAddresses: TStringList;
    FAge: Integer;
    FName: string;
  public
    property Age: Integer read FAge write FAge;
    property Name: string read FName write FName;
    [NeonInclude(IncludeIf.NotNull)]
    property Addresses: TStringList read FAddresses write FAddresses;
  end;

In this example if (for some reason) the field Addresses is nil the field doesn’t get serialized.

Including a value only if it’s not empty

Well, let’s assume you have a list or an array of elements and you don’t want to serialize that list if the list doesn’t have any items, or perhaps you have an object and sometimes because of serialization’s rules you have no field to serialize resulting in an empty JSON object. To avoid all that you can use the IncludeIf.NotEmpty as a first parameter.

TCommand = record
public
	Name: string;
	Parameters: string;
	[NeonInclude(IncludeIf.NotEmpty)]
	Packet: TArray<Integer>;
end;

The IncludeIf.NotEmpty value is effective with: arrays, lists, generic lists, object, records, strings, dates.

Including a value only if has not a default value

There is a bit of an overlap on the meaning of NotDefault and NotEmpty. The NotDefault value applies also on Integer, Int64 and Float, so when the value is 0 the field doesn’t get serialized.

The NotDefault applies also to arrays, lists, generic lists, object, records, strings, dates.

TCommand = record
public
	Name: string;
	Parameters: string;
	[NeonInclude(IncludeIf.NotDefault)]
	Value: Integer;
end;

In this example the resulting JSON will not have the Value field if the content of the field is 0

Including a value based on custom conditions

The previous parameters of the NeonInclude attribute deal with “fixed” condition such as nil values, empty values, default values; instead if you want to include/ignore a field based on custom conditions (other fields value, a state in your application, anything…) you have to provide some code to do that!

The last value of the enum IncludeIf is CustomFunction and that means that you have to provide the function name to be called in the next parameter of the attribute:

TPerson = class
  private
    FName: string;
    FAge: Integer;
    FTitle: string;

    function ShouldInclude(const AContext: TNeonIgnoreIfContext): Boolean;
  public
    property Age: Integer read FAge write FAge;
    property Name: string read FName write FName;
    [NeonInclude(IncludeIf.CustomFunction, 'ShouldInclude')]
    property Title: string read FTitle write FTitle;
  end;

Here is an example of the ShouldInclude function

function TPerson.ShouldInclude(const AContext: TNeonIgnoreIfContext): Boolean;
begin
  Result := False;

  // You can filter by the member name
  if SameText(AContext.MemberName, 'Title') then
  begin
    // And you can filter on additional conditions
    if Age > 18 then
      Result := True;
  end

  // You can reuse (only if you want) the same function for several members
  else if SameText(AContext.MemberName, 'Name') then
  begin
    Result := True;
  end;
end;

As you can see the function to be called has a determined signature:

function ShouldInclude(const AContext: TNeonIgnoreIfContext): Boolean

The function can address more than one field, you get the member’s name in the AContext parameter, along with the operation type (Serialize, Deserialize). The ShouldInclude name is the default so you can write:

  function ShouldInclude(const AContext: TNeonIgnoreIfContext): Boolean;
public
	[NeonInclude(IncludeIf.CustomFunction)]
  property Title: string read FTitle write FTitle;
end;
Clone this wiki locally