JSON Serialization with Neon - Part 1

Welcome to the first part of the JSON Serialization with Neon series that will show you how to serialize Delphi types (simple types, objects, records, etc...) into JSON and deserialize JSON strings (or JSON objects) into Delphi types.

This first part serves as an introduction for the Neon JSON Serializer Library for Delphi.


What is serialization?

Serialization is the process of converting objects into a series of bytes that saves the state in an easily transmittable form that can be a file, a stream or, for example, sent over TCP/HTTP. Deserialization is, of course, the reverse process: constructing a data structure or object from a stream of bytes.

serialization-diagram-800x364-1

For example, if you're creating a REST web service in Delphi that reads or returns JSON, you need a library to convert Delphi objects (records, simple types) into JSON and vice-versa.

Delphi already has some serialization engines, the first that comes to mind is the TComponent-based persistence mechanism that writes and reads Delphi objects into and from DFM files, another one is the XML-based persistence used in SOAP Web Services.

Why JSON?

JSON

JSON stands for JavaScript Object Notation and is a text-based data interchange format that is lightweight, language-independent, and easy for humans to read and write. JSON is used for enterprise messaging, communicating with RESTful web services, and AJAX-based communications.

A full JSON tutorial is outside the scope of this article, but if you want to learn more about JSON I can recommend some resources:

JSON is also extensively used by NoSQL databases such as MongoDB, DynamoDB, Cosmos DB, etc... to store records as JSON documents. Traditional relational databases, such as PostgreSQL or MSSQL are also constantly gaining more JSON capabilities. Oracle Database also supports JSON data natively with SQL features, such as transactions, indexing, declarative querying, and views.

REST & JSON

In REST development, you will often (more like always) need to read data in JSON format, or provide JSON data as an output and of course you don't wan to create your JSON manually concatenating strings!

JSON is also used in another important technology: OpenAPI. OpenAPI is based on JSON (or YAML) and JSON-Schema. Neon has support for JSON-Schema generation from Delphi data structures (objects, records).

JSON Schema

If you are interested in generating OpenAPI documentation from Delphi please have a look at my project OpenAPI for Delphi

OpenAPI for Delphi

Why Neon?

Neon

Initially I wrote Neon because the TJSON.* based serialization engine was not nearly enough to meet my needs, also in the process of writing the WiRL REST library I was facing the problem of data exchange: I needed a JSON (de)serialization engine that was able to manage without (or very little) configuration and information about the data exchanged.

The first constraint was that the engine had to be able to manage data without "metadata", so no $type or $class extrafields. As a matter of fact REST services are supposed to work only with data with no external information "embedded" in it and for sure public web services present pure data (resources) as interface.

An then... Neon was born!

NeonDemo

Why use Neon?

So, let's see what are the reasons to start using Neon in your Delphi applications instead of the standard TJSON engine:

Features

  • Easy to use: Neon provides high level API to simplify common use cases
  • Works on existing (PODO) classes, no constraints on the inheritance tree
  • Perfect fit for REST technologies (already used in WiRL REST library)
  • Feature-rich: Neon has a wide range of attributes in order to address complex use cases
  • Highly configurable: You can configure a lot of parameters about JSON output
  • Quite mature and reliable: the project started 4 years ago, in 2018 and was released to public domain in 2019.
  • No mapping info needed(but of course you can): Neon provides default mapping for most of the Delphi objects
  • Clean JSON: Neon creates a clean and compact JSON results which is easy to read
  • Support for JSON-Schema
  • Unit tests coverage (although not 100%)
  • Very good performance (in comparison with the TJSON engine)
  • No Dependency: Neon library does not require any other library apart from the Delphi standard RTL
  • Open Source: Neon library is open source and is free to use and supports many Delphi versions
  • Widely adopted and used by other Delphi libraries and applications: You can use Neon directly, but you are likely to encounter its functionalities exposed by other libraries and frameworks.

Performance

Although Neon was built with some features in mind, is also considerably faster than Delphi TJSON serializer engine. In the next part of this article I will show the code and the results of the benchmarks.

Ok but what you can use Neon for? What are the typical use cases?

Use cases

  • REST services with (JSON) data exchange
  • Application with TCP or HTTP communication with (JSON) data exchange
  • Use JSON files as configuration for your applications (opposed to INI files)
  • Objects persistence (JSON) in databases (or files)
  • Objects exchange between application (even built in different programming languages)

Delphi Types Support

JSON has very few data types, but sometimes it's hard to guess and convert the right correspondent Delphi type.

JSON / Delphi Types

Neon has an extensive support of Delphi types: basic types, complex types, Delphi standard classes (TDataSet, TStringList, etc...), let's see a detailed list:

