# How to use

Serialization is the process of translating a data structure into a sequence of bytes. We can serialize both simple types and real-world object models, which are described as classes and are called business objects or entities, and meet the following conditions:

  • serializable class contains a public constructor without parameters;
  • each business object class is associated with BusinessObjectId — a unique integer identifier of the ulong type;
  • serializable class properties are public and have a mapping to TagId — a unique integer property identifier within the business object class (of type ulong).

The BusinessObjectId and TagId matchings are done by annotating the data with attributes or using the Fluent API.

# Data Annotations

Attributes provide a powerful means for associating metadata or declarative information with the code and can be used to map class identifiers to properties of business objects. In case you have full control over the code, this approach may be the most preferable.

The following attributes are defined for this approach:

  • BusinessObjectIdAttribute — sets the value of the unique identifier of the business object class (it is used during serialization and will be ignored for deserialization);

  • TagIdAttribute — sets the value of the unique property identifier within the class;

  • DateTimeEncodingRuleAttribute — is for properties with types DateTime and DateTimeOffset and allows you to define an encoding rule (DateOnly, DateTime, DateTimeOffset);

  • EnumAsStringAttribute — is for properties with type enumeration (enum) and sets the encoding rule as a string in UTF-8 encoding (the integer base data type is used by default);

  • StringEncodingRuleAttribute — is for properties with type string (string), sets the encoding into which the text will be converted before encoding it (in case of a conversion error and if the SafeMode mode is on, the data will be saved in UTF-8 encoding).

Let's choose a pass for access to social infrastructure objects as a real world object and describe its model in the form of the ExtraCityPass class using attributes.

[BusinessObjectId(1)]
public class ExtraCityPass1
{
    [TagId(1)]
    public int Id { get; set; }

    [TagId(2)]
    [EnumAsString]
    public CountryCode1 Nationality { get; set; }

    [TagId(3)]
    [StringEncodingRule(StringEncodingMode.Numeric)]
    public string PassportNumber { get; set; }

    [TagId(4)]
    [StringEncodingRule(StringEncodingMode.Predefined, SafeMode = true)]
    public string FullName { get; set; }

    [TagId(5)]
    public Gender1 Sex { get; set; }

    [TagId(6)]
    [DateTimeEncodingRule(DateTimeEncodingMode.DateOnly)]
    public DateTime DateOfBirth { get; set; }

    [TagId(7)]
    public string Address { get; set; }

    [TagId(8)]
    [StringEncodingRule(StringEncodingMode.Numeric)]
    public string MobileId { get; set; }

    [TagId(9)]
    public string Email { get; set; }

    [TagId(10)]
    public bool IsVactinated { get; set; }

    public string Authority { get; set; }

    [TagId(12)]
    [DateTimeEncodingRule(DateTimeEncodingMode.DateTimeOffset)]
    public DateTime NotValidAfter { get; set; }

    [TagId(63)]
    public byte[] Signature { get; set; }
}


// Dependent Elements
public enum Gender1 : byte
{
    None = 0,
    Male,
    Female
}

public enum CountryCode1
{
    None = 0,
    GBR,
    CHN,
    KAZ,
    RUS,
    UKR,
    USA,
}

# Fluent API

It is not always possible for us to use the data annotation method using attributes and apply it to the required business object class, as a rule, this is due to the following factors:

  • we need to work with an already existing class from someone else's assembly (for example, another developer);
  • dynamic matching of BusinessObjectId and TagId is required;
  • requirements of the information security policy of the enterprise.

In this case, we operate a business object class that at least has a public constructor without parameters and public properties:

public class ExtraCityPass2
{
    public int Id { get; set; }
    public CountryCode2 Nationality { get; set; }
    public string PassportNumber { get; set; }
    public string FullName { get; set; }
    public Gender2 Sex { get; set; }
    public DateTime DateOfBirth { get; set; }
    public string Address { get; set; }
    public string MobileId { get; set; }
    public string Email { get; set; }
    public bool IsVactinated { get; set; }
    public string Authority { get; set; }
    public DateTime NotValidAfter { get; set; }
    public byte[] Signature { get; set; }
}


