-
Notifications
You must be signed in to change notification settings - Fork 1.2k
Description
With the move to utf8json in #3493, the serialization of NEST types is strict, serializing only those properties specified on implemented interfaces in many places. For example, PropertyFormatter
only serializes the I*Property
interface members:
elasticsearch-net/src/Nest/Mapping/Types/PropertyFormatter.cs
Lines 97 to 192 in f3ec719
public void Serialize(ref JsonWriter writer, IProperty value, IJsonFormatterResolver formatterResolver) | |
{ | |
if (value == null) | |
{ | |
writer.WriteNull(); | |
return; | |
} | |
switch (value.Type) | |
{ | |
case "text": | |
Serialize<ITextProperty>(ref writer, value, formatterResolver); | |
break; | |
case "keyword": | |
Serialize<IKeywordProperty>(ref writer, value, formatterResolver); | |
break; | |
case "float": | |
case "double": | |
case "byte": | |
case "short": | |
case "integer": | |
case "long": | |
case "scaled_float": | |
case "half_float": | |
Serialize<INumberProperty>(ref writer, value, formatterResolver); | |
break; | |
case "date": | |
Serialize<IDateProperty>(ref writer, value, formatterResolver); | |
break; | |
case "boolean": | |
Serialize<IBooleanProperty>(ref writer, value, formatterResolver); | |
break; | |
case "binary": | |
Serialize<IBinaryProperty>(ref writer, value, formatterResolver); | |
break; | |
case "object": | |
Serialize<IObjectProperty>(ref writer, value, formatterResolver); | |
break; | |
case "nested": | |
Serialize<INestedProperty>(ref writer, value, formatterResolver); | |
break; | |
case "ip": | |
Serialize<IIpProperty>(ref writer, value, formatterResolver); | |
break; | |
case "geo_point": | |
Serialize<IGeoPointProperty>(ref writer, value, formatterResolver); | |
break; | |
case "geo_shape": | |
Serialize<IGeoShapeProperty>(ref writer, value, formatterResolver); | |
break; | |
case "completion": | |
Serialize<ICompletionProperty>(ref writer, value, formatterResolver); | |
break; | |
case "token_count": | |
Serialize<ITokenCountProperty>(ref writer, value, formatterResolver); | |
break; | |
case "murmur3": | |
Serialize<IMurmur3HashProperty>(ref writer, value, formatterResolver); | |
break; | |
case "percolator": | |
Serialize<IPercolatorProperty>(ref writer, value, formatterResolver); | |
break; | |
case "date_range": | |
Serialize<IDateRangeProperty>(ref writer, value, formatterResolver); | |
break; | |
case "double_range": | |
Serialize<IDoubleRangeProperty>(ref writer, value, formatterResolver); | |
break; | |
case "float_range": | |
Serialize<IFloatRangeProperty>(ref writer, value, formatterResolver); | |
break; | |
case "integer_range": | |
Serialize<IIntegerRangeProperty>(ref writer, value, formatterResolver); | |
break; | |
case "long_range": | |
Serialize<ILongRangeProperty>(ref writer, value, formatterResolver); | |
break; | |
case "ip_range": | |
Serialize<IIpRangeProperty>(ref writer, value, formatterResolver); | |
break; | |
case "join": | |
Serialize<IJoinProperty>(ref writer, value, formatterResolver); | |
break; | |
case "alias": | |
Serialize<IFieldAliasProperty>(ref writer, value, formatterResolver); | |
break; | |
default: | |
if (value is IGenericProperty genericProperty) | |
Serialize<IGenericProperty>(ref writer, genericProperty, formatterResolver); | |
else | |
{ | |
var formatter = DynamicObjectResolver.ExcludeNullCamelCase.GetFormatter<IProperty>(); | |
formatter.Serialize(ref writer, value, formatterResolver); | |
} | |
break; | |
} |
With such strictness, it is not currently possible to derive a type from a NEST type, add additional properties, and have those properties serialized by the internal serializer. This is demonstrated in InjectACustomIPropertyImplementation()
elasticsearch-net/src/Tests/Tests/ClientConcepts/HighLevel/Serialization/ExtendingNestTypes.doc.cs
Lines 53 to 97 in e2b7f75
[U (Skip = "TODO: Does not work with utf8json")] | |
public void InjectACustomIPropertyImplementation() | |
{ | |
/** | |
* `PropertyNameAttribute` can be used to mark properties that should be serialized. Without this attribute, | |
* NEST won't pick up the property for serialization. | |
* | |
* Now that we have our own `IProperty` implementation we can add it to our propertes mapping when creating an index | |
*/ | |
var createIndexResponse = _client.CreateIndex("myindex", c => c | |
.Map<Project>(m => m | |
.Properties(props => props | |
.Custom(new MyPluginProperty("fieldName", "dutch")) | |
) | |
) | |
); | |
/** | |
* which will serialize to the following JSON request | |
*/ | |
// json | |
var expected = new | |
{ | |
mappings = new | |
{ | |
properties = new | |
{ | |
fieldName = new | |
{ | |
type = "my_plugin_property", | |
language = "dutch", | |
numeric = true | |
} | |
} | |
} | |
}; | |
/** | |
* Whilst NEST can _serialize_ our `my_plugin_property`, it does not know how to _deserialize_ it; | |
* We plan to make this more pluggable in the future | |
*/ | |
// hide | |
Expect(expected).FromRequest(createIndexResponse); | |
} | |
} |
utf8json is able to support this feature through using the fallback formatter, DynamicObjectTypeFallbackFormatter
,exposed by resolving a IJsonFormatter<object>
, which internally inspects the type and generates a custom serialization method for it, caching it in a ThreadsafeTypeKeyHashTable<KeyValuePair<object, SerializeMethod>>
. This path looks like it would be a plausible way to support extending NEST types but requires some guarding when serializing Attribute types; when a type to serialize is an Attribute
type, the walking of properties performed by MetaType
traverses down to a framework enum type with multiple members with the same undelying value, which trips up the EnumFormatter
. For an Attribute type, this walking should not be performed.