Punit Naik
Punit Naik

Reputation: 515

Elasticsearch - search for keys inside nested field value, which is a json

I needed some help with a query that I trying to execute.

I have a field called scores inside one of my indexes which stores some ai model scores for as a json. The basic structure of this field is like (showing a couple of records from the index):

[{"scores":
    {"a/b": 1.231,
     "a/c": 23.11,
     "x/a": 1232.1}},
 {"scores":
    {"a/d": 3.1}}]

And the mapping is something like:

{"scores":
  {"properties":
    {"a/b": {"type":"float"},
     "a/c": {"type":"float"},
     "a/d": {"type":"float"}}}}

The value for the field scores, which is another json will have arbitrary fields i.e. I won't know which fields are present in that json beforehand.

What I want to do is to search for the keys inside that json i.e. if I search for a/, it should return ["a/b", "a/d", "a/c"].

I know this is possible via nested query, but looks like for that I'll need to re-map my indexes, so that the field scores has a flatter data structure. However I didn't want to do that for now and just wanted to use the existing structure and execute my query.

If anyone has ideas about this, kindly throw them my way.

Upvotes: 0

Views: 715

Answers (2)

Joe - Check out my books
Joe - Check out my books

Reputation: 16895

You can use _source to limit what's retrieved:

POST indexname/_search
{
 "_source": "scores.a/*"
}

Alternatively, you could employ script_fields which do exactly the same but offer playroom for value modification too:

POST indexname/_search
{
  "script_fields": {
   "scores_prefixed_with_a": {
     "script": {
       "source": """params._source.scores.entrySet()
                        .stream()
                        .filter(e->e.getKey().startsWith(params.prefix))
                        .collect(Collectors.toMap(e->e.getKey(),e->e.getValue()))""",
       "params": {
         "prefix": "a/"
       }
     }
   }
 }
}

Upvotes: 1

0stone0
0stone0

Reputation: 43904

Use .filter() - .reduce() on Object.keys()

const data = [{"scores": {"a/b": 1.231, "a/c": 23.11, "x/a": 1232.1}}, {"scores": {"a/d": 3.1}}];

const allowed = 'a/';

const res = data.map((d) => 
    Object.keys(d.scores)
    .filter(key => key.startsWith(allowed)))
    .reduce((prev, curr) => prev.concat(curr)); 

console.log(res)

[
  "a/b",
  "a/c",
  "a/d"
]


Originial, with object instead off keys;

const data = [{"scores": {"a/b": 1.231, "a/c": 23.11, "x/a": 1232.1}}, {"scores": {"a/d": 3.1}}];

const allowed = 'a/';

const res = data.map((d) => 
  Object.keys(d.scores)
    .filter(key => key.startsWith(allowed))
    .reduce((obj, key) => { obj[key] = d.scores[key]; return obj; }, {})); 
console.log(res);

Upvotes: 0

Related Questions