1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
|
#pragma once
#include "StarLexicalCast.hpp"
#include "StarJson.hpp"
namespace Star {
namespace JsonPath {
enum class TypeHint {
Array,
Object
};
typedef function<TypeHint(String&, String const&, String::const_iterator&, String::const_iterator)> PathParser;
STAR_EXCEPTION(ParsingException, JsonException);
STAR_EXCEPTION(TraversalException, JsonException);
// Parses RFC 6901 JSON Pointers, e.g. /foo/bar/4/baz
TypeHint parsePointer(String& outputBuffer, String const& path, String::const_iterator& iterator, String::const_iterator end);
// Parses JavaScript-like paths, e.g. foo.bar[4].baz
TypeHint parseQueryPath(String& outputBuffer, String const& path, String::const_iterator& iterator, String::const_iterator end);
// Retrieves the portion of the Json document referred to by the given path.
template <typename Jsonlike>
Jsonlike pathGet(Jsonlike base, PathParser parser, String const& path);
// Find a given portion of the JSON document, if it exists. Instead of
// throwing a TraversalException if a portion of the path is invalid, simply
// returns nothing.
template <typename Jsonlike>
Maybe<Jsonlike> pathFind(Jsonlike base, PathParser parser, String const& path);
template <typename Jsonlike>
using JsonOp = function<Jsonlike(Jsonlike const&, Maybe<String> const&)>;
// Applies a function to the portion of the Json document referred to by the
// given path, returning the resulting new document. If the end of the path
// doesn't exist, the JsonOp is called with None, and its result will be
// inserted into the document. If the path already existed and the JsonOp
// returns None, it is erased. This is not as well-optimized as pathGet, but
// also not on the critical path for anything.
template <typename Jsonlike>
Jsonlike pathApply(Jsonlike const& base, PathParser parser, String const& path, JsonOp<Jsonlike> op);
// Sets a value on a Json document at the location referred to by path,
// returning the resulting new document.
template <typename Jsonlike>
Jsonlike pathSet(Jsonlike const& base, PathParser parser, String const& path, Jsonlike const& value);
// Erases the location referred to by the path from the document
template <typename Jsonlike>
Jsonlike pathRemove(Jsonlike const& base, PathParser parser, String const& path);
// Performs RFC6902 (JSON Patching) add operation. Inserts into arrays, or
// appends if the last path segment is "-". On objects, does the same as
// pathSet.
template <typename Jsonlike>
Jsonlike pathAdd(Jsonlike const& base, PathParser parser, String const& path, Jsonlike const& value);
template <typename Jsonlike>
using EmptyPathOp = function<Jsonlike(Jsonlike const&)>;
template <typename Jsonlike>
using ObjectOp = function<Jsonlike(Jsonlike const&, String const&)>;
template <typename Jsonlike>
using ArrayOp = function<Jsonlike(Jsonlike const&, Maybe<size_t>)>;
template <typename Jsonlike>
JsonOp<Jsonlike> genericObjectArrayOp(String path, EmptyPathOp<Jsonlike> emptyPathOp, ObjectOp<Jsonlike> objectOp, ArrayOp<Jsonlike> arrayOp);
STAR_CLASS(Path);
STAR_CLASS(Pointer);
STAR_CLASS(QueryPath);
class Path {
public:
Path(PathParser parser, String const& path) : m_parser(parser), m_path(path) {}
template <typename Jsonlike>
Jsonlike get(Jsonlike const& base) {
return pathGet(base, m_parser, m_path);
}
template <typename Jsonlike>
Jsonlike apply(Jsonlike const& base, JsonOp<Jsonlike> op) {
return pathApply(base, m_parser, m_path, op);
}
template <typename Jsonlike>
Jsonlike apply(Jsonlike const& base,
EmptyPathOp<Jsonlike> emptyPathOp,
ObjectOp<Jsonlike> objectOp,
ArrayOp<Jsonlike> arrayOp) {
JsonOp<Jsonlike> combinedOp = genericObjectArrayOp(m_path, emptyPathOp, objectOp, arrayOp);
return pathApply(base, m_parser, m_path, combinedOp);
}
template <typename Jsonlike>
Jsonlike set(Jsonlike const& base, Jsonlike const& value) {
return pathSet(base, m_parser, m_path, value);
}
template <typename Jsonlike>
Jsonlike remove(Jsonlike const& base) {
return pathRemove(base, m_parser, m_path);
}
template <typename Jsonlike>
Jsonlike add(Jsonlike const& base, Jsonlike const& value) {
return pathAdd(base, m_parser, m_path, value);
}
String const& path() const {
return m_path;
}
private:
PathParser m_parser;
String m_path;
};
class Pointer : public Path {
public:
Pointer(String const& path) : Path(parsePointer, path) {}
};
class QueryPath : public Path {
public:
QueryPath(String const& path) : Path(parseQueryPath, path) {}
};
template <typename Jsonlike>
Jsonlike pathGet(Jsonlike value, PathParser parser, String const& path) {
String buffer;
buffer.reserve(path.size());
auto pos = path.begin();
while (pos != path.end()) {
parser(buffer, path, pos, path.end());
if (value.type() == Json::Type::Array) {
if (buffer == "-")
throw TraversalException::format("Tried to get key '{}' in non-object type in pathGet(\"{}\")", buffer, path);
Maybe<size_t> i = maybeLexicalCast<size_t>(buffer);
if (!i)
throw TraversalException::format("Cannot parse '{}' as index in pathGet(\"{}\")", buffer, path);
if (*i < value.size())
value = value.get(*i);
else
throw TraversalException::format("Index {} out of range in pathGet(\"{}\")", buffer, path);
} else if (value.type() == Json::Type::Object) {
if (value.contains(buffer))
value = value.get(buffer);
else
throw TraversalException::format("No such key '{}' in pathGet(\"{}\")", buffer, path);
} else {
throw TraversalException::format("Tried to get key '{}' in non-object type in pathGet(\"{}\")", buffer, path);
}
}
return value;
}
template <typename Jsonlike>
Maybe<Jsonlike> pathFind(Jsonlike value, PathParser parser, String const& path) {
String buffer;
buffer.reserve(path.size());
auto pos = path.begin();
while (pos != path.end()) {
parser(buffer, path, pos, path.end());
if (value.type() == Json::Type::Array) {
if (buffer == "-")
return {};
Maybe<size_t> i = maybeLexicalCast<size_t>(buffer);
if (i && *i < value.size())
value = value.get(*i);
else
return {};
} else if (value.type() == Json::Type::Object) {
if (value.contains(buffer))
value = value.get(buffer);
else
return {};
} else {
return {};
}
}
return value;
}
template <typename Jsonlike>
Jsonlike pathApply(String& buffer,
Jsonlike const& value,
PathParser parser,
String const& path,
String::const_iterator const current,
JsonOp<Jsonlike> op) {
if (current == path.end())
return op(value, {});
String::const_iterator iterator = current;
parser(buffer, path, iterator, path.end());
if (value.type() == Json::Type::Array) {
if (iterator == path.end()) {
return op(value, buffer);
} else {
Maybe<size_t> i = maybeLexicalCast<size_t>(buffer);
if (!i)
throw TraversalException::format("Cannot parse '{}' as index in pathApply(\"{}\")", buffer, path);
if (*i >= value.size())
throw TraversalException::format("Index {} out of range in pathApply(\"{}\")", buffer, path);
return value.set(*i, pathApply(buffer, value.get(*i), parser, path, iterator, op));
}
} else if (value.type() == Json::Type::Object) {
if (iterator == path.end()) {
return op(value, buffer);
} else {
if (!value.contains(buffer))
throw TraversalException::format("No such key '{}' in pathApply(\"{}\")", buffer, path);
Jsonlike newChild = pathApply(buffer, value.get(buffer), parser, path, iterator, op);
iterator = current;
// pathApply just mutated buffer. Recover the current path component:
parser(buffer, path, iterator, path.end());
return value.set(buffer, newChild);
}
} else {
throw TraversalException::format("Tried to get key '{}' in non-object type in pathApply(\"{}\")", buffer, path);
}
}
template <typename Jsonlike>
Jsonlike pathApply(Jsonlike const& base, PathParser parser, String const& path, JsonOp<Jsonlike> op) {
String buffer;
return pathApply(buffer, base, parser, path, path.begin(), op);
}
template <typename Jsonlike>
JsonOp<Jsonlike> genericObjectArrayOp(String path, EmptyPathOp<Jsonlike> emptyPathOp, ObjectOp<Jsonlike> objectOp, ArrayOp<Jsonlike> arrayOp) {
return [=](Jsonlike const& parent, Maybe<String> const& key) -> Jsonlike {
if (key.isNothing())
return emptyPathOp(parent);
if (parent.type() == Json::Type::Array) {
if (*key == "-")
return arrayOp(parent, {});
Maybe<size_t> i = maybeLexicalCast<size_t>(*key);
if (!i)
throw TraversalException::format("Cannot parse '{}' as index in Json path \"{}\"", *key, path);
if (i && *i > parent.size())
throw TraversalException::format("Index {} out of range in Json path \"{}\"", *key, path);
if (i && *i == parent.size())
i = {};
return arrayOp(parent, i);
} else if (parent.type() == Json::Type::Object) {
return objectOp(parent, *key);
} else {
throw TraversalException::format("Tried to set key '{}' in non-object type in pathSet(\"{}\")", *key, path);
}
};
}
template <typename Jsonlike>
Jsonlike pathSet(Jsonlike const& base, PathParser parser, String const& path, Jsonlike const& value) {
EmptyPathOp<Jsonlike> emptyPathOp = [&value](Jsonlike const&) {
return value;
};
ObjectOp<Jsonlike> objectOp = [&value](Jsonlike const& object, String const& key) {
return object.set(key, value);
};
ArrayOp<Jsonlike> arrayOp = [&value](Jsonlike const& array, Maybe<size_t> i) {
if (i.isValid())
return array.set(*i, value);
return array.append(value);
};
return pathApply(base, parser, path, genericObjectArrayOp(path, emptyPathOp, objectOp, arrayOp));
}
template <typename Jsonlike>
Jsonlike pathRemove(Jsonlike const& base, PathParser parser, String const& path) {
EmptyPathOp<Jsonlike> emptyPathOp = [](Jsonlike const&) { return Json{}; };
ObjectOp<Jsonlike> objectOp = [](Jsonlike const& object, String const& key) {
if (!object.contains(key))
throw TraversalException::format("Could not find \"{}\" to remove", key);
return object.eraseKey(key);
};
ArrayOp<Jsonlike> arrayOp = [](Jsonlike const& array, Maybe<size_t> i) {
if (i.isValid())
return array.eraseIndex(*i);
throw TraversalException("Could not remove element after end of array");
};
return pathApply(base, parser, path, genericObjectArrayOp(path, emptyPathOp, objectOp, arrayOp));
}
template <typename Jsonlike>
Jsonlike pathAdd(Jsonlike const& base, PathParser parser, String const& path, Jsonlike const& value) {
EmptyPathOp<Jsonlike> emptyPathOp = [&value](Jsonlike const& document) {
if (document.type() == Json::Type::Null)
return value;
throw JsonException("Cannot add a value to the entire document, it is not empty.");
};
ObjectOp<Jsonlike> objectOp = [&value](Jsonlike const& object, String const& key) {
return object.set(key, value);
};
ArrayOp<Jsonlike> arrayOp = [&value](Jsonlike const& array, Maybe<size_t> i) {
if (i.isValid())
return array.insert(*i, value);
return array.append(value);
};
return pathApply(base, parser, path, genericObjectArrayOp(path, emptyPathOp, objectOp, arrayOp));
}
}
}
|