Search code examples
.netelasticsearchnest

Is there a way to deserialize script field values on a search result without an extra loop?


I need to use Script Fields in my .net code, in order to run some query logic on Elasticsearch. This doc says that:

Script fields can be accessed on the response using .Fields, similarly to stored fields.

In order to retrieve the Script Fields results, it looks like I need to loop over the results (as suggested here or here). I want to avoid that loop, and somehow get the data within my call to NEST's client.search(), which already populates a list of results. Is that possible?

Here's my call:

    public async Task<List<SomeObj>> GetSomeDocsFromEs()
    {
        var result = await client.SearchAsync<SomeObj>(s => s
            .Index(...)
            .Query(...)
            .Size(...)
            .From(...)
            .Sort(...)
            .ScriptFields(sf => sf
                     .ScriptField("StoredField1", sc => sc
                        .Source(some Painless script)
                     )
                     .ScriptField("StoredField2", sc => sc
                        .Source(some Painless script)
                     )
                     .ScriptField("StoredField3", sc => sc
                        .Source(some Painless script)
                      )
                )
            .StoredFields(sf => sf
                    .Fields(
                            f => f.StoredField1,
                            f => f.StoredField2,
                            f => f.StoredField3
                    )
                )
            .Source(so => so
                .Includes(i => i
                    .Fields(f => f.SkuEntry.Field1,
                            f => f.SkuEntry.Field2,
                            f => f.SkuEntry.Field3
                    )
                )
            )
        );

        return result.Documents.ToList();
    }

Solution

  • I want to avoid that loop, and somehow get the data within my call to NEST's client.search(), which already populates a list of results. Is that possible?

    Script fields are returned for each hit in the hits array, so there's no way to avoid looping over hits.

    result.Documents in the example maps to the _source field for each document, which is the original JSON object sent to Elasticsearch and indexed. It's a convenient shorthand for result.Hits.Select(h => h.Source).ToList().

    Script fields are not part of the _source document, they are returned in a separate field for each hit. result.Fields is a convenient shorthand for result.Hits.Select(h => h.Fields).ToList().

    As an example, given the following query

    var searchResponse = client.Search<Project>(s => s
        .ScriptFields(sf => sf
            .ScriptField("test1", sc => sc
                .Source("doc['numberOfCommits'].value * 2")
            )
            .ScriptField("test2", sc => sc
                .Source("doc['numberOfCommits'].value * params.factor")
                .Params(p => p
                    .Add("factor", 2.0)
                )
            )
        )
    );
    

    which sends the following request

    {
      "script_fields": {
        "test1": {
          "script": {
            "source": "doc['numberOfCommits'].value * 2"
          }
        },
        "test2": {
          "script": {
            "source": "doc['numberOfCommits'].value * params.factor",
            "params": {
              "factor": 2.0
            }
          }
        }
      }
    }
    

    The JSON response is

    {
      "took" : 26,
      "timed_out" : false,
      "_shards" : {
        "total" : 2,
        "successful" : 2,
        "skipped" : 0,
        "failed" : 0
      },
      "hits" : {
        "total" : {
          "value" : 1100,
          "relation" : "eq"
        },
        "max_score" : 1.0,
        "hits" : [
          {
            "_index" : "project",
            "_type" : "_doc",
            "_id" : "Konopelski Inc2032",
            "_score" : 1.0,
            "_routing" : "Konopelski Inc2032",
            "fields" : {
              "test2" : [
                308.0
              ],
              "test1" : [
                308
              ]
            }
          },
          {
            "_index" : "project",
            "_type" : "_doc",
            "_id" : "Feest Group2047",
            "_score" : 1.0,
            "_routing" : "Feest Group2047",
            "fields" : {
              "test2" : [
                1986.0
              ],
              "test1" : [
                1986
              ]
            }
          }
        ]
      }
    }
    
    

    To iterate over the script fields of each hit in the response

    foreach (var fields in response.Fields)
    {
        // do something with test1 and test2 values
        var test1 = fields.Value<int>("test1");
        var test2 = fields.Value<double>("test2");
    }