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

Add SystemTextJsonSerializer base class and relevant extensions met… #122

Merged
Merged
Show file tree
Hide file tree
Changes from all 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
@@ -0,0 +1,60 @@
// Licensed to Elasticsearch B.V under one or more agreements.
// Elasticsearch B.V licenses this file to you under the Apache 2.0 License.
// See the LICENSE file in the project root for more information

using System;
using System.Collections.Generic;
using System.Text.Json;
using System.Text.Json.Serialization;

namespace Elastic.Transport;

/// <summary>
/// Provides an instance of <see cref="JsonSerializerOptions"/> to <see cref="SystemTextJsonSerializer"/>
/// </summary>
public interface IJsonSerializerOptionsProvider
{
/// <inheritdoc cref="IJsonSerializerOptionsProvider"/>
JsonSerializerOptions CreateJsonSerializerOptions();
}
/// <summary>
/// Default implementation of <see cref="IJsonSerializerOptionsProvider"/> specialized in providing more converters and
/// altering the shared <see cref="JsonSerializerOptions"/> used by <see cref="SystemTextJsonSerializer"/> and its derrived classes
/// </summary>
public class TransportSerializerOptionsProvider : IJsonSerializerOptionsProvider
{
private readonly IReadOnlyCollection<JsonConverter>? _bakedInConverters;
private readonly IReadOnlyCollection<JsonConverter>? _userProvidedConverters;
private readonly Action<JsonSerializerOptions>? _mutateOptions;

/// <inheritdoc cref="IJsonSerializerOptionsProvider"/>
public JsonSerializerOptions? CreateJsonSerializerOptions()
{
var options = new JsonSerializerOptions();
foreach (var converter in _bakedInConverters ?? [])
options.Converters.Add(converter);

foreach (var converter in _userProvidedConverters ?? [])
options.Converters.Add(converter);

_mutateOptions?.Invoke(options);

return options;
}

/// <inheritdoc cref="TransportSerializerOptionsProvider"/>
public TransportSerializerOptionsProvider() { }

/// <inheritdoc cref="TransportSerializerOptionsProvider"/>
public TransportSerializerOptionsProvider(
IReadOnlyCollection<JsonConverter> bakedInConverters,
IReadOnlyCollection<JsonConverter>? userProvidedConverters,
Action<JsonSerializerOptions>? mutateOptions = null
)
{
_bakedInConverters = bakedInConverters;
_userProvidedConverters = userProvidedConverters;
_mutateOptions = mutateOptions;
}
}

Original file line number Diff line number Diff line change
Expand Up @@ -3,66 +3,35 @@
// See the LICENSE file in the project root for more information

using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Text.Json;
using System.Text.Json.Serialization;

using Elastic.Transport.Extensions;

namespace Elastic.Transport;

