源序列化
编辑源序列化
编辑源序列化指的是在消费者应用程序中将 POCO 类型 (反)序列化为从 Elasticsearch 索引和检索的源文档的过程。源序列化程序实现负责处理序列化,默认实现使用 System.Text.Json
库。因此,您可以使用 System.Text.Json
属性和转换器来控制序列化行为。
使用类型建模文档
编辑Elasticsearch 提供了对其接收和索引的文档进行搜索和聚合的功能。这些文档作为 HTTP 请求的请求正文中的 JSON 对象发送。在 Elasticsearch .NET 客户端中使用 POCO(普通旧 CLR 对象) 来建模文档是很自然的。
本节概述了如何使用类型和类型层次结构来建模文档。
默认行为
编辑默认行为是将类型属性名称序列化为驼峰式大小写 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>
来实现,该 Action
在设置我们的默认值后应用。此机制允许您应用其他设置或更改默认值。
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");
Customer
实例使用自定义的 enum
转换器进行序列化,生成以下 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
委托提供了对默认内置序列化器的访问,以便您在必要时可以访问它。例如,如果您想使用渗透,则需要将 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 类型的序列化委托回它。