加载 JSON

Web API 允许您访问并将来自不同来源的数据集成到您的图数据库中。它们中的大多数以 JSON 格式提供数据。

Load JSON 过程从 URL 或映射中检索数据,并将其转换为 Cypher 可消费的 MAP 值。Cypher 支持使用点语法、切片、UNWIND 等解构嵌套文档,因此可以轻松地将嵌套数据转换为图。

同样支持流中包含多个 JSON 对象(JSONL,JSON Lines)的源,例如 Twitter 流格式 或 Yelp Kaggle 数据集,

过程概述

下表描述了可用的过程:

限定名称 类型

apoc.load.json
apoc.load.json(urlOrKeyOrBinary ANY, path STRING, config MAP<STRING, ANY>) - 如果给定的 JSON 文件是 LIST<ANY>,则将 JSON 文件作为值流导入。如果给定的 JSON 文件是 MAP,此过程则导入单个值。

过程

apoc.load.jsonParams
apoc.load.jsonParams(urlOrKeyOrBinary ANY, headers MAP<STRING, ANY>, payload STRING, path STRING, config MAP<STRING, ANY>) - 从 URL(例如 web-API)加载 JSON 文档。如果给定的 JSON 文档是 LIST<ANY>,则作为值流导入。如果给定的 JSON 文件是 MAP,此过程则导入单个值。

过程 在 Cypher 5 中已弃用 在 Cypher 25 中已移除

apoc.load.jsonArray
apoc.load.jsonArray(url STRING, path STRING, config MAP<STRING, ANY>) - 从 JSON URL(例如 web-API)加载数组,然后将给定的 JSON 文件作为值流导入。

过程

apoc.import.json
apoc.import.json(urlOrBinaryFile ANY, config MAP<STRING, ANY>) - 从提供的 JSON 文件中导入图形。

过程

apoc.load.json

此过程接收文件或 HTTP URL,并将 JSON 解析为映射数据结构。

签名

apoc.load.json(urlOrKeyOrBinary :: ANY, path = :: STRING, config = {} :: MAP) :: (value :: MAP)

它支持以下配置参数

表 1. 配置
名称 (name) type 默认 description(描述)

failOnError

布尔值 (BOOLEAN)

true

解析 JSON 时如果遇到错误则失败

binary

枚举 [NONE, BYTES, GZIP, BZIP2, DEFLATE, BLOCK_LZ4, FRAMED_SNAPPY]

null

如果不为 null,则允许接收二进制数据而不是文件名/URL 作为第一个参数。类似于 二进制文件示例

charset

java.nio.charset.Charset

UTF_8

可选的字符集,当 binary 配置不为 null 且文件为字符串时使用

apoc.load.jsonParams

该过程的 'Params' 名称是指在连接到给定的 URL 时 HTTP 请求的 headerspayload 被参数化,而不是指 Neo4j 的 $ 参数。

此过程接收文件或 HTTP URL,并将 JSON 解析为映射数据结构。它是 apoc.load.json 的更可配置版本,能够处理需要 HTTP 标头或 JSON 有效负载的端点。

签名

apoc.load.jsonParams(urlOrKeyOrBinary :: ANY, headers :: MAP, payload :: STRING, path = :: STRING, config = {} :: MAP) :: (value :: MAP)

它支持以下配置参数

表 2. 配置
名称 (name) type 默认 description(描述)

failOnError

布尔值 (BOOLEAN)

true

解析 JSON 时如果遇到错误则失败

apoc.load.jsonArray

此过程接收包含 JSON 数组的文件或 HTTP URL,并将其解析为映射流。

签名

apoc.load.jsonArray(url :: STRING, path = :: STRING, config = {} :: MAP) :: (value :: ANY)

apoc.import.json

此过程可用于导入由 Export JSON 过程创建的 JSON 文件,这些文件使用配置参数 jsonFormat: 'JSON_LINES'(默认配置)导出。

签名

apoc.import.json(urlOrBinaryFile :: ANY, config = {} :: MAP) :: (file :: STRING, source :: STRING, format :: STRING, nodes :: INTEGER, relationships :: INTEGER, properties :: INTEGER, time :: INTEGER, rows :: INTEGER, batchSize :: INTEGER, batches :: INTEGER, done :: BOOLEAN, data :: STRING)