Basic types:

  • String
  • Integer
  • Float (Single, Double, etc...)
  • Boolean
  • Date and Time
  • Enumerations (1)
  • Variants
  • etc...

Complex values:

  • Dynamic Arrays of (basic types, records, classes, etc...) (2)
  • Records with fields of (basic types, records, classes, arrays, etc...)
  • Objects with fields of (basic types, records, classes, arrays, etc...)
  • Generic lists
  • Dictionaries (3)
  • DataSets
  • Streamable classes

Attributes

If you can't change your class but you want to change the JSON output, you can use the attributes that Neon provides. With these attributes you can modify the property name, the string case, you can ignore or include a property, you can control if a property must be serialized or not or even flatten a sub-object on the parent properties. As you can see with attributes you can heavily modify the JSON output without writing code!
In the next part of this series I will go through all the attributes you can use with Neon with detailed explanation and examples.

Custom Serializers

If your Delphi class is way too complex and even attributes cannot help, yoou can always write a custom serializer and register along with the type in the Neon Engine, now every time that the engine encounter the type you registered uses the code in your custom serializer. Writing a custom serializer is very easy, you have to inherit from TCustomSerializer, implement a couple of methods and finally register this new class in the Neon configuration. Custom serializers will be the subject of another post.

Notes

  1. Neon serialize Delphi enums by their name or even with custom strings provided with an attribute!
  2. Neon can't serialize static arrays because Delphi doesn't generate Rtti information for these
  3. There is a constraint (imposed by JSON not Neon) on serializing a TDictionary<K, V> in Delphi: the key type must be a string (or you must be able to convert to a string)

How to install Neon

  • Download Neon from GitHub
  • Add Neon/Source to the Library Path (alternatively use Boss package manager)
  • Start serializing objects!

Prerequisites

This library has been tested with Delphi 11 Alexandria, Delphi 10.4 Sydney, Delphi 10.3 Rio and Delphi 10.2 Tokyo, but with a minimum amount of work it should compile with Delphi XE7 and higher.

Libraries/Units dependencies

This library has no dependencies on external libraries or units,.

Delphi (2007+) units used:

  • System.JSON (DXE6+)
  • System.Rtti (D2010+)
  • System.Generics.Collections (D2009+)

Show me some code!

After this long introduction let's finally see some basic code (the other installments of this article will be focused more on code though)

Simple object serialization

The easiest way to use Neon is through the static class TNeon and its methods. Please remember to have a look at the Neon Main Demo that demonstrate the extensive support for all types.

Object to JSON

The first step is to use the serializer in order to transform a Delphi object in a string (in JSON format), let's see how it's done with Neon (the easy way):


type
  TLanguage = class
  private
    FName: string;
    FAge: Integer;
  public
    property Name: string read FName write FName;
    property Age: Integer read FAge write FAge;
  end;

.......

begin
  // Assume the Language object is already created and initialized
  memoLog.Lines.Text := TNeon.ObjectToJSONString(Language);
end;

Yeah, that's it! and that will produce the following output:

{
  "Name": "Delphi",
  "Age": 27
}

JSON to Object

Given the same TLanguage type let's see how to deserialize a JSON string into an object.


begin
  LJSON := '{"Name":"Delphi","Age":27}';
  Language := TNeon.JSONToObject<TLanguage>(LJSON);

  // Language.Age is 27
  // Language.Name is Delphi
end;

In this code Neon even create a new object for you and then set the properties with the values found in the JSON string.

If you have already an object and you only want to set its properties from JSON, you simply write:

begin
  LJSON := '{"Name":"Delphi","Age":27}';
  TNeon.JSONToObject(Language, LJSON);

  // Language.Age is 27
  // Language.Name is Delphi
end;

Value to JSON

Yes, Neon works for value-based types too. All you have to do in order to serialize a record (for example) is to call the ValueToJSONString() of the static class TNeon.

type
  TLanguage = record
    Name: string;
    Age: Integer;
    Kind: string;
  end;

...
  
begin
  memoLog.Lines.Text := TNeon.ValueToJSONString(TValue.From<TLanguage>(Language));
end;

Again, that's it! And the output will be:

{
  "Name": "Delphi",
  "Age": 27,
  "Kind": "Compiled"
}

JSON to Value

Given the same TLanguage type let's see how to deserialize a JSON string into an object.


begin
  LJSON := '{"Name":"Delphi","Age":27}';
  Language := TNeon.JSONToValue<TLanguage>(LJSON);

  // Language.Age is 27
  // Language.Name is Delphi
end;


Next Article

In the next post we are going to explore the features that (IMHO) are setting the Neon engine apart. Also we are going to talk about how Neon compares in term of performance with the standard TJSON provided with Delphi (in the REST unit).

Stay tuned!