源序列化
编辑源序列化编辑
源序列化是指在消费者应用程序中将 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>
来实现,该操作将在设置我们的默认值后应用。此机制允许您应用其他设置或更改我们默认值的值。
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
edit
假设您更喜欢为源类型使用替代 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 类型的序列化委托给它。