它支持以下配置参数

表 3. 配置参数
名称 (name) type 默认 description(描述)

unwindBatchSize

INTEGER(整数)

5000

unwind 的批处理大小

txBatchSize

INTEGER(整数)

5000

事务的批处理大小

importIdName

STRING

neo4jImportId

要填充 JSON 中存在的 "id" 字段的属性名称。例如,一行 {"type":"node", "labels":["Language"], "id":"10"},设置 importIdName:`foo`,将创建一个节点 (:User {foo: "10"})

nodePropertyMappings

MAP

{}

自定义 Neo4j 类型(点、日期)的映射:标签/属性名称/属性类型。

例如:{ User: { born: 'Point', dateOfBirth: 'Datetime' } }

relPropertyMappings

MAP

{}

自定义 Neo4j 类型(点、日期)的映射:关系类型/属性名称/属性类型。

例如:{ KNOWS: { since: 'Datetime' } }

nodePropertyMappingsrelPropertyMappings 支持以下 Neo4j 类型

PointLocaldateLocaltimeLocaldatetimeDurationoffsettimeZoneddatetime

从文件导入

默认情况下,禁止从文件系统导入。我们可以通过在 apoc.conf 中设置以下属性来启用它

apoc.conf
apoc.import.file.enabled=true

如果我们尝试在未先设置此属性的情况下使用任何导入过程,将会收到以下错误消息

Failed to invoke procedure: Caused by: java.lang.RuntimeException: Import from files not enabled, please set apoc.import.file.enabled=true in your apoc.conf

导入文件从 import 目录读取,该目录由 server.directories.import 属性定义。这意味着我们提供的任何文件路径都是相对于此目录的。如果我们尝试从绝对路径(例如 /tmp/filename)读取,将会收到类似于以下的错误消息

Failed to invoke procedure: Caused by: java.lang.RuntimeException: Can’t read url or key file:/path/to/neo4j/import/tmp/filename as json: /path/to/neo4j//import/tmp/filename (No such file or directory)

我们可以通过在 apoc.conf 中设置以下属性来启用从文件系统任意位置读取文件

apoc.conf
apoc.import.file.use_neo4j_config=false

Neo4j 现在可以从文件系统的任何位置读取,因此在设置此属性之前,请确保这是您的意图。

JSON-Path

使用 JSON 路径为您提供了一种简洁的方法来读取和处理来自嵌套 JSON 结构的子文档和子值。如果您需要跳过展开更高级别的父对象以访问更多嵌套数据,或者需要操作这些子结构中的值,这一点特别有用。

无需传入大型 JSON 文件并使用 Cypher 来展开每个对象并访问所需内容,您可以传入文件并提供所需子结构的 JSON 路径,从而缩短嵌套 JSON 的语句。JSON 路径格式遵循 Jayway 的 Java 实现,即 Stefan Gössner 的 JSONPath,为路径提供了一致的语法。

许多 apoc.convert.*Json* 过程和函数,以及 apoc.load.json 过程,也接受 JSON 路径作为其最后一个参数。请注意,这些函数旨在流式传输数组(值或对象)和映射,而不是单个值。如果将包含单个值的项目指定为路径,则该函数必须尝试对其进行包装,并且不会返回预期的结果。

还有 apoc.json.path(json,path) 函数,它接收一个 JSON 字符串(不是映射或列表)并从作为第二个参数提供的 json 路径中检索值。注意:如果 JSON 尚未采用字符串格式,您可以使用 apoc.convert.toJson 函数将其转换。

更多示例可以在上面提供的链接中找到,但让我们看看 JSON 路径语法的一个例子。下面显示的语法从 Neo4j 问题的 StackOverflow API 中提取 items 数组,并从项目列表中的第一个对象中检索 tags 数组。

$.items[0].tags

指定 JSON 路径的所有运算符和选项包含在下表中。

表 4. 运算符
运算符 描述 示例

$

要查询的根元素。这是所有路径表达式的起点。

$ - 检索父对象中的所有数据

@

当前由过滤谓词处理的节点。

$.items[?(@.answer_count > 0)] - 如果 item 的 answer_count 大于 0,则检索它

