除了我们提到的简单标量数据类型,JSON还有null值,数组,对象,这些Elasticsearch都是支持的。

多值域

很有可能,我们希望tag域包含多个标签。我们可以以数组的形式索引标签:

  1. { "tag": [ "search", "nosql" ]}

对于数组,没有特殊的映射需求。任何域都可以包含0,1或者多个值,就像全文域分析得到多个词条。

这暗示,数组中所有的值必须是相同数据类型的。你不能将日期和字符串混在一起。如果你通过索引数组来创建新的域,Elasticsearch会用数组中第一个值的数据类型作为这个域的类型。

NOTE

当你从Elasticsearch得到一个文档,每个数组的顺序和你当初索引文档时一样。你得到的_source域,包含与你索引的一模一样的JSON文档。

但是,数组是以多值域 索引的——可以搜索,但是无序的。在搜索的时候,你不能指定“第一个”或者“最后一个”。更确切的说,把数组想象成 装在袋子里的值。

空域

当然,数组可以为空。这相当于存在零值。事实上,在Lucene中是不能存储null 值的,所以我们认为存在null 值的域为空域。

下面三种域被认为是空的,他们将不会被索引:

"null_value":               null,
"empty_array":              [],
"array_with_null_value":    [ null ]

多层级对象

我们讨论的最后一个JSON 原生数据类是 对象—在其他语言中称为哈希,哈希map,字典或者关联数组。

内部对象经常用于嵌入一个实体或对象到其他对象中。例如,与其在tweet 文档中包含 user_name和user_id 域,我们也可以这样写:

{
    "tweet":            "Elasticsearch is very flexible",
    "user": {
        "id":           "@johnsmith",
        "gender":       "male",
        "age":          26,
        "name": {
            "full":     "John Smith",
            "first":    "John",
            "last":     "Smith"
        }
    }
}

内部对象的映射

Elasticsearch 会动态监测新的对象域并映射他们为对象,在properties属性下列出内部域:

{
  "gb": {
    "tweet": {  //1
      "properties": {
        "tweet":            { "type": "string" },
        "user": { //2
          "type":             "object",
          "properties": {
            "id":           { "type": "string" },
            "gender":       { "type": "string" },
            "age":          { "type": "long"   },
            "name":   { //2
              "type":         "object",
              "properties": {
                "full":     { "type": "string" },
                "first":    { "type": "string" },
                "last":     { "type": "string" }
              }
            }
          }
        }
      }
    }
  }
}
  1. 根对象
  2. 内部对象

user 和name 域的映射结构域 tweet 类型的相同。事实上,type映射只是一种特殊的 对象映射,我们称之为 根对象。除了他有一些文档元数据的特殊顶级域,例如_source 和 _all 域,他和其他对象一样。

内部对象是如何索引的

Lucene 不理解内部对象。Lucene 文档是由一组键值对列表组成的。为了能让Elasticsearch有效的索引内部类,他把我们的文档转化成这样:

{
    "tweet":            [elasticsearch, flexible, very],
    "user.id":          [@johnsmith],
    "user.gender":      [male],
    "user.age":         [26],
    "user.name.full":   [john, smith],
    "user.name.first":  [john],
    "user.name.last":   [smith]
}

内部域可以通过名称引用(例如,first)。为了区分同名的两个域,我们可以使用全路径(例如,user.name.first)或type名加路径(tweet.user.name.first)。

NOTE

在前面简单扁平的文档中,没有user和user.name域。Lucene索引只有标量和简单值,没有复杂数据结构。

内部对象数组

最后,考虑包含内部对象的数组是如何被索引的。假设我们有个 followers 数组:

{
    "followers": [
        { "age": 35, "name": "Mary White"},
        { "age": 26, "name": "Alex Jones"},
        { "age": 19, "name": "Lisa Smith"}
    ]
}

这个文档会像我们之前描述的那样被扁平化处理,结果如下所示:

{
    "followers.age":    [19, 26, 35],
    "followers.name":   [alex, jones, lisa, smith, mary, white]
}

{age:35} 和{name:Mary White} 之间的相关性已经丢失了,因为每个多值域只是一包无序的值,而不是有序数组。这足以让我们问,“有一个26岁的追随者?”

但是我们不能得到一个准确的答案:“是否有一个26岁名字叫做Alex Jones 的追随者?”

相关内部对象被称为nested 对象,可以回答上面的查询,我们稍后会在嵌套对象 中介绍他。