Skip to content

Commit 29f665d

Browse files
committed
Initial Json.NET serialiser
1 parent 50d5d31 commit 29f665d

File tree

51 files changed

+1434
-909
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

51 files changed

+1434
-909
lines changed

Elasticsearch.sln

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,8 @@ Project("{6EC3EE1D-3C4E-46DD-8F32-0CC8E7565705}") = "scripts", "build\scripts\sc
5656
EndProject
5757
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Tests.ClusterLauncher", "tests\Tests.ClusterLauncher\Tests.ClusterLauncher.csproj", "{F6162603-D134-4121-8106-2BA4DAD7350B}"
5858
EndProject
59+
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Elastic.Clients.Elasticsearch.JsonNetSerializer", "src\Elastic.Clients.Elasticsearch.JsonNetSerializer\Elastic.Clients.Elasticsearch.JsonNetSerializer.csproj", "{8C9275D9-29CE-4A20-8FD5-6B26C6CAAADB}"
60+
EndProject
5961
Global
6062
GlobalSection(SolutionConfigurationPlatforms) = preSolution
6163
Debug|Any CPU = Debug|Any CPU
@@ -222,6 +224,18 @@ Global
222224
{F6162603-D134-4121-8106-2BA4DAD7350B}.Release|x64.Build.0 = Release|Any CPU
223225
{F6162603-D134-4121-8106-2BA4DAD7350B}.Release|x86.ActiveCfg = Release|Any CPU
224226
{F6162603-D134-4121-8106-2BA4DAD7350B}.Release|x86.Build.0 = Release|Any CPU
227+
{8C9275D9-29CE-4A20-8FD5-6B26C6CAAADB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
228+
{8C9275D9-29CE-4A20-8FD5-6B26C6CAAADB}.Debug|Any CPU.Build.0 = Debug|Any CPU
229+
{8C9275D9-29CE-4A20-8FD5-6B26C6CAAADB}.Debug|x64.ActiveCfg = Debug|Any CPU
230+
{8C9275D9-29CE-4A20-8FD5-6B26C6CAAADB}.Debug|x64.Build.0 = Debug|Any CPU
231+
{8C9275D9-29CE-4A20-8FD5-6B26C6CAAADB}.Debug|x86.ActiveCfg = Debug|Any CPU
232+
{8C9275D9-29CE-4A20-8FD5-6B26C6CAAADB}.Debug|x86.Build.0 = Debug|Any CPU
233+
{8C9275D9-29CE-4A20-8FD5-6B26C6CAAADB}.Release|Any CPU.ActiveCfg = Release|Any CPU
234+
{8C9275D9-29CE-4A20-8FD5-6B26C6CAAADB}.Release|Any CPU.Build.0 = Release|Any CPU
235+
{8C9275D9-29CE-4A20-8FD5-6B26C6CAAADB}.Release|x64.ActiveCfg = Release|Any CPU
236+
{8C9275D9-29CE-4A20-8FD5-6B26C6CAAADB}.Release|x64.Build.0 = Release|Any CPU
237+
{8C9275D9-29CE-4A20-8FD5-6B26C6CAAADB}.Release|x86.ActiveCfg = Release|Any CPU
238+
{8C9275D9-29CE-4A20-8FD5-6B26C6CAAADB}.Release|x86.Build.0 = Release|Any CPU
225239
EndGlobalSection
226240
GlobalSection(SolutionProperties) = preSolution
227241
HideSolutionNode = FALSE
@@ -240,6 +254,7 @@ Global
240254
{11362CEE-B4B3-4EFE-A9A1-A6CDEEFCEA10} = {D455EC79-E1E0-4509-B297-0DA3AED8DFF7}
241255
{68D1BFDC-F447-4D2C-AF81-537807636610} = {1FE49D14-216A-41EE-A177-E42BFF53E0DC}
242256
{F6162603-D134-4121-8106-2BA4DAD7350B} = {362B2776-4B29-46AB-B237-56776B5372B6}
257+
{8C9275D9-29CE-4A20-8FD5-6B26C6CAAADB} = {D455EC79-E1E0-4509-B297-0DA3AED8DFF7}
243258
EndGlobalSection
244259
GlobalSection(ExtensibilityGlobals) = postSolution
245260
SolutionGuid = {CE74F821-B001-4C69-A58D-CF81F8B0B632}

build/scripts/Commandline.fs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ Execution hints can be provided anywhere on the command line
7171
let private (|IsProject|_|) (candidate:string) =
7272
let c = candidate.ToLowerInvariant()
7373
match c with
74-
| "nest" | "elasticsearch.net" | "nest.jsonnetserializer" -> Some c
74+
| "nest" | "elasticsearch.net" | "elastic.clients.elasticsearch.jsonnetserializer" -> Some c
7575
| _ -> None
7676

7777
let private (|IsFormat|_|) (candidate:string) =

build/scripts/Testing.fs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ module Tests =
6262
let RunReleaseUnitTests version args =
6363
//xUnit always does its own build, this env var is picked up by Tests.csproj
6464
//if its set it will include the local package source (build/output/)
65-
//and references NEST and NEST.JsonNetSerializer by the current version
65+
//and references NEST and Elastic.Clients.Elasticsearch.JsonNetSerializer by the current version
6666
//this works by not including the local package cache (nay source)
6767
//in the project file via:
6868
//<RestoreSources></RestoreSources>
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
// Licensed to Elasticsearch B.V under one or more agreements.
2+
// Elasticsearch B.V licenses this file to you under the Apache 2.0 License.
3+
// See the LICENSE file in the project root for more information
4+
5+
using System;
6+
using System.Collections.Generic;
7+
using System.Linq;
8+
using System.Reflection;
9+
using Elastic.Clients.Elasticsearch;
10+
using Elastic.Clients.Elasticsearch.QueryDsl;
11+
using Newtonsoft.Json;
12+
using Newtonsoft.Json.Converters;
13+
using Newtonsoft.Json.Serialization;
14+
using JsonProperty = Newtonsoft.Json.Serialization.JsonProperty;
15+
16+
namespace Elastic.Clients.JsonNetSerializer
17+
{
18+
public class ConnectionSettingsAwareContractResolver : DefaultContractResolver
19+
{
20+
public ConnectionSettingsAwareContractResolver(IElasticsearchClientSettings connectionSettings) =>
21+
ConnectionSettings = connectionSettings ?? throw new ArgumentNullException(nameof(connectionSettings));
22+
23+
protected IElasticsearchClientSettings ConnectionSettings { get; }
24+
25+
protected override string ResolvePropertyName(string fieldName) =>
26+
ConnectionSettings.DefaultFieldNameInferrer != null
27+
? ConnectionSettings.DefaultFieldNameInferrer(fieldName)
28+
: base.ResolvePropertyName(fieldName);
29+
30+
protected override JsonContract CreateContract(Type objectType)
31+
{
32+
var contract = base.CreateContract(objectType);
33+
if (objectType.IsEnum && objectType.GetCustomAttribute<StringEnumAttribute>() != null)
34+
contract.Converter = new StringEnumConverter();
35+
36+
return contract;
37+
}
38+
39+
protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization)
40+
{
41+
var property = base.CreateProperty(member, memberSerialization);
42+
ApplyShouldSerializer(property);
43+
ApplyPropertyOverrides(member, property);
44+
return property;
45+
}
46+
47+
/// <summary> Renames/Ignores a property based on the connection settings mapping or custom attributes for the property </summary>
48+
private void ApplyPropertyOverrides(MemberInfo member, JsonProperty property)
49+
{
50+
if (!ConnectionSettings.PropertyMappings.TryGetValue(member, out var propertyMapping))
51+
propertyMapping = ElasticsearchPropertyAttributeBase.From(member);
52+
53+
var serializerMapping = ConnectionSettings.PropertyMappingProvider?.CreatePropertyMapping(member);
54+
55+
var nameOverride = propertyMapping?.Name ?? serializerMapping?.Name;
56+
if (!string.IsNullOrWhiteSpace(nameOverride)) property.PropertyName = nameOverride;
57+
58+
var overrideIgnore = propertyMapping?.Ignore ?? serializerMapping?.Ignore;
59+
if (overrideIgnore.HasValue)
60+
property.Ignored = overrideIgnore.Value;
61+
}
62+
63+
private static void ApplyShouldSerializer(JsonProperty property)
64+
{
65+
if (property.PropertyType == typeof(QueryContainer))
66+
property.ShouldSerialize = o => ShouldSerializeQueryContainer(o, property);
67+
else if (property.PropertyType == typeof(IEnumerable<QueryContainer>))
68+
property.ShouldSerialize = o => ShouldSerializeQueryContainers(o, property);
69+
}
70+
71+
private static bool ShouldSerializeQueryContainer(object o, JsonProperty prop)
72+
{
73+
if (o == null) return false;
74+
if (prop.ValueProvider.GetValue(o) is not QueryContainer q) return false;
75+
//return q.IsWritable;
76+
return true;
77+
}
78+
79+
private static bool ShouldSerializeQueryContainers(object o, JsonProperty prop)
80+
{
81+
if (o == null) return false;
82+
if (prop.ValueProvider.GetValue(o) is not IEnumerable<QueryContainer> q) return false;
83+
84+
var queryContainers = q as QueryContainer[] ?? q.ToArray();
85+
//return queryContainers.Any(qq => qq != null && ((QueryContainer)qq).IsWritable);
86+
return queryContainers.Any(qq => qq != null);
87+
}
88+
}
89+
}
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
// Licensed to Elasticsearch B.V under one or more agreements.
2+
// Elasticsearch B.V licenses this file to you under the Apache 2.0 License.
3+
// See the LICENSE file in the project root for more information
4+
5+
using System;
6+
using System.IO;
7+
using System.Text;
8+
using System.Threading;
9+
using System.Threading.Tasks;
10+
using Elastic.Transport;
11+
using Newtonsoft.Json;
12+
13+
namespace Elastic.Clients.JsonNetSerializer
14+
{
15+
public abstract partial class ConnectionSettingsAwareSerializerBase : SerializerBase
16+
{
17+
// Default buffer size of StreamWriter, which is private :(
18+
internal const int DefaultBufferSize = 1024;
19+
20+
private static readonly Task CompletedTask = Task.CompletedTask;
21+
22+
internal static readonly Encoding ExpectedEncoding = new UTF8Encoding(false);
23+
private readonly JsonSerializer _collapsedSerializer;
24+
25+
private readonly JsonSerializer _serializer;
26+
protected virtual int BufferSize => DefaultBufferSize;
27+
28+
public override T Deserialize<T>(Stream stream)
29+
{
30+
using var streamReader = new StreamReader(stream);
31+
using var jsonTextReader = new JsonTextReader(streamReader);
32+
return _serializer.Deserialize<T>(jsonTextReader);
33+
}
34+
35+
public override object Deserialize(Type type, Stream stream)
36+
{
37+
using var streamReader = new StreamReader(stream);
38+
using var jsonTextReader = new JsonTextReader(streamReader);
39+
return _serializer.Deserialize(jsonTextReader, type);
40+
}
41+
42+
public override async Task<T> DeserializeAsync<T>(Stream stream, CancellationToken cancellationToken = default)
43+
{
44+
using var streamReader = new StreamReader(stream);
45+
using var jsonTextReader = new JsonTextReader(streamReader);
46+
var token = await jsonTextReader.ReadTokenWithDateParseHandlingNoneAsync(cancellationToken).ConfigureAwait(false);
47+
return token.ToObject<T>(_serializer);
48+
}
49+
50+
public override async Task<object> DeserializeAsync(Type type, Stream stream, CancellationToken cancellationToken = default(CancellationToken))
51+
{
52+
using var streamReader = new StreamReader(stream);
53+
using var jsonTextReader = new JsonTextReader(streamReader);
54+
var token = await jsonTextReader.ReadTokenWithDateParseHandlingNoneAsync(cancellationToken).ConfigureAwait(false);
55+
return token.ToObject(type, _serializer);
56+
}
57+
58+
public override void Serialize<T>(T data, Stream stream, SerializationFormatting formatting = SerializationFormatting.None)
59+
{
60+
using var writer = new StreamWriter(stream, ExpectedEncoding, BufferSize, true);
61+
using var jsonWriter = new JsonTextWriter(writer);
62+
var serializer = formatting == SerializationFormatting.Indented ? _serializer : _collapsedSerializer;
63+
serializer.Serialize(jsonWriter, data);
64+
}
65+
66+
public override Task SerializeAsync<T>(T data, Stream stream, SerializationFormatting formatting = SerializationFormatting.None,
67+
CancellationToken cancellationToken = default)
68+
{
69+
//This makes no sense now but we need the async method on the interface in 6.x so we can start swapping this out
70+
//for an implementation that does make sense without having to wait for 7.x
71+
Serialize(data, stream, formatting);
72+
return CompletedTask;
73+
}
74+
}
75+
}
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
// Licensed to Elasticsearch B.V under one or more agreements.
2+
// Elasticsearch B.V licenses this file to you under the Apache 2.0 License.
3+
// See the LICENSE file in the project root for more information
4+
5+
using System.Collections.Concurrent;
6+
using System.Reflection;
7+
using System.Runtime.Serialization;
8+
using Elastic.Clients.Elasticsearch;
9+
using Newtonsoft.Json;
10+
11+
namespace Elastic.Clients.JsonNetSerializer
12+
{
13+
public abstract partial class ConnectionSettingsAwareSerializerBase : IPropertyMappingProvider
14+
{
15+
protected readonly ConcurrentDictionary<string, PropertyMapping> Properties = new();
16+
17+
public PropertyMapping CreatePropertyMapping(MemberInfo memberInfo)
18+
{
19+
var memberInfoString = $"{memberInfo.DeclaringType?.FullName}.{memberInfo.Name}";
20+
if (Properties.TryGetValue(memberInfoString, out var mapping)) return mapping;
21+
mapping = FromAttributes(memberInfo);
22+
23+
Properties.TryAdd(memberInfoString, mapping);
24+
return mapping;
25+
}
26+
27+
private static PropertyMapping FromAttributes(MemberInfo memberInfo)
28+
{
29+
var jsonProperty = memberInfo.GetCustomAttribute<JsonPropertyAttribute>(true);
30+
var dataMemberProperty = memberInfo.GetCustomAttribute<DataMemberAttribute>(true);
31+
var propertyName = memberInfo.GetCustomAttribute<PropertyNameAttribute>(true);
32+
var ignore = memberInfo.GetCustomAttribute<IgnoreAttribute>(true);
33+
var jsonIgnore = memberInfo.GetCustomAttribute<JsonIgnoreAttribute>(true);
34+
if (jsonProperty == null && ignore == null && propertyName == null && dataMemberProperty == null && jsonIgnore == null) return null;
35+
36+
return new PropertyMapping
37+
{ Name = propertyName?.Name ?? jsonProperty?.PropertyName ?? dataMemberProperty?.Name, Ignore = ignore != null || jsonIgnore != null };
38+
}
39+
}
40+
}
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
// Licensed to Elasticsearch B.V under one or more agreements.
2+
// Elasticsearch B.V licenses this file to you under the Apache 2.0 License.
3+
// See the LICENSE file in the project root for more information
4+
5+
using System;
6+
using System.Collections.Generic;
7+
using System.Linq;
8+
using Elastic.Clients.JsonNetSerializer.Converters;
9+
using Newtonsoft.Json;
10+
using Elastic.Transport;
11+
using Elastic.Clients.Elasticsearch;
12+
13+
namespace Elastic.Clients.JsonNetSerializer
14+
{
15+
public abstract partial class ConnectionSettingsAwareSerializerBase
16+
{
17+
protected ConnectionSettingsAwareSerializerBase(SerializerBase builtinSerializer, IElasticsearchClientSettings connectionSettings)
18+
: this(builtinSerializer, connectionSettings, null, null, null) { }
19+
20+
internal ConnectionSettingsAwareSerializerBase(
21+
SerializerBase builtinSerializer,
22+
IElasticsearchClientSettings connectionSettings,
23+
Func<JsonSerializerSettings> jsonSerializerSettingsFactory,
24+
Action<ConnectionSettingsAwareContractResolver> modifyContractResolver,
25+
IEnumerable<JsonConverter> contractJsonConverters
26+
)
27+
{
28+
JsonSerializerSettingsFactory = jsonSerializerSettingsFactory;
29+
ModifyContractResolverCallback = modifyContractResolver;
30+
ContractJsonConverters = contractJsonConverters ?? Enumerable.Empty<JsonConverter>();
31+
32+
ConnectionSettings = connectionSettings;
33+
BuiltinSerializer = builtinSerializer;
34+
Converters = new List<JsonConverter>
35+
{
36+
new HandleNestTypesOnSourceJsonConverter(BuiltinSerializer, connectionSettings.MemoryStreamFactory),
37+
new TimeSpanToStringConverter()
38+
};
39+
_serializer = CreateSerializer(SerializationFormatting.Indented);
40+
_collapsedSerializer = CreateSerializer(SerializationFormatting.None);
41+
}
42+
43+
protected SerializerBase BuiltinSerializer { get; }
44+
45+
protected IElasticsearchClientSettings ConnectionSettings { get; }
46+
protected IEnumerable<JsonConverter> ContractJsonConverters { get; }
47+
protected Func<JsonSerializerSettings> JsonSerializerSettingsFactory { get; }
48+
protected Action<ConnectionSettingsAwareContractResolver> ModifyContractResolverCallback { get; }
49+
50+
private List<JsonConverter> Converters { get; }
51+
52+
private JsonSerializer CreateSerializer(SerializationFormatting formatting)
53+
{
54+
var s = CreateJsonSerializerSettings() ?? new JsonSerializerSettings();
55+
var converters = CreateJsonConverters() ?? Enumerable.Empty<JsonConverter>();
56+
var contract = CreateContractResolver();
57+
s.Formatting = formatting == SerializationFormatting.Indented ? Formatting.Indented : Formatting.None;
58+
s.ContractResolver = contract;
59+
foreach (var converter in converters.Concat(Converters))
60+
s.Converters.Add(converter);
61+
62+
return JsonSerializer.Create(s);
63+
}
64+
65+
protected virtual ConnectionSettingsAwareContractResolver CreateContractResolver()
66+
{
67+
var contract = new ConnectionSettingsAwareContractResolver(ConnectionSettings);
68+
ModifyContractResolver(contract);
69+
return contract;
70+
}
71+
72+
protected virtual JsonSerializerSettings CreateJsonSerializerSettings() => JsonSerializerSettingsFactory?.Invoke();
73+
74+
protected virtual IEnumerable<JsonConverter> CreateJsonConverters() => ContractJsonConverters;
75+
76+
protected virtual void ModifyContractResolver(ConnectionSettingsAwareContractResolver resolver) =>
77+
ModifyContractResolverCallback?.Invoke(resolver);
78+
}
79+
}

0 commit comments

Comments
 (0)