Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

v14: Refactor and enhance System.Text.Json converters #15960

Merged
merged 14 commits into from
Apr 10, 2024
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ public void Configure(JsonOptions options)
options.JsonSerializerOptions.PropertyNamingPolicy = JsonNamingPolicy.CamelCase;
options.JsonSerializerOptions.Converters.Add(new JsonStringEnumConverter());
options.JsonSerializerOptions.Converters.Add(new JsonUdiConverter());
options.JsonSerializerOptions.Converters.Add(new JsonGuidUdiConverter());
options.JsonSerializerOptions.Converters.Add(new JsonUdiRangeConverter());
options.JsonSerializerOptions.Converters.Add(new JsonObjectConverter());

options.JsonSerializerOptions.TypeInfoResolver = _umbracoJsonTypeInfoResolver;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
namespace Umbraco.Cms.Core.Serialization;

/// <summary>
/// Provides functionality to serialize objects or value types to JSON and to deserialize JSON into objects or value types, used for data type configuration.
/// </summary>
public interface IConfigurationEditorJsonSerializer : IJsonSerializer
{
}
{ }
19 changes: 19 additions & 0 deletions src/Umbraco.Core/Serialization/IJsonSerializer.cs
Original file line number Diff line number Diff line change
@@ -1,8 +1,27 @@
namespace Umbraco.Cms.Core.Serialization;

