1 # This file is part of NIT ( http://www.nitlanguage.org ).
3 # This file is free software, which comes along with NIT. This software is
4 # distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
5 # without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
6 # PARTICULAR PURPOSE. You can modify it is you want, provided this header
7 # is kept unaltered, and a notification of the changes is added.
8 # You are allowed to redistribute it and sell it, alone or is a part of
11 # Provides JSON as a mean to store graphs.
12 module neo4j
::graph
::json_graph_store
16 # Save or load a graph using a JSON document.
18 # The graph (or the specified part of it) is stored as a JSON object with the
19 # following properties:
21 # * `"nodes"`: An array with all nodes. Each node is an object with the
22 # following properties:
23 # * `"labels"`: An array of all applied labels.
24 # * `"properties"`: An object mapping each defined property to its value.
25 # * `"edges"`: An array with all relationships. Each relationship is an object
26 # with the following properties:
27 # * `"type"`: The type (`String`) of the relationship.
28 # * `"properties"`: An object mapping each defined property to its value.
29 # * `"from"`: The local ID of the source node.
30 # * `"to"`: The local ID of the destination node.
33 # import neo4j::graph::sequential_id
35 # var graph = new NeoGraph(new SequentialNodeCollection("nid"))
39 # a["Ultimate question of"] = new JsonArray.from(["life",
40 # "the Universe", "and Everything."])
41 # graph.nodes.register a
42 # var b = graph.create_node
45 # graph.edges.add new NeoEdge(a, "BAZ", b)
47 # var ostream = new StringWriter
48 # var store = new JsonGraphStore(graph)
49 # store.ostream = ostream
51 # assert ostream.to_s == """{"nodes":[""" + """
52 # {"labels":["Foo"],"properties":{"answer":42,""" + """
53 # "Ultimate question of":["life","the Universe","and Everything."],""" + """
55 # {"labels":["Foo","Bar"],"properties":{"nid":2}}],""" + """
56 # "edges":[{"type":"BAZ","properties":{},"from":1,"to":2}]}"""
60 # store.istream = new StringReader(ostream.to_s)
62 # assert 1 == graph.edges.length
63 # for edge in graph.edges do
64 # assert "BAZ" == edge.rel_type
65 # assert a.labels == edge.from.labels
66 # for k, v in a.properties do assert v == edge.from.properties[k]
67 # assert b.labels == edge.to.labels
68 # for k, v in b.properties do assert v == edge.to.properties[k]
70 # assert 2 == graph.nodes.length
75 # The stream to use for `load`.
76 var istream
: nullable Reader = null is writable
78 # The stream to use for `save` and `save_part`.
79 var ostream
: nullable Writer = null is writable
81 # Use the specified `Duplex`.
82 init from_io
(graph
: NeoGraph, iostream
: Duplex) do
88 # Use the specified string to load the graph.
89 init from_string
(graph
: NeoGraph, string
: String) do
91 istream
= new StringReader(string
)
94 redef fun isolated_save
do return true
97 var istream
= self.istream
98 assert istream
isa Reader
100 graph
.load_json
(istream
.read_all
)
104 redef fun save_part
(nodes
, edges
) do
105 var ostream
= self.ostream
106 assert ostream
isa Writer
108 ostream
.write
(graph
.to_json
)
116 # Retrieve the graph from the specified JSON document.
118 # For the expected format, see `JsonGraphStore`.
121 # import neo4j::graph::sequential_id
123 # var graph = new NeoGraph(new SequentialNodeCollection("node_id"))
124 # var a = new NeoNode
127 # a["Ultimate question of"] = new JsonArray.from(["life",
128 # "the Universe", "and Everything."])
129 # graph.nodes.register a
130 # var b = graph.create_node
133 # graph.edges.add new NeoEdge(a, "BAZ", b)
135 # graph = new NeoGraph.from_json(
136 # new SequentialNodeCollection("node_id"), graph.to_json)
137 # assert 1 == graph.edges.length
138 # for edge in graph.edges do
139 # assert "BAZ" == edge.rel_type
140 # assert a.labels == edge.from.labels
141 # for k, v in a.properties do assert v == edge.from.properties[k]
142 # assert b.labels == edge.to.labels
143 # for k, v in b.properties do assert v == edge.to.properties[k]
145 # assert 2 == graph.nodes.length
147 init from_json
(nodes
: NeoNodeCollection, t
: Text) do
148 from_json_object
(nodes
, t
.parse_json
.as(JsonObject))
151 # Retrieve the graph from the specified JSON object.
153 # For the expected format, see `JsonGraphStore`.
154 init from_json_object
(nodes
: NeoNodeCollection, o
: JsonObject) do
159 # Retrieve a part of the graph from the specified JSON document.
161 # For the expected format, see `JsonGraphStore`.
162 fun load_json
(t
: Text) do
163 load_json_object
(t
.parse_json
.as(JsonObject))
166 # Retrieve a part of the graph from the specified JSON object.
168 # For the expected format, see `JsonGraphStore`.
169 fun load_json_object
(o
: JsonObject) do
170 var json_nodes
= o
["nodes"].as(JsonArray)
171 var nodes
= self.nodes
172 nodes
.enlarge
(nodes
.length
)
173 for json_node
in json_nodes
do
174 assert json_node
isa JsonObject
175 var node
= new NeoNode.from_json_object
(json_node
)
179 var json_edges
= o
["edges"].as(JsonArray)
180 var edges
= self.edges
181 if edges
isa AbstractArray[NeoEdge] then edges
.enlarge
(edges
.length
)
182 for json_edge
in json_edges
do
183 assert json_edge
isa JsonObject
184 var from
= nodes
[nodes
.id_from_jsonable
(json_edge
["from"])]
185 var to
= nodes
[nodes
.id_from_jsonable
(json_edge
["to"])]
186 var rel_type
= json_edge
["type"].as(String)
187 var json_properties
= json_edge
["properties"].as(JsonObject)
188 var edge
= new NeoEdge(from
, rel_type
, to
)
189 edge
.properties
.add_all
(json_properties
)
194 redef fun accept_json_serializer
(v
) do
195 v
.stream
.write
"\{\"nodes\
":["
196 append_entities_json
(nodes
, v
)
197 v
.stream
.write
"],\"edges\
":["
198 append_entities_json
(edges
, v
)
202 # Encode `self` in JSON.
204 # For a description of the format, see `JsonGraphStore`.
205 private fun append_entities_json
(entities
: Collection[NeoEntity], v
: JsonSerializer) do
206 var i
= entities
.iterator
208 i
.item
.append_json_for
(self, v
)
212 entity
.append_json_for
(self, v
)
218 redef class NeoNodeCollection
219 # Convert the specified JSON value into a local ID.
220 fun id_from_jsonable
(id
: nullable Serializable): ID_TYPE do return id
.as(ID_TYPE)
223 redef class NeoEntity
225 # Append the JSON representation of the entity to the specified buffer.
226 fun append_json_for
(graph
: NeoGraph, v
: JsonSerializer) is abstract
229 # Make `NeoNode` `Serializable`.
233 # Retrieve the node from the specified JSON value.
235 # Note: Here, the `"id"` is optional and ignored.
239 # var node = new NeoNode.from_json("""
241 # "labels": ["foo", "Bar"],
247 # assert ["foo", "Bar"] == node.labels
248 # assert 42 == node["baz"]
249 init from_json
(t
: Text) do
250 from_json_object
(t
.parse_json
.as(JsonObject))
253 # Retrieve the node from the specified JSON value.
255 # Note: Here, the `"id"` is optional and ignored.
258 init from_json_object
(o
: JsonObject) do
260 var labels
= o
["labels"].as(JsonArray)
261 for lab
in labels
do self.labels
.add
(lab
.as(String))
262 var json_properties
= o
["properties"].as(JsonObject)
263 properties
.add_all
(json_properties
)
266 redef fun accept_json_serializer
(v
) do
267 v
.stream
.write
"\{\"labels\
":["
268 var i
= labels
.iterator
270 i
.item
.serialize_to v
277 v
.stream
.write
"],\"properties\
":"
278 properties
.serialize_to v
282 redef fun to_s
do return to_json
284 # Append the JSON representation of the node to the specified buffer.
285 redef fun append_json_for
(graph
, v
) do
286 accept_json_serializer v
292 # Append the JSON representation of the relationship to the specified buffer.
294 # Use the IDs specfied by `graph.nodes`.
295 redef fun append_json_for
(graph
, v
) do
296 v
.stream
.write
"\{\"type\
":"
297 rel_type
.as(not null).serialize_to
(v
)
298 v
.stream
.write
",\"properties\
":"
299 properties
.serialize_to
(v
)
300 v
.stream
.write
",\"from\
":"
301 graph
.nodes
.id_of
(from
).serialize_to
(v
)
302 v
.stream
.write
",\"to\
":"
303 graph
.nodes
.id_of
(to
).serialize_to
(v
)