// Dependent Elements
public enum Gender2 : byte
{
    None = 0,
    Male,
    Female
}

public enum CountryCode2
{
    None = 0,
    GBR,
    CHN,
    KAZ,
    RUS,
    UKR,
    USA,
}

To work with a business object class that does not have a data annotation, we must prepare declarative information using the BusinessObjectConfiguration and PropertyConfiguration classes, and pass it to the serializer instance using the AddBusinessObjectConfiguration() method.

Using the GetProperty() method of the BusinessObjectConfiguration class, we get access to a chain of methods for configuring class properties, similar in functionality to data annotation attributes:

  • SetTagId();
  • SetDateTimeEncodingRule();
  • SetEnumAsString();
  • SetStringEncodingRule().

Let's prepare and apply the same declarative information for our ExtraCityPass class in an alternative way:

var conf2 = new BusinessObjectConfiguration<ExtraCityPass2>(id: 2);

conf2.GetProperty(nameof(ExtraCityPass2.Id))
    .SetTagId(1)
    .SetStringEncodingRule(StringEncodingMode.Numeric);

conf2.GetProperty(nameof(ExtraCityPass2.Nationality))
    .SetTagId(2)
    .SetEnumAsString();

conf2.GetProperty(nameof(ExtraCityPass2.PassportNumber))
    .SetTagId(3)
    .SetStringEncodingRule(StringEncodingMode.Numeric);

conf2.GetProperty(nameof(ExtraCityPass2.FullName))
    .SetTagId(4)
    .SetStringEncodingRule(StringEncodingMode.Predefined, SafeMode = true);

conf2.GetProperty(nameof(ExtraCityPass2.Sex))
    .SetTagId(5);

conf2.GetProperty(nameof(ExtraCityPass2.DateOfBirth))
    .SetTagId(6)
    .SetDateTimeEncodingRule(DateTimeEncodingMode.DateOnly);

conf2.GetProperty(nameof(ExtraCityPass2.Address))
    .SetTagId(7);

conf2.GetProperty(nameof(ExtraCityPass2.MobileId))
    .SetTagId(8)
    .SetStringEncodingRule(StringEncodingMode.Numeric);

conf2.GetProperty(nameof(ExtraCityPass2.Email))
    .SetTagId(9);

conf2.GetProperty(nameof(ExtraCityPass2.IsVactinated))
    .SetTagId(10);

conf2.GetProperty(nameof(ExtraCityPass2.NotValidAfter))
    .SetTagId(12)
    .SetDateTimeEncodingRule(DateTimeEncodingMode.DateTimeOffset);

conf2.GetProperty(nameof(ExtraCityPass2.Signature))
    .SetTagId(63);

conf2.SyncTags();
//serializer.AddBusinessObjectConfiguration(conf2);

Using the SyncTags() method of the BusinessObjectConfiguration class, unique property identifiers are synchronized, perhaps this looks like a "crutch" and this functionality will be changed in the future.

# Examples

Let's demonstrate the operation of the binary serializer using the already defined classes ExtraCityPass1 and ExtraCityPass2 as an example, as well as the prepared declarative information contained in the conf2 variable. We initialize an instance of the ExtraCityPass1 class and fill it with sample data:

var obj = new ExtraCityPass1
{
    Id = 7714377,
    Nationality = CountryCode.USA,
    PassportNumber = "2571459",
    FullName = "Elvis Aaron Presley",
    Sex = Gender.Male,
    DateOfBirth = new DateTime(1935, 1, 8),
    Address = "3764 Elvis Presley Boulevard, Memphis, TN 38116",
    MobileId = "19013323322",
    Email = "elvis@example.com",
    IsVactinated = false,
    Authority = "Tennessee DMV",
    NotValidAfter = new DateTime(1977, 8, 16),
};

# Serialization

var serializer = new XyloCode.BusinessData.Serializer();
serializer.DefaultCharacterEncoding = Encoding.Latin1;

