Skip to content

Commit

Permalink
Support Discriminator for ObjectFormat
Browse files Browse the repository at this point in the history
- Use semantig tag 39 to tag discriminator when CborObjectFormat is Array
- Discriminator MemberIndex is always 0 when CborObjectFormat is Array or IntKeyMap
  • Loading branch information
mcatanzariti committed Oct 29, 2022
1 parent 4ced30d commit 33e3898
Show file tree
Hide file tree
Showing 6 changed files with 264 additions and 48 deletions.
120 changes: 120 additions & 0 deletions src/Dahomey.Cbor.Tests/ObjectFormatTests.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
using Dahomey.Cbor.Attributes;
using System.Reflection;
using System.Xml.Linq;
using Xunit;
using static Dahomey.Cbor.Tests.DiscriminatorTests;

namespace Dahomey.Cbor.Tests
{
Expand Down Expand Up @@ -240,5 +242,123 @@ public void ConstructorByAttribute(CborObjectFormat objectFormat, string hexBuff
Assert.Equal(expectedName, obj.Name);
Assert.Equal(expectedAge, obj.Age);
}

[CborObjectFormat(CborObjectFormat.IntKeyMap)]
public class WithId
{
[CborProperty(1)]
public int Id { get; set; }
}

[CborDiscriminator("Person4")]
[CborObjectFormat(CborObjectFormat.IntKeyMap)]
public class Person4 : WithId
{
[CborProperty(2)]
public string Name { get; set; }
}

[Fact]
public void ReadIntKeyMapWithDiscrimator()
{
CborOptions options = new CborOptions();
options.Registry.DiscriminatorConventionRegistry.RegisterType<Person4>();

const string hexBuffer = "A30067506572736F6E34010C0263666F6F"; // {0: "Person4", 1: 12, 2: "foo"}
WithId obj = Helper.Read<WithId>(hexBuffer, options);
Assert.NotNull(obj);
Person4 person = Assert.IsType<Person4>(obj);
Assert.Equal(12, person.Id);
Assert.Equal("foo", person.Name);

const string hexBuffer2 = "A2010C0263666F6F"; // {1: 12, 2: "foo"}
Person4 obj2 = Helper.Read<Person4>(hexBuffer2, options); // no inheritance, no discriminator written
Assert.Equal(12, obj2.Id);
Assert.Equal("foo", obj2.Name);
}

[Fact]
public void WriteIntKeyMapWithDiscrimator()
{
CborOptions options = new CborOptions();
options.Registry.DiscriminatorConventionRegistry.RegisterType<Person4>();

WithId person = new Person4
{
Id = 12,
Name = "foo"
};

const string hexBuffer = "A30067506572736F6E34010C0263666F6F"; // {0: "Person4", 1: 12, 2: "foo"}
Helper.TestWrite(person, hexBuffer, null, options);

Person4 person2 = new Person4 // no inheritance, no discriminator written
{
Id = 12,
Name = "foo"
};

const string hexBuffer2 = "A2010C0263666F6F"; // {1: 12, 2: "foo"}
Helper.TestWrite(person2, hexBuffer2, null, options);
}

[CborObjectFormat(CborObjectFormat.Array)]
public class WithId2
{
[CborProperty(1)]
public int Id { get; set; }
}

[CborDiscriminator("Person4")]
[CborObjectFormat(CborObjectFormat.Array)]
public class Person5 : WithId2
{
[CborProperty(2)]
public string Name { get; set; }
}

[Fact]
public void ReadArrayWithDiscrimator()
{
CborOptions options = new CborOptions();
options.Registry.DiscriminatorConventionRegistry.RegisterType<Person5>();

const string hexBuffer = "83D82767506572736F6E340C63666F6F"; // [39("Person4"), 12, "foo"]
WithId2 obj = Helper.Read<WithId2>(hexBuffer, options);
Assert.NotNull(obj);
Person5 person = Assert.IsType<Person5>(obj);
Assert.Equal(12, person.Id);
Assert.Equal("foo", person.Name);

const string hexBuffer2 = "820C63666F6F"; // [12, "foo"]
Person5 obj2 = Helper.Read<Person5>(hexBuffer2, options); // no inheritance, no discriminator written
Assert.Equal(12, obj2.Id);
Assert.Equal("foo", obj2.Name);
}

[Fact]
public void WriteArrayWithDiscrimator()
{
CborOptions options = new CborOptions();
options.Registry.DiscriminatorConventionRegistry.RegisterType<Person5>();

WithId2 person = new Person5
{
Id = 12,
Name = "foo"
};

const string hexBuffer = "83D82767506572736F6E340C63666F6F"; // [39("Person4"), 12, "foo"]
Helper.TestWrite(person, hexBuffer, null, options);

Person5 person2 = new Person5 // no inheritance, no discriminator written
{
Id = 12,
Name = "foo"
};

const string hexBuffer2 = "820C63666F6F"; // [12, "foo"]
Helper.TestWrite(person2, hexBuffer2, null, options);
}
}
}
7 changes: 6 additions & 1 deletion src/Dahomey.Cbor/CborOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -53,8 +53,13 @@ public class CborOptions
public CborObjectFormat ObjectFormat { get; set; } = CborObjectFormat.StringKeyMap;
public LengthMode ArrayLengthMode { get; set; } = LengthMode.DefiniteLength;
public LengthMode MapLengthMode { get; set; } = LengthMode.DefiniteLength;
/// <summary>
/// Semantic Tag to check if the discriminator is present when ObjectFormat is Array
/// </summary>
/// Default value is 39 (see: https://github.com/lucas-clemente/cbor-specs/blob/master/id.md)
public ulong DiscriminatorSemanticTag { get; set; } = 39;

