diff --git a/HalKit.Backend/HalKit.Backend.csproj b/HalKit.Backend/HalKit.Backend.csproj
new file mode 100644
index 0000000..cab6c2c
--- /dev/null
+++ b/HalKit.Backend/HalKit.Backend.csproj
@@ -0,0 +1,22 @@
+
+
+
+ netstandard2.0
+ enable
+ enable
+ default
+ true
+ https://github.com/viagogo/HalKit
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/HalKit.Backend/Json/HalKitTypeBuilder.cs b/HalKit.Backend/Json/HalKitTypeBuilder.cs
new file mode 100644
index 0000000..957d1d3
--- /dev/null
+++ b/HalKit.Backend/Json/HalKitTypeBuilder.cs
@@ -0,0 +1,80 @@
+using System;
+using System.Collections.Generic;
+using System.Reflection;
+using System.Reflection.Emit;
+using Newtonsoft.Json.Serialization;
+
+namespace HalKit.Backend.Json
+{
+ ///
+ /// Generates type run-time, based on a sample code found at https://stackoverflow.com/questions/3862226/how-to-dynamically-create-a-class
+ ///
+ public static class HalKitTypeBuilder
+ {
+ public static Type CompileResultType(string typeName, IReadOnlyDictionary properties)
+ {
+ var tb = GetTypeBuilder(typeName);
+ var constructor = tb.DefineDefaultConstructor(MethodAttributes.Public | MethodAttributes.SpecialName | MethodAttributes.RTSpecialName);
+
+ foreach (var field in properties)
+ {
+ CreateProperty(tb, field.Key, field.Value.PropertyType);
+ }
+
+ var objectType = tb.CreateTypeInfo().AsType();
+ return objectType;
+ }
+
+ private static TypeBuilder GetTypeBuilder(string typeName)
+ {
+ var an = new AssemblyName("DynamicHalKit");
+ AssemblyBuilder assemblyBuilder = AssemblyBuilder.DefineDynamicAssembly(an, AssemblyBuilderAccess.Run);
+ ModuleBuilder moduleBuilder = assemblyBuilder.DefineDynamicModule("MainModule");
+ TypeBuilder tb = moduleBuilder.DefineType(typeName,
+ TypeAttributes.Public |
+ TypeAttributes.Class |
+ TypeAttributes.AutoClass |
+ TypeAttributes.AnsiClass |
+ TypeAttributes.BeforeFieldInit |
+ TypeAttributes.AutoLayout,
+ null);
+ return tb;
+ }
+
+ private static void CreateProperty(TypeBuilder tb, string propertyName, Type propertyType)
+ {
+ var fieldBuilder = tb.DefineField("_" + propertyName, propertyType, FieldAttributes.Private);
+
+ var propertyBuilder = tb.DefineProperty(propertyName, PropertyAttributes.HasDefault, propertyType, null);
+ var getPropertyMethodBuilder = tb.DefineMethod("get_" + propertyName, MethodAttributes.Public | MethodAttributes.SpecialName | MethodAttributes.HideBySig, propertyType, Type.EmptyTypes);
+ var getIl = getPropertyMethodBuilder.GetILGenerator();
+
+ getIl.Emit(OpCodes.Ldarg_0);
+ getIl.Emit(OpCodes.Ldfld, fieldBuilder);
+ getIl.Emit(OpCodes.Ret);
+
+ var setPropertyMethodBuilder =
+ tb.DefineMethod("set_" + propertyName,
+ MethodAttributes.Public |
+ MethodAttributes.SpecialName |
+ MethodAttributes.HideBySig,
+ null, new[] { propertyType });
+
+ var setIl = setPropertyMethodBuilder.GetILGenerator();
+ var modifyProperty = setIl.DefineLabel();
+ var exitSet = setIl.DefineLabel();
+
+ setIl.MarkLabel(modifyProperty);
+ setIl.Emit(OpCodes.Ldarg_0);
+ setIl.Emit(OpCodes.Ldarg_1);
+ setIl.Emit(OpCodes.Stfld, fieldBuilder);
+
+ setIl.Emit(OpCodes.Nop);
+ setIl.MarkLabel(exitSet);
+ setIl.Emit(OpCodes.Ret);
+
+ propertyBuilder.SetGetMethod(getPropertyMethodBuilder);
+ propertyBuilder.SetSetMethod(setPropertyMethodBuilder);
+ }
+ }
+}
\ No newline at end of file
diff --git a/HalKit.Backend/Json/TypedResourceContractResolver.cs b/HalKit.Backend/Json/TypedResourceContractResolver.cs
new file mode 100644
index 0000000..aead6f5
--- /dev/null
+++ b/HalKit.Backend/Json/TypedResourceContractResolver.cs
@@ -0,0 +1,197 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Reflection;
+using HalKit.Json;
+using HalKit.Models.Response;
+using Newtonsoft.Json;
+using Newtonsoft.Json.Linq;
+using Newtonsoft.Json.Serialization;
+
+namespace HalKit.Backend.Json
+{
+ ///
+ /// Resolves a for a given .
+ /// Generates proper type for the reserved JSON properties of "_links" and "_embedded"
+ /// through reflection so that Swashbuckle.AspNetCore or other tools can properly work out the schema
+ ///
+ public class TypedResourceContractResolver : DefaultContractResolver
+ {
+ private static readonly Dictionary> ContractPropertiesByType
+ = new Dictionary>();
+
+ private readonly JsonSerializerSettings _settings;
+
+ ///
+ /// Initializes a new instance of the
+ /// class.
+ ///
+ public TypedResourceContractResolver(JsonSerializerSettings settings)
+ {
+ _settings = settings;
+ }
+
+ protected override IList CreateProperties(Type type, MemberSerialization memberSerialization)
+ {
+ var allProperties = base.CreateProperties(type, memberSerialization);
+ if (!typeof(Resource).GetTypeInfo().IsAssignableFrom(type.GetTypeInfo()))
+ {
+ return allProperties;
+ }
+
+ IList props;
+ if (!ContractPropertiesByType.TryGetValue(type, out props))
+ {
+ var contractProperties = new List();
+ var embeddedPropertyMap = new Dictionary();
+ var linksPropertyMap = new Dictionary();
+ foreach (var property in allProperties)
+ {
+ var isLinkOrEmbeddedProperty = false;
+ var attributes = property.AttributeProvider.GetAttributes(false);
+ foreach (var attribute in attributes)
+ {
+ var embeddedAttribute = attribute as EmbeddedAttribute;
+ if (embeddedAttribute != null)
+ {
+ isLinkOrEmbeddedProperty = true;
+ embeddedPropertyMap.Add(embeddedAttribute.Rel, property);
+ }
+
+ var relAttribute = attribute as RelAttribute;
+ if (relAttribute != null)
+ {
+ isLinkOrEmbeddedProperty = true;
+ linksPropertyMap.Add(relAttribute.Rel, property);
+ }
+ }
+
+ // This doesn't have a Rel or Embedded attribute so it's just a normal property
+ if (!isLinkOrEmbeddedProperty)
+ {
+ contractProperties.Add(property);
+ }
+ }
+
+ if (linksPropertyMap.Any())
+ {
+ contractProperties.Add(CreateReservedHalJsonProperty(type, "_links", linksPropertyMap));
+ }
+
+ if (embeddedPropertyMap.Any())
+ {
+ contractProperties.Add(CreateReservedHalJsonProperty(type, "_embedded", embeddedPropertyMap));
+ }
+
+ if (!ContractPropertiesByType.TryGetValue(type, out props))
+ {
+ lock (ContractPropertiesByType)
+ {
+ if (!ContractPropertiesByType.TryGetValue(type, out props))
+ {
+ ContractPropertiesByType.Add(type, contractProperties);
+ props = contractProperties;
+ }
+ }
+ }
+ }
+ return props;
+ }
+
+ private JsonProperty CreateReservedHalJsonProperty(
+ Type type,
+ string name,
+ IReadOnlyDictionary propertyMap)
+ {
+ return new JsonProperty
+ {
+ PropertyName = name,
+ PropertyType = HalKitTypeBuilder.CompileResultType($"{type.Name}{name}", propertyMap),
+ ValueProvider = new ReservedHalPropertyValueProvider(_settings, propertyMap),
+ NullValueHandling = NullValueHandling.Ignore,
+ Readable = propertyMap.Values.Any(p => p.Readable),
+ Writable = propertyMap.Values.Any(p => p.Writable),
+ ShouldSerialize = o => true,
+ GetIsSpecified = o => true,
+ SetIsSpecified = null,
+ Order = int.MaxValue,
+ };
+ }
+
+ private class ReservedHalPropertyValueProvider : IValueProvider
+ {
+ private readonly JsonSerializerSettings _settings;
+ private readonly IReadOnlyDictionary _propertyMap;
+
+ public ReservedHalPropertyValueProvider(
+ JsonSerializerSettings settings,
+ IReadOnlyDictionary propertyMap)
+ {
+ _settings = settings;
+ _propertyMap = propertyMap;
+ }
+
+ public object GetValue(object target)
+ {
+ // Use a SortedDictionary since it just seems "right" for the
+ // "self" link to be first
+ var reservedPropertyValue = new SortedDictionary(new RelComparer());
+ foreach (var rel in _propertyMap.Keys)
+ {
+ var property = _propertyMap[rel];
+ var propertyValue = property.ValueProvider.GetValue(target);
+ if (propertyValue != null)
+ {
+ reservedPropertyValue.Add(rel, propertyValue);
+ }
+ }
+
+ return reservedPropertyValue.Count > 0 ? reservedPropertyValue : null;
+ }
+
+ public void SetValue(object target, object value)
+ {
+ var valueDictionary = value as IDictionary;
+ if (valueDictionary == null)
+ {
+ return;
+ }
+
+ foreach (var rel in valueDictionary.Keys)
+ {
+ JsonProperty property;
+ if (!_propertyMap.TryGetValue(rel, out property))
+ {
+ continue;
+ }
+
+ var serializer = JsonSerializer.Create(_settings);
+ var propertyJson = valueDictionary[rel] as JToken;
+ var propertyValue = propertyJson != null
+ ? propertyJson.ToObject(property.PropertyType, serializer)
+ : null;
+ property.ValueProvider.SetValue(target, propertyValue);
+ }
+ }
+ }
+
+ private class RelComparer : IComparer
+ {
+ public int Compare(string rel, string otherRel)
+ {
+ const string self = "self";
+ if (rel == self)
+ {
+ return -1;
+ }
+
+ if (otherRel == self)
+ {
+ return 1;
+ }
+
+ return string.Compare(rel, otherRel, StringComparison.Ordinal);
+ }
+ }
+ }
+}
diff --git a/HalKit.sln b/HalKit.sln
index 19ca998..cb72252 100644
--- a/HalKit.sln
+++ b/HalKit.sln
@@ -23,6 +23,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Meta", "Meta", "{EC8D73FD-F
Version.props = Version.props
EndProjectSection
EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "HalKit.Backend", "HalKit.Backend\HalKit.Backend.csproj", "{F018259F-3574-4675-A78C-5D3165039522}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -37,6 +39,10 @@ Global
{56D3F770-FD3A-4ADF-A4A0-A5104F5A0012}.Debug|Any CPU.Build.0 = Debug|Any CPU
{56D3F770-FD3A-4ADF-A4A0-A5104F5A0012}.Release|Any CPU.ActiveCfg = Release|Any CPU
{56D3F770-FD3A-4ADF-A4A0-A5104F5A0012}.Release|Any CPU.Build.0 = Release|Any CPU
+ {F018259F-3574-4675-A78C-5D3165039522}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {F018259F-3574-4675-A78C-5D3165039522}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {F018259F-3574-4675-A78C-5D3165039522}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {F018259F-3574-4675-A78C-5D3165039522}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
diff --git a/ReleaseNotes.md b/ReleaseNotes.md
index c8d8ea3..95979d0 100644
--- a/ReleaseNotes.md
+++ b/ReleaseNotes.md
@@ -1,3 +1,6 @@
+### New in 1.0.3 (Released 30/11/2021)
+* Remove DeclaringType from reserved HAL JSON properties
+
### New in 1.0 (Released 05/09/2018)
* Upgrade packages to netstandard
diff --git a/Version.props b/Version.props
index a3a44a4..53f3cff 100644
--- a/Version.props
+++ b/Version.props
@@ -1,6 +1,6 @@
- 1.0.2
+ 1.0.4
diff --git a/src/HalKit/Json/ResourceContractResolver.cs b/src/HalKit/Json/ResourceContractResolver.cs
index 3f89e96..b287fbd 100644
--- a/src/HalKit/Json/ResourceContractResolver.cs
+++ b/src/HalKit/Json/ResourceContractResolver.cs
@@ -104,7 +104,6 @@ private JsonProperty CreateReservedHalJsonProperty(
{
PropertyName = name,
PropertyType = typeof(Dictionary),
- DeclaringType = type,
ValueProvider = new ReservedHalPropertyValueProvider(_settings, propertyMap),
NullValueHandling = NullValueHandling.Ignore,
Readable = propertyMap.Values.Any(p => p.Readable),