Handling Nested Fields in Conditionals

Source documents often contain nested fields. Care should be taken to avoid NullPointerExceptions if the parent object does not exist in the document. For example ctx.a.b.c can throw an NullPointerExceptions if the source document does not have top level a object, or a second level b object.

To help protect against NullPointerExceptions, null safe operations should be used. Fortunately, Painless makes null safe operations easy with the ?. operator.

PUT _ingest/pipeline/drop_guests_network
{
  "processors": [
    {
      "drop": {
        "if": "ctx.network?.name == 'Guest'"
      }
    }
  ]
}

The following document will get dropped correctly:

POST test/_doc/1?pipeline=drop_guests_network
{
  "network": {
    "name": "Guest"
  }
}

Thanks to the ?. operator the following document will not throw an error. If the pipeline used a . the following document would throw a NullPointerException since the network object is not part of the source document.

POST test/_doc/2?pipeline=drop_guests_network
{
  "foo" : "bar"
}

The source document can also use dot delimited fields to represent nested fields.

For example instead the source document defining the fields nested:

{
  "network": {
    "name": "Guest"
  }
}

The source document may have the nested fields flattened as such:

{
  "network.name": "Guest"
}

If this is the case, use the Dot Expand Processor so that the nested fields may be used in a conditional.

PUT _ingest/pipeline/drop_guests_network
{
  "processors": [
    {
      "dot_expander": {
        "field": "network.name"
      }
    },
    {
      "drop": {
        "if": "ctx.network?.name == 'Guest'"
      }
    }
  ]
}

Now the following input document can be used with a conditional in the pipeline.

POST test/_doc/3?pipeline=drop_guests_network
{
  "network.name": "Guest"
}

The ?. operators works well for use in the if conditional because the null safe operator returns null if the object is null and == is null safe (as well as many other painless operators).

However, calling a method such as .equalsIgnoreCase is not null safe and can result in a NullPointerException.

Some situations allow for the same functionality but done so in a null safe manner. For example: 'Guest'.equalsIgnoreCase(ctx.network?.name) is null safe because Guest is always non null, but ctx.network?.name.equalsIgnoreCase('Guest') is not null safe since ctx.network?.name can return null.

Some situations require an explicit null check. In the following example there is not null safe alternative, so an explict null check is needed.

{
  "drop": {
    "if": "ctx.network?.name != null && ctx.network.name.contains('Guest')"
  }
}