-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* added inline json converter * fixed readme * added DTO tests
- Loading branch information
Showing
6 changed files
with
197 additions
and
27 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,64 @@ | ||
|
||
using System.Reflection; | ||
using System.Text.Json; | ||
using System.Text.Json.Serialization; | ||
using System.Runtime.CompilerServices; | ||
using System.Runtime.InteropServices; | ||
using static SystemTextJson.FluentApi.SerializationHelpers; | ||
namespace SystemTextJson.FluentApi; | ||
#if NET8_0_OR_GREATER | ||
|
||
public class InlineArrayJsonConverter : JsonConverterFactory | ||
{ | ||
public override bool CanConvert(Type typeToConvert) => | ||
typeToConvert.GetCustomAttribute<InlineArrayAttribute>() != null; | ||
|
||
public override JsonConverter? CreateConverter(Type typeToConvert, JsonSerializerOptions options) | ||
{ | ||
var attribute = typeToConvert.GetCustomAttribute<InlineArrayAttribute>(); | ||
if (attribute is null) | ||
return null; | ||
|
||
var length = attribute.Length; | ||
var itemType = typeToConvert.GetFields()[0].FieldType; // inline array can have only one field | ||
|
||
var converterType = typeof(ConcreteInlineArrayJsonConverter<,>).MakeGenericType(typeToConvert, itemType); | ||
return (JsonConverter)Activator.CreateInstance(converterType, length, options)!; | ||
} | ||
|
||
private class ConcreteInlineArrayJsonConverter<TStruct, TItem>(int length, JsonSerializerOptions options) : JsonConverter<TStruct> | ||
where TStruct : struct | ||
{ | ||
private readonly JsonConverter<TItem?> _itemConverter = (JsonConverter<TItem?>)options.GetConverter(typeof(TItem)); | ||
public override TStruct Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) | ||
{ | ||
if (reader.TokenType != JsonTokenType.StartArray) | ||
throw new JsonException("Start token must be '['."); | ||
|
||
reader.Read(); | ||
|
||
var result = default(TStruct); | ||
var span = MemoryMarshal.CreateSpan(ref Unsafe.As<TStruct, TItem?>(ref result), length); | ||
for (var i = 0; i < span.Length; i++) | ||
span[i] = ReadValue(_itemConverter, ref reader, options); | ||
|
||
if (reader.TokenType != JsonTokenType.EndArray) | ||
throw new JsonException("Expected end token ']'."); | ||
|
||
return result; | ||
} | ||
|
||
public override void Write(Utf8JsonWriter writer, TStruct value, JsonSerializerOptions options) | ||
{ | ||
writer.WriteStartArray(); | ||
|
||
var span = MemoryMarshal.CreateSpan(ref Unsafe.As<TStruct, TItem?>(ref value), length); | ||
for (var i = 0; i < span.Length; i++) | ||
WriteValue(_itemConverter, writer, span[i], options); | ||
|
||
writer.WriteEndArray(); | ||
} | ||
} | ||
|
||
} | ||
#endif |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
using System; | ||
using System.Collections.Generic; | ||
using System.Linq; | ||
using System.Text; | ||
using System.Text.Json.Serialization; | ||
using System.Text.Json; | ||
using System.Threading.Tasks; | ||
|
||
namespace SystemTextJson.FluentApi; | ||
internal class SerializationHelpers | ||
{ | ||
public static T? ReadValue<T>(JsonConverter<T?> converter, ref Utf8JsonReader reader, JsonSerializerOptions options) | ||
{ | ||
var value = default(T); | ||
if (reader.TokenType == JsonTokenType.Null && !converter.HandleNull) | ||
{ | ||
if (value is not null) | ||
throw new JsonException("Expected not null value."); | ||
} | ||
else | ||
{ | ||
value = converter.Read(ref reader, typeof(T), options); | ||
} | ||
reader.Read(); | ||
return value; | ||
} | ||
|
||
public static void WriteValue<T>(JsonConverter<T?> converter, Utf8JsonWriter writer, T value, JsonSerializerOptions options) | ||
{ | ||
if (value is null && !converter.HandleNull) | ||
writer.WriteNullValue(); | ||
else | ||
converter.Write(writer, value, options); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
73 changes: 73 additions & 0 deletions
73
tests/SystemTextJson.FluentApi.Tests/InlineArrayJsonConverterTests.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,73 @@ | ||
using Xunit; | ||
using System.Runtime.CompilerServices; | ||
using System.Text.Json; | ||
using System.Text.Json.Nodes; | ||
|
||
namespace SystemTextJson.FluentApi.Tests; | ||
public class InlineArrayJsonConverterTests | ||
{ | ||
readonly JsonSerializerOptions _options = new() { Converters = { new InlineArrayJsonConverter() } }; | ||
|
||
[Fact] | ||
public void Write() | ||
{ | ||
var array = new InlineArray(); | ||
array[0] = null; | ||
array[1] = 1; | ||
array[2] = -1; | ||
|
||
var actualJson = JsonSerializer.Serialize(array, _options); | ||
|
||
var isEquals = JsonNode.DeepEquals(JsonNode.Parse("[null,1,-1]"), JsonNode.Parse(actualJson)); | ||
Assert.True(isEquals, "Json not equal."); | ||
} | ||
|
||
[Fact] | ||
public void WriteDto() | ||
{ | ||
var array = new InlineArray(); | ||
array[0] = null; | ||
array[1] = 1; | ||
array[2] = -1; | ||
var dto = new Dto() { Array = array }; | ||
|
||
var actualJson = JsonSerializer.Serialize(dto, _options); | ||
|
||
var isEquals = JsonNode.DeepEquals(JsonNode.Parse("{\"Array\":[null,1,-1]}"), JsonNode.Parse(actualJson)); | ||
Assert.True(isEquals, "Json not equal."); | ||
} | ||
|
||
[Fact] | ||
public void Read() | ||
{ | ||
var actual = JsonSerializer.Deserialize<InlineArray>("[null,1,-1]", _options); | ||
|
||
Assert.Null(actual[0]); | ||
Assert.Equal(actual[1], 1); | ||
Assert.Equal(actual[2], -1); | ||
} | ||
|
||
|
||
[Fact] | ||
public void ReadDto() | ||
{ | ||
var actual = JsonSerializer.Deserialize<Dto>("{\"Array\":[null,1,-1]}", _options); | ||
|
||
Assert.NotNull(actual); | ||
var array = Assert.NotNull(actual.Array); | ||
Assert.Null(array[0]); | ||
Assert.Equal(array[1], 1); | ||
Assert.Equal(array[2], -1); | ||
} | ||
|
||
private class Dto | ||
{ | ||
public InlineArray? Array { get; set; } | ||
} | ||
|
||
[InlineArray(3)] | ||
private struct InlineArray | ||
{ | ||
public int? Value; | ||
} | ||
} |