/// <summary>
/// Default low level request/response-serializer implementation for <see cref="Serializer"/> which serializes using
/// the Microsoft <c>System.Text.Json</c> library
/// </summary>
internal sealed class LowLevelRequestResponseSerializer :
SystemTextJsonSerializer
internal sealed class LowLevelRequestResponseSerializer : SystemTextJsonSerializer
{
/// <summary>
/// Provides a static reusable reference to an instance of <see cref="LowLevelRequestResponseSerializer"/> to promote reuse.
/// </summary>
internal static readonly LowLevelRequestResponseSerializer Instance = new();

private IReadOnlyCollection<JsonConverter> AdditionalConverters { get; }

private IList<JsonConverter> BakedInConverters { get; } = new List<JsonConverter>
{
new ExceptionConverter(),
new ErrorCauseConverter(),
new ErrorConverter(),
new DynamicDictionaryConverter()
};

/// <inheritdoc cref="LowLevelRequestResponseSerializer"/>>
public LowLevelRequestResponseSerializer() : this(null) { }

/// <summary>
/// <inheritdoc cref="LowLevelRequestResponseSerializer"/>>
/// </summary>
/// <param name="converters">Add more default converters onto <see cref="JsonSerializerOptions"/> being used</param>
public LowLevelRequestResponseSerializer(IEnumerable<JsonConverter>? converters) =>
AdditionalConverters = converters != null
? new ReadOnlyCollection<JsonConverter>(converters.ToList())
: EmptyReadOnly<JsonConverter>.Collection;

/// <summary>
/// Creates <see cref="JsonSerializerOptions"/> used for serialization.
/// Override on a derived serializer to change serialization.
/// </summary>
protected override JsonSerializerOptions? CreateJsonSerializerOptions()
{
var options = new JsonSerializerOptions
{
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull
};

foreach (var converter in BakedInConverters)
options.Converters.Add(converter);

foreach (var converter in AdditionalConverters)
options.Converters.Add(converter);
public LowLevelRequestResponseSerializer(IReadOnlyCollection<JsonConverter>? converters)
: base(new TransportSerializerOptionsProvider([
new ExceptionConverter(),
new ErrorCauseConverter(),
new ErrorConverter(),
new DynamicDictionaryConverter()
], converters, options => { options.DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull; })) { }

return options;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,10 @@
// See the LICENSE file in the project root for more information

using System;
using System.Collections.Generic;
using System.IO;
using System.Text.Json;
using System.Text.Json.Serialization;
using System.Threading;
using System.Threading.Tasks;

Expand All @@ -14,14 +16,23 @@ namespace Elastic.Transport;
/// An abstract implementation of a transport <see cref="Serializer"/> which serializes using the Microsoft
/// <c>System.Text.Json</c> library.
/// </summary>
public abstract class SystemTextJsonSerializer :
Serializer
public abstract class SystemTextJsonSerializer : Serializer
{
private readonly SemaphoreSlim _semaphore = new(1);
private readonly JsonSerializerOptions? _options;
private readonly JsonSerializerOptions? _indentedOptions;

private bool _initialized;
private JsonSerializerOptions? _options;
private JsonSerializerOptions? _indentedOptions;
/// <summary>
/// An abstract implementation of a transport <see cref="Serializer"/> which serializes using the Microsoft
/// <c>System.Text.Json</c> library.
/// </summary>
protected SystemTextJsonSerializer(IJsonSerializerOptionsProvider? provider = null)
{

provider ??= new TransportSerializerOptionsProvider();
_options = provider.CreateJsonSerializerOptions();
_indentedOptions = provider.CreateJsonSerializerOptions();
_indentedOptions.WriteIndented = true;
}

#region Serializer

Expand Down Expand Up @@ -74,80 +85,14 @@ public override Task SerializeAsync<T>(T data, Stream stream,

#endregion Serializer

/// <summary>
/// A factory method that can create an instance of <see cref="JsonSerializerOptions"/> that will
/// be used when serializing.
/// </summary>
/// <returns></returns>
protected abstract JsonSerializerOptions? CreateJsonSerializerOptions();

/// <summary>
/// A callback function that is invoked after the <see cref="JsonSerializerOptions"/> have been created and the
/// serializer got fully initialized.
/// </summary>
protected virtual void Initialized()
{
}

/// <summary>
/// Returns the <see cref="JsonSerializerOptions"/> for this serializer, based on the given <paramref name="formatting"/>.
/// </summary>
/// <param name="formatting">The serialization formatting.</param>
/// <returns>The requested <see cref="JsonSerializerOptions"/> or <c>null</c>, if the serializer is not initialized yet.</returns>
protected internal JsonSerializerOptions? GetJsonSerializerOptions(SerializationFormatting formatting = SerializationFormatting.None)
{
Initialize();

return (formatting is SerializationFormatting.None)
? _options
: _indentedOptions;
}
protected internal JsonSerializerOptions? GetJsonSerializerOptions(SerializationFormatting formatting = SerializationFormatting.None) =>
formatting is SerializationFormatting.None ? _options : _indentedOptions;

/// <summary>
/// Initializes a serializer instance such that its <see cref="JsonSerializerOptions"/> are populated.
/// </summary>
private void Initialize()
{
// Exit early, if already initialized
if (_initialized)
return;

_semaphore.Wait();

try
{
// Exit early, if the current thread lost the race
if (_initialized)
return;

var options = CreateJsonSerializerOptions();

if (options is null)
{
_options = new JsonSerializerOptions();
_indentedOptions = new JsonSerializerOptions
{
WriteIndented = true
};
}
else
{
_options = options;
_indentedOptions = new JsonSerializerOptions(options)
{
WriteIndented = true
};
}

_initialized = true;

Initialized();
}
finally
{
_semaphore.Release();
}
}

private static bool TryReturnDefault<T>(Stream? stream, out T deserialize)
{
Expand Down
Loading