Skip to content

Commit 33e3898

Browse files
committed
Support Discriminator for ObjectFormat
- Use semantig tag 39 to tag discriminator when CborObjectFormat is Array - Discriminator MemberIndex is always 0 when CborObjectFormat is Array or IntKeyMap
1 parent 4ced30d commit 33e3898

File tree

6 files changed

+264
-48
lines changed

6 files changed

+264
-48
lines changed

src/Dahomey.Cbor.Tests/ObjectFormatTests.cs

Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
using Dahomey.Cbor.Attributes;
22
using System.Reflection;
3+
using System.Xml.Linq;
34
using Xunit;
5+
using static Dahomey.Cbor.Tests.DiscriminatorTests;
46

57
namespace Dahomey.Cbor.Tests
68
{
@@ -240,5 +242,123 @@ public void ConstructorByAttribute(CborObjectFormat objectFormat, string hexBuff
240242
Assert.Equal(expectedName, obj.Name);
241243
Assert.Equal(expectedAge, obj.Age);
242244
}
245+
246+
[CborObjectFormat(CborObjectFormat.IntKeyMap)]
247+
public class WithId
248+
{
249+
[CborProperty(1)]
250+
public int Id { get; set; }
251+
}
252+
253+
[CborDiscriminator("Person4")]
254+
[CborObjectFormat(CborObjectFormat.IntKeyMap)]
255+
public class Person4 : WithId
256+
{
257+
[CborProperty(2)]
258+
public string Name { get; set; }
259+
}
260+
261+
[Fact]
262+
public void ReadIntKeyMapWithDiscrimator()
263+
{
264+
CborOptions options = new CborOptions();
265+
options.Registry.DiscriminatorConventionRegistry.RegisterType<Person4>();
266+
267+
const string hexBuffer = "A30067506572736F6E34010C0263666F6F"; // {0: "Person4", 1: 12, 2: "foo"}
268+
WithId obj = Helper.Read<WithId>(hexBuffer, options);
269+
Assert.NotNull(obj);
270+
Person4 person = Assert.IsType<Person4>(obj);
271+
Assert.Equal(12, person.Id);
272+
Assert.Equal("foo", person.Name);
273+
274+
const string hexBuffer2 = "A2010C0263666F6F"; // {1: 12, 2: "foo"}
275+
Person4 obj2 = Helper.Read<Person4>(hexBuffer2, options); // no inheritance, no discriminator written
276+
Assert.Equal(12, obj2.Id);
277+
Assert.Equal("foo", obj2.Name);
278+
}
279+
280+
[Fact]
281+
public void WriteIntKeyMapWithDiscrimator()
282+
{
283+
CborOptions options = new CborOptions();
284+
options.Registry.DiscriminatorConventionRegistry.RegisterType<Person4>();
285+
286+
WithId person = new Person4
287+
{
288+
Id = 12,
289+
Name = "foo"
290+
};
291+
292+
const string hexBuffer = "A30067506572736F6E34010C0263666F6F"; // {0: "Person4", 1: 12, 2: "foo"}
293+
Helper.TestWrite(person, hexBuffer, null, options);
294+
295+
Person4 person2 = new Person4 // no inheritance, no discriminator written
296+
{
297+
Id = 12,
298+
Name = "foo"
299+
};
300+
301+
const string hexBuffer2 = "A2010C0263666F6F"; // {1: 12, 2: "foo"}
302+
Helper.TestWrite(person2, hexBuffer2, null, options);
303+
}
304+
305+
[CborObjectFormat(CborObjectFormat.Array)]
306+
public class WithId2
307+
{
308+
[CborProperty(1)]
309+
public int Id { get; set; }
310+
}
311+
312+
[CborDiscriminator("Person4")]
313+
[CborObjectFormat(CborObjectFormat.Array)]
314+
public class Person5 : WithId2
315+
{
316+
[CborProperty(2)]
317+
public string Name { get; set; }
318+
}
319+
320+
[Fact]
321+
public void ReadArrayWithDiscrimator()
322+
{
323+
CborOptions options = new CborOptions();
324+
options.Registry.DiscriminatorConventionRegistry.RegisterType<Person5>();
325+
326+
const string hexBuffer = "83D82767506572736F6E340C63666F6F"; // [39("Person4"), 12, "foo"]
327+
WithId2 obj = Helper.Read<WithId2>(hexBuffer, options);
328+
Assert.NotNull(obj);
329+
Person5 person = Assert.IsType<Person5>(obj);
330+
Assert.Equal(12, person.Id);
331+
Assert.Equal("foo", person.Name);
332+
333+
const string hexBuffer2 = "820C63666F6F"; // [12, "foo"]
334+
Person5 obj2 = Helper.Read<Person5>(hexBuffer2, options); // no inheritance, no discriminator written
335+
Assert.Equal(12, obj2.Id);
336+
Assert.Equal("foo", obj2.Name);
337+
}
338+
339+
[Fact]
340+
public void WriteArrayWithDiscrimator()
341+
{
342+
CborOptions options = new CborOptions();
343+
options.Registry.DiscriminatorConventionRegistry.RegisterType<Person5>();
344+
345+
WithId2 person = new Person5
346+
{
347+
Id = 12,
348+
Name = "foo"
349+
};
350+
351+
const string hexBuffer = "83D82767506572736F6E340C63666F6F"; // [39("Person4"), 12, "foo"]
352+
Helper.TestWrite(person, hexBuffer, null, options);
353+
354+
Person5 person2 = new Person5 // no inheritance, no discriminator written
355+
{
356+
Id = 12,
357+
Name = "foo"
358+
};
359+
360+
const string hexBuffer2 = "820C63666F6F"; // [12, "foo"]
361+
Helper.TestWrite(person2, hexBuffer2, null, options);
362+
}
243363
}
244364
}

