Search code examples
elasticsearchnest

NEST V7.12.1 - Self referencing loop detected for property 'response' with type 'Elasticsearch.Net.ApiCallDetails'


I am fairly new to ElasticSearch and I am currently trying it out for work, however when trying to make an Regex Query, it gives the following error:

Newtonsoft.Json.JsonSerializationException: Self referencing loop detected for property 'response' with type 'Elasticsearch.Net.ApiCallDetails'. Path 'apiCall.originalException'. at Newtonsoft.Json.Serialization.JsonSerializerInternalWriter.CheckForCircularReference(JsonWriter writer, Object value, JsonProperty property, JsonContract contract, JsonContainerContract containerContract, JsonProperty containerProperty) at Newtonsoft.Json.Serialization.JsonSerializerInternalWriter.CalculatePropertyValues(JsonWriter writer, Object value, JsonContainerContract contract, JsonProperty member, JsonProperty property, JsonContract& memberContract, Object& memberValue) ......

The query:

var ChildFileItems = ElasticClient.Search<FileData>(s => s
                //.From(0).Size(10)
                .Query(q => q
                    .Regexp(r => r
                        .Field(p => p.FilePath)
                        //.Value($"/{SearchPath}\\\\([A-Z a-z0-9_.-]+)[.]([A-Z a-z0-9]+)/g")
                        .Value(@$"/([A-Z a-z:0-9_.\\-]+)/g")
                    )
                )
            );

(I am just trying to get any Regex Query working). When I perform a Regex query which does not find any results, it gives no error, so it is (I would guess) something within my object. Online I have found that you can Ignore this Error, however these were all for version 6, and I cannot get it working within version 7. This is the best (Read: I get no errors) version, however with this I still get the error.

When I perform a "normal" match query, it does however work fine and I get my results back.

var settings = new ConnectionSettings(pool, (builtInSerializer, connectionSettings) =>
                new JsonNetSerializer(builtInSerializer, connectionSettings, () => new JsonSerializerSettings
                {
                    ReferenceLoopHandling = ReferenceLoopHandling.Ignore
                }))
                .DefaultFieldNameInferrer(p => p)
                .PrettyJson();
            ;

Solution

  • The error is strange; it's a Newtonsoft.Json.JsonSerializationException, but the 7.12.1 client doesn't use Newtonsoft.Json internally.

    A serializer that uses Newtonsoft.Json can be configured with JsonNetSerializer as per your second code example, but this serializer is used to deserialize your documents, and wouldn't be used to deserialize Elasticsearch.Net.ApiCallDetails, which is a low level client type.

    I tried to replicate the error with Nest 7.12.1 but am not able to. There are a couple of problems with the regular expression however;

    1. The enclosing / and g modifier are not valid or supported by the regular expression engine and can be omitted.
    2. Since the regex value is a verbatim string, the \\ is the literal \\\\ when sent in the query, which results in an error:
    // Request
    POST http://localhost:9200/default_index/_search?pretty=true&typed_keys=true 
    {
      "query": {
        "regexp": {
          "FilePath": {
            "value": "/([A-Z a-z:0-9_.\\\\-]+)/g"
          }
        }
      }
    }
    
    // Response
    Status: 400
    
    {
      "error" : {
        "root_cause" : [
          {
            "type" : "query_shard_exception",
            "reason" : "failed to create query: expected ']' at position 24",
            "index_uuid" : "83Lsg5kRR32c6iSK-1L5rw",
            "index" : "default_index"
          }
        ],
        "type" : "search_phase_execution_exception",
        "reason" : "all shards failed",
        "phase" : "query",
        "grouped" : true,
        "failed_shards" : [
          {
            "shard" : 0,
            "index" : "default_index",
            "node" : "1k1iMRXORXSEKvOH4Iz46Q",
            "reason" : {
              "type" : "query_shard_exception",
              "reason" : "failed to create query: expected ']' at position 24",
              "index_uuid" : "83Lsg5kRR32c6iSK-1L5rw",
              "index" : "default_index",
              "caused_by" : {
                "type" : "illegal_argument_exception",
                "reason" : "expected ']' at position 24"
              }
            }
          }
        ]
      },
      "status" : 400
    }
    
    1. I think the . also needs to be escaped (otherwise is a match for any character).

    Putting all together, here's a working example

    private static void Main()
    {
        var defaultIndex = "default_index";
        var pool = new SingleNodeConnectionPool(new Uri($"http://localhost:9200"));
        var settings = new ConnectionSettings(pool, JsonNetSerializer.Default)
            .DefaultIndex(defaultIndex)
            .DefaultFieldNameInferrer(p => p)
            // The following settings are useful during development but come
            // with overhead and so likely don't want them in production.
            .DisableDirectStreaming()
            .PrettyJson()
            .OnRequestCompleted(callDetails =>
            {
                // Added this so that you can see the requests/responses to and
                // from Elasticsearch.
    
                if (callDetails.RequestBodyInBytes != null)
                {
                    var serializer = new JsonSerializer();
                    var jObjects = new List<JObject>();
                    using (var sr = new StringReader(Encoding.UTF8.GetString(callDetails.RequestBodyInBytes)))
                    using (var jsonTextReader = new JsonTextReader(sr))
                    {
                        jsonTextReader.SupportMultipleContent = true;
                        while (jsonTextReader.Read())
                            jObjects.Add((JObject)JObject.ReadFrom(jsonTextReader));
                    }
                    
                    var formatting =  jObjects.Count == 1 
                        ? Newtonsoft.Json.Formatting.Indented 
                        : Newtonsoft.Json.Formatting.None;
                    var json = string.Join("\n", jObjects.Select(j => j.ToString(formatting)));
                    
                    Console.WriteLine($"{callDetails.HttpMethod} {callDetails.Uri} \n{json}");
                }
                else
                {
                    Console.WriteLine($"{callDetails.HttpMethod} {callDetails.Uri}");
                }
        
                Console.WriteLine();
        
                if (callDetails.ResponseBodyInBytes != null)
                {
                    Console.WriteLine($"Status: {callDetails.HttpStatusCode}\n" +
                             $"{Encoding.UTF8.GetString(callDetails.ResponseBodyInBytes)}\n" +
                             $"{new string('-', 30)}\n");
                }
                else
                {
                    Console.WriteLine($"Status: {callDetails.HttpStatusCode}\n" +
                             $"{new string('-', 30)}\n");
                }
            });
            
        var client = new ElasticClient(settings);
    
        if (client.Indices.Exists(defaultIndex).Exists)
            client.Indices.Delete(defaultIndex);
        
        client.Bulk(b => b
            .Index(defaultIndex)
            .IndexMany(new [] {
                new FileData { FilePath = "Foo.jpg" },
                new FileData { FilePath = "^$%*#@" },
            })
            .Refresh(Refresh.WaitFor)
        );
        
        var searchResponse = client.Search<FileData>(s => s
            .Query(q => q
                .Regexp(r => r
                    .Field(p => p.FilePath)
                    .Value(@"([A-Z a-z:0-9_\.\-]+)")
                )
            )
        );
    }
    
    public class FileData 
    {
        public string FilePath {get;set;}
    }
    

    which finds the document with file path Foo.jpg