Search code examples
c#elasticsearchjson.netnestelasticsearch-geo-shape

Documents with a geo_shape field cannot be deserialized?


My index contains a field that is of type Nest.GeoShape.

----------

Problem #1 -- Kibana shows that field as "indexed = false" even though it has been defined like this (with .MapFromAttributes() during index creation)...

    [ElasticProperty(Index = FieldIndexOption.NotAnalyzed, Store = true, IncludeInAll = false)]
    public Nest.GeoShape ElasticShape { get; set; }

here's the index creation, in case that's the problem...

    client.CreateIndex(c => c
        .Index(indexName)
        .InitializeUsing(set)
        .AddMapping<ItemSearchable>(m => m
                    .MapFromAttributes()
                    .Properties(props => props
                            .GeoShape(x => x
                                .Name(n => n.ElasticShape)
                                .Tree(GeoTree.Geohash)
                                .TreeLevels(9)
                                .DistanceErrorPercentage(0.025))))

----------

Problem #2 -- When I do a query, the results that come back fail to deserialize.

{"Could not create an instance of type Nest.GeoShape. Type is an interface or abstract class and cannot be instantiated. Path 'hits.hits[0]._source.elasticShape.coordinates', line 10, position 19."}

I expect it's because I'm using Nest.GeoShape rather than an explicit GeoShape type (like EnvelopeGeoShape), but in my case, each document will have a different shape (5 might be circles, 3 rectangles, 2 polygons, and 74 points).

So is there a way I can further control the Json Deserialization to check the type and explicitly map it to generate a particular type? Or (ideally) is there a way to simply let the deserialization "figure it out" from the type field automatically?


Solution

  • Okay, here's what I found as a solution to the Deserialization (Problem #2)...

    It requires a CustomCreationConverter to be written to handle the specific fields available for the different GeoShape types. Here's a sample for points:

    public class CustomNestGeoShapeConverter : CustomCreationConverter<Nest.GeoShape>
    {
        public override Nest.GeoShape Create(Type objectType)
        {
            return null;
        }
    
        public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
        {
            JToken token = JToken.Load(reader);
            if(token == null) return null;
    
            switch (token["type"].ToString())
            {
                case "point":
                    {
                        var coords = new List<double>();
                        coords.Add(Double.Parse(token["coordinates"][0].ToString()));
                        coords.Add(Double.Parse(token["coordinates"][1].ToString()));
                        return new Nest.PointGeoShape() { Coordinates = coords };
                    }
            }
    
            return null;
        }
    
        public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
        {
            throw new NotImplementedException();
        }
    }
    

    Then, to use this configuration, I set up a decorator on the field itself within my class...

        [JsonConverter(typeof(CustomNestGeoShapeConverter)), ElasticProperty(Index = FieldIndexOption.NotAnalyzed, Store = true, IncludeInAll = false)]
        public Nest.GeoShape ElasticShape { get; set; }
    

    This is working great for me now, but I still need to test whether I can search against the shape even if Kibana thinks the field is not actually indexed (Problem #1).