Skip to content

Commit

Permalink
Add support for generic jsonDerivedAttribute (#32)
Browse files Browse the repository at this point in the history
* Add generic JsonDerivedTypeAttribute

* Parse generic JsonDerivedTypeAttribute

* Update tests

* Add GenericAttributeWorks

* Use generic JsonDerivedType in Target tests

* Update Readme
  • Loading branch information
trejjam authored Nov 7, 2023
1 parent 3165a88 commit 8b6f2be
Show file tree
Hide file tree
Showing 21 changed files with 578 additions and 4 deletions.
6 changes: 6 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ using Aviationexam.GeneratedJsonConverters.Attributes;
[JsonPolymorphic] // notice, that attributes are from `Aviationexam.GeneratedJsonConverters.Attributes` namespace, not `System.Text.Json.Serialization`
[JsonDerivedType(typeof(LeafContract), typeDiscriminator: nameof(LeafContract))]
[JsonDerivedType(typeof(AnotherLeafContract), typeDiscriminator: 2)]
[JsonDerivedType<GenericLeafContract>(typeDiscriminator: nameof(GenericLeafContract))]
public abstract class BaseContract
{
public int BaseProperty { get; set; }
Expand All @@ -62,6 +63,10 @@ public sealed class AnotherLeafContract : BaseContract
{
public int AnotherLeafProperty { get; set; }
}
public sealed class GenericLeafContract : BaseContract
{
public int Property { get; set; }
}

[EnumJsonConverter] // this use project defined configuration
public enum EMyEnum
Expand Down Expand Up @@ -97,6 +102,7 @@ using System.Text.Json.Serialization;
[JsonSerializable(typeof(BaseContract))] // this line is neccesary, generator searches for JsonSerializableAttribute with argument type decorated by JsonPolymorphicAttribute
[JsonSerializable(typeof(LeafContract))] // notice, it's necessary to specify leaf types
[JsonSerializable(typeof(AnotherLeafContract))]
[JsonSerializable(typeof(GenericLeafContract))]

[JsonSerializable(typeof(EMyEnum))] // only necessary for not referenced enums from other contracts
[JsonSerializable(typeof(EMyEnumWithExplicitConfiguration))]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,11 @@ public void DeserializeBaseContractWorks(string json, Type targetType)
{
Assert.Equal(2, anotherLeafContract.AnotherLeafProperty);
}

if (baseContract is GenericLeafContract genericLeafContract)
{
Assert.Equal(2, genericLeafContract.Property);
}
}

[Theory]
Expand Down Expand Up @@ -118,6 +123,18 @@ public static IEnumerable<object[]> BaseJsonContractData()
""",
typeof(AnotherLeafContract),
};
yield return new object[]
{
// language=json
"""
{
"baseProperty": 1,
"property": 2,
"$type": "GenericLeafContract"
}
""",
typeof(GenericLeafContract),
};
}

public static IEnumerable<object[]> LeafContractData()
Expand Down Expand Up @@ -154,5 +171,21 @@ public static IEnumerable<object[]> LeafContractData()
}
""".Replace("\r\n", Environment.NewLine)
};
yield return new object[]
{
new GenericLeafContract
{
BaseProperty = 1,
Property = 2
},
// language=json
"""
{
"$type": "GenericLeafContract",
"property": 2,
"baseProperty": 1
}
""".Replace("\r\n", Environment.NewLine)
};
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ namespace Aviationexam.GeneratedJsonConverters.SourceGenerator.Target.Contracts;
[JsonPolymorphic]
[JsonDerivedType(typeof(LeafContract), typeDiscriminator: nameof(LeafContract))]
[JsonDerivedType(typeof(AnotherLeafContract), typeDiscriminator: 2)]
[JsonDerivedType<GenericLeafContract>(typeDiscriminator: nameof(GenericLeafContract))]
public abstract class BaseContract
{
public int BaseProperty { get; set; }
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
namespace Aviationexam.GeneratedJsonConverters.SourceGenerator.Target.Contracts;

public sealed class GenericLeafContract : BaseContract
{
public int Property { get; set; }
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ namespace Aviationexam.GeneratedJsonConverters.SourceGenerator.Target;
[JsonSerializable(typeof(BaseContract))]
[JsonSerializable(typeof(LeafContract))]
[JsonSerializable(typeof(AnotherLeafContract))]
[JsonSerializable(typeof(GenericLeafContract))]
[JsonSerializable(typeof(BaseContractWithCustomDelimiter))]
[JsonSerializable(typeof(LeafContractWithCustomDelimiter))]
[JsonSerializable(typeof(EBackingEnum))]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,3 +53,33 @@ public JsonDerivedTypeAttribute(System.Type derivedType, int typeDiscriminator)
/// </summary>
public object? TypeDiscriminator { get; }
}

[System.AttributeUsage(System.AttributeTargets.Class | System.AttributeTargets.Interface, AllowMultiple = true, Inherited = false)]
internal class JsonDerivedTypeAttribute<TDerivedType> : JsonDerivedTypeAttribute
{
/// <summary>
/// Initializes a new attribute with specified parameters.
/// </summary>
/// <param name="derivedType">A derived type that should be supported in polymorphic serialization of the declared based type.</param>
public JsonDerivedTypeAttribute() : base(typeof(TDerivedType))
{
}

/// <summary>
/// Initializes a new attribute with specified parameters.
/// </summary>
/// <param name="derivedType">A derived type that should be supported in polymorphic serialization of the declared base type.</param>
/// <param name="typeDiscriminator">The type discriminator identifier to be used for the serialization of the subtype.</param>
public JsonDerivedTypeAttribute(string typeDiscriminator) : base(typeof(TDerivedType), typeDiscriminator)
{
}

/// <summary>
/// Initializes a new attribute with specified parameters.
/// </summary>
/// <param name="derivedType">A derived type that should be supported in polymorphic serialization of the declared base type.</param>
/// <param name="typeDiscriminator">The type discriminator identifier to be used for the serialization of the subtype.</param>
public JsonDerivedTypeAttribute(int typeDiscriminator) : base(typeof(TDerivedType), typeDiscriminator)
{
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -53,3 +53,33 @@ public JsonDerivedTypeAttribute(System.Type derivedType, int typeDiscriminator)
/// </summary>
public object? TypeDiscriminator { get; }
}

[System.AttributeUsage(System.AttributeTargets.Class | System.AttributeTargets.Interface, AllowMultiple = true, Inherited = false)]
internal class JsonDerivedTypeAttribute<TDerivedType> : JsonDerivedTypeAttribute
{
/// <summary>
/// Initializes a new attribute with specified parameters.
/// </summary>
/// <param name="derivedType">A derived type that should be supported in polymorphic serialization of the declared based type.</param>
public JsonDerivedTypeAttribute() : base(typeof(TDerivedType))
{
}

/// <summary>
/// Initializes a new attribute with specified parameters.
/// </summary>
/// <param name="derivedType">A derived type that should be supported in polymorphic serialization of the declared base type.</param>
/// <param name="typeDiscriminator">The type discriminator identifier to be used for the serialization of the subtype.</param>
public JsonDerivedTypeAttribute(string typeDiscriminator) : base(typeof(TDerivedType), typeDiscriminator)
{
}

/// <summary>
/// Initializes a new attribute with specified parameters.
/// </summary>
/// <param name="derivedType">A derived type that should be supported in polymorphic serialization of the declared base type.</param>
/// <param name="typeDiscriminator">The type discriminator identifier to be used for the serialization of the subtype.</param>
public JsonDerivedTypeAttribute(int typeDiscriminator) : base(typeof(TDerivedType), typeDiscriminator)
{
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
//HintName: BaseContractOfInt32JsonPolymorphicConverter.g.cs
#nullable enable

namespace ApplicationNamespace;

internal class BaseContractOfInt32JsonPolymorphicConverter : Aviationexam.GeneratedJsonConverters.PolymorphicJsonConvertor<ApplicationNamespace.Contracts.BaseContract<System.Int32>>
{
protected override System.ReadOnlySpan<byte> GetDiscriminatorPropertyName() => "myCustomDiscriminator"u8;

protected override System.Type GetTypeForDiscriminator(
Aviationexam.GeneratedJsonConverters.IDiscriminatorStruct discriminator
) => discriminator switch
{
Aviationexam.GeneratedJsonConverters.DiscriminatorStruct<string> { Value: "IntLeafContract" } => typeof(ApplicationNamespace.Contracts.IntLeafContract),
Aviationexam.GeneratedJsonConverters.DiscriminatorStruct<string> { Value: "StringLeafContract" } => typeof(ApplicationNamespace.Contracts.StringLeafContract),

_ => throw new System.ArgumentOutOfRangeException(nameof(discriminator), discriminator, null),
};

protected override Aviationexam.GeneratedJsonConverters.IDiscriminatorStruct GetDiscriminatorForType(
System.Type type
)
{
if (type == typeof(ApplicationNamespace.Contracts.IntLeafContract))
{
return new Aviationexam.GeneratedJsonConverters.DiscriminatorStruct<string>("IntLeafContract");
}
if (type == typeof(ApplicationNamespace.Contracts.StringLeafContract))
{
return new Aviationexam.GeneratedJsonConverters.DiscriminatorStruct<string>("StringLeafContract");
}

throw new System.ArgumentOutOfRangeException(nameof(type), type, null);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
//HintName: BaseContractOfStringJsonPolymorphicConverter.g.cs
#nullable enable

namespace ApplicationNamespace;

internal class BaseContractOfStringJsonPolymorphicConverter : Aviationexam.GeneratedJsonConverters.PolymorphicJsonConvertor<ApplicationNamespace.Contracts.BaseContract<System.String>>
{
protected override System.ReadOnlySpan<byte> GetDiscriminatorPropertyName() => "myCustomDiscriminator"u8;

protected override System.Type GetTypeForDiscriminator(
Aviationexam.GeneratedJsonConverters.IDiscriminatorStruct discriminator
) => discriminator switch
{
Aviationexam.GeneratedJsonConverters.DiscriminatorStruct<string> { Value: "IntLeafContract" } => typeof(ApplicationNamespace.Contracts.IntLeafContract),
Aviationexam.GeneratedJsonConverters.DiscriminatorStruct<string> { Value: "StringLeafContract" } => typeof(ApplicationNamespace.Contracts.StringLeafContract),

_ => throw new System.ArgumentOutOfRangeException(nameof(discriminator), discriminator, null),
};

protected override Aviationexam.GeneratedJsonConverters.IDiscriminatorStruct GetDiscriminatorForType(
System.Type type
)
{
if (type == typeof(ApplicationNamespace.Contracts.IntLeafContract))
{
return new Aviationexam.GeneratedJsonConverters.DiscriminatorStruct<string>("IntLeafContract");
}
if (type == typeof(ApplicationNamespace.Contracts.StringLeafContract))
{
return new Aviationexam.GeneratedJsonConverters.DiscriminatorStruct<string>("StringLeafContract");
}

throw new System.ArgumentOutOfRangeException(nameof(type), type, null);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
//HintName: DiscriminatorStruct.g.cs
// ReSharper disable once RedundantNullableDirective

#nullable enable

namespace Aviationexam.GeneratedJsonConverters;

internal readonly struct DiscriminatorStruct<T> : IDiscriminatorStruct
{
public T Value { get; init; }

public DiscriminatorStruct(T value)
{
Value = value;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
//HintName: IDiscriminatorStruct.g.cs
// ReSharper disable once RedundantNullableDirective

#nullable enable

namespace Aviationexam.GeneratedJsonConverters;

internal interface IDiscriminatorStruct
{
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
//HintName: JsonDerivedTypeAttribute.g.cs
#nullable enable

namespace Aviationexam.GeneratedJsonConverters.Attributes;

/// <summary>
/// This is a copy of System.Text.Json.Serialization.JsonDerivedTypeAttribute.
/// It's purpose is to replace this attribute to silence System.Text.Json.Serialization.Metadata.PolymorphicTypeResolver{ThrowHelper.ThrowNotSupportedException_BaseConverterDoesNotSupportMetadata}
///
/// When placed on a type declaration, indicates that the specified subtype should be opted into polymorphic serialization.
/// </summary>
[System.AttributeUsage(System.AttributeTargets.Class | System.AttributeTargets.Interface, AllowMultiple = true, Inherited = false)]
internal class JsonDerivedTypeAttribute : System.Text.Json.Serialization.JsonAttribute
{
/// <summary>
/// Initializes a new attribute with specified parameters.
/// </summary>
/// <param name="derivedType">A derived type that should be supported in polymorphic serialization of the declared based type.</param>
public JsonDerivedTypeAttribute(System.Type derivedType)
{
DerivedType = derivedType;
}

/// <summary>
/// Initializes a new attribute with specified parameters.
/// </summary>
/// <param name="derivedType">A derived type that should be supported in polymorphic serialization of the declared base type.</param>
/// <param name="typeDiscriminator">The type discriminator identifier to be used for the serialization of the subtype.</param>
public JsonDerivedTypeAttribute(System.Type derivedType, string typeDiscriminator)
{
DerivedType = derivedType;
TypeDiscriminator = typeDiscriminator;
}

/// <summary>
/// Initializes a new attribute with specified parameters.
/// </summary>
/// <param name="derivedType">A derived type that should be supported in polymorphic serialization of the declared base type.</param>
/// <param name="typeDiscriminator">The type discriminator identifier to be used for the serialization of the subtype.</param>
public JsonDerivedTypeAttribute(System.Type derivedType, int typeDiscriminator)
{
DerivedType = derivedType;
TypeDiscriminator = typeDiscriminator;
}

/// <summary>
/// A derived type that should be supported in polymorphic serialization of the declared base type.
/// </summary>
public System.Type DerivedType { get; }

/// <summary>
/// The type discriminator identifier to be used for the serialization of the subtype.
/// </summary>
public object? TypeDiscriminator { get; }
}

[System.AttributeUsage(System.AttributeTargets.Class | System.AttributeTargets.Interface, AllowMultiple = true, Inherited = false)]
internal class JsonDerivedTypeAttribute<TDerivedType> : JsonDerivedTypeAttribute
{
/// <summary>
/// Initializes a new attribute with specified parameters.
/// </summary>
/// <param name="derivedType">A derived type that should be supported in polymorphic serialization of the declared based type.</param>
public JsonDerivedTypeAttribute() : base(typeof(TDerivedType))
{
}

/// <summary>
/// Initializes a new attribute with specified parameters.
/// </summary>
/// <param name="derivedType">A derived type that should be supported in polymorphic serialization of the declared base type.</param>
/// <param name="typeDiscriminator">The type discriminator identifier to be used for the serialization of the subtype.</param>
public JsonDerivedTypeAttribute(string typeDiscriminator) : base(typeof(TDerivedType), typeDiscriminator)
{
}

/// <summary>
/// Initializes a new attribute with specified parameters.
/// </summary>
/// <param name="derivedType">A derived type that should be supported in polymorphic serialization of the declared base type.</param>
/// <param name="typeDiscriminator">The type discriminator identifier to be used for the serialization of the subtype.</param>
public JsonDerivedTypeAttribute(int typeDiscriminator) : base(typeof(TDerivedType), typeDiscriminator)
{
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
//HintName: JsonPolymorphicAttribute.g.cs
#nullable enable

namespace Aviationexam.GeneratedJsonConverters.Attributes;

/// <summary>
/// This is a copy of System.Text.Json.Serialization.JsonPolymorphicAttribute.
/// It's purpose is to replace this attribute to silence System.Text.Json.Serialization.Metadata.PolymorphicTypeResolver{ThrowHelper.ThrowNotSupportedException_BaseConverterDoesNotSupportMetadata}
///
/// When placed on a type, indicates that the type should be serialized polymorphically.
/// </summary>
[System.AttributeUsage(System.AttributeTargets.Class | System.AttributeTargets.Interface, AllowMultiple = false, Inherited = false)]
internal sealed class JsonPolymorphicAttribute : System.Text.Json.Serialization.JsonAttribute
{
/// <summary>
/// Gets or sets a custom type discriminator property name for the polymorhic type.
/// Uses the default '$type' property name if left unset.
/// </summary>
public string? TypeDiscriminatorPropertyName { get; set; }

/// <summary>
/// Gets or sets the behavior when serializing an undeclared derived runtime type.
/// </summary>
public System.Text.Json.Serialization.JsonUnknownDerivedTypeHandling UnknownDerivedTypeHandling { get; set; }

/// <summary>
/// When set to <see langword="true"/>, instructs the deserializer to ignore any
/// unrecognized type discriminator id's and reverts to the contract of the base type.
/// Otherwise, it will fail the deserialization.
/// </summary>
public bool IgnoreUnrecognizedTypeDiscriminators { get; set; }
}
Loading

0 comments on commit 8b6f2be

Please sign in to comment.