Skip to content
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
Expand Up @@ -27,7 +27,7 @@ public sealed partial class ContextConverter : System.Text.Json.Serialization.Js
{
public override Elastic.Clients.Elasticsearch.Core.Search.Context Read(ref System.Text.Json.Utf8JsonReader reader, System.Type typeToConvert, System.Text.Json.JsonSerializerOptions options)
{
var selector = static (ref System.Text.Json.Utf8JsonReader r, System.Text.Json.JsonSerializerOptions o) => JsonUnionSelector.ByPropertyOfT1(ref r, o, "dummy");
var selector = static (ref System.Text.Json.Utf8JsonReader r, System.Text.Json.JsonSerializerOptions o) => JsonUnionSelector.Match(ref r, o, static (ref System.Text.Json.Utf8JsonReader r, System.Text.Json.JsonSerializerOptions o) => JsonUnionSelector.MatchTokenTypes(ref r, o, Elastic.Clients.Elasticsearch.Serialization.JsonTokenTypes.StartObject | Elastic.Clients.Elasticsearch.Serialization.JsonTokenTypes.StartArray, static (ref System.Text.Json.Utf8JsonReader r, System.Text.Json.JsonSerializerOptions o) => JsonUnionSelector.T2(ref r, o)), static (ref System.Text.Json.Utf8JsonReader r, System.Text.Json.JsonSerializerOptions o) => JsonUnionSelector.T1(ref r, o));
return selector(ref reader, options) switch
{
Elastic.Clients.Elasticsearch.UnionTag.T1 => new Elastic.Clients.Elasticsearch.Core.Search.Context(reader.ReadValue<string>(options, null)),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -474,6 +474,9 @@ public static KeyValuePair<TKey, TValue> ReadKeyValuePairValue<TKey, TValue>(thi
public static List<T>? ReadSingleOrManyCollectionValue<T>(this ref Utf8JsonReader reader, JsonSerializerOptions options,
JsonReadFunc<T>? readElement)
{
// TODO: Allow passing a selector function to distinguish between single or many in complex scenarios
// (e.g. when the single element can be an array, see: GeoLocation).

if (reader.TokenType is JsonTokenType.Null)
{
return null;
Expand Down
132 changes: 108 additions & 24 deletions src/Elastic.Clients.Elasticsearch/_Shared/Next/JsonUnionSelector.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
// See the LICENSE file in the project root for more information.

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

namespace Elastic.Clients.Elasticsearch.Serialization;
Expand Down Expand Up @@ -38,61 +37,146 @@ internal enum JsonTokenTypes

internal static class JsonUnionSelector
{
public static UnionTag ByTokenType(ref Utf8JsonReader reader, JsonSerializerOptions options, JsonTokenTypes first, JsonTokenTypes second)
/// <summary>
/// A selector function that always returns <see cref="UnionTag.None"/>.
/// </summary>
/// <param name="reader">A reference to the <see cref="Utf8JsonReader"/> instance.</param>
/// <param name="options">The <see cref="JsonSerializerOptions"/> to use.</param>
/// <returns>A static value of <see cref="UnionTag.None"/>.</returns>
public static UnionTag None(ref Utf8JsonReader reader, JsonSerializerOptions options)
{
_ = reader;
_ = options;
return UnionTag.None;
}

/// <summary>
/// A selector function that always returns <see cref="UnionTag.T1"/>.
/// </summary>
/// <param name="reader">A reference to the <see cref="Utf8JsonReader"/> instance.</param>
/// <param name="options">The <see cref="JsonSerializerOptions"/> to use.</param>
/// <returns>A static value of <see cref="UnionTag.T1"/>.</returns>
public static UnionTag T1(ref Utf8JsonReader reader, JsonSerializerOptions options)
{
_ = reader;
_ = options;
return UnionTag.T1;
}

if (((int)first & (1 << (int)reader.TokenType)) is not 0)
/// <summary>
/// A selector function that always returns <see cref="UnionTag.T2"/>.
/// </summary>
/// <param name="reader">A reference to the <see cref="Utf8JsonReader"/> instance.</param>
/// <param name="options">The <see cref="JsonSerializerOptions"/> to use.</param>
/// <returns>A static value of <see cref="UnionTag.T2"/>.</returns>
public static UnionTag T2(ref Utf8JsonReader reader, JsonSerializerOptions options)
{
_ = reader;
_ = options;
return UnionTag.T2;
}

// We avoid using a `params` array for performance reasons. Create `Match()` overloads with additional parameters as needed.
public static UnionTag Match(ref Utf8JsonReader reader, JsonSerializerOptions options, JsonUnionSelectorFunc case1, JsonUnionSelectorFunc case2)
{
if (case1(ref reader, options) is var tag1 and not UnionTag.None)
{
return UnionTag.T1;
return tag1;
}

if (((int)second & (1 << (int)reader.TokenType)) is not 0)
if (case2(ref reader, options) is var tag2 and not UnionTag.None)
{
return UnionTag.T2;
return tag2;
}

return UnionTag.None;
}

public static UnionTag ByPropertyOfT1(ref Utf8JsonReader reader, JsonSerializerOptions options, string name)
public static UnionTag MatchTokenTypes(ref Utf8JsonReader reader, JsonSerializerOptions options, JsonTokenTypes types, JsonUnionSelectorFunc next)
{
reader.ValidateToken(JsonTokenType.StartObject);

var internalReader = reader;

while (internalReader.Read() && (internalReader.TokenType is JsonTokenType.PropertyName))
if (((int)types & (1 << (int)reader.TokenType)) is not 0)
{
if (internalReader.ValueTextEquals(name))
{
return UnionTag.T1;
}

internalReader.Read();
internalReader.Skip();
return next(ref reader, options);
}

return UnionTag.T2;
return UnionTag.None;
}

public static UnionTag ByPropertyOfT2(ref Utf8JsonReader reader, JsonSerializerOptions options, string name)
public static UnionTag MatchProperty(ref Utf8JsonReader reader, JsonSerializerOptions options, string name, UnionTag result)
{
reader.ValidateToken(JsonTokenType.StartObject);
if (reader.TokenType is not JsonTokenType.StartObject)
{
return UnionTag.None;
}

var internalReader = reader;

while (internalReader.Read() && (internalReader.TokenType is JsonTokenType.PropertyName))
{
if (internalReader.ValueTextEquals(name))
{
return UnionTag.T2;
return result;
}

internalReader.Read();
internalReader.Skip();
}

return UnionTag.T1;
return UnionTag.None;
}

/// <summary>
/// A selector function that selects a union variant based on the current JSON token type.
/// </summary>
/// <param name="reader">A reference to the <see cref="Utf8JsonReader"/> instance.</param>
/// <param name="options">The <see cref="JsonSerializerOptions"/> to use.</param>
/// <param name="first">The JSON token types that resolve to <see cref="UnionTag.T1"/>.</param>
/// <param name="second">The JSON token types that resolve to <see cref="UnionTag.T2"/>.</param>
/// <returns>
/// Either <see cref="UnionTag.T1"/> or <see cref="UnionTag.T2"/> if the current token type of <paramref name="reader"/> matches one
/// of the provided JSON token types or <see cref="UnionTag.None"/>, if not.
/// </returns>
public static UnionTag ByTokenType(ref Utf8JsonReader reader, JsonSerializerOptions options, JsonTokenTypes first, JsonTokenTypes second)
{
return Match(ref reader, options,
(ref r, o) => MatchTokenTypes(ref r, o, first, T1),
(ref r, o) => MatchTokenTypes(ref r, o, second, T2)
);
}

/// <summary>
/// A selector function that selects a union variant based on the current JSON token type.
/// </summary>
/// <param name="reader">A reference to the <see cref="Utf8JsonReader"/> instance.</param>
/// <param name="options">The <see cref="JsonSerializerOptions"/> to use.</param>
/// <param name="name">The property name to look for.</param>
/// <returns>
/// <see cref="UnionTag.T1"/> if the <paramref name="reader"/> points to an object that contains a property of the given
/// <paramref name="name"/> or <see cref="UnionTag.T2"/>, if not.
/// </returns>
public static UnionTag ByPropertyOfT1(ref Utf8JsonReader reader, JsonSerializerOptions options, string name)
{
return Match(ref reader, options,
(ref r, o) => MatchProperty(ref r, o, name, UnionTag.T1),
T2
);
}

/// <summary>
/// A selector function that selects a union variant based on the current JSON token type.
/// </summary>
/// <param name="reader">A reference to the <see cref="Utf8JsonReader"/> instance.</param>
/// <param name="options">The <see cref="JsonSerializerOptions"/> to use.</param>
/// <param name="name">The property name to look for.</param>
/// <returns>
/// <see cref="UnionTag.T2"/> if the <paramref name="reader"/> points to an object that contains a property of the given
/// <paramref name="name"/> or <see cref="UnionTag.T1"/>, if not.
/// </returns>
public static UnionTag ByPropertyOfT2(ref Utf8JsonReader reader, JsonSerializerOptions options, string name)
{
return Match(ref reader, options,
(ref r, o) => MatchProperty(ref r, o, name, UnionTag.T2),
T1
);
}
}
Loading