/// <summary>
/// Provides functionality to serialize objects or value types to JSON and to deserialize JSON into objects or value types.
/// </summary>
public interface IJsonSerializer
{
/// <summary>
/// Converts the specified <paramref name="input" /> into a JSON string.
/// </summary>
/// <param name="input">The input.</param>
/// <returns>
/// A JSON string representation of the value.
/// </returns>
string Serialize(object? input);


/// <summary>
/// Parses the text representing a single JSON value into an instance of the type specified by a generic type parameter.
/// </summary>
/// <typeparam name="T">The target type of the JSON value.</typeparam>
/// <param name="input">The JSON input to parse.</param>
/// <returns>
/// A <typeparamref name="T" /> representation of the JSON value.
/// </returns>
T? Deserialize<T>(string input);
}
Original file line number Diff line number Diff line change
Expand Up @@ -119,8 +119,8 @@ public static IUmbracoBuilder AddCoreInitialServices(this IUmbracoBuilder builde

builder.Services.AddScoped<IHttpScopeReference, HttpScopeReference>();

builder.Services.AddSingleton<IJsonSerializer, SystemTextJsonSerializer>();
builder.Services.AddSingleton<IConfigurationEditorJsonSerializer, SystemTextConfigurationEditorJsonSerializer>();
builder.Services.AddSingleton<IJsonSerializer, DefaultJsonSerializer>();
builder.Services.AddSingleton<IConfigurationEditorJsonSerializer, DefaultConfigurationEditorJsonSerializer>();

// register database builder
// *not* a singleton, don't want to keep it around
Expand Down

This file was deleted.

This file was deleted.

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
using System.Text.Json;
using System.Text.Json.Serialization;
using Umbraco.Cms.Core.Serialization;

namespace Umbraco.Cms.Infrastructure.Serialization;

/// <inheritdoc />
public sealed class DefaultConfigurationEditorJsonSerializer : IConfigurationEditorJsonSerializer
{
private readonly JsonSerializerOptions _jsonSerializerOptions;

/// <summary>
/// Initializes a new instance of the <see cref="DefaultConfigurationEditorJsonSerializer" /> class.
/// </summary>
public DefaultConfigurationEditorJsonSerializer()
=> _jsonSerializerOptions = new JsonSerializerOptions()
{
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
// in some cases, configs aren't camel cased in the DB, so we have to resort to case insensitive
// property name resolving when creating configuration objects (deserializing DB configs)
PropertyNameCaseInsensitive = true,
NumberHandling = JsonNumberHandling.AllowReadingFromString,
Converters =
{
new JsonStringEnumConverter(),
new JsonObjectConverter(),
new JsonUdiConverter(),
new JsonUdiRangeConverter(),
new JsonFuzzyBooleanConverter()
}
};

/// <inheritdoc />
public string Serialize(object? input) => JsonSerializer.Serialize(input, _jsonSerializerOptions);

/// <inheritdoc />
public T? Deserialize<T>(string input) => JsonSerializer.Deserialize<T>(input, _jsonSerializerOptions);
}
34 changes: 34 additions & 0 deletions src/Umbraco.Infrastructure/Serialization/DefaultJsonSerializer.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
using System.Text.Json;
using System.Text.Json.Serialization;
using Umbraco.Cms.Core.Serialization;

namespace Umbraco.Cms.Infrastructure.Serialization;

/// <inheritdoc />
public sealed class DefaultJsonSerializer : IJsonSerializer
{
private readonly JsonSerializerOptions _jsonSerializerOptions;

/// <summary>
/// Initializes a new instance of the <see cref="DefaultJsonSerializer" /> class.
/// </summary>
public DefaultJsonSerializer()
=> _jsonSerializerOptions = new JsonSerializerOptions
{
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
Converters =
{
new JsonStringEnumConverter(),
new JsonUdiConverter(),
new JsonUdiRangeConverter(),
// We may need to add JsonObjectConverter at some point, but for the time being things work fine without
//new JsonObjectConverter()
}
};

/// <inheritdoc />
public string Serialize(object? input) => JsonSerializer.Serialize(input, _jsonSerializerOptions);

/// <inheritdoc />
public T? Deserialize<T>(string input) => JsonSerializer.Deserialize<T>(input, _jsonSerializerOptions);
}

This file was deleted.

36 changes: 0 additions & 36 deletions src/Umbraco.Infrastructure/Serialization/JsonBoolConverter.cs

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
using System.Text.Json;

namespace Umbraco.Cms.Infrastructure.Serialization;

/// <summary>
/// Converts a dictionary with a string key to or from JSON, using the <see cref="StringComparer.OrdinalIgnoreCase" /> comparer.
/// </summary>
/// <typeparam name="TValue">The type of the dictionary value.</typeparam>
public sealed class JsonDictionaryStringIgnoreCaseConverter<TValue> : ReadOnlyJsonConverter<Dictionary<string, TValue>>
{
/// <inheritdoc />
public override Dictionary<string, TValue>? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
if (reader.TokenType != JsonTokenType.StartObject)
{
throw new JsonException();
}

var dictionary = new Dictionary<string, TValue>(StringComparer.OrdinalIgnoreCase);

while (reader.Read())
{
if (reader.TokenType == JsonTokenType.EndObject)
{
return dictionary;
}

// Get key
if (reader.TokenType != JsonTokenType.PropertyName)
{
throw new JsonException();
}

string propertyName = reader.GetString() ?? throw new JsonException();

// Get value
reader.Read();
TValue? value = JsonSerializer.Deserialize<TValue>(ref reader, options);
if (value is not null)
{
dictionary[propertyName] = value;
}
}

throw new JsonException();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
using System.Text.Json;

namespace Umbraco.Cms.Infrastructure.Serialization;

/// <summary>
/// Converts a dictionary with a string key to or from JSON, using the <see cref="StringComparer.OrdinalIgnoreCase" /> comparer and interning the string key when reading.
/// </summary>
/// <typeparam name="TValue">The type of the dictionary value.</typeparam>
public sealed class JsonDictionaryStringInternIgnoreCaseConverter<TValue> : ReadOnlyJsonConverter<Dictionary<string, TValue>>
{
/// <inheritdoc />
public override Dictionary<string, TValue>? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
if (reader.TokenType != JsonTokenType.StartObject)
{
throw new JsonException();
}

var dictionary = new Dictionary<string, TValue>(StringComparer.OrdinalIgnoreCase);

while (reader.Read())
{
if (reader.TokenType == JsonTokenType.EndObject)
{
return dictionary;
}

// Get key
if (reader.TokenType != JsonTokenType.PropertyName)
{
throw new JsonException();
}

string propertyName = reader.GetString() ?? throw new JsonException();

// Get value
reader.Read();
TValue? value = JsonSerializer.Deserialize<TValue>(ref reader, options);
if (value is not null)
{
dictionary[string.Intern(propertyName)] = value;
}
}

throw new JsonException();
}
}
Loading
Loading