diff --git a/.github/workflows/dotnet.yml b/.github/workflows/dotnet.yml
index 217f7cb..23aee29 100644
--- a/.github/workflows/dotnet.yml
+++ b/.github/workflows/dotnet.yml
@@ -16,10 +16,14 @@ jobs:
steps:
- uses: actions/checkout@v4
- - name: Setup .NET
+ - name: Setup .NET 8
uses: actions/setup-dotnet@v4
with:
dotnet-version: 8.0.x
+ - name: Setup .NET 6
+ uses: actions/setup-dotnet@v4
+ with:
+ dotnet-version: 6.0.x
- name: Restore dependencies
run: dotnet restore
- name: Build
diff --git a/F23.Hateoas.NewtonsoftJson/F23.Hateoas.NewtonsoftJson.csproj b/F23.Hateoas.NewtonsoftJson/F23.Hateoas.NewtonsoftJson.csproj
new file mode 100644
index 0000000..48fa513
--- /dev/null
+++ b/F23.Hateoas.NewtonsoftJson/F23.Hateoas.NewtonsoftJson.csproj
@@ -0,0 +1,40 @@
+
+
+
+ F23.Hateoas.NewtonsoftJson
+ 2.0.0
+ net8.0;netstandard2.0
+ feature[23]
+ Newtonsoft.Json support for F23.Hateoas.
+ 2025, feature[23]
+ MIT
+ https://github.com/feature23/hateoas
+ https://github.com/feature23/hateoas.git
+ git
+ hateoas hypermedia asp.net core mvc api newtonsoft json
+ logo.png
+ README.md
+ false
+ true
+ true
+ snupkg
+ enable
+ enable
+ true
+ latest
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/F23.Hateoas.NewtonsoftJson/HypermediaLinkJsonConverter.cs b/F23.Hateoas.NewtonsoftJson/HypermediaLinkJsonConverter.cs
new file mode 100644
index 0000000..d4082ec
--- /dev/null
+++ b/F23.Hateoas.NewtonsoftJson/HypermediaLinkJsonConverter.cs
@@ -0,0 +1,50 @@
+using Newtonsoft.Json;
+using Newtonsoft.Json.Linq;
+
+namespace F23.Hateoas.NewtonsoftJson;
+
+public class HypermediaLinkJsonConverter : JsonConverter
+{
+ public override void WriteJson(JsonWriter writer, object? value, JsonSerializer serializer)
+ {
+ if (value is not HypermediaLink link)
+ {
+ writer.WriteNull();
+ return;
+ }
+
+ writer.WriteStartObject();
+
+ writer.WritePropertyName("rel");
+ serializer.Serialize(writer, link.Rel);
+
+ writer.WritePropertyName("href");
+ serializer.Serialize(writer, link.Href);
+
+ if (link.Method != null)
+ {
+ writer.WritePropertyName("method");
+ serializer.Serialize(writer, link.Method);
+ }
+
+ writer.WriteEndObject();
+ }
+
+ public override object ReadJson(JsonReader reader, Type objectType, object? existingValue, JsonSerializer serializer)
+ {
+ var jsonObject = JObject.Load(reader);
+
+ var rel = jsonObject["rel"]
+ ?? throw new JsonSerializationException("Missing required property 'rel'.");
+
+ var href = jsonObject["href"]
+ ?? throw new JsonSerializationException("Missing required property 'href'.");
+
+ var method = jsonObject["method"];
+
+ return new HypermediaLink(rel.Value()!, href.Value()!, method?.Value());
+ }
+
+ public override bool CanConvert(Type objectType)
+ => objectType == typeof(HypermediaLink);
+}
diff --git a/F23.Hateoas.NewtonsoftJson/HypermediaResponseJsonConverter.cs b/F23.Hateoas.NewtonsoftJson/HypermediaResponseJsonConverter.cs
new file mode 100644
index 0000000..c74a2b7
--- /dev/null
+++ b/F23.Hateoas.NewtonsoftJson/HypermediaResponseJsonConverter.cs
@@ -0,0 +1,61 @@
+using Newtonsoft.Json;
+using Newtonsoft.Json.Linq;
+
+namespace F23.Hateoas.NewtonsoftJson;
+
+public class HypermediaResponseJsonConverter : JsonConverter
+{
+ public override void WriteJson(JsonWriter writer, object? value, JsonSerializer serializer)
+ {
+ if (value is not HypermediaResponse response)
+ {
+ writer.WriteNull();
+ return;
+ }
+
+ writer.WriteStartObject();
+
+ writer.WritePropertyName("content");
+ serializer.Serialize(writer, response.Content);
+
+ if (response.Links != null)
+ {
+ writer.WritePropertyName("_links");
+ writer.WriteStartArray();
+
+ foreach (var link in response.Links)
+ {
+ serializer.Serialize(writer, link);
+ }
+
+ writer.WriteEndArray();
+ }
+
+ writer.WriteEndObject();
+ }
+
+ public override object ReadJson(JsonReader reader, Type objectType, object? existingValue, JsonSerializer serializer)
+ {
+ var jsonObject = JObject.Load(reader);
+
+ var content = jsonObject["content"]
+ ?? throw new JsonSerializationException("Missing required property 'content'.");
+
+ var response = new HypermediaResponse(content);
+
+ var links = jsonObject["_links"];
+
+ if (links != null)
+ {
+ foreach (var link in links)
+ {
+ response.Add(serializer.Deserialize(link.CreateReader())!);
+ }
+ }
+
+ return response;
+ }
+
+ public override bool CanConvert(Type objectType)
+ => objectType == typeof(HypermediaResponse);
+}
diff --git a/F23.Hateoas.NewtonsoftJson/README.md b/F23.Hateoas.NewtonsoftJson/README.md
new file mode 100644
index 0000000..9e261da
--- /dev/null
+++ b/F23.Hateoas.NewtonsoftJson/README.md
@@ -0,0 +1,40 @@
+# F23.Hateoas.NewtonsoftJson
+
+This library provides compatibility between [F23.Hateoas](https://github.com/feature23/hateoas)
+and [Newtonsoft.Json](https://www.newtonsoft.com/json).
+
+## Installation
+
+You can install this library via NuGet:
+
+```bash
+dotnet add package F23.Hateoas.NewtonsoftJson
+```
+
+## Usage
+
+Add the `HypermediaResponseJsonConverter` and `HypermediaLinkJsonConverter` to the `JsonSerializerSettings`:
+
+```csharp
+using F23.Hateoas.NewtonsoftJson;
+
+var options = new JsonSerializerSettings
+{
+ Converters =
+ {
+ new HypermediaResponseJsonConverter(),
+ new HypermediaLinkJsonConverter(),
+ }
+};
+```
+
+This can be done during ASP.NET Core startup:
+
+```csharp
+builder.Services.AddControllers()
+ .AddNewtonsoftJson(options =>
+ {
+ options.SerializerSettings.Converters.Add(new HypermediaResponseJsonConverter());
+ options.SerializerSettings.Converters.Add(new HypermediaLinkJsonConverter());
+ });
+```
diff --git a/F23.Hateoas.NewtonsoftJson/RequiredPropertiesBackCompat.cs b/F23.Hateoas.NewtonsoftJson/RequiredPropertiesBackCompat.cs
new file mode 100644
index 0000000..f4aa81e
--- /dev/null
+++ b/F23.Hateoas.NewtonsoftJson/RequiredPropertiesBackCompat.cs
@@ -0,0 +1,41 @@
+using System.ComponentModel;
+
+namespace System.Runtime.CompilerServices
+{
+#if !NET5_0_OR_GREATER
+
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ internal static class IsExternalInit {}
+
+#endif // !NET5_0_OR_GREATER
+
+#if !NET7_0_OR_GREATER
+
+ [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Field | AttributeTargets.Property, AllowMultiple = false, Inherited = false)]
+ internal sealed class RequiredMemberAttribute : Attribute {}
+
+ [AttributeUsage(AttributeTargets.All, AllowMultiple = true, Inherited = false)]
+ internal sealed class CompilerFeatureRequiredAttribute : Attribute
+ {
+ public CompilerFeatureRequiredAttribute(string featureName)
+ {
+ FeatureName = featureName;
+ }
+
+ public string FeatureName { get; }
+ public bool IsOptional { get; init; }
+
+ public const string RefStructs = nameof(RefStructs);
+ public const string RequiredMembers = nameof(RequiredMembers);
+ }
+
+#endif // !NET7_0_OR_GREATER
+}
+
+namespace System.Diagnostics.CodeAnalysis
+{
+#if !NET7_0_OR_GREATER
+ [AttributeUsage(AttributeTargets.Constructor, AllowMultiple = false, Inherited = false)]
+ internal sealed class SetsRequiredMembersAttribute : Attribute {}
+#endif
+}
diff --git a/F23.Hateoas.Sample.NewtonsoftJson/F23.Hateoas.Sample.NewtonsoftJson.csproj b/F23.Hateoas.Sample.NewtonsoftJson/F23.Hateoas.Sample.NewtonsoftJson.csproj
index 80b813c..004d004 100644
--- a/F23.Hateoas.Sample.NewtonsoftJson/F23.Hateoas.Sample.NewtonsoftJson.csproj
+++ b/F23.Hateoas.Sample.NewtonsoftJson/F23.Hateoas.Sample.NewtonsoftJson.csproj
@@ -13,7 +13,8 @@
-
+
+
diff --git a/F23.Hateoas.Sample.NewtonsoftJson/Program.cs b/F23.Hateoas.Sample.NewtonsoftJson/Program.cs
index 734da51..cbc6dc7 100644
--- a/F23.Hateoas.Sample.NewtonsoftJson/Program.cs
+++ b/F23.Hateoas.Sample.NewtonsoftJson/Program.cs
@@ -1,4 +1,5 @@
using F23.Hateoas;
+using F23.Hateoas.NewtonsoftJson;
var builder = WebApplication.CreateBuilder(args);
@@ -8,7 +9,11 @@
builder.Services.AddSwaggerGen();
builder.Services.AddControllers()
- .AddNewtonsoftJson();
+ .AddNewtonsoftJson(options =>
+ {
+ options.SerializerSettings.Converters.Add(new HypermediaResponseJsonConverter());
+ options.SerializerSettings.Converters.Add(new HypermediaLinkJsonConverter());
+ });
var app = builder.Build();
diff --git a/F23.Hateoas.Sample.NewtonsoftJson/Properties/launchSettings.json b/F23.Hateoas.Sample.NewtonsoftJson/Properties/launchSettings.json
index 39f05c5..a265483 100644
--- a/F23.Hateoas.Sample.NewtonsoftJson/Properties/launchSettings.json
+++ b/F23.Hateoas.Sample.NewtonsoftJson/Properties/launchSettings.json
@@ -1,13 +1,5 @@
{
"$schema": "http://json.schemastore.org/launchsettings.json",
- "iisSettings": {
- "windowsAuthentication": false,
- "anonymousAuthentication": true,
- "iisExpress": {
- "applicationUrl": "http://localhost:10201",
- "sslPort": 44369
- }
- },
"profiles": {
"http": {
"commandName": "Project",
@@ -28,14 +20,6 @@
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
- },
- "IIS Express": {
- "commandName": "IISExpress",
- "launchBrowser": true,
- "launchUrl": "swagger",
- "environmentVariables": {
- "ASPNETCORE_ENVIRONMENT": "Development"
- }
}
}
}
diff --git a/F23.Hateoas.Sample.SystemTextJson/AppBaseController.cs b/F23.Hateoas.Sample.SystemTextJson/AppBaseController.cs
new file mode 100644
index 0000000..358ec18
--- /dev/null
+++ b/F23.Hateoas.Sample.SystemTextJson/AppBaseController.cs
@@ -0,0 +1,11 @@
+using Microsoft.AspNetCore.Mvc;
+
+namespace F23.Hateoas.Sample.SystemTextJson;
+
+public abstract class AppBaseController : ControllerBase
+{
+ protected IActionResult HypermediaOk(object content, IList? links = null)
+ {
+ return Ok(new HypermediaResponse(content) { Links = links });
+ }
+}
diff --git a/F23.Hateoas.Sample.SystemTextJson/ExampleController.cs b/F23.Hateoas.Sample.SystemTextJson/ExampleController.cs
new file mode 100644
index 0000000..6210f9d
--- /dev/null
+++ b/F23.Hateoas.Sample.SystemTextJson/ExampleController.cs
@@ -0,0 +1,20 @@
+using Microsoft.AspNetCore.Mvc;
+
+namespace F23.Hateoas.Sample.SystemTextJson;
+
+public class ExampleController : AppBaseController
+{
+ [HttpGet("api/example")]
+ public IActionResult GetExample()
+ {
+ var data = new
+ {
+ Message = "Hello World!",
+ };
+
+ return HypermediaOk(data, [
+ new HypermediaLink("self", "/api/example"),
+ new HypermediaLink("google", "https://www.google.com"),
+ ]);
+ }
+}
diff --git a/F23.Hateoas.Sample.SystemTextJson/F23.Hateoas.Sample.SystemTextJson.csproj b/F23.Hateoas.Sample.SystemTextJson/F23.Hateoas.Sample.SystemTextJson.csproj
new file mode 100644
index 0000000..1c23340
--- /dev/null
+++ b/F23.Hateoas.Sample.SystemTextJson/F23.Hateoas.Sample.SystemTextJson.csproj
@@ -0,0 +1,18 @@
+
+
+
+ net8.0
+ enable
+ enable
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/F23.Hateoas.Sample.SystemTextJson/Program.cs b/F23.Hateoas.Sample.SystemTextJson/Program.cs
new file mode 100644
index 0000000..e0a8a04
--- /dev/null
+++ b/F23.Hateoas.Sample.SystemTextJson/Program.cs
@@ -0,0 +1,23 @@
+var builder = WebApplication.CreateBuilder(args);
+
+// Add services to the container.
+// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
+builder.Services.AddEndpointsApiExplorer();
+builder.Services.AddSwaggerGen();
+
+builder.Services.AddControllers(); // uses System.Text.Json by default
+
+var app = builder.Build();
+
+// Configure the HTTP request pipeline.
+if (app.Environment.IsDevelopment())
+{
+ app.UseSwagger();
+ app.UseSwaggerUI();
+}
+
+app.UseHttpsRedirection();
+
+app.MapControllers();
+
+app.Run();
diff --git a/F23.Hateoas.Sample.SystemTextJson/Properties/launchSettings.json b/F23.Hateoas.Sample.SystemTextJson/Properties/launchSettings.json
new file mode 100644
index 0000000..f6679c7
--- /dev/null
+++ b/F23.Hateoas.Sample.SystemTextJson/Properties/launchSettings.json
@@ -0,0 +1,25 @@
+{
+ "$schema": "http://json.schemastore.org/launchsettings.json",
+ "profiles": {
+ "http": {
+ "commandName": "Project",
+ "dotnetRunMessages": true,
+ "launchBrowser": true,
+ "launchUrl": "swagger",
+ "applicationUrl": "http://localhost:5158",
+ "environmentVariables": {
+ "ASPNETCORE_ENVIRONMENT": "Development"
+ }
+ },
+ "https": {
+ "commandName": "Project",
+ "dotnetRunMessages": true,
+ "launchBrowser": true,
+ "launchUrl": "swagger",
+ "applicationUrl": "https://localhost:7156;http://localhost:5158",
+ "environmentVariables": {
+ "ASPNETCORE_ENVIRONMENT": "Development"
+ }
+ }
+ }
+}
diff --git a/F23.Hateoas.Tests/F23.Hateoas.Tests.csproj b/F23.Hateoas.Tests/F23.Hateoas.Tests.csproj
index 6b8475f..b6795cd 100644
--- a/F23.Hateoas.Tests/F23.Hateoas.Tests.csproj
+++ b/F23.Hateoas.Tests/F23.Hateoas.Tests.csproj
@@ -1,10 +1,11 @@
- net8.0
+
+ net8.0;net6.0
enable
enable
-
+ latest
false
true
@@ -24,6 +25,7 @@
+
diff --git a/F23.Hateoas.Tests/NewtonsoftJsonSerializationTests.cs b/F23.Hateoas.Tests/NewtonsoftJsonSerializationTests.cs
index 2c30f1a..f8ed1b5 100644
--- a/F23.Hateoas.Tests/NewtonsoftJsonSerializationTests.cs
+++ b/F23.Hateoas.Tests/NewtonsoftJsonSerializationTests.cs
@@ -1,15 +1,25 @@
+using F23.Hateoas.NewtonsoftJson;
using Newtonsoft.Json;
namespace F23.Hateoas.Tests;
public class NewtonsoftJsonSerializationTests
{
+ private static readonly JsonSerializerSettings _settings = new JsonSerializerSettings
+ {
+ Converters =
+ {
+ new HypermediaResponseJsonConverter(),
+ new HypermediaLinkJsonConverter(),
+ }
+ };
+
[Fact]
public void Serialize_WithNullLinks_ShouldNotIncludeLinks()
{
var response = new HypermediaResponse(42);
- var json = JsonConvert.SerializeObject(response);
+ var json = JsonConvert.SerializeObject(response, _settings);
Assert.Equal("""{"content":42}""", json);
}
@@ -22,12 +32,12 @@ public void Serialize_WithLinks_ShouldIncludeLinks()
Links = new List
{
new HypermediaLink("self", "/api/example"),
- new HypermediaLink("google", "https://www.google.com"),
+ new HypermediaLink("google", "https://www.google.com", "GET"),
}
};
- var json = JsonConvert.SerializeObject(response);
+ var json = JsonConvert.SerializeObject(response, _settings);
- Assert.Equal("""{"content":42,"_links":[{"rel":"self","href":"/api/example"},{"rel":"google","href":"https://www.google.com"}]}""", json);
+ Assert.Equal("""{"content":42,"_links":[{"rel":"self","href":"/api/example"},{"rel":"google","href":"https://www.google.com","method":"GET"}]}""", json);
}
}
diff --git a/F23.Hateoas.Tests/SystemTextJsonSerializationTests.cs b/F23.Hateoas.Tests/SystemTextJsonSerializationTests.cs
new file mode 100644
index 0000000..48a5365
--- /dev/null
+++ b/F23.Hateoas.Tests/SystemTextJsonSerializationTests.cs
@@ -0,0 +1,33 @@
+using System.Text.Json;
+
+namespace F23.Hateoas.Tests;
+
+public class SystemTextJsonSerializationTests
+{
+ [Fact]
+ public void Serialize_WithNullLinks_ShouldNotIncludeLinks()
+ {
+ var response = new HypermediaResponse(42);
+
+ var json = JsonSerializer.Serialize(response);
+
+ Assert.Equal("""{"content":42}""", json);
+ }
+
+ [Fact]
+ public void Serialize_WithLinks_ShouldIncludeLinks()
+ {
+ var response = new HypermediaResponse(42)
+ {
+ Links = new List
+ {
+ new HypermediaLink("self", "/api/example"),
+ new HypermediaLink("google", "https://www.google.com", "GET"),
+ }
+ };
+
+ var json = JsonSerializer.Serialize(response);
+
+ Assert.Equal("""{"content":42,"_links":[{"rel":"self","href":"/api/example"},{"rel":"google","href":"https://www.google.com","method":"GET"}]}""", json);
+ }
+}
diff --git a/F23.Hateoas.sln b/F23.Hateoas.sln
index bb7b3d1..c1ea49c 100644
--- a/F23.Hateoas.sln
+++ b/F23.Hateoas.sln
@@ -9,6 +9,10 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "F23.Hateoas.Sample.Newtonso
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "F23.Hateoas.Tests", "F23.Hateoas.Tests\F23.Hateoas.Tests.csproj", "{39735FE1-5D95-4BA9-AF6F-86143888CDF0}"
EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "F23.Hateoas.NewtonsoftJson", "F23.Hateoas.NewtonsoftJson\F23.Hateoas.NewtonsoftJson.csproj", "{7C417AE9-084E-4076-AB10-6AF96A663567}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "F23.Hateoas.Sample.SystemTextJson", "F23.Hateoas.Sample.SystemTextJson\F23.Hateoas.Sample.SystemTextJson.csproj", "{E40C060B-432F-4FB1-9B6C-579D3AB64228}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -30,5 +34,13 @@ Global
{39735FE1-5D95-4BA9-AF6F-86143888CDF0}.Debug|Any CPU.Build.0 = Debug|Any CPU
{39735FE1-5D95-4BA9-AF6F-86143888CDF0}.Release|Any CPU.ActiveCfg = Release|Any CPU
{39735FE1-5D95-4BA9-AF6F-86143888CDF0}.Release|Any CPU.Build.0 = Release|Any CPU
+ {7C417AE9-084E-4076-AB10-6AF96A663567}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {7C417AE9-084E-4076-AB10-6AF96A663567}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {7C417AE9-084E-4076-AB10-6AF96A663567}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {7C417AE9-084E-4076-AB10-6AF96A663567}.Release|Any CPU.Build.0 = Release|Any CPU
+ {E40C060B-432F-4FB1-9B6C-579D3AB64228}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {E40C060B-432F-4FB1-9B6C-579D3AB64228}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {E40C060B-432F-4FB1-9B6C-579D3AB64228}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {E40C060B-432F-4FB1-9B6C-579D3AB64228}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
EndGlobal
diff --git a/F23.Hateoas/F23.Hateoas.csproj b/F23.Hateoas/F23.Hateoas.csproj
index b67b666..0f15e1a 100644
--- a/F23.Hateoas/F23.Hateoas.csproj
+++ b/F23.Hateoas/F23.Hateoas.csproj
@@ -2,8 +2,8 @@
F23.Hateoas
- 1.1.0
- netstandard2.0
+ 2.0.0
+ net8.0;netstandard2.0
feature[23]
A set of basic HATEOAS (Hypermedia As The Engine Of Application State) types to help with implementing consistent hypermedia-enabled APIs.
2019, feature[23]
@@ -18,15 +18,19 @@
true
true
snupkg
+ enable
+ enable
+ true
+ latest
-
-
-
-
+
+
+
+
diff --git a/F23.Hateoas/HypermediaBase.cs b/F23.Hateoas/HypermediaBase.cs
deleted file mode 100644
index 6eee5ec..0000000
--- a/F23.Hateoas/HypermediaBase.cs
+++ /dev/null
@@ -1,14 +0,0 @@
-using System.Collections.Generic;
-using Newtonsoft.Json;
-
-namespace F23.Hateoas
-{
- ///
- /// A base class for including hypermedia (HATEOAS) links.
- ///
- public abstract class HypermediaBase
- {
- [JsonProperty("_links", NullValueHandling = NullValueHandling.Ignore)]
- public IList Links { get; set; }
- }
-}
diff --git a/F23.Hateoas/HypermediaLink.cs b/F23.Hateoas/HypermediaLink.cs
index 6abf028..8288ca0 100644
--- a/F23.Hateoas/HypermediaLink.cs
+++ b/F23.Hateoas/HypermediaLink.cs
@@ -1,33 +1,29 @@
-using Newtonsoft.Json;
+using System.Diagnostics.CodeAnalysis;
+using System.Text.Json.Serialization;
-namespace F23.Hateoas
+namespace F23.Hateoas;
+
+public class HypermediaLink
{
- [JsonObject(ItemNullValueHandling = NullValueHandling.Ignore)]
- public class HypermediaLink
+ public HypermediaLink()
{
- public HypermediaLink()
- {
- }
-
- public HypermediaLink(string rel, string href)
- : this(rel, href, null)
- {
- }
+ }
- public HypermediaLink(string rel, string href, string method)
- {
- Rel = rel;
- Href = href;
- Method = method;
- }
+ [SetsRequiredMembers]
+ public HypermediaLink(string rel, string href, string? method = null)
+ {
+ Rel = rel;
+ Href = href;
+ Method = method;
+ }
- [JsonProperty("rel")]
- public string Rel { get; set; }
+ [JsonPropertyName("rel")]
+ public required string Rel { get; init; }
- [JsonProperty("href")]
- public string Href { get; set; }
+ [JsonPropertyName("href")]
+ public required string Href { get; init; }
- [JsonProperty("method")]
- public string Method { get; set; }
- }
-}
\ No newline at end of file
+ [JsonPropertyName("method")]
+ [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
+ public string? Method { get; set; }
+}
diff --git a/F23.Hateoas/HypermediaResponse.cs b/F23.Hateoas/HypermediaResponse.cs
index deb38ef..aeff844 100644
--- a/F23.Hateoas/HypermediaResponse.cs
+++ b/F23.Hateoas/HypermediaResponse.cs
@@ -1,27 +1,20 @@
-using System.Collections.Generic;
-using Newtonsoft.Json;
+using System.Text.Json.Serialization;
-namespace F23.Hateoas
-{
- public class HypermediaResponse
- {
- public HypermediaResponse(object content)
- {
- Content = content;
- }
-
- [JsonProperty("content", NullValueHandling = NullValueHandling.Include)]
- public object Content { get; }
+namespace F23.Hateoas;
- [JsonProperty("_links", NullValueHandling = NullValueHandling.Ignore)]
- public IList Links { get; set; }
+public class HypermediaResponse(object content)
+{
+ [JsonPropertyName("content")]
+ [JsonIgnore(Condition = JsonIgnoreCondition.Never)]
+ public object Content { get; } = content;
- public void Add(HypermediaLink link)
- {
- if (Links == null)
- Links = new List();
+ [JsonPropertyName("_links")]
+ [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
+ public IList? Links { get; set; }
- Links.Add(link);
- }
+ public void Add(HypermediaLink link)
+ {
+ Links ??= new List();
+ Links.Add(link);
}
}
diff --git a/F23.Hateoas/RequiredPropertiesBackCompat.cs b/F23.Hateoas/RequiredPropertiesBackCompat.cs
new file mode 100644
index 0000000..f4aa81e
--- /dev/null
+++ b/F23.Hateoas/RequiredPropertiesBackCompat.cs
@@ -0,0 +1,41 @@
+using System.ComponentModel;
+
+namespace System.Runtime.CompilerServices
+{
+#if !NET5_0_OR_GREATER
+
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ internal static class IsExternalInit {}
+
+#endif // !NET5_0_OR_GREATER
+
+#if !NET7_0_OR_GREATER
+
+ [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Field | AttributeTargets.Property, AllowMultiple = false, Inherited = false)]
+ internal sealed class RequiredMemberAttribute : Attribute {}
+
+ [AttributeUsage(AttributeTargets.All, AllowMultiple = true, Inherited = false)]
+ internal sealed class CompilerFeatureRequiredAttribute : Attribute
+ {
+ public CompilerFeatureRequiredAttribute(string featureName)
+ {
+ FeatureName = featureName;
+ }
+
+ public string FeatureName { get; }
+ public bool IsOptional { get; init; }
+
+ public const string RefStructs = nameof(RefStructs);
+ public const string RequiredMembers = nameof(RequiredMembers);
+ }
+
+#endif // !NET7_0_OR_GREATER
+}
+
+namespace System.Diagnostics.CodeAnalysis
+{
+#if !NET7_0_OR_GREATER
+ [AttributeUsage(AttributeTargets.Constructor, AllowMultiple = false, Inherited = false)]
+ internal sealed class SetsRequiredMembersAttribute : Attribute {}
+#endif
+}
diff --git a/README.md b/README.md
index dab211e..c6df26c 100644
--- a/README.md
+++ b/README.md
@@ -64,3 +64,24 @@ public IActionResult Get()
## Contributing
This package is very minimalistic and is not likely to need many updates. If you have a feature request or bug report, please file a GitHub issue. Once accepted by our team, you may submit a pull request. PRs without an accepted issue have a higher likelihood of being rejected.
+
+## Migrating from v1 to v2
+
+Version 2.0.0 of this library introduced a breaking change by removing the Newtonsoft.Json dependency.
+If you need Newtonsoft.Json support, add a reference to the `F23.Hateoas.NewtonsoftJson` package
+and register the `HypermediaResponseJsonConverter` and `HypermediaLinkJsonConverter` with your `JsonSerializerSettings`.
+
+For example, in ASP.NET Core startup:
+
+```csharp
+using F23.Hateoas.NewtonsoftJson;
+
+...
+
+builder.Services.AddControllers()
+ .AddNewtonsoftJson(options =>
+ {
+ options.SerializerSettings.Converters.Add(new HypermediaResponseJsonConverter());
+ options.SerializerSettings.Converters.Add(new HypermediaLinkJsonConverter());
+ });
+```