subobjects

edit

When indexing a document or updating mappings, Elasticsearch accepts fields that contain dots in their names, which get expanded to their corresponding object structure. For instance, the field metrics.time.max is mapped as a max leaf field with a parent time object, belonging to its parent metrics object.

The described default behaviour is reasonable for most scenarios, but causes problems in certain situations where for instance a field metrics.time holds a value too, which is common when indexing metrics data. A document holding a value for both metrics.time.max and metrics.time gets rejected given that time would need to be a leaf field to hold a value as well as an object to hold the max sub-field.

The subobjects setting, which can be applied only to the top-level mapping definition and to object fields, disables the ability for an object to hold further subobjects and makes it possible to store documents where field names contain dots and share common prefixes. From the example above, if the object container metrics has subobjects set to false, it can hold values for both time and time.max directly without the need for any intermediate object, as dots in field names are preserved.

resp = client.indices.create(
    index="my-index-000001",
    mappings={
        "properties": {
            "metrics": {
                "type": "object",
                "subobjects": False,
                "properties": {
                    "time": {
                        "type": "long"
                    },
                    "time.min": {
                        "type": "long"
                    },
                    "time.max": {
                        "type": "long"
                    }
                }
            }
        }
    },
)
print(resp)

resp1 = client.index(
    index="my-index-000001",
    id="metric_1",
    document={
        "metrics.time": 100,
        "metrics.time.min": 10,
        "metrics.time.max": 900
    },
)
print(resp1)

resp2 = client.index(
    index="my-index-000001",
    id="metric_2",
    document={
        "metrics": {
            "time": 100,
            "time.min": 10,
            "time.max": 900
        }
    },
)
print(resp2)

resp3 = client.indices.get_mapping(
    index="my-index-000001",
)
print(resp3)
response = client.indices.create(
  index: 'my-index-000001',
  body: {
    mappings: {
      properties: {
        metrics: {
          type: 'object',
          subobjects: false,
          properties: {
            time: {
              type: 'long'
            },
            'time.min' => {
              type: 'long'
            },
            'time.max' => {
              type: 'long'
            }
          }
        }
      }
    }
  }
)
puts response

response = client.index(
  index: 'my-index-000001',
  id: 'metric_1',
  body: {
    'metrics.time' => 100,
    'metrics.time.min' => 10,
    'metrics.time.max' => 900
  }
)
puts response

response = client.index(
  index: 'my-index-000001',
  id: 'metric_2',
  body: {
    metrics: {
      time: 100,
      'time.min' => 10,
      'time.max' => 900
    }
  }
)
puts response

response = client.indices.get_mapping(
  index: 'my-index-000001'
)
puts response
const response = await client.indices.create({
  index: "my-index-000001",
  mappings: {
    properties: {
      metrics: {
        type: "object",
        subobjects: false,
        properties: {
          time: {
            type: "long",
          },
          "time.min": {
            type: "long",
          },
          "time.max": {
            type: "long",
          },
        },
      },
    },
  },
});
console.log(response);

const response1 = await client.index({
  index: "my-index-000001",
  id: "metric_1",
  document: {
    "metrics.time": 100,
    "metrics.time.min": 10,
    "metrics.time.max": 900,
  },
});
console.log(response1);

const response2 = await client.index({
  index: "my-index-000001",
  id: "metric_2",
  document: {
    metrics: {
      time: 100,
      "time.min": 10,
      "time.max": 900,
    },
  },
});
console.log(response2);

const response3 = await client.indices.getMapping({
  index: "my-index-000001",
});
console.log(response3);
PUT my-index-000001
{
  "mappings": {
    "properties": {
      "metrics": {
        "type":  "object",
        "subobjects": false, 
        "properties": {
          "time": { "type": "long" },
          "time.min": { "type": "long" },
          "time.max": { "type": "long" }
        }
      }
    }
  }
}

PUT my-index-000001/_doc/metric_1
{
  "metrics.time" : 100, 
  "metrics.time.min" : 10,
  "metrics.time.max" : 900
}

PUT my-index-000001/_doc/metric_2
{
  "metrics" : {
    "time" : 100, 
    "time.min" : 10,
    "time.max" : 900
  }
}

GET my-index-000001/_mapping
{
  "my-index-000001" : {
    "mappings" : {
      "properties" : {
        "metrics" : {
          "subobjects" : false,
          "properties" : {
            "time" : {
              "type" : "long"
            },
            "time.min" : { 
              "type" : "long"
            },
            "time.max" : {
              "type" : "long"
            }
          }
        }
      }
    }
  }
}

The metrics field cannot hold other objects.

