Skip to content

Commit

Permalink
added json converter
Browse files Browse the repository at this point in the history
  • Loading branch information
mattiasnordqvist committed Jun 9, 2024
1 parent 14761c8 commit 62fd7d3
Show file tree
Hide file tree
Showing 13 changed files with 405 additions and 4 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="FluentAssertions" Version="6.12.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.10.0" />
<PackageReference Include="Verify.Xunit" Version="25.0.1" />
<PackageReference Include="xunit.core" Version="2.8.1" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.8.1">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\DotNetThoughts.Results.Json\DotNetThoughts.Results.Json.csproj" />
</ItemGroup>

</Project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
[
{
Type: MyError,
Message: MyError,
Data: {
Code: {
ValueKind: Number
},
Description: {
ValueKind: String
}
}
}
]
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{
"Success": false,
"Errors": [
{
"Type": "MyError",
"Message": "MyError",
"Data": {
"Code": 123,
"Description": "hej fel"
}
}
]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
{
"Success": true,
"Value": {
"Id": 123,
"Name": "Complex name",
"Ints": [
1,
2,
3
],
"ComplexTypes": [
{
"Id": 2345,
"Description": "Description"
},
{
"Id": 2345,
"Description": "Description"
}
]
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"Success": true,
"Value": 12
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"Success": true
}
105 changes: 105 additions & 0 deletions DotNetThoughts.Results.Json.Tests/Tests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
using FluentAssertions;

using System.Text.Json;


namespace DotNetThoughts.Results.Json.Tests;

public class Tests
{
private static JsonSerializerOptions Options => new()
{
WriteIndented = true,
Converters = { new JsonConverterFactoryForResultOfT() }
};

[Fact]
public async Task SerializeSuccessfulResultOfUnit()
{
var unitResult = UnitResult.Ok;
var json = JsonSerializer.Serialize(unitResult, Options);
await Verify(json);
}

[Fact]
public async Task SerializeSuccessfulResultOfPrimitive()
{
var primitiveResult = Result<int>.Ok(12);
var json = JsonSerializer.Serialize(primitiveResult, Options);
await Verify(json);
}

public record ComplexType
{
public int Id { get; set; } = 123;
public string Name { get; set; } = "Complex name";

public List<int> Ints { get; set; } = [1, 2, 3];

public List<NestedComplexType> ComplexTypes { get; set; } = [new(), new()];

public record NestedComplexType
{
public int Id { get; set; } = 2345;
public string Description { get; set; } = "Description";
}
}
[Fact]
public async Task SerializeSuccessfulResultOfComplexType()
{

var complextResult = Result<ComplexType>.Ok(new ComplexType());
var json = JsonSerializer.Serialize(complextResult, Options);
await Verify(json);
}

[Fact]
public async Task SerializeErrorResultOfUnit()
{
var unitResult = UnitResult.Error(new MyError(123, "hej fel"));
var json = JsonSerializer.Serialize(unitResult, Options);
await Verify(json);
}
public record MyError(int Code, string Description) : ErrorBase;


[Fact]
public void DeserializeSuccessfulResultOfUnit()
{
var unitResultJson = @"{""success"": true}";
var unitResult = JsonSerializer.Deserialize<Result<Unit>>(unitResultJson, Options);

unitResult.Success.Should().BeTrue();
unitResult.Value.Should().Be(Unit.Instance);
}

[Fact]
public void DeserializeSuccessfulResultOfPrimitive()
{
var unitResultJson = @"{""success"": true, ""value"": 123 }";
var unitResult = JsonSerializer.Deserialize<Result<int>>(unitResultJson, Options);

unitResult.Success.Should().BeTrue();
unitResult.Value.Should().Be(123);
}

[Fact]
public void DeserializeSuccessfulResultOfComplextType()
{
var complextResult = Result<ComplexType>.Ok(new ComplexType());
var json = JsonSerializer.Serialize(complextResult, Options);
var deserialized = JsonSerializer.Deserialize<Result<ComplexType>>(json, Options);
deserialized.Success.Should().BeTrue();
deserialized.Value.Should().BeEquivalentTo(new ComplexType());
}

[Fact]
public async Task DeserializeErrorResultOfUnit()
{
var unitResult = UnitResult.Error(new MyError(123, "hej fel"));
var json = JsonSerializer.Serialize(unitResult, Options);
var deserialized = JsonSerializer.Deserialize<Result<Unit>>(json, Options);
deserialized.Success.Should().BeFalse();
await Verify(deserialized.Errors);
}
}
10 changes: 10 additions & 0 deletions DotNetThoughts.Results.Json/DeserializedError.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
namespace DotNetThoughts.Results.Json;

public class DeserializedError : IError
{
public required string Type { get; init; }
public required string Message { get; init; }
public required Dictionary<string, object?> Data { get; init; } = [];

public Dictionary<string, object?> GetData() => Data;
}
15 changes: 15 additions & 0 deletions DotNetThoughts.Results.Json/DotNetThoughts.Results.Json.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<Version>1.5.7</Version>
<GenerateDocumentationFile>True</GenerateDocumentationFile>
<PackageLicenseExpression>MIT</PackageLicenseExpression>
</PropertyGroup>

<ItemGroup>
<ProjectReference Include="..\Results\DotNetThoughts.Results.csproj" />
</ItemGroup>
</Project>
47 changes: 47 additions & 0 deletions DotNetThoughts.Results.Json/JsonConverterFactoryForResultOfT.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
using System.Reflection;
using System.Text.Json.Serialization;
using System.Text.Json;

namespace DotNetThoughts.Results.Json;

/// <summary>
/// Creates converters for Result<typeparamref name="T"/> and Results of Unit.
/// Hardcoded to not be case sensitive.
/// Serializes the result as an object with a success-property and either a value-property or an errors-property.
/// Value-property is ommitted if T is Unit, or if success is false.
///
/// Feel free to add customization options and figure out how to honor JsonSerializationOptions like PropertyNameCaseSensitive.
/// </summary>
public class JsonConverterFactoryForResultOfT : JsonConverterFactory
{
public override bool CanConvert(Type typeToConvert)
=> typeToConvert.IsGenericType
&& typeToConvert.GetGenericTypeDefinition() == typeof(Result<>);

public override JsonConverter CreateConverter(
Type typeToConvert, JsonSerializerOptions options)
{

Type elementType = typeToConvert.GetGenericArguments()[0];
if (typeof(Unit) == elementType)
{
var converter = (JsonConverter)Activator.CreateInstance(
typeof(JsonConverterForResultOfUnit),
BindingFlags.Instance | BindingFlags.Public,
binder: null,
args: null,
culture: null)!;
return converter;
}
else
{
var converter = (JsonConverter)Activator.CreateInstance(
typeof(JsonConverterForResultOfT<>).MakeGenericType([elementType]),
BindingFlags.Instance | BindingFlags.Public,
binder: null,
args: null,
culture: null)!;
return converter;
}
}
}
69 changes: 69 additions & 0 deletions DotNetThoughts.Results.Json/JsonConverterForResultOfT.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
 using System.Text.Json.Serialization;
using System.Text.Json;

namespace DotNetThoughts.Results.Json;

internal class JsonConverterForResultOfT<T> : JsonConverter<Result<T>>
{
public override Result<T> Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
if (reader.TokenType != JsonTokenType.StartObject)
{
throw new JsonException();
}

T? t = default;
bool success = false;
List<DeserializedError> errors = [];
while (reader.Read())
{
if (reader.TokenType == JsonTokenType.EndObject)
{
return success
? Result<T>.Ok(t!)
: Result<T>.Error(errors);
}
if (reader.TokenType != JsonTokenType.PropertyName)
{
throw new JsonException();
}
string propertyName = reader.GetString()!.ToLower();
if (propertyName == nameof(Result<T>.Success).ToLower())
{
reader.Read();
success = reader.GetBoolean();
}
else if (propertyName == nameof(Result<T>.Value).ToLower())
{
t = JsonSerializer.Deserialize<T>(ref reader, options);
}
else if (propertyName == nameof(Result<T>.Errors).ToLower())
{
errors = JsonSerializer.Deserialize<List<DeserializedError>>(ref reader, options)!;
}
}
throw new JsonException();

}

public override void Write(Utf8JsonWriter writer, Result<T> value, JsonSerializerOptions options)
{
if (value.Success)
{
JsonSerializer.Serialize(writer, new
{
value.Success,
value.Value
}, options);
}
else
{
JsonSerializer.Serialize(writer, new
{
value.Success,
Errors = value.Errors.Select(x => new { x.Type, x.Message, Data = x.GetData() })
}, options);
}

}
}
Loading

0 comments on commit 62fd7d3

Please sign in to comment.