加载 JSON

Web API 允许您访问并整合来自不同来源的数据到您的图中。大多数 Web API 提供 JSON 格式的数据。

加载 JSON 过程从 URL 或映射中检索数据,并将其转换为 MAP 值供 Cypher 使用。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>) - 如果给定的 JSON 文档是 LIST<ANY>,则从 URL(例如 Web API)加载 JSON 文档作为值流。如果给定的 JSON 文件是 MAP,则此过程导入单个值。

过程

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. 配置
名称 类型 默认值 描述

出错时失败

BOOLEAN

true

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

二进制

Enum[NONE, BYTES, GZIP, BZIP2, DEFLATE, BLOCK_LZ4, FRAMED_SNAPPY]

null

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

字符集

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. 配置
名称 类型 默认值 描述

出错时失败

BOOLEAN

true

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

apoc.load.jsonArray

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

签名

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

apoc.import.json

此过程可用于导入由 导出 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. 配置参数
名称 类型 默认值 描述

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 类型

Point, Localdate, Localtime, Localdatetime, Duration, offsettime, 和 Zoneddatetime

从文件导入

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

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

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

调用过程失败:原因:java.lang.RuntimeException: 未启用文件导入,请在 apoc.conf 中设置 apoc.import.file.enabled=true

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

调用过程失败:原因:java.lang.RuntimeException: 无法将 url 或 key file:/path/to/neo4j/import/tmp/filename 读取为 json:/path/to/neo4j//import/tmp/filename (没有此文件或目录)

我们可以通过在 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,则检索该 item

*

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

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

..

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

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

.<名称>

点表示法子项

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

[<数字> (,<数字>)]

数组索引或多个索引

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

[开始:结束]

数组切片操作符

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

[?(<表达式>)]

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

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

此外,我们可以自定义 Json 路径选项,添加配置 {pathOptions: 字符串列表},其中字符串基于 枚举<选项>。默认值为 ["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,其中一个用于检索最近的问题和答案。用于检索 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 个答案到我们的图中。然后我们可以探索这个图,找出哪些用户提交的答案最多,拥有最高的评分等等。

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

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

此过程支持以下配置参数

表 9. 配置参数
名称 默认值 描述

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 电影图的一个子集,由 导出 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:///all.json"

"文件"

"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