byte[] data = serializer.Encode(obj);
Console.WriteLine(data.Length);
> 153

The data from the data variable will be used for later examples.

# Deserialization to a business object class with data annotation using attributes

var serializer = new XyloCode.BusinessData.Serializer();
serializer.DefaultCharacterEncoding = Encoding.Latin1;

ExtraCityPass1 newObj = serializer.Decode<ExtraCityPass1>(data);

# Deserialization to an arbitrary class with preliminary application of declarative information

var serializer = new XyloCode.BusinessData.Serializer();
serializer.DefaultCharacterEncoding = Encoding.Latin1;
serializer.AddBusinessObjectConfiguration(conf2);

ExtraCityPass2 newObj = serializer.Decode<ExtraCityPass2>(data);

# Decoding serialized data in case the object structure is unknown

var serializer = new XyloCode.BusinessData.Serializer();
serializer.DefaultCharacterEncoding = Encoding.Latin1;

string verbose = serializer.Print(data);
Console.WriteLine(verbose);
> BusinessData v1
> ********************************************
> (BusinessObjectId = 0)
> [1] => (Int32Var) 7714377
> [2] => (UTF8) USA
> [3] => (StringNumber) 2571459
> [4] => (String) Elvis Aaron Presley
> [5] => (Enum : Byte) 1
> [6] => (DateOnly) 08.01.1935
> [7] => (String) 3764 Elvis Presley Boulevard, Memphis, TN 38116
> [8] => (StringNumber) 19013323322
> [9] => (String) elvis@example.com
> [10] => (BooleanFalse) False
> [12] => (DateTimeOffset) 16.08.1977 0:00:00 -05:00
> ********************************************

# Specialized string encodings

The binary serializer allows you to use specialized encodings (derived classes from the abstract System.Text.Encoding) to convert public property data of the string type, which is especially important for languages whose alphabets are not covered by ASCII characters and contain up to 40..45 characters . In addition, the use of specialized encodings can be considered as a means of protecting information.

The string encoding method can be set directly for a particular public string property using the parameter mode:

  • data annotation attribute StringEncodingRuleAttribute;
  • property setting method SetStringEncodingRule() Fluent API. The parameter mode is an enum of type StringEncodingMode and can take the following values:
  • UTF8 — use UTF-8 encoding;
  • UTF16LE — use UTF-16 encoding with big endian;
  • CodePage - use the encoding with the code specified in the named argument of the data annotation attribute CodePage or property setting method Fluent API;
  • Numeric — use 4-bit encoding with character support: 1, 2, 3, 4, 5, 6, 7, 8, 9 , 0, *, -, ., /, :, ' ' (space);
  • Predefined — use the encoding whose class instance is assigned to the property of the binary serializer DefaultCharacterEncoding.

# Explicit indication of the encoding code

With the encoding mode StringEncodingMode.CodePage, the serializer adds the code of the encoding used to each string property that is encoded in this way. In the event when a business object contains many of these properties, it might make sense to consider a usage of a predefined encoding.

An example of setting a property by data annotation method using attributes:

[StringEncodingRule(StringEncodingMode.CodePage, CodePage = 866)]
public string FullName { get; set; }

An example of setting a property using the means of Fluent API:

conf.GetProperty("FullName")
    .SetStringEncodingRule(StringEncodingMode.CodePage, CodePage = 866);

# Using a predefined encoding

This method may be the most economical in terms of the size of the serialized object. At the same time, it will be very difficult to decode string data stored in this way without a library with the necessary encoding.

Encoding and decoding properties of a string type in the mode StringEncodingMode.Predefined assumes that the serializer already knows how to encode strings in this mode, and it is the only one, for example:

var serializer = new XyloCode.BusinessData.Serializer();
serializer.DefaultCharacterEncoding = new RussianSixBitEncoding();

Setting a property using data annotation attributes:

[StringEncodingRule(StringEncodingMode.Predefined)]
public string FullName { get; set; }

Setting a property using the Fluent API:

conf.GetProperty("FullName")
    .SetStringEncodingRule(StringEncodingMode.Predefined);