源序列化
编辑源序列化
编辑源序列化指的是在消费者应用程序中(反)序列化 POCO 类型,作为从 Elasticsearch 索引和检索的源文档的过程。源序列化器实现处理序列化,默认实现使用 System.Text.Json
库。因此,您可以使用 System.Text.Json
属性和转换器来控制序列化行为。
使用类型建模文档
编辑Elasticsearch 提供对其发送和索引的文档的搜索和聚合功能。这些文档作为 JSON 对象在 HTTP 请求的请求正文中发送。使用 POCO(Plain Old CLR Objects)在 Elasticsearch .NET 客户端中建模文档是很自然的。
本节概述了如何使用类型和类型层次结构来建模文档。
默认行为
编辑默认行为是将类型属性名称序列化为小驼峰式 JSON 对象成员。
我们可以使用常规类(POCO)来建模文档。
public class MyDocument { public string StringProperty { get; set; } }
然后,我们可以将文档实例索引到 Elasticsearch 中。
using System.Threading.Tasks; using Elastic.Clients.Elasticsearch; var document = new MyDocument { StringProperty = "value" }; var indexResponse = await Client .IndexAsync(document, "my-index-name");
索引请求被序列化,源序列化器处理 MyDocument
类型,将名为 StringProperty
的 POCO 属性序列化为名为 stringProperty
的 JSON 对象成员。
{ "stringProperty": "value" }
自定义源序列化
编辑内置的源序列化器可以正确处理大多数 POCO 文档模型。有时,您可能需要进一步控制类型的序列化方式。
内置的源序列化器在内部使用 Microsoft System.Text.Json
库。您可以应用 System.Text.Json
属性和转换器来控制文档类型的序列化。
使用 System.Text.Json
属性
编辑System.Text.Json
包括可以应用于类型和属性以控制其序列化的属性。这些属性可以应用于您的 POCO 文档类型,以执行诸如控制属性的名称或完全忽略属性等操作。请访问 Microsoft 文档以获取更多示例。
我们可以使用常规类(POCO)来建模一个表示人员数据的文档,并根据需要应用 System.Text.Json
属性。
using System.Text.Json.Serialization; public class Person { [JsonPropertyName("forename")] public string FirstName { get; set; } [JsonIgnore] public int Age { get; set; } }
然后,我们可以将文档实例索引到 Elasticsearch 中。
using System; using System.Text.Json; using System.Text.Json.Serialization; using System.Threading.Tasks; using Elastic.Transport; using Elastic.Clients.Elasticsearch; using Elastic.Clients.Elasticsearch.Serialization; var person = new Person { FirstName = "Steve", Age = 35 }; var indexResponse = await Client.IndexAsync(person, "my-index-name");
索引请求被序列化,源序列化器处理 Person
类型,将名为 FirstName
的 POCO 属性序列化为名为 forename
的 JSON 对象成员。Age
属性被忽略,并且不会出现在 JSON 中。
{ "forename": "Steve" }
配置自定义 JsonSerializerOptions
编辑默认源序列化器在序列化源文档类型时应用一组标准 JsonSerializerOptions
。在某些情况下,您可能需要覆盖我们的一些默认设置。这可以通过创建 DefaultSourceSerializer
实例并传递一个 Action<JsonSerializerOptions>
来实现,该实例在设置我们的默认设置后应用。此机制允许您应用其他设置或更改我们默认设置的值。
DefaultSourceSerializer
包括一个接受当前 IElasticsearchClientSettings
和一个 configureOptions
Action
的构造函数。
public DefaultSourceSerializer(IElasticsearchClientSettings settings, Action<JsonSerializerOptions> configureOptions);
我们的应用程序定义了以下 Person
类,该类对我们将要索引到 Elasticsearch 的文档进行建模。
public class Person { public string FirstName { get; set; } }
我们希望使用 Pascal 大小写来序列化 JSON 属性中的源文档。由于在 DefaultSouceSerializer
中应用的选项将 PropertyNamingPolicy
设置为 JsonNamingPolicy.CamelCase
,因此我们必须覆盖此设置。在配置 ElasticsearchClientSettings
后,我们将文档索引到 Elasticsearch。
using System; using System.Text.Json; using System.Text.Json.Serialization; using System.Threading.Tasks; using Elastic.Transport; using Elastic.Clients.Elasticsearch; using Elastic.Clients.Elasticsearch.Serialization; static void ConfigureOptions(JsonSerializerOptions o) => o.PropertyNamingPolicy = null; var nodePool = new SingleNodePool(new Uri("https://127.0.0.1:9200")); var settings = new ElasticsearchClientSettings( nodePool, sourceSerializer: (defaultSerializer, settings) => new DefaultSourceSerializer(settings, ConfigureOptions)); var client = new ElasticsearchClient(settings); var person = new Person { FirstName = "Steve" }; var indexResponse = await client.IndexAsync(person, "my-index-name");
可以定义一个接受 |
|
在创建 |
Person
实例被序列化,源序列化器使用 Pascal 大小写序列化名为 FirstName
的 POCO 属性。
{ "FirstName": "Steve" }
作为使用本地函数的替代方法,我们可以将 Action<JsonSerializerOptions>
存储到一个变量中,该变量可以传递给 DefaultSouceSerializer
构造函数。
Action<JsonSerializerOptions> configureOptions = o => o.PropertyNamingPolicy = null;
注册自定义 System.Text.Json
转换器
编辑在某些更高级的情况下,您的类型可能需要在序列化期间进行进一步的自定义,而这是使用 System.Text.Json
属性无法实现的。在这些情况下,Microsoft 的建议是利用自定义 JsonConverter
。使用 DefaultSourceSerializer
序列化的源文档类型可以利用自定义转换器的强大功能。
对于此示例,我们的应用程序有一个文档类,该文档类应使用旧的 JSON 结构来继续使用现有的索引文档进行操作。有几个选项可用,但在此情况下,我们将应用自定义转换器。
定义了我们的类,并且 JsonConverter
属性应用于类类型,指定了自定义转换器的类型。
using System.Text.Json.Serialization; [JsonConverter(typeof(CustomerConverter))] public class Customer { public string CustomerName { get; set; } public CustomerType CustomerType { get; set; } } public enum CustomerType { Standard, Enhanced }
在序列化此类时,我们必须发送一个名为 isStandard
的布尔属性,而不是包含一个表示 CustomerType
属性值的字符串值。此要求可以通过自定义 JsonConverter 实现来实现。
using System; using System.Text.Json; using System.Text.Json.Serialization; public class CustomerConverter : JsonConverter<Customer> { public override Customer Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { var customer = new Customer(); while (reader.Read() && reader.TokenType != JsonTokenType.EndObject) { if (reader.TokenType == JsonTokenType.PropertyName) { if (reader.ValueTextEquals("customerName")) { reader.Read(); customer.CustomerName = reader.GetString(); continue; } if (reader.ValueTextEquals("isStandard")) { reader.Read(); var isStandard = reader.GetBoolean(); if (isStandard) { customer.CustomerType = CustomerType.Standard; } else { customer.CustomerType = CustomerType.Enhanced; } continue; } } } return customer; } public override void Write(Utf8JsonWriter writer, Customer value, JsonSerializerOptions options) { if (value is null) { writer.WriteNullValue(); return; } writer.WriteStartObject(); if (!string.IsNullOrEmpty(value.CustomerName)) { writer.WritePropertyName("customerName"); writer.WriteStringValue(value.CustomerName); } writer.WritePropertyName("isStandard"); if (value.CustomerType == CustomerType.Standard) { writer.WriteBooleanValue(true); } else { writer.WriteBooleanValue(false); } writer.WriteEndObject(); } }
然后,我们可以将客户文档索引到 Elasticsearch 中。
using System; using System.Text.Json; using System.Text.Json.Serialization; using System.Threading.Tasks; using Elastic.Transport; using Elastic.Clients.Elasticsearch; using Elastic.Clients.Elasticsearch.Serialization; var customer = new Customer { CustomerName = "Customer Ltd", CustomerType = CustomerType.Enhanced }; var indexResponse = await Client.IndexAsync(customer, "my-index-name");
使用自定义转换器序列化 Customer
实例,创建以下 JSON 文档。
{ "customerName": "Customer Ltd", "isStandard": false }
创建自定义 SystemTextJsonSerializer
编辑内置的 DefaultSourceSerializer
包括在源序列化期间应用的 JsonConverter
实例的注册。在大多数情况下,这些实例为序列化源文档提供了正确的行为,包括在其属性上使用 Elastic.Clients.Elasticsearch
类型的文档。
您可能需要更多控制转换器注册顺序的一个示例是序列化 enum
类型。 DefaultSourceSerializer
注册 System.Text.Json.Serialization.JsonStringEnumConverter
,因此枚举值使用其字符串表示形式进行序列化。通常,这是用于索引到 Elasticsearch 的文档类型的首选选项。
在某些情况下,您可能需要控制为枚举值发送的字符串值。这在 System.Text.Json
中不直接支持,但可以通过为您希望自定义的 enum
类型创建自定义 JsonConverter
来实现。在这种情况下,仅在 enum
类型上使用 JsonConverterAttribute
来注册转换器是不够的。 System.Text.Json
将优先选择添加到 JsonSerializerOptions
上 Converters
集合中的转换器,而不是应用于 enum
类型的属性。因此,必须从 Converters
集合中删除 JsonStringEnumConverter
,或者在 JsonStringEnumConverter
之前为您的 enum
类型注册一个专门的转换器。
后者可以通过多种技术实现。使用 Elasticsearch .NET 库时,我们可以通过从抽象 SystemTextJsonSerializer
类派生来实现此目的。
这里我们有一个 POCO,它使用 CustomerType
枚举作为属性的类型。
using System.Text.Json.Serialization; public class Customer { public string CustomerName { get; set; } public CustomerType CustomerType { get; set; } } public enum CustomerType { Standard, Enhanced }
为了自定义 CustomerType
序列化期间使用的字符串,我们定义了一个特定于我们的 enum
类型的自定义 JsonConverter
。
using System.Text.Json.Serialization; public class CustomerTypeConverter : JsonConverter<CustomerType> { public override CustomerType Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { return reader.GetString() switch { "basic" => CustomerType.Standard, "premium" => CustomerType.Enhanced, _ => throw new JsonException( $"Unknown value read when deserializing {nameof(CustomerType)}."), }; } public override void Write(Utf8JsonWriter writer, CustomerType value, JsonSerializerOptions options) { switch (value) { case CustomerType.Standard: writer.WriteStringValue("basic"); return; case CustomerType.Enhanced: writer.WriteStringValue("premium"); return; } writer.WriteNullValue(); } }
我们创建了一个从 SystemTextJsonSerializer
派生的序列化器,以便完全控制转换器的注册顺序。
using System.Text.Json; using Elastic.Clients.Elasticsearch.Serialization; public class MyCustomSerializer : SystemTextJsonSerializer { private readonly JsonSerializerOptions _options; public MyCustomSerializer(IElasticsearchClientSettings settings) : base(settings) { var options = DefaultSourceSerializer.CreateDefaultJsonSerializerOptions(false); options.Converters.Add(new CustomerTypeConverter()); _options = DefaultSourceSerializer.AddDefaultConverters(options); } protected override JsonSerializerOptions CreateJsonSerializerOptions() => _options; }
继承自 |
|
在构造函数中,使用工厂方法 |
|
将我们的 |
|
要应用任何默认转换器,请调用 |
|
实现 |
由于我们在默认转换器(包括 JsonStringEnumConverter
)之前注册了 CustomerTypeConverter
,因此在序列化源文档中的 CustomerType
实例时,我们的转换器具有优先权。
基类 SystemTextJsonSerializer
处理绑定的实现细节,这对于确保内置转换器在需要时可以访问 IElasticsearchClientSettings
是必需的。
然后,我们可以将客户文档索引到 Elasticsearch 中。
using System; using System.Text.Json; using System.Text.Json.Serialization; using System.Threading.Tasks; using Elastic.Transport; using Elastic.Clients.Elasticsearch; using Elastic.Clients.Elasticsearch.Serialization; var customer = new Customer { CustomerName = "Customer Ltd", CustomerType = CustomerType.Enhanced }; var indexResponse = await client.IndexAsync(customer, "my-index-name");
使用自定义 enum
转换器序列化 Customer
实例,从而创建以下 JSON 文档。
创建自定义 Serializer
编辑假设你更喜欢为你的源类型使用替代的 JSON 序列化库。在这种情况下,你可以注入一个独立的序列化器,仅用于序列化 _source
、_fields
或任何需要写入和返回用户提供值的地方。
从技术上讲,实现 Elastic.Transport.Serializer
足以创建一个自定义源序列化器。
using System; using System.IO; using System.Threading; using System.Threading.Tasks; using Elastic.Transport; public class VanillaSerializer : Serializer { public override object Deserialize(Type type, Stream stream) => throw new NotImplementedException(); public override T Deserialize<T>(Stream stream) => throw new NotImplementedException(); public override ValueTask<object> DeserializeAsync(Type type, Stream stream, CancellationToken cancellationToken = default) => throw new NotImplementedException(); public override ValueTask<T> DeserializeAsync<T>(Stream stream, CancellationToken cancellationToken = default) => throw new NotImplementedException(); public override void Serialize<T>(T data, Stream stream, SerializationFormatting formatting = SerializationFormatting.None) => throw new NotImplementedException(); public override Task SerializeAsync<T>(T data, Stream stream, SerializationFormatting formatting = SerializationFormatting.None, CancellationToken cancellationToken = default) => throw new NotImplementedException(); }
序列化器的注册在 ConnectionSettings
构造函数中执行。
using System; using System.Text.Json; using System.Text.Json.Serialization; using System.Threading.Tasks; using Elastic.Transport; using Elastic.Clients.Elasticsearch; using Elastic.Clients.Elasticsearch.Serialization; var nodePool = new SingleNodePool(new Uri("https://127.0.0.1:9200")); var settings = new ElasticsearchClientSettings( nodePool, sourceSerializer: (defaultSerializer, settings) => new VanillaSerializer()); var client = new ElasticsearchClient(settings);
在各种情况下,你可能有一个 POCO 类型,其中包含 Elastic.Clients.Elasticsearch
类型作为其属性之一。SourceSerializerFactory
委托提供对默认内置序列化器的访问权限,以便你在必要时可以访问它。例如,考虑你是否想使用 percolation;你需要将 Elasticsearch 查询存储为文档的 _source
的一部分,这意味着你需要一个看起来像这样的 POCO。
using Elastic.Clients.Elasticsearch.QueryDsl; public class MyPercolationDocument { public Query Query { get; set; } public string Category { get; set; } }
自定义序列化器不知道如何序列化 Query
或其他可能作为文档 _source
一部分出现的 Elastic.Clients.Elasticsearch
类型。因此,你的自定义 Serializer
需要存储对我们内置序列化器的引用,并将 Elastic 类型的序列化委托回它。