src/Dahomey.Cbor/CborOptions.cs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,8 +53,13 @@ public class CborOptions
5353
public CborObjectFormat ObjectFormat { get; set; } = CborObjectFormat.StringKeyMap;
5454
public LengthMode ArrayLengthMode { get; set; } = LengthMode.DefiniteLength;
5555
public LengthMode MapLengthMode { get; set; } = LengthMode.DefiniteLength;
56+
/// <summary>
57+
/// Semantic Tag to check if the discriminator is present when ObjectFormat is Array
58+
/// </summary>
59+
/// Default value is 39 (see: https://github.com/lucas-clemente/cbor-specs/blob/master/id.md)
60+
public ulong DiscriminatorSemanticTag { get; set; } = 39;
5661

57-
public CborOptions()
62+
public CborOptions()
5863
{
5964
Registry = new SerializationRegistry(this);
6065
}

src/Dahomey.Cbor/Serialization/Converters/Mappings/DiscriminatorMapping.cs

Lines changed: 8 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -13,21 +13,14 @@ public interface IDiscriminatorMapping : IMemberMapping
1313
public class DiscriminatorMapping<T> : IDiscriminatorMapping
1414
{
1515
private bool _isInitialized = false;
16+
private readonly CborOptions _options;
1617
private readonly DiscriminatorConventionRegistry _discriminatorConventionRegistry;
1718
private readonly IObjectMapping _objectMapping;
1819
private string? _memberName = null;
19-
private int? _memberIndex = null;
2020

2121
public MemberInfo? MemberInfo => null;
2222
public Type MemberType => throw new NotSupportedException();
23-
public int? MemberIndex
24-
{
25-
get
26-
{
27-
EnsureInitialize();
28-
return _memberIndex;
29-
}
30-
}
23+
public int? MemberIndex => 0; // discriminator gets always the first index
3124
public string? MemberName
3225
{
3326
get
@@ -45,10 +38,10 @@ public string? MemberName
4538
public LengthMode LengthMode => LengthMode.Default;
4639
public RequirementPolicy RequirementPolicy => RequirementPolicy.Never;
4740

48-
public DiscriminatorMapping(DiscriminatorConventionRegistry discriminatorConventionRegistry,
49-
IObjectMapping objectMapping)
41+
public DiscriminatorMapping(CborOptions options, IObjectMapping objectMapping)
5042
{
51-
_discriminatorConventionRegistry = discriminatorConventionRegistry;
43+
_options = options;
44+
_discriminatorConventionRegistry = options.Registry.DiscriminatorConventionRegistry;
5245
_objectMapping = objectMapping;
5346
}
5447

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

75+
int? memberIndex = _objectMapping.ObjectFormat == CborObjectFormat.Array ? 0 : null;
76+
8277
IMemberConverter memberConverter = new DiscriminatorMemberConverter<T>(
83-
discriminatorConvention, _objectMapping.DiscriminatorPolicy);
78+
_options, discriminatorConvention, _objectMapping.DiscriminatorPolicy, _objectMapping.ObjectFormat);
8479

8580
return memberConverter;
8681
}

