Neon JSON Serialization for Delphi

repository·master·Indexed 43 days ago

https://github.com/paolo-rossi/delphi-neon

A high-performance JSON serialization library for Delphi designed for RESTful data exchange and serving as the default engine for the WiRL REST Library. It supports a wide range of Delphi types, including classes and records, and includes the Classify CLI tool for automatically generating Delphi entities from JSON documents.

Tokens
4.5K
Snippets
11
Records
19
Agent score
7%

What's inside Neon

  1. Overview of Neon JSON Serialization

    master
    Neon is a serialization library for Delphi designed to convert Delphi objects, records, and simple types to and from JSON. It is optimized for RESTful communication, ensuring that the exchanged data is pure JSON without extra metadata or injected fields. Neon is the default serialization engine used by the WiRL REST Library.
  2. Handle read-only and write-only members

    master

    Neon determines serialization and deserialization capability based on the RTTI read and write clauses of a property.

    Serialization (Writing JSON)

    By default, read-only properties are included in the JSON. To exclude them, use:

    LConfig := TNeonConfiguration.Default.SetIgnoreReadOnlyProps(True);

    Exemption: A read-only property of a class or interface type is always kept in the JSON to ensure composite entities (like collections) are preserved.

    Deserialization (Reading JSON)

    Deserialization is governed by whether a member is writable. A read-only property (one without a write clause or a setter) will be skipped during deserialization.

    Overriding Behavior

    If you need to change how a member is treated, use these attributes:

    • [NeonInclude(IncludeIf.Always)]: Forces serialization even if IgnoreReadOnlyProps is enabled. Note: This does not create a setter; the property will still be skipped during reading.
    • [NeonIgnore]: Explicitly drops the member from both reading and writing.
    • [NeonSetter('MethodName')]: Tells Neon to use a specific method to write the value, making the property effectively writable for deserialization.
    type
      TEntity = class
      private
        FVersion: string;
      public
        procedure SetVersionValue(const AValue: string);
    
        [NeonSetter('SetVersionValue')]
        property Version: string read FVersion;
      end;
  3. Register custom serializers for Delphi types

    master

    Neon supports most standard Delphi types, but specialized types like TGUID, TBytes, TCollection, or TDataSet require explicit registration of their serializers in the configuration.

    Important: TNeonConfiguration.Default starts with an empty registry. If you do not register a serializer, Neon falls back to generic RTTI handling, which often produces incorrect results for these types.

    To register serializers, use the RegisterSerializer method on your configuration object. It is recommended to use the INeonConfiguration.RegisterSerializer path to ensure the serializer's ChangeConfig hook is executed.

    Example registration:

    uses
      Neon.Core.Serializers.RTL,
      Neon.Core.Serializers.DB,
      Neon.Core.Serializers.VCL,
      Neon.Core.Serializers.Nullables;
    
    LConfig := TNeonConfiguration.Default
      .RegisterSerializer(TGUIDSerializer)
      .RegisterSerializer(TBytesSerializer)
      .RegisterSerializer(TStreamSerializer)
      .RegisterSerializer(TJSONValueSerializer)
      .RegisterSerializer(TCollectionSerializer)
      .RegisterSerializer(TDataSetSerializer)
      .RegisterSerializer(TImageSerializer);
    
    RegisterNullableSerializers(LConfig.GetSerializers);
    LConfig := TNeonConfiguration.Default
      .RegisterSerializer(TGUIDSerializer)
      .RegisterSerializer(TBytesSerializer)
      .RegisterSerializer(TStreamSerializer)
      .RegisterSerializer(TJSONValueSerializer)
      .RegisterSerializer(TCollectionSerializer)
      .RegisterSerializer(TDataSetSerializer)
      .RegisterSerializer(TImageSerializer);
    
    RegisterNullableSerializers(LConfig.GetSerializers);
  4. Profile Neon performance with TNeonLogger

    master

    You can profile the performance of the Neon engine using TNeonLogger. It tracks elapsed time and call counts for various internal sections (e.g., Serialize:Object, Core:GetNeonMembers).

    Profiling is disabled by default to minimize overhead. When enabled, timings are inclusive of nested calls.

    TNeonLogger.ProfileReset;
    TNeonLogger.ProfileEnabled := True;
    try
      LJSON := TNeon.ObjectToJSON(AObject, LConfig);
      LJSON.Free;
    finally
      TNeonLogger.ProfileEnabled := False;
    end;
    WriteLn(TNeonLogger.ProfileReport); // Outputs a report with Section | Calls | Total (ms) | Avg (us) | %
    TNeonLogger.ProfileReset;
    TNeonLogger.ProfileEnabled := True;
    try
      LJSON := TNeon.ObjectToJSON(AObject, LConfig);
      LJSON.Free;
    finally
      TNeonLogger.ProfileEnabled := False;
    end;
    WriteLn(TNeonLogger.ProfileReport);
  5. Use the Classify CLI to generate Delphi entities from JSON

    master

    The Classify command-line tool is a front-end for TNeonEntityGenerator. It reads a JSON document and automatically writes the corresponding Delphi classes or records required to hold that data.

    Basic Usage:

    Classify [options] <document.json>

    If you run Classify with no arguments, it provides a guided tour of the generator's capabilities. If you provide a JSON file but no options, it prints the generated Delphi code to standard output.

    ..
    \..	extbackslash Bin\Classify ..\..	extbackslash Data\customer.json
  6. Handle JSON documents with an array root

    master

    When the JSON document's root is an array (e.g., [{}, {}]), the items in the array are treated as the primary entity. The root name (set via -r) becomes the name of the items, and the container itself is aliased as <prefix><name>List (e.g., TOrderList).

    Example command:

    Classify -r Order -u Api.Orders --no-header ..\..\Data\orders.json
    ..
    \..	extbackslash Bin\Classify -r Order -u Api.Orders --no-header ..\..	extbackslash Data\orders.json
  7. Install Neon in your Delphi project

    master

    To install Neon, simply add the Source directory from the repository to your Delphi project search path. The library has no external dependencies, but requires the following Delphi units:

    • System.JSON (Delphi XE6+)
    • System.Rtti (Delphi 2010+)
    • System.Generics.Collections (Delphi 2009+)

    Tested compatibility includes Delphi 12 Athens, 11 Alexandria, 10.4 Sydney, 10.3 Rio, and 10.2 Tokyo. It should compile on Delphi XE7 and higher with minimal effort.

    # Add the 'Source' folder to your Delphi Project Path
  8. Explore Neon via Demos

    master

    The repository includes several demonstration projects to help you understand the library's capabilities:

    • Neon Main Demo: Demonstrates serialization/deserialization of simple types, records, classes, and Delphi-specific types like TStringList and TDataSet.
    • Neon Benchmarks Demo: Compares Neon's performance against other serialization engines.
    • Console Demos: A collection of command-line tools for advanced usage:
      • BenchmarksConsole: Compares Neon against REST.Json and System.JSON.Serializers (TJsonSerializer).
      • ProfilingConsole: Uses TNeonLogger to profile internal stages like RTTI resolution and member preparation.
      • Classify: Converts JSON documents into Delphi entities.
      • SchemaConsole: Generates JSON Schema Draft 2020-12 from Delphi types and validates JSON against them.
      • ReadOnlyConsole: Demonstrates handling of read-only and write-only members.
  9. Configure Neon serialization via INeonConfiguration

    master

    Neon provides extensive configuration through the INeonConfiguration interface. You can customize how Delphi members are mapped to JSON keys, how visibility is handled, and how specific types are processed.

    Key configuration options include:

    • Word Case: Set the JSON key casing using options like Unchanged, UPPERCASE, lowercase, PascalCase, camelCase, snake_case, kebab-case, or SCREAMING_SNAKE_CASE.
    • Member Types: Choose whether to serialize Fields, Properties, or both.
    • Member Visibility: Filter by private, protected, public, or published.
    • Ignore Field Prefix: An option to automatically strip the F prefix from private/protected fields (e.g., FFirstName becomes FirstName). Warning: TNeonConfiguration.Snake and .ScreamingSnake turn this on by default.
    • Read-only Properties: Use SetIgnoreReadOnlyProps(True) to omit properties that only have a read clause during serialization.
    • Map/Dictionary Sort Order: Control key ordering with natural, reverse, alphabetical, or reverse-alphabetical.
    • Custom Serializers: Register custom logic for specific types.
  10. Configure Neon settings

    master

    Neon uses an INeonConfiguration interface (instantiated via TNeonConfiguration.Default) to define how serialization behaves. You can configure member casing, which members to include, field prefixes, visibility, and custom serializers.

    Case Settings

    TNeonCase affects both member names and enum names:

    • TNeonCase.CamelCase: Converts Admin to "admin".
    • TNeonCase.SnakeCase: Converts VeryHighSpeed to "very_high_speed".
    • Note: Explicit [NeonEnumNames(...)] or [NeonProperty] attributes take precedence over these settings.

    Configuration Options

    • .SetMemberCase(TNeonCase): Sets the naming convention.
    • .SetMembers(TNeonMembers): Defines which member types to include (e.g., TNeonMembers.Properties).
    • .SetIgnoreFieldPrefix(Boolean): Handles prefixes like F (common in Delphi fields).
    • .SetVisibility([TVisibility]): Controls which RTTI visibility levels are processed (e.g., mvPublic, mvPublished).
    • .RegisterSerializer(TSerializer): Registers a custom serializer for specific types.
    var
      LConfig: INeonConfiguration;
    begin
      LConfig := TNeonConfiguration.Default
        .SetMemberCase(TNeonCase.SnakeCase)     // Case settings
        .SetMembers(TNeonMembers.Properties)    // Member type settings
        .SetIgnoreFieldPrefix(True)             // F Prefix settings
        .SetVisibility([mvPublic, mvPublished]) // Visibility settings
    
        // Custom serializer registration
        .RegisterSerializer(TGUIDSerializer)
      ;
    end;
  11. Generate Delphi records instead of classes

    master

    To generate Delphi record types instead of class types, use the -k record switch.

    Note: Records own nothing, so the generator will not produce constructors or destructors, and arrays of entities will always be generated as TArray<T> regardless of the --array setting.

    ..
    \..	extbackslash Bin\Classify -k record --no-header ..\..	extbackslash Data\customer.json
  12. Serialize and deserialize objects using TNeon

    master

    The TNeon utility class provides the simplest way to perform JSON operations. Use TNeon.ObjectToJSON to convert an object to a TJSONValue and TNeon.JSONToObject to populate an object from a TJSONValue using a configuration object.

    var
      LJSON: TJSONValue;
    begin
      // Serialization
      LJSON := TNeon.ObjectToJSON(AObject);
      try
        Memo1.Lines.Text := TNeon.Print(LJSON, True);
      finally
        LJSON.Free;
      end;
    end;
    
    // Deserialization
    var
      LJSON: TJSONValue;
    begin
      LJSON := TJSONObject.ParseJSONValue(Memo1.Lines.Text);
      try
        TNeon.JSONToObject(AObject, LJSON, AConfig);
      finally
        LJSON.Free;
      end;
    end;