Search code examples
elasticsearchjson.netnestregioninfo

RegionInfo serializing in ElasticSearch


It seems that the RegionInfo object has kinda been forgotten in terms of serialization. CultureInfo works great and is serialized to and from a string. When attempting to throw in a RegionInfo object, I get a mess of all the properties of RegionInfo that can't be deserialized because there is no constructor that takes all those properties in reverse. I would love to just serialize and deserialize the RegionInfos as strings, like CultureInfo, but can't quite figure that out.

My attempt:

I created a regioninfo converter

public class RegionInfoConverter : JsonConverter
{
    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        serializer.Serialize(writer, ((RegionInfo)value).Name);
    }

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        var token = JToken.Load(reader);
        return new RegionInfo(token.ToObject<string>());
    }

    public override bool CanConvert(Type objectType)
    {
        return typeof(RegionInfo) == objectType;
    }
}

I stuffed that into the ConnectionSettings:

var connectionSettings = new ConnectionSettings(pool,
    (builtin, settings) => new JsonNetSerializer(
        builtin,
        settings,
        contractJsonConverters: new JsonConverter[] { new RegionInfoConverter() })
);

but I get the error: object mapping for [region] tried to parse field [region] as object, but found a concrete value

That sounds like one of my serializer pieces is wrong, but I don't feel like I quite understand enough to figure out which part it is. Thanks.


Solution

  • I think the issue here may be that Elasticsearch has initially inferred an object datatype mapping for RegionInfo from a document to be indexed, and is now being passed a string value for RegionInfo. You may need to delete the index and create again, mapping the RegionInfo property as a keyword datatype.

    Here's a working example

    private static void Main()
    {
        var defaultIndex = "my_index";
        var pool = new SingleNodeConnectionPool(new Uri("http://localhost:9200"));
    
        var settings = new ConnectionSettings(pool, (b, s) => 
            new JsonNetSerializer(b, s, contractJsonConverters: new JsonConverter[] { new RegionInfoConverter() })
            )
            .DefaultIndex(defaultIndex);
    
        var client = new ElasticClient(settings);
    
        if (client.IndexExists(defaultIndex).Exists)
            client.DeleteIndex(defaultIndex);
    
        var createIndexResponse = client.CreateIndex(defaultIndex, c => c
            .Settings(s => s
                .NumberOfShards(1)
                .NumberOfReplicas(0)
            )
            .Mappings(m => m
                .Map<MyEntity>(mm => mm
                    .AutoMap()
                    .Properties(p => p
                        .Keyword(k => k
                            .Name(n => n.RegionInfo)
                        )
                    )
                )
            )
        );
    
        var indexResponse = client.Index(new MyEntity 
        {
            RegionInfo = RegionInfo.CurrentRegion
        }, i => i.Refresh(Refresh.WaitFor));
    }
    
    public class MyEntity
    {
        public RegionInfo RegionInfo { get; set; }
    }
    
    public class RegionInfoConverter : JsonConverter
    {
        public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
        {
            if (value == null)
            {
                writer.WriteNull();
                return;
            }
    
            writer.WriteValue(((RegionInfo)value).Name);
        }
    
        public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
        {
            if (reader.TokenType == JsonToken.Null)
                return null;
    
            if (reader.TokenType != JsonToken.String)
                throw new JsonSerializationException($"Cannot deserialize {nameof(RegionInfo)} from {reader.TokenType}");
    
            return new RegionInfo((string)reader.Value);
        }
    
        public override bool CanConvert(Type objectType)
        {
            return typeof(RegionInfo) == objectType;
        }
    }
    

    The index request sends the following JSON

    {
      "regionInfo": "AU"
    }