Sample document holding flat paths

Sample document holding an object (configured to not hold subobjects) and its leaf sub-fields

The resulting mapping where dots in field names were preserved

The entire mapping may be configured to not support subobjects as well, in which case the document can only ever hold leaf sub-fields:

resp = client.indices.create(
    index="my-index-000001",
    mappings={
        "subobjects": False
    },
)
print(resp)

resp1 = client.index(
    index="my-index-000001",
    id="metric_1",
    document={
        "time": "100ms",
        "time.min": "10ms",
        "time.max": "900ms"
    },
)
print(resp1)
response = client.indices.create(
  index: 'my-index-000001',
  body: {
    mappings: {
      subobjects: false
    }
  }
)
puts response

response = client.index(
  index: 'my-index-000001',
  id: 'metric_1',
  body: {
    time: '100ms',
    'time.min' => '10ms',
    'time.max' => '900ms'
  }
)
puts response
const response = await client.indices.create({
  index: "my-index-000001",
  mappings: {
    subobjects: false,
  },
});
console.log(response);

const response1 = await client.index({
  index: "my-index-000001",
  id: "metric_1",
  document: {
    time: "100ms",
    "time.min": "10ms",
    "time.max": "900ms",
  },
});
console.log(response1);
PUT my-index-000001
{
  "mappings": {
    "subobjects": false 
  }
}

PUT my-index-000001/_doc/metric_1
{
  "time" : "100ms", 
  "time.min" : "10ms",
  "time.max" : "900ms"
}

The entire mapping is configured to not support objects.

The document does not support objects

The subobjects setting for existing fields and the top-level mapping definition cannot be updated.

Auto-flattening object mappings

edit

It is generally recommended to define the properties of an object that is configured with subobjects: false with dotted field names (as shown in the first example). However, it is also possible to define these properties as sub-objects in the mappings. In that case, the mapping will be automatically flattened before it is stored. This makes it easier to re-use existing mappings without having to re-write them.

Note that auto-flattening will not work when certain mapping parameters are set on object mappings that are defined under an object configured with subobjects: false:

  • The enabled mapping parameter must not be false.
  • The dynamic mapping parameter must not contradict the implicit or explicit value of the parent. For example, when dynamic is set to false in the root of the mapping, object mappers that set dynamic to true can’t be auto-flattened.
  • The subobjects mapping parameter must not be set to true explicitly.
resp = client.indices.create(
    index="my-index-000002",
    mappings={
        "properties": {
            "metrics": {
                "subobjects": False,
                "properties": {
                    "time": {
                        "type": "object",
                        "properties": {
                            "min": {
                                "type": "long"
                            },
                            "max": {
                                "type": "long"
                            }
                        }
                    }
                }
            }
        }
    },
)
print(resp)

resp1 = client.indices.get_mapping(
    index="my-index-000002",
)
print(resp1)
response = client.indices.create(
  index: 'my-index-000002',
  body: {
    mappings: {
      properties: {
        metrics: {
          subobjects: false,
          properties: {
            time: {
              type: 'object',
              properties: {
                min: {
                  type: 'long'
                },
                max: {
                  type: 'long'
                }
              }
            }
          }
        }
      }
    }
  }
)
puts response

response = client.indices.get_mapping(
  index: 'my-index-000002'
)
puts response
const response = await client.indices.create({
  index: "my-index-000002",
  mappings: {
    properties: {
      metrics: {
        subobjects: false,
        properties: {
          time: {
            type: "object",
            properties: {
              min: {
                type: "long",
              },
              max: {
                type: "long",
              },
            },
          },
        },
      },
    },
  },
});
console.log(response);

const response1 = await client.indices.getMapping({
  index: "my-index-000002",
});
console.log(response1);
PUT my-index-000002
{
  "mappings": {
    "properties": {
      "metrics": {
        "subobjects": false,
        "properties": {
          "time": {
            "type": "object", 
            "properties": {
              "min": { "type": "long" }, 
              "max": { "type": "long" }
            }
          }
        }
      }
    }
  }
}
GET my-index-000002/_mapping
{
  "my-index-000002" : {
    "mappings" : {
      "properties" : {
        "metrics" : {
          "subobjects" : false,
          "properties" : {
            "time.min" : { 
              "type" : "long"
            },
            "time.max" : {
              "type" : "long"
            }
          }
        }
      }
    }
  }
}

The metrics object can contain further object mappings that will be auto-flattened. Object mappings at this level must not set certain mapping parameters as explained above.

This field will be auto-flattened to "time.min" before the mapping is stored.

The auto-flattened "time.min" field can be inspected by looking at the index mapping.