Skip to content

Commit

Permalink
Introduce providing json options as its own construct
Browse files Browse the repository at this point in the history
  • Loading branch information
Mpdreamz committed Oct 16, 2024
1 parent 2871d17 commit 9fcd857
Show file tree
Hide file tree
Showing 3 changed files with 74 additions and 113 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
// 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 JsonSerializerOptions _options = new();

/// <inheritdoc cref="IJsonSerializerOptionsProvider"/>
public JsonSerializerOptions? CreateJsonSerializerOptions() => _options;

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

/// <inheritdoc cref="TransportSerializerOptionsProvider"/>
public TransportSerializerOptionsProvider(IReadOnlyCollection<JsonConverter> bakedIn, IReadOnlyCollection<JsonConverter>? userProvided, Action<JsonSerializerOptions>? optionsAction = null)
{
foreach (var converter in bakedIn)
_options.Converters.Add(converter);

foreach (var converter in userProvided ?? [])
_options.Converters.Add(converter);

optionsAction?.Invoke(_options);

}
}

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

0 comments on commit 9fcd857

Please sign in to comment.