Skip to content

Commit 233cf91

Browse files
authored
BREAKING: Break Newtonsoft.Json dependency, project clean-up (#1)
* BREAKING: Break Newtonsoft.Json dependency, project clean-up - Removes Newtonsoft.Json dependency from F23.Hateoas project, adds System.Text.Json (for netstandard2.0 target only; implicitly included in net8.0 target) - Adds new F23.Hateoas.NewtonsoftJson project for Newtonsoft.Json compat - Adds net8.0 target to project - Adds sample project for System.Text.Json support and unit tests - Updates code to use modern C# features (file-scoped namespaces, etc.) - Removes HypermediaBase class
1 parent 5fea90f commit 233cf91

24 files changed

Lines changed: 513 additions & 92 deletions

.github/workflows/dotnet.yml

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,14 @@ jobs:
1616

1717
steps:
1818
- uses: actions/checkout@v4
19-
- name: Setup .NET
19+
- name: Setup .NET 8
2020
uses: actions/setup-dotnet@v4
2121
with:
2222
dotnet-version: 8.0.x
23+
- name: Setup .NET 6
24+
uses: actions/setup-dotnet@v4
25+
with:
26+
dotnet-version: 6.0.x
2327
- name: Restore dependencies
2428
run: dotnet restore
2529
- name: Build
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
3+
<PropertyGroup>
4+
<PackageId>F23.Hateoas.NewtonsoftJson</PackageId>
5+
<Version>2.0.0</Version>
6+
<TargetFrameworks>net8.0;netstandard2.0</TargetFrameworks>
7+
<Authors>feature[23]</Authors>
8+
<Description>Newtonsoft.Json support for F23.Hateoas.</Description>
9+
<Copyright>2025, feature[23]</Copyright>
10+
<PackageLicenseExpression>MIT</PackageLicenseExpression>
11+
<PackageProjectUrl>https://github.com/feature23/hateoas</PackageProjectUrl>
12+
<RepositoryUrl>https://github.com/feature23/hateoas.git</RepositoryUrl>
13+
<RepositoryType>git</RepositoryType>
14+
<PackageTags>hateoas hypermedia asp.net core mvc api newtonsoft json</PackageTags>
15+
<PackageIcon>logo.png</PackageIcon>
16+
<PackageReadmeFile>README.md</PackageReadmeFile>
17+
<PackageRequireLicenseAcceptance>false</PackageRequireLicenseAcceptance>
18+
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
19+
<IncludeSymbols>true</IncludeSymbols>
20+
<SymbolPackageFormat>snupkg</SymbolPackageFormat>
21+
<Nullable>enable</Nullable>
22+
<ImplicitUsings>enable</ImplicitUsings>
23+
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
24+
<LangVersion>latest</LangVersion>
25+
</PropertyGroup>
26+
27+
<ItemGroup>
28+
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
29+
</ItemGroup>
30+
31+
<ItemGroup>
32+
<ProjectReference Include="..\F23.Hateoas\F23.Hateoas.csproj" />
33+
</ItemGroup>
34+
35+
<ItemGroup>
36+
<None Include="../F23.Hateoas/logo.png" Pack="true" PackagePath="\" />
37+
<None Include="README.md" Pack="true" PackagePath="\" />
38+
</ItemGroup>
39+
40+
</Project>
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
using Newtonsoft.Json;
2+
using Newtonsoft.Json.Linq;
3+
4+
namespace F23.Hateoas.NewtonsoftJson;
5+
6+
public class HypermediaLinkJsonConverter : JsonConverter
7+
{
8+
public override void WriteJson(JsonWriter writer, object? value, JsonSerializer serializer)
9+
{
10+
if (value is not HypermediaLink link)
11+
{
12+
writer.WriteNull();
13+
return;
14+
}
15+
16+
writer.WriteStartObject();
17+
18+
writer.WritePropertyName("rel");
19+
serializer.Serialize(writer, link.Rel);
20+
21+
writer.WritePropertyName("href");
22+
serializer.Serialize(writer, link.Href);
23+
24+
if (link.Method != null)
25+
{
26+
writer.WritePropertyName("method");
27+
serializer.Serialize(writer, link.Method);
28+
}
29+
30+
writer.WriteEndObject();
31+
}
32+
33+
public override object ReadJson(JsonReader reader, Type objectType, object? existingValue, JsonSerializer serializer)
34+
{
35+
var jsonObject = JObject.Load(reader);
36+
37+
var rel = jsonObject["rel"]
38+
?? throw new JsonSerializationException("Missing required property 'rel'.");
39+
40+
var href = jsonObject["href"]
41+
?? throw new JsonSerializationException("Missing required property 'href'.");
42+
43+
var method = jsonObject["method"];
44+
45+
return new HypermediaLink(rel.Value<string>()!, href.Value<string>()!, method?.Value<string>());
46+
}
47+
48+
public override bool CanConvert(Type objectType)
49+
=> objectType == typeof(HypermediaLink);
50+
}
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
using Newtonsoft.Json;
2+
using Newtonsoft.Json.Linq;
3+
4+
namespace F23.Hateoas.NewtonsoftJson;
5+
6+
public class HypermediaResponseJsonConverter : JsonConverter
7+
{
8+
public override void WriteJson(JsonWriter writer, object? value, JsonSerializer serializer)
9+
{
10+
if (value is not HypermediaResponse response)
11+
{
12+
writer.WriteNull();
13+
return;
14+
}
15+
16+
writer.WriteStartObject();
17+
18+
writer.WritePropertyName("content");
19+
serializer.Serialize(writer, response.Content);
20+
21+
if (response.Links != null)
22+
{
23+
writer.WritePropertyName("_links");
24+
writer.WriteStartArray();
25+
26+
foreach (var link in response.Links)
27+
{
28+
serializer.Serialize(writer, link);
29+
}
30+
31+
writer.WriteEndArray();
32+
}
33+
34+
writer.WriteEndObject();
35+
}
36+
37+
public override object ReadJson(JsonReader reader, Type objectType, object? existingValue, JsonSerializer serializer)
38+
{
39+
var jsonObject = JObject.Load(reader);
40+
41+
var content = jsonObject["content"]
42+
?? throw new JsonSerializationException("Missing required property 'content'.");
43+
44+
var response = new HypermediaResponse(content);
45+
46+
var links = jsonObject["_links"];
47+
48+
if (links != null)
49+
{
50+
foreach (var link in links)
51+
{
52+
response.Add(serializer.Deserialize<HypermediaLink>(link.CreateReader())!);
53+
}
54+
}
55+
56+
return response;
57+
}
58+
59+
public override bool CanConvert(Type objectType)
60+
=> objectType == typeof(HypermediaResponse);
61+
}
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
# F23.Hateoas.NewtonsoftJson
2+
3+
This library provides compatibility between [F23.Hateoas](https://github.com/feature23/hateoas)
4+
and [Newtonsoft.Json](https://www.newtonsoft.com/json).
5+
6+
## Installation
7+
8+
You can install this library via NuGet:
9+
10+
```bash
11+
dotnet add package F23.Hateoas.NewtonsoftJson
12+
```
13+
14+
## Usage
15+
16+
Add the `HypermediaResponseJsonConverter` and `HypermediaLinkJsonConverter` to the `JsonSerializerSettings`:
17+
18+
```csharp
19+
using F23.Hateoas.NewtonsoftJson;
20+
21+
var options = new JsonSerializerSettings
22+
{
23+
Converters =
24+
{
25+
new HypermediaResponseJsonConverter(),
26+
new HypermediaLinkJsonConverter(),
27+
}
28+
};
29+
```
30+
31+
This can be done during ASP.NET Core startup:
32+
33+
```csharp
34+
builder.Services.AddControllers()
35+
.AddNewtonsoftJson(options =>
36+
{
37+
options.SerializerSettings.Converters.Add(new HypermediaResponseJsonConverter());
38+
options.SerializerSettings.Converters.Add(new HypermediaLinkJsonConverter());
39+
});
40+
```
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
using System.ComponentModel;
2+
3+
namespace System.Runtime.CompilerServices
4+
{
5+
#if !NET5_0_OR_GREATER
6+
7+
[EditorBrowsable(EditorBrowsableState.Never)]
8+
internal static class IsExternalInit {}
9+
10+
#endif // !NET5_0_OR_GREATER
11+
12+
#if !NET7_0_OR_GREATER
13+
14+
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Field | AttributeTargets.Property, AllowMultiple = false, Inherited = false)]
15+
internal sealed class RequiredMemberAttribute : Attribute {}
16+
17+
[AttributeUsage(AttributeTargets.All, AllowMultiple = true, Inherited = false)]
18+
internal sealed class CompilerFeatureRequiredAttribute : Attribute
19+
{
20+
public CompilerFeatureRequiredAttribute(string featureName)
21+
{
22+
FeatureName = featureName;
23+
}
24+
25+
public string FeatureName { get; }
26+
public bool IsOptional { get; init; }
27+
28+
public const string RefStructs = nameof(RefStructs);
29+
public const string RequiredMembers = nameof(RequiredMembers);
30+
}
31+
32+
#endif // !NET7_0_OR_GREATER
33+
}
34+
35+
namespace System.Diagnostics.CodeAnalysis
36+
{
37+
#if !NET7_0_OR_GREATER
38+
[AttributeUsage(AttributeTargets.Constructor, AllowMultiple = false, Inherited = false)]
39+
internal sealed class SetsRequiredMembersAttribute : Attribute {}
40+
#endif
41+
}

F23.Hateoas.Sample.NewtonsoftJson/F23.Hateoas.Sample.NewtonsoftJson.csproj

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,8 @@
1313
</ItemGroup>
1414

1515
<ItemGroup>
16-
<ProjectReference Include="..\F23.Hateoas\F23.Hateoas.csproj" />
16+
<ProjectReference Include="..\F23.Hateoas\F23.Hateoas.csproj" />
17+
<ProjectReference Include="..\F23.Hateoas.NewtonsoftJson\F23.Hateoas.NewtonsoftJson.csproj" />
1718
</ItemGroup>
1819

1920
</Project>

F23.Hateoas.Sample.NewtonsoftJson/Program.cs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
using F23.Hateoas;
2+
using F23.Hateoas.NewtonsoftJson;
23

34
var builder = WebApplication.CreateBuilder(args);
45

@@ -8,7 +9,11 @@
89
builder.Services.AddSwaggerGen();
910

1011
builder.Services.AddControllers()
11-
.AddNewtonsoftJson();
12+
.AddNewtonsoftJson(options =>
13+
{
14+
options.SerializerSettings.Converters.Add(new HypermediaResponseJsonConverter());
15+
options.SerializerSettings.Converters.Add(new HypermediaLinkJsonConverter());
16+
});
1217

1318
var app = builder.Build();
1419

Lines changed: 0 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,5 @@
11
{
22
"$schema": "http://json.schemastore.org/launchsettings.json",
3-
"iisSettings": {
4-
"windowsAuthentication": false,
5-
"anonymousAuthentication": true,
6-
"iisExpress": {
7-
"applicationUrl": "http://localhost:10201",
8-
"sslPort": 44369
9-
}
10-
},
113
"profiles": {
124
"http": {
135
"commandName": "Project",
@@ -28,14 +20,6 @@
2820
"environmentVariables": {
2921
"ASPNETCORE_ENVIRONMENT": "Development"
3022
}
31-
},
32-
"IIS Express": {
33-
"commandName": "IISExpress",
34-
"launchBrowser": true,
35-
"launchUrl": "swagger",
36-
"environmentVariables": {
37-
"ASPNETCORE_ENVIRONMENT": "Development"
38-
}
3923
}
4024
}
4125
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
using Microsoft.AspNetCore.Mvc;
2+
3+
namespace F23.Hateoas.Sample.SystemTextJson;
4+
5+
public abstract class AppBaseController : ControllerBase
6+
{
7+
protected IActionResult HypermediaOk(object content, IList<HypermediaLink>? links = null)
8+
{
9+
return Ok(new HypermediaResponse(content) { Links = links });
10+
}
11+
}

0 commit comments

Comments
 (0)