src/Dahomey.Cbor/Serialization/Converters/Mappings/ObjectMapping.cs

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,7 @@ public ObjectMapping<T> SetDiscriminator(object discriminator)
8181
&& _registry.DiscriminatorConventionRegistry.AnyConvention()
8282
&& (_memberMappings.Count == 0 || _memberMappings[0] is not DiscriminatorMapping<T>))
8383
{
84-
DiscriminatorMapping<T> memberMapping = new DiscriminatorMapping<T>(_registry.DiscriminatorConventionRegistry, this);
84+
DiscriminatorMapping<T> memberMapping = new DiscriminatorMapping<T>(_options, this);
8585
_memberMappings.Insert(0, memberMapping);
8686
}
8787

@@ -238,7 +238,7 @@ public ObjectMapping<T> SetDiscriminatorPolicy(CborDiscriminatorPolicy discrimin
238238
&& _registry.DiscriminatorConventionRegistry.AnyConvention()
239239
&& (_memberMappings.Count == 0 || _memberMappings[0] is not DiscriminatorMapping<T>))
240240
{
241-
DiscriminatorMapping<T> memberMapping = new DiscriminatorMapping<T>(_registry.DiscriminatorConventionRegistry, this);
241+
DiscriminatorMapping<T> memberMapping = new DiscriminatorMapping<T>(_options, this);
242242
_memberMappings.Insert(0, memberMapping);
243243
}
244244

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

369-
_memberMappings = _memberMappings
370-
.OrderBy(m => m.MemberIndex)
371-
.ToList();
369+
bool indexDuplicates = _memberMappings
370+
.GroupBy(x => x.MemberIndex)
371+
.Any(g => g.Count() > 1);
372372

373-
for (int i = 0; i < _memberMappings.Count; i++)
373+
if (indexDuplicates)
374374
{
375-
if (_memberMappings[i].MemberIndex != i)
376-
{
377-
throw new CborException($"class/struct {ObjectType.Name} MemberIndexes must follow one another with no holes");
378-
}
375+
throw new CborException($"class/struct {ObjectType.Name} holds duplicated MemberIndex fields/properties");
379376
}
377+
378+
_memberMappings = _memberMappings
379+
.OrderBy(m => m.MemberIndex)
380+
.ToList();
380381
}
381382
break;
382383
}

src/Dahomey.Cbor/Serialization/Converters/MemberConverter.cs

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -373,16 +373,22 @@ public bool ShouldSerialize(ref T instance, Type declaredType)
373373

374374
public class DiscriminatorMemberConverter<T> : IMemberConverter
375375
{
376+
private readonly CborOptions _options;
376377
private readonly IDiscriminatorConvention _discriminatorConvention;
377378
private readonly CborDiscriminatorPolicy _discriminatorPolicy;
379+
private readonly CborObjectFormat _objectFormat;
378380
private readonly ReadOnlyMemory<byte> _memberName;
379381

380382
public DiscriminatorMemberConverter(
381-
IDiscriminatorConvention discriminatorConvention,
382-
CborDiscriminatorPolicy discriminatorPolicy)
383+
CborOptions options,
384+
IDiscriminatorConvention discriminatorConvention,
385+
CborDiscriminatorPolicy discriminatorPolicy,
386+
CborObjectFormat objectFormat)
383387
{
388+
_options = options;
384389
_discriminatorConvention = discriminatorConvention;
385390
_discriminatorPolicy = discriminatorPolicy;
391+
_objectFormat = objectFormat;
386392

387393
if (discriminatorConvention != null)
388394
{
@@ -391,7 +397,7 @@ public DiscriminatorMemberConverter(
391397
}
392398

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

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

427433
public void Write(ref CborWriter writer, object obj)
428434
{
435+
if (_objectFormat == CborObjectFormat.Array)
436+
{
437+
// we need a Semantic Tag to check if the discriminator is present
438+
writer.WriteSemanticTag(_options.DiscriminatorSemanticTag);
439+
}
429440
_discriminatorConvention.WriteDiscriminator(ref writer, obj.GetType());
430441
}
431442
}

0 commit comments

Comments
 (0)