public CborOptions()
public CborOptions()
{
Registry = new SerializationRegistry(this);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,21 +13,14 @@ public interface IDiscriminatorMapping : IMemberMapping
public class DiscriminatorMapping<T> : IDiscriminatorMapping
{
private bool _isInitialized = false;
private readonly CborOptions _options;
private readonly DiscriminatorConventionRegistry _discriminatorConventionRegistry;
private readonly IObjectMapping _objectMapping;
private string? _memberName = null;
private int? _memberIndex = null;

public MemberInfo? MemberInfo => null;
public Type MemberType => throw new NotSupportedException();
public int? MemberIndex
{
get
{
EnsureInitialize();
return _memberIndex;
}
}
public int? MemberIndex => 0; // discriminator gets always the first index
public string? MemberName
{
get
Expand All @@ -45,10 +38,10 @@ public class DiscriminatorMapping<T> : IDiscriminatorMapping
public LengthMode LengthMode => LengthMode.Default;
public RequirementPolicy RequirementPolicy => RequirementPolicy.Never;

public DiscriminatorMapping(DiscriminatorConventionRegistry discriminatorConventionRegistry,
IObjectMapping objectMapping)
public DiscriminatorMapping(CborOptions options, IObjectMapping objectMapping)
{
_discriminatorConventionRegistry = discriminatorConventionRegistry;
_options = options;
_discriminatorConventionRegistry = options.Registry.DiscriminatorConventionRegistry;
_objectMapping = objectMapping;
}

Expand Down Expand Up @@ -79,8 +72,10 @@ public IMemberConverter GenerateMemberConverter()
throw new CborException($"Cannot find a discriminator convention for type {_objectMapping.ObjectType}");
}

int? memberIndex = _objectMapping.ObjectFormat == CborObjectFormat.Array ? 0 : null;

IMemberConverter memberConverter = new DiscriminatorMemberConverter<T>(
discriminatorConvention, _objectMapping.DiscriminatorPolicy);
_options, discriminatorConvention, _objectMapping.DiscriminatorPolicy, _objectMapping.ObjectFormat);

return memberConverter;
}
Expand Down
21 changes: 11 additions & 10 deletions src/Dahomey.Cbor/Serialization/Converters/Mappings/ObjectMapping.cs
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ public ObjectMapping<T> SetDiscriminator(object discriminator)
&& _registry.DiscriminatorConventionRegistry.AnyConvention()
&& (_memberMappings.Count == 0 || _memberMappings[0] is not DiscriminatorMapping<T>))
{
DiscriminatorMapping<T> memberMapping = new DiscriminatorMapping<T>(_registry.DiscriminatorConventionRegistry, this);
DiscriminatorMapping<T> memberMapping = new DiscriminatorMapping<T>(_options, this);
_memberMappings.Insert(0, memberMapping);
}