*

通配符。可以在需要名称或数字的任何地方使用。

$.items[\*] - 检索数组中的所有项目

..

深度扫描。可以在需要名称的任何地方使用。

$..tags[\*] - 查找名为 tags 的子结构并提取所有值

.<name>

点表示法的子级

$.items[0:1].owner.user_id - 检索第一个项目的 user_id(在 owner 对象中)

[<number> (,<number>)]

数组索引

$.items[0,-1] - 检索数组中的第一个和最后一个项目

[start:end]

数组切片运算符

$.items[0:5] - 检索数组中前五个项目

[?(<expression>)]

过滤表达式。表达式必须计算为布尔值。

$.items[?(@.is_answered == true)] - 检索 is_answered 字段为 true 的项目

此外,我们可以自定义 Json 路径选项,添加配置 {pathOptions: LIST OF STRINGS},其中字符串基于 Enum<Option>。默认值为 ["SUPPRESS_EXCEPTIONS", "DEFAULT_PATH_LEAF_TO_NULL"]。请注意,我们也可以插入 [],即“无选项”。所以对于以下 json

{ "columns": {
      "col2": {
        "_id": "772col2"
      }
    }
}

我们可以执行(使用默认 pathOptions

CALL apoc.load.json($url, '$..columns');
表 5. 结果

[ {"col2": { "_id": "772col2" }}, null, null ]

或者,使用自定义路径选项

CALL apoc.load.json($url, '$..columns', ['ALWAYS_RETURN_LIST']);
表 6. 结果
输出

[ {"col2": { "_id": "772col2" }} ]

示例

以下部分包含显示如何从各种 JSON 源导入数据的示例。

从本地文件导入

person.json 包含一个表示某人及其子女的 JSON 文档。

person.json
{
 "name":"Michael",
 "age": 41,
 "children": ["Selina","Rana","Selma"]
}

我们将此文件放入 Neo4j 实例的 import 目录中。现在让我们编写一个使用 apoc.load.json 过程来浏览此文件的查询。

以下查询处理 person.json 并将内容作为 Cypher 数据结构返回
CALL apoc.load.json("file:///person.json")
YIELD value
RETURN value;
表 7. 结果

{name: "Michael", children: ["Selina", "Rana", "Selma"], age: 41}

我们得到的映射与 JSON 文档几乎相同。我们现在可以扩展该查询,根据此 JSON 文件创建一个图。我们将为 Michael 和他的每个孩子创建一个 Person 节点,并为每个孩子到 Michael 节点创建一个 CHILD_OF 关系。

以下代码根据 person.json 创建一个图
CALL apoc.load.json("file:///person.json")
YIELD value
MERGE (p:Person {name: value.name})
SET p.age = value.age
WITH p, value
UNWIND value.children AS child
MERGE (c:Person {name: child})
MERGE (c)-[:CHILD_OF]->(p);

下方的 Neo4j Browser 可视化显示了导入的图

apoc.load.json.local.file

您可以使用 failOnError 配置来处理 url 或 json 不正确的情况。例如,在 apoc.when 过程的帮助下,当 url 不正确时,您可以返回 nothingToDo 作为结果

CALL apoc.load.json("MY_JSON_URL", null, {failOnError:false})
YIELD value
WITH collect(value) as values
call apoc.do.when(values = [], "return 'nothingToDo' as result", "return values as result", {values: values})
YIELD value
UNWIND value["result"] as result
RETURN result

从 StackOverflow API 导入

apoc.load.json 允许从任何文件或 URL 加载 JSON 数据。如果结果是 JSON 对象,则作为单个映射返回。如果结果是数组,则将其转换为映射流。

StackOverflow 提供了多个 API,包括一个用于检索最近问题和答案的 API。用于检索 neo4j 标签 的最后问题和答案的 URL 是

https://api.stackexchange.com/2.2/questions?pagesize=100&order=desc&sort=creation&tagged=neo4j&site=stackoverflow&filter=!5-i6Zw8Y)4W7vpy91PMYsKM-k9yzEsSC1_Uxlf

由于这是一个相当长的 URL 字符串,我们可以通过在 conf/apoc.conf 中配置别名来简化语法

apoc.conf
apoc.json.myJson.url=https://api.stackexchange.com/2.2/questions?pagesize=100&order=desc&sort=creation&tagged=neo4j&site=stackoverflow&filter=!5-i6Zw8Y)4W7vpy91PMYsKM-k9yzEsSC1_Uxlf

apoc.json.<alias>.url= 中的第三个值有效地定义了在 apoc.load.json('<alias>',…​. 中使用的变量。这样,下面的海量 JSON url 字符串可以别名为更短的字符串。

原始调用,带有完整的 json url 字符串
CALL apoc.load.json('https://api.stackexchange.com/2.2/questions?pagesize=100&order=desc&sort=creation&tagged=neo4j&site=stackoverflow&filter=!5-i6Zw8Y)4W7vpy91PMYsKM-k9yzEsSC1_Uxlf')
新调用,带有别名字符串,且在 apoc.conf 中有完整字符串
CALL apoc.load.json('myJson')

让我们内省一下从这个端点返回的数据。

以下查询查找 StackOverflow 上带有 neo4j 标签的 5 个最新问题
WITH "https://api.stackexchange.com/2.2/questions?pagesize=100&order=desc&sort=creation&tagged=neo4j&site=stackoverflow&filter=!5-i6Zw8Y)4W7vpy91PMYsKM-k9yzEsSC1_Uxlf" AS url
CALL apoc.load.json(url) YIELD value
UNWIND value.items AS item
RETURN item.title, item.owner, item.creation_date, keys(item)
LIMIT 5;
表 8. 结果
item.title item.owner item.creation_date keys(item)

"用于获取自相关节点的 Cypher 模式"

{profile_image: "https://lh3.googleusercontent.com/-1FWbhuaEBiQ/AAAAAAAAAAI/AAAAAAAAAIA/tLM_mEb-8MY/photo.jpg?sz=128", user_type: "registered", user_id: 5730203, link: "https://stackoverflow.com/users/5730203/asif-ali", reputation: 1148, display_name: "Asif Ali", accept_rate: 90}

1586944991

["owner", "comment_count", "link", "last_activity_date", "creation_date", "answer_count", "title", "question_id", "tags", "share_link", "score", "down_vote_count", "body_markdown", "favorite_count", "is_answered", "delete_vote_count", "close_vote_count", "view_count", "up_vote_count"]

"将 .NET 客户端连接到 Neo4j Desktop 版本 4 的问题"

{profile_image: "https://www.gravatar.com/avatar/a3fac35d600d1d462d8fc12f3926074c?s=128&d=identicon&r=PG&f=1", user_type: "registered", user_id: 2853912, link: "https://stackoverflow.com/users/2853912/user2853912", reputation: 21, display_name: "user2853912"}

1586938954

["owner", "comment_count", "link", "last_activity_date", "creation_date", "answer_count", "title", "question_id", "tags", "share_link", "score", "down_vote_count", "body_markdown", "favorite_count", "is_answered", "delete_vote_count", "close_vote_count", "view_count", "up_vote_count"]

"Neo4j 使用哪种图算法?"

{profile_image: "https://www.gravatar.com/avatar/736024b862a229111d4b3119875753b0?s=128&d=identicon&r=PG&f=1", user_type: "registered", user_id: 4402081, link: "https://stackoverflow.com/users/4402081/mariappan", reputation: 7, display_name: "Mariappan"}

1586901300

["owner", "comment_count", "answers", "link", "last_activity_date", "creation_date", "answer_count", "title", "question_id", "tags", "share_link", "score", "down_vote_count", "body_markdown", "favorite_count", "is_answered", "delete_vote_count", "close_vote_count", "view_count", "up_vote_count"]

"导入 json 文件到 Neo4j"

{profile_image: "https://lh3.googleusercontent.com/-PWDC85Kp2ig/AAAAAAAAAAI/AAAAAAAAAAA/AB6qoq3nhmVZl-_0VDKESOG5MsyHvXnw_A/mo/photo.jpg?sz=128", user_type: "registered", user_id: 9964138, link: "https://stackoverflow.com/users/9964138/jo%c3%a3o-costa", reputation: 23, display_name: "João Costa"}

1586897574

["owner", "comment_count", "answers", "link", "last_activity_date", "creation_date", "answer_count", "title", "question_id", "tags", "share_link", "score", "down_vote_count", "body_markdown", "favorite_count", "is_answered", "delete_vote_count", "close_vote_count", "view_count", "up_vote_count"]

"Neo4j 图算法与图数据科学之间的区别"

{profile_image: "https://i.stack.imgur.com/2rLPZ.jpg?s=128&g=1", user_type: "registered", user_id: 3297954, link: "https://stackoverflow.com/users/3297954/rotten", reputation: 1295, display_name: "rotten", accept_rate: 75}

1586872077

["owner", "comment_count", "answers", "link", "last_activity_date", "creation_date", "answer_count", "title", "question_id", "tags", "share_link", "score", "down_vote_count", "body_markdown", "favorite_count", "is_answered", "delete_vote_count", "close_vote_count", "view_count", "up_vote_count"]

现在让我们根据这些实体创建一个 Neo4j 图。

以下代码根据 StackOverflow API 中的数据创建一个图
WITH "https://api.stackexchange.com/2.2/questions?pagesize=100&order=desc&sort=creation&tagged=neo4j&site=stackoverflow&filter=!5-i6Zw8Y)4W7vpy91PMYsKM-k9yzEsSC1_Uxlf" AS url
CALL apoc.load.json(url) YIELD value
UNWIND value.items AS q
MERGE (question:Question {id:q.question_id})
ON CREATE SET question.title = q.title,
              question.share_link = q.share_link,
              question.favorite_count = q.favorite_count

FOREACH (tagName IN q.tags | MERGE (tag:Tag {name:tagName}) MERGE (question)-[:TAGGED]->(tag))
FOREACH (a IN q.answers |
   MERGE (question)<-[:ANSWERS]-(answer:Answer {id:a.answer_id})
   MERGE (answerer:User {id:a.owner.user_id}) ON CREATE SET answerer.display_name = a.owner.display_name
   MERGE (answer)<-[:PROVIDED]-(answerer)
)

WITH * WHERE NOT q.owner.user_id IS NULL
MERGE (owner:User {id:q.owner.user_id}) ON CREATE SET owner.display_name = q.owner.display_name
MERGE (owner)-[:ASKED]->(question)

下方的 Neo4j Browser 可视化显示了导入的图

apoc load json so

使用 JSON 路径并从 StackOverflow API 导入

我们可以使用 JSON 路径语法缩小我们要筛选和导入的数据范围。这将允许我们指定要导入的子结构并忽略其余数据。对于此示例,我们只想导入答案以及发布这些答案的成员。

使用 JSON 路径查找 StackOverflow 答案(仅检索 5 个样本)
WITH "https://api.stackexchange.com/2.2/questions?pagesize=100&order=desc&sort=creation&tagged=neo4j&site=stackoverflow&filter=!5-i6Zw8Y)4W7vpy91PMYsKM-k9yzEsSC1_Uxlf" AS url
CALL apoc.load.json(url,'$.items[?(@.answer_count>0)].answers[*]') YIELD value
RETURN value LIMIT 5;

请注意,我们只查看答案数大于 0 的 StackOverflow 问题。这意味着我们只传递带有答案的问题 JSON 对象,因为其余部分与我们的用例无关。考虑到这一点,让我们用此语句导入它们

WITH "https://api.stackexchange.com/2.2/questions?pagesize=100&order=desc&sort=creation&tagged=neo4j&site=stackoverflow&filter=!5-i6Zw8Y)4W7vpy91PMYsKM-k9yzEsSC1_Uxlf" AS url
CALL apoc.load.json(url,'$.items[?(@.answer_count>0)].answers[*]') YIELD value
MERGE (a:Answer {id: value.answer_id})
  ON CREATE SET a.accepted = value.is_accepted,
                a.shareLink = value.share_link,
                a.lastActivityDate = value.last_activity_date,
                a.creationDate = value.creation_date,
                a.title = value.title,
                a.score = value.score
MERGE (q:Question {id: value.question_id})
MERGE (a)-[rel:POSTED_TO]->(q)
WITH a as answer, value.owner as value
MERGE (u:User {userId: value.user_id})
  ON CREATE SET u.displayName = value.display_name,
                u.userType = value.user_type,
                u.reputation = value.reputation,
                u.userLink = value.link
MERGE (u)-[rel2:SUBMITTED]->(answer)
RETURN count(answer)

这将向我们的图导入大约 78 个答案。然后我们可以探索这个图,找出哪些用户提交了最多的答案、拥有最高的评分等等。

导入由 Export JSON 过程创建的 JSON 文件

apoc.import.json 过程可用于导入由 apoc.export.json.* 过程创建的 JSON 文件,这些文件使用配置参数 jsonFormat: 'JSON_LINES'(默认配置)导出。

此过程支持以下配置参数

表 9. 配置参数
名称 (name) 默认 description(描述)

unwindBatchSize

5000

unwind 的批处理大小

txBatchSize

5000

事务的批处理大小

importIdName

neo4jImportId

要填充 JSON 中存在的 "id" 字段的属性名称。例如,一行 {"type":"node", "labels":["Language"], "id":"10"},设置 importIdName:`foo`,将创建一个节点 (:User {foo: "10"})

nodePropertyMappings

{}

自定义 Neo4j 类型(点、日期)的映射:标签/属性名称/属性类型。例如:{ User: { born: 'Point', dateOfBirth: 'Datetime' } }

relPropertyMappings

{}

自定义 Neo4j 类型(点、日期)的映射:关系类型/属性名称/属性类型。例如:{ KNOWS: { since: 'Datetime' } }

nodePropertyMappingsrelPropertyMappings 支持以下 Neo4j 类型

  • Point

  • Localdate

  • Localtime

  • Localdatetime

  • Duration

  • offsettime

  • Zoneddatetime

all.json 包含 Neo4j 电影图的一个子集,由 Export JSON 过程 生成。

all.json
{"type":"node","id":"0","labels":["User"],"properties":{"born":"2015-07-04T19:32:24","name":"Adam","place":{"crs":"wgs-84","latitude":13.1,"longitude":33.46789,"height":null},"age":42,"male":true,"kids":["Sam","Anna","Grace"]}}
{"type":"node","id":"1","labels":["User"],"properties":{"name":"Jim","age":42}}
{"type":"node","id":"2","labels":["User"],"properties":{"age":12}}
{"id":"0","type":"relationship","label":"KNOWS","properties":{"bffSince":"P5M1DT12H","since":1993},"start":{"id":"0","labels":["User"],"properties":{"born":"2015-07-04T19:32:24","name":"Adam","place":{"crs":"wgs-84","latitude":13.1,"longitude":33.46789,"height":null},"age":42,"male":true,"kids":["Sam","Anna","Grace"]}},"end":{"id":"1","labels":["User"],"properties":{"name":"Jim","age":42}}}

我们可以使用 apoc.import.json 导入此文件。

CALL apoc.import.json("file:///all.json")
表 10. 结果
file source format 节点 relationships 属性 time rows batchSize batches done data

"file:///all.json"

"file"

"json"

3

1

15

105

4

-1

0

TRUE

NULL

向 Wikipedia Action API 发出 GET 请求

以下代码向 Wikipedia Action API 发出 GET 请求

apoc.load.jsonParams 允许明确指定 HTTP 请求参数。要对 JSON 端点执行 GET 请求,可以将配置中的 method 参数设置为 GET

CALL apoc.load.jsonParams(
  "https://en.wikipedia.org/w/api.php?action=query&titles=Neo4j&format=json&formatversion=2",
  {method: "GET"},
  "",
  ".query.pages[0]"
) YIELD value
RETURN value
表 11. 结果

{ "title": "Neo4j", "ns": 0, "pageid": 25505874 }

请注意,URL 需要正确编码。这可以通过使用 apoc.text.urlencode 来实现。

WITH apoc.text.urlencode("Auvergne-Rhône-Alpes") AS title
CALL apoc.load.jsonParams(
  "https://en.wikipedia.org/w/api.php?action=query&titles="+title+"&format=json&formatversion=2",
  {method: "GET"}, "", ".query.pages[0]"
) YIELD value
RETURN value.title AS title, value.pageid AS pageid
表 12. 结果
标题 pageid

"奥弗涅-罗讷-阿尔卑斯大区"

45093325