Skip to content

Advanced Usage

This page describes how to use Nino in more advanced scenarios.

Former Name

Nino supports renaming the type name and namespace after serialization. This feature is useful when you want to refactor your codebase and rename the type name or namespace and still be able to deserialize the old data serialized by the old type name or namespace. This feature is available since v3.2.2. Before this version, renaming the type name or namespace would cause deserialization to fail.

To use this feature, you need to add the [NinoFormerName] attribute to the type you want to rename and specify the old type name and namespace in the attribute.

INFO

The old name comes from the old version of NinoTypeConst.g.cs, just simply copy the desired name from the file and paste it into the attribute.

For generic types, you only need to specify the old type name and namespace, not the generic type parameters.

csharp
// <auto-generated/>

using System;

namespace Nino.UnitTests.NinoGen
{
    public static class NinoTypeConst
    {
        ...
		// global::Nino.UnitTests.Generic<int>
		public const int Nino_UnitTests_Generic_int_ = -388704858;
		// global::Nino.UnitTests.Generic<string>
		public const int Nino_UnitTests_Generic_string_ = -1996567692;
		// global::ListElementClass2Renamed
		public const int ListElementClass2Renamed = 329143115;
        ...

Examples

csharp
[NinoType]
[NinoFormerName("global::Nino.UnitTests.Generic")]
public class Generic<T> 
public class GenericRenamed<T> 
{
    ...
}

[NinoType]
[NinoFormerName("global::ListElementClass2")]
public class ListElementClass2
public class ListElementClass2Renamed 
{
    ...
}

String Encoding

Nino supports custom string encoding for members in a NinoType. By default, Nino uses UTF-16 encoding to serialize and deserialize these string members. However, you can specify a different encoding by using the [NinoUtf8] attribute. The benefit of using UTF-8 encoding is that it can reduce the size of the serialized data (it halves the size of the serialized binary data).

WARNING

Using UTF-8 encoding may cause a slight performance overhead during serialization and deserialization for these member, at around 5% to 10%.

INFO

When annotating a non-string member using [NinoUtf8], Nino will ignore this attribute

Example

csharp
[NinoType]
public class StringData
{
    [NinoUtf8] public string Str;
    [NinoUtf8] public bool ShouldHaveNoEffect;
}

Version Compatibility

  • Renaming fields/properties that have been serialized is allowed (but to ensure either the order of the member stays identical, or the id given in NinoMember remains the same)
  • Changing the type of serialized fields/properties (of an unmanaged struct) to an unmanaged struct of the same memory size (int->uint, int->float, List<long>->List<double>, List<int[]>->List<float[]>) is allowed
  • Changing the type of serialized fields/properties to its base/derived type is allowed
  • Adding new fields/properties to the end of the data structure is allowed (for example, while using auto collect via [NinoType] attribute, put the new member after the last collected member of the previous members, or if using [NinoType(false)] and [NinoMember(id)] attribute, set the id to a reasonable value so that it orders after the last member of the previous members, Nino orders the members by the id in ascending order)

    Requires the symbol WEAK_VERSION_TOLERANCE to be defined to the project to enable this feature

  • Deleting serialized fields/properties is not allowed

WARNING

WEAK_VERSION_TOLERANCE defined project cannot deserialize data serialized by a project without WEAK_VERSION_TOLERANCE defined, and vice versa.

Examples

Valid alterations:

csharp
[NinoType]
public class SampleClass
{
    //float and int have the same memory size, so List<int> can be used
    public List<float> Data; 
    public List<int> Data; 
}
csharp
[NinoType]
public interface IBase
{
}
[NinoType]
public class Derived : IBase;
[NinoType]
public class SampleClass2
{
    public Derived Data; 
    public IBase Data; 
}
csharp
[NinoType]
public interface IBase
{
}
[NinoType]
public class Derived : IBase;
[NinoType]
public struct DerivedStruct : IBase;
[NinoType]
public class SampleClass2
{
    public IBase Data; 
    // If Data is DerivedStruct
    public DerivedStruct Data; 
    // If Data is Derived
    public Derived Data; 
}

Invalid alterations:

csharp
[NinoType]
public class SampleClass2
{
public SampleClass Data; 
// Assuming SampleClass2 is not a base/derived type of SampleClass
public SampleClass2 Data; 
}
csharp
[NinoType]
public interface IBase
{
}
[NinoType]
public class Derived : IBase;
[NinoType]
public struct DerivedStruct : IBase;
[NinoType]
public class SampleClass2
{
    public IBase Data; 
    // If Data is DerivedStruct
    public Derived Data; 
    // If Data is Derived
    public DerivedStruct Data; 
}
csharp
[NinoType]
public class SampleClass2
{
public int Data; 
public long Data; 
}

Valid adding new members:

csharp
// assuming WEAK_VERSION_TOLERANCE is defined
[NinoType]
public class SampleClass
{
    public int Id;
    public string Name;
    public bool Extra; 
}
csharp
// assuming WEAK_VERSION_TOLERANCE is defined
[NinoType(false)]
public class SampleClass
{
    [NinoMember(0)] public int Id;
    [NinoMember(1)] public string Name;
    [NinoMember(2)] public bool Extra; 
}

Invalid adding new members:

csharp
// assuming WEAK_VERSION_TOLERANCE is not defined
[NinoType]
public class SampleClass
{
    public int Id;
    public string Name;
    public bool Extra; 
}
csharp
// assuming WEAK_VERSION_TOLERANCE is defined
[NinoType]
public class SampleClass
{
    public int Id;
    public bool Extra; 
    public string Name;
}
csharp
// assuming WEAK_VERSION_TOLERANCE is defined
[NinoType(false)]
public class SampleClass
{
    [NinoMember(0)] public int Id;
    [NinoMember(1)] public string Name;
    [NinoMember(-1)] public bool Extra; 
}

Custom Constructors

Nino now supports using custom constructors to deserialize a nino serialized object. This is useful when you want to perform additional operations when deserializing an object, such as initializing fields or properties that are not serialized.

In addition, this is useful for records, nino can either use the primary constructor or a custom constructor to deserialize the record.

Usage

The following attribute uses Field1, Field2, ... as the arguments for a constructor to deserialize the object.

csharp
[NinoConstructor(nameof(Field1), nameof(Field2), ...)]

For records, Nino will prioritize using the primary constructor to deserialize the object. If the primary constructor is not suitable for deserialization, you can use the [NinoConstructor] attribute to specify a custom constructor to deserialize the object.

For classes, Nino will automatically use the first public constructor to deserialize the object. Where either the constructor is parameterless or all parameters shares the same name as the fields or properties in the class that are to be deserialized. If the constructor is not suitable for deserialization, you can use the [NinoConstructor] attribute to specify a custom constructor to deserialize the object.

For structs, Nino will automatically use the parameterless constructor to deserialize the object. If the struct should not use a parameterless constructor (struct always have parameterless constructor though) when deserializing, you can use the [NinoConstructor] attribute to specify a custom constructor to deserialize the object.

Restrictions

  • The custom constructor must be a public constructor.
  • The custom constructor must have the same number of parameters as the number of parameters in NinoConstructor attribute.
  • The parameters in NinoConstructor attribute must match the name (ignoring cases) of the fields or properties in the class, struct, or record that are to be deserialized (i.e. these members have to be serialized in the first place).
  • The parameters in NinoConstructor attribute represents the name of the type's members to be passed as the parameters in the constructor.

Examples

csharp
[NinoType]
public record SimpleRecord
{
    public int Id;
    public string Name;
    public DateTime CreateTime;

    public SimpleRecord()
    {
        Id = 0;
        Name = string.Empty;
        CreateTime = DateTime.MinValue;
    }

    [NinoConstructor(nameof(Id), nameof(Name))]
    public SimpleRecord(int id, string name)
    {
        Id = id;
        Name = name;
        CreateTime = DateTime.Now;
    }
}
csharp
[NinoType]
public record SimpleRecord2(int Id, string Name, DateTime CreateTime);

[NinoType]
public record SimpleRecord4(int Id, string Name, DateTime CreateTime) // Will be used
{
    [NinoIgnore] public bool Flag;

    public int ShouldNotIgnore;

    // Nino will not use this
    public SimpleRecord4() : this(0, "", DateTime.MinValue)
    {
    }
}
csharp
[NinoType]
public record SimpleRecord5(int Id, string Name, DateTime CreateTime) // Will not be used
{
    [NinoIgnore] public bool Flag;

    public int ShouldNotIgnore;

    // Nino will use this, but not good since we will discard the primary constructor values when deserializing
    [NinoConstructor]
    public SimpleRecord5() : this(0, "", DateTime.MinValue)
    {
    }
}

Moreover, not only for records we have the ability to use custom constructors, but also for classes and structs.

csharp
[NinoType]
public struct SimpleStruct
{
    public int Id;
    public string Name;
    public DateTime CreateTime;
    
    // Will use this constructor to create the struct when deserializing
    [NinoConstructor(nameof(Id), nameof(Name), nameof(CreateTime))]
    public SimpleStruct(int a, string b, DateTime c)
    {
        Id = a;
        Name = b;
        CreateTime = c;
    }
}
csharp
[NinoType]
public class SimpleClassWithConstructor
{
    public int Id;
    public string Name;
    public DateTime CreateTime;
    
    // [NinoConstructor(nameof(Id), nameof(Name), nameof(CreateTime))] - we try not to use this and test if it still works
    // should automatically use this constructor since this is the only public constructor
    public SimpleClassWithConstructor(int id, string name, DateTime createTime)
    {
        Id = id;
        Name = name;
        CreateTime = createTime;
    }
}