Expand Down Expand Up @@ -238,7 +238,7 @@ public ObjectMapping<T> SetDiscriminatorPolicy(CborDiscriminatorPolicy discrimin
&& _registry.DiscriminatorConventionRegistry.AnyConvention()
&& (_memberMappings.Count == 0 || _memberMappings[0] is not DiscriminatorMapping<T>))
{
DiscriminatorMapping<T> memberMapping = new DiscriminatorMapping<T>(_registry.DiscriminatorConventionRegistry, this);
DiscriminatorMapping<T> memberMapping = new DiscriminatorMapping<T>(_options, this);
_memberMappings.Insert(0, memberMapping);
}

Expand Down Expand Up @@ -366,17 +366,18 @@ private void ValidateMemberNamesAndindexes()
throw new CborException($"exepcting all fields/properties to get a member index in class/struct {ObjectType.Name}");
}

_memberMappings = _memberMappings
.OrderBy(m => m.MemberIndex)
.ToList();
bool indexDuplicates = _memberMappings
.GroupBy(x => x.MemberIndex)
.Any(g => g.Count() > 1);

for (int i = 0; i < _memberMappings.Count; i++)
if (indexDuplicates)
{
if (_memberMappings[i].MemberIndex != i)
{
throw new CborException($"class/struct {ObjectType.Name} MemberIndexes must follow one another with no holes");
}
throw new CborException($"class/struct {ObjectType.Name} holds duplicated MemberIndex fields/properties");
}

_memberMappings = _memberMappings
.OrderBy(m => m.MemberIndex)
.ToList();
}
break;
}
Expand Down
17 changes: 14 additions & 3 deletions src/Dahomey.Cbor/Serialization/Converters/MemberConverter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -373,16 +373,22 @@ public bool ShouldSerialize(ref T instance, Type declaredType)

public class DiscriminatorMemberConverter<T> : IMemberConverter
{
private readonly CborOptions _options;
private readonly IDiscriminatorConvention _discriminatorConvention;
private readonly CborDiscriminatorPolicy _discriminatorPolicy;
private readonly CborObjectFormat _objectFormat;
private readonly ReadOnlyMemory<byte> _memberName;

public DiscriminatorMemberConverter(
IDiscriminatorConvention discriminatorConvention,
CborDiscriminatorPolicy discriminatorPolicy)
CborOptions options,
IDiscriminatorConvention discriminatorConvention,
CborDiscriminatorPolicy discriminatorPolicy,
CborObjectFormat objectFormat)
{
_options = options;
_discriminatorConvention = discriminatorConvention;
_discriminatorPolicy = discriminatorPolicy;
_objectFormat = objectFormat;

if (discriminatorConvention != null)
{
Expand All @@ -391,7 +397,7 @@ public class DiscriminatorMemberConverter<T> : IMemberConverter
}

public ReadOnlySpan<byte> MemberName => _memberName.Span;
public int? MemberIndex => throw new NotSupportedException();
public int? MemberIndex => 0; // discriminator gets always the first index
public bool IgnoreIfDefault => false;
public RequirementPolicy RequirementPolicy => RequirementPolicy.Never;

Expand Down Expand Up @@ -426,6 +432,11 @@ public bool ShouldSerialize(object obj, Type declaredType, CborOptions options)

public void Write(ref CborWriter writer, object obj)
{
if (_objectFormat == CborObjectFormat.Array)
{
// we need a Semantic Tag to check if the discriminator is present
writer.WriteSemanticTag(_options.DiscriminatorSemanticTag);
}
_discriminatorConvention.WriteDiscriminator(ref writer, obj.GetType());
}
}
Expand Down

0 comments on commit 33e3898

Please sign in to comment.