*: update redefs of `to_json`
[nit.git] / lib / neo4j / graph / json_graph_store.nit
1 # This file is part of NIT ( http://www.nitlanguage.org ).
2 #
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
9 # another product.
10
11 # Provides JSON as a mean to store graphs.
12 module neo4j::graph::json_graph_store
13
14 import graph
15
16 # Save or load a graph using a JSON document.
17 #
18 # The graph (or the specified part of it) is stored as a JSON object with the
19 # following properties:
20 #
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.
31 #
32 # ~~~nit
33 # import neo4j::graph::sequential_id
34 #
35 # var graph = new NeoGraph(new SequentialNodeCollection("nid"))
36 # var a = new NeoNode
37 # a.labels.add "Foo"
38 # a["answer"] = 42
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
43 # b.labels.add "Foo"
44 # b.labels.add "Bar"
45 # graph.edges.add new NeoEdge(a, "BAZ", b)
46 #
47 # var ostream = new StringWriter
48 # var store = new JsonGraphStore(graph)
49 # store.ostream = ostream
50 # store.save
51 # assert ostream.to_s == """{"nodes":[""" + """
52 # {"labels":["Foo"],"properties":{"answer":42,""" + """
53 # "Ultimate question of":["life","the Universe","and Everything."],""" + """
54 # "nid":1}},""" + """
55 # {"labels":["Foo","Bar"],"properties":{"nid":2}}],""" + """
56 # "edges":[{"type":"BAZ","properties":{},"from":1,"to":2}]}"""
57 #
58 # graph.nodes.clear
59 # graph.edges.clear
60 # store.istream = new StringReader(ostream.to_s)
61 # store.load
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]
69 # end
70 # assert 2 == graph.nodes.length
71 # ~~~
72 class JsonGraphStore
73 super GraphStore
74
75 # The stream to use for `load`.
76 var istream: nullable Reader = null is writable
77
78 # The stream to use for `save` and `save_part`.
79 var ostream: nullable Writer = null is writable
80
81 # Use the specified `Duplex`.
82 init from_io(graph: NeoGraph, iostream: Duplex) do
83 init(graph)
84 istream = iostream
85 ostream = iostream
86 end
87
88 # Use the specified string to load the graph.
89 init from_string(graph: NeoGraph, string: String) do
90 init(graph)
91 istream = new StringReader(string)
92 end
93
94 redef fun isolated_save do return true
95
96 redef fun load do
97 var istream = self.istream
98 assert istream isa Reader
99 fire_started
100 graph.load_json(istream.read_all)
101 fire_done
102 end
103
104 redef fun save_part(nodes, edges) do
105 var ostream = self.ostream
106 assert ostream isa Writer
107 fire_started
108 ostream.write(graph.to_json)
109 fire_done
110 end
111 end
112
113 redef class NeoGraph
114 super Jsonable
115
116 # Retrieve the graph from the specified JSON document.
117 #
118 # For the expected format, see `JsonGraphStore`.
119 #
120 # ~~~nit
121 # import neo4j::graph::sequential_id
122 #
123 # var graph = new NeoGraph(new SequentialNodeCollection("node_id"))
124 # var a = new NeoNode
125 # a.labels.add "Foo"
126 # a["answer"] = 42
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
131 # b.labels.add "Foo"
132 # b.labels.add "Bar"
133 # graph.edges.add new NeoEdge(a, "BAZ", b)
134 #
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]
144 # end
145 # assert 2 == graph.nodes.length
146 # ~~~
147 init from_json(nodes: NeoNodeCollection, t: Text) do
148 from_json_object(nodes, t.parse_json.as(JsonObject))
149 end
150
151 # Retrieve the graph from the specified JSON object.
152 #
153 # For the expected format, see `JsonGraphStore`.
154 init from_json_object(nodes: NeoNodeCollection, o: JsonObject) do
155 init(nodes)
156 load_json_object(o)
157 end
158
159 # Retrieve a part of the graph from the specified JSON document.
160 #
161 # For the expected format, see `JsonGraphStore`.
162 fun load_json(t: Text) do
163 load_json_object(t.parse_json.as(JsonObject))
164 end
165
166 # Retrieve a part of the graph from the specified JSON object.
167 #
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)
176 nodes.add node
177 end
178
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)
190 edges.add edge
191 end
192 end
193
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)
199 v.stream.write "]\}"
200 end
201
202 # Encode `self` in JSON.
203 #
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
207 if i.is_ok then
208 i.item.append_json_for(self, v)
209 i.next
210 for entity in i do
211 v.stream.write ","
212 entity.append_json_for(self, v)
213 end
214 end
215 end
216 end
217
218 redef class NeoNodeCollection
219 # Convert the specified JSON value into a local ID.
220 fun id_from_jsonable(id: nullable Jsonable): ID_TYPE do return id.as(ID_TYPE)
221 end
222
223 redef class NeoEntity
224
225 # Append the JSON representation of the entity to the specified buffer.
226 fun append_json_for(graph: NeoGraph, v: JsonSerializer) is abstract
227 end
228
229 # Make `NeoNode` `Jsonable`.
230 redef class NeoNode
231 super Jsonable
232
233 # Retrieve the node from the specified JSON value.
234 #
235 # Note: Here, the `"id"` is optional and ignored.
236 #
237 # SEE: `JsonGraph`
238 #
239 # var node = new NeoNode.from_json("""
240 # {
241 # "labels": ["foo", "Bar"],
242 # "properties": {
243 # "baz": 42
244 # }
245 # }
246 # """)
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))
251 end
252
253 # Retrieve the node from the specified JSON value.
254 #
255 # Note: Here, the `"id"` is optional and ignored.
256 #
257 # SEE: `JsonGraph`
258 init from_json_object(o: JsonObject) do
259 init
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)
264 end
265
266 redef fun accept_json_serializer(v) do
267 v.stream.write "\{\"labels\":["
268 var i = labels.iterator
269 if i.is_ok then
270 i.item.serialize_to v
271 i.next
272 for lab in i do
273 v.stream.write ","
274 lab.serialize_to v
275 end
276 end
277 v.stream.write "],\"properties\":"
278 properties.serialize_to v
279 v.stream.write "}"
280 end
281
282 redef fun to_s do return to_json
283
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
287 end
288 end
289
290 redef class NeoEdge
291
292 # Append the JSON representation of the relationship to the specified buffer.
293 #
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)
304 v.stream.write "}"
305 end
306 end