1 /++ 2 Mutable ASDF data structure. 3 The representation can be used to compute a difference between JSON object-trees. 4 5 Copyright: Tamedia Digital, 2016 6 7 Authors: Ilia Ki 8 9 License: BSL-1.0 10 +/ 11 module asdf.transform; 12 13 import asdf.asdf; 14 import asdf.serialization; 15 import std.exception: enforce; 16 17 /++ 18 Object-tree structure for mutable Asdf representation. 19 20 `AsdfNode` can be used to construct and manipulate JSON objects. 21 Each `AsdfNode` can represent either a dynamic JSON object (associative array of `AsdfNode` nodes) or a ASDF JSON value. 22 JSON arrays can be represented only as JSON values. 23 +/ 24 struct AsdfNode 25 { 26 /++ 27 Children nodes. 28 +/ 29 AsdfNode[const(char)[]] children; 30 /++ 31 Leaf data. 32 +/ 33 Asdf data; 34 35 pure: 36 37 /++ 38 Returns `true` if the node is leaf. 39 +/ 40 bool isLeaf() const @safe pure nothrow @nogc 41 { 42 return cast(bool) data.data.length; 43 } 44 45 /++ 46 Construct `AsdfNode` recursively. 47 +/ 48 this(Asdf data) 49 { 50 if(data.kind == Asdf.Kind.object) 51 { 52 foreach(kv; data.byKeyValue) 53 { 54 children[kv.key] = AsdfNode(kv.value); 55 } 56 } 57 else 58 { 59 this.data = data; 60 enforce(isLeaf); 61 } 62 } 63 64 /// 65 ref AsdfNode opIndex(scope const(char)[][] keys...) scope return 66 { 67 if(keys.length == 0) 68 return this; 69 auto ret = this; 70 for(;;) 71 { 72 auto ptr = keys[0] in ret.children; 73 enforce(ptr, "AsdfNode.opIndex: keys do not exist"); 74 keys = keys[1 .. $]; 75 if(keys.length == 0) 76 return *ptr; 77 ret = *ptr; 78 } 79 } 80 81 /// 82 unittest 83 { 84 import asdf; 85 auto text = `{"foo":"bar","inner":{"a":true,"b":false,"c":"32323","d":null,"e":{}}}`; 86 auto root = AsdfNode(text.parseJson); 87 assert(root["inner", "a"].data == `true`.parseJson); 88 } 89 90 /// 91 void opIndexAssign(AsdfNode value, scope const(char)[][] keys...) 92 { 93 auto root = &this; 94 foreach(key; keys) 95 { 96 L: 97 auto ptr = key in root.children; 98 if(ptr) 99 { 100 enforce(ptr, "AsdfNode.opIndex: keys do not exist"); 101 keys = keys[1 .. $]; 102 root = ptr; 103 } 104 else 105 { 106 root.children[keys[0]] = AsdfNode.init; 107 goto L; 108 } 109 } 110 *root = value; 111 } 112 113 /// 114 unittest 115 { 116 import asdf; 117 auto text = `{"foo":"bar","inner":{"a":true,"b":false,"c":"32323","d":null,"e":{}}}`; 118 auto root = AsdfNode(text.parseJson); 119 auto value = AsdfNode(`true`.parseJson); 120 root["inner", "g", "u"] = value; 121 assert(root["inner", "g", "u"].data == true); 122 } 123 124 /++ 125 Params: 126 value = default value 127 keys = list of keys 128 Returns: `[keys]` if any and `value` othervise. 129 +/ 130 AsdfNode get(AsdfNode value, in char[][] keys...) 131 { 132 auto ret = this; 133 foreach(key; keys) 134 if(auto ptr = key in ret.children) 135 ret = *ptr; 136 else 137 { 138 ret = value; 139 break; 140 } 141 return ret; 142 } 143 144 /// 145 unittest 146 { 147 import asdf; 148 auto text = `{"foo":"bar","inner":{"a":true,"b":false,"c":"32323","d":null,"e":{}}}`; 149 auto root = AsdfNode(text.parseJson); 150 auto value = AsdfNode(`false`.parseJson); 151 assert(root.get(value, "inner", "a").data == true); 152 assert(root.get(value, "inner", "f").data == false); 153 } 154 155 /// Serialization primitive 156 void serialize(ref AsdfSerializer serializer) 157 { 158 if(isLeaf) 159 { 160 serializer.app.put(cast(const(char)[])data.data); 161 return; 162 } 163 auto state = serializer.structBegin; 164 foreach(key, ref value; children) 165 { 166 serializer.putKey(key); 167 value.serialize(serializer); 168 } 169 serializer.structEnd(state); 170 } 171 172 /// 173 Asdf opCast(T : Asdf)() 174 { 175 return serializeToAsdf(this); 176 } 177 178 /// 179 unittest 180 { 181 import asdf; 182 auto text = `{"foo":"bar","inner":{"a":true,"b":false,"c":"32323","d":null,"e":{}}}`; 183 auto root = AsdfNode(text.parseJson); 184 import std.stdio; 185 Asdf flat = cast(Asdf) root; 186 assert(flat["inner", "a"] == true); 187 } 188 189 /// 190 bool opEquals(in AsdfNode rhs) const @safe pure nothrow @nogc 191 { 192 if(isLeaf) 193 if(rhs.isLeaf) 194 return data == rhs.data; 195 else 196 return false; 197 else 198 if(rhs.isLeaf) 199 return false; 200 else 201 return children == rhs.children; 202 } 203 204 /// 205 unittest 206 { 207 import asdf; 208 auto text = `{"foo":"bar","inner":{"a":true,"b":false,"c":"32323","d":null,"e":{}}}`; 209 auto root1 = AsdfNode(text.parseJson); 210 auto root2= AsdfNode(text.parseJson); 211 assert(root1 == root2); 212 assert(root1["inner"].children.remove("b")); 213 assert(root1 != root2); 214 } 215 216 /// Adds data to the object-tree recursively. 217 void add(Asdf data) 218 { 219 if(data.kind == Asdf.Kind.object) 220 { 221 this.data = Asdf.init; 222 foreach(kv; data.byKeyValue) 223 { 224 if(auto nodePtr = kv.key in children) 225 { 226 nodePtr.add(kv.value); 227 } 228 else 229 { 230 children[kv.key] = AsdfNode(kv.value); 231 } 232 } 233 } 234 else 235 { 236 this.data = data; 237 children = null; 238 } 239 } 240 241 /// 242 unittest 243 { 244 import asdf; 245 auto text = `{"foo":"bar","inner":{"a":true,"b":false,"c":"32323","d":null,"e":{}}}`; 246 auto addition = `{"do":"re","inner":{"a":false,"u":2}}`; 247 auto root = AsdfNode(text.parseJson); 248 root.add(addition.parseJson); 249 auto result = `{"do":"re","foo":"bar","inner":{"a":false,"u":2,"b":false,"c":"32323","d":null,"e":{}}}`; 250 assert(root == AsdfNode(result.parseJson)); 251 } 252 253 /// Removes keys from the object-tree recursively. 254 void remove(Asdf data) 255 { 256 enforce(children, "AsdfNode.remove: asdf data must be a sub-tree"); 257 foreach(kv; data.byKeyValue) 258 { 259 if(kv.value.kind == Asdf.Kind.object) 260 { 261 if(auto nodePtr = kv.key in children) 262 { 263 nodePtr.remove(kv.value); 264 } 265 } 266 else 267 { 268 children.remove(kv.key); 269 } 270 } 271 } 272 273 /// 274 unittest 275 { 276 import asdf; 277 auto text = `{"foo":"bar","inner":{"a":true,"b":false,"c":"32323","d":null,"e":{}}}`; 278 auto rem = `{"do":null,"foo":null,"inner":{"c":null,"e":null}}`; 279 auto root = AsdfNode(text.parseJson); 280 root.remove(rem.parseJson); 281 auto result = `{"inner":{"a":true,"b":false,"d":null}}`; 282 assert(root == AsdfNode(result.parseJson)); 283 } 284 285 private void removedImpl(ref AsdfSerializer serializer, AsdfNode node) 286 { 287 import std.exception : enforce; 288 enforce(!isLeaf); 289 enforce(!node.isLeaf); 290 auto state = serializer.structBegin; 291 foreach(key, ref value; children) 292 { 293 auto nodePtr = key in node.children; 294 if(nodePtr && *nodePtr == value) 295 continue; 296 serializer.putKey(key); 297 if(nodePtr && !nodePtr.isLeaf && !value.isLeaf) 298 value.removedImpl(serializer, *nodePtr); 299 else 300 serializer.putValue(null); 301 } 302 serializer.structEnd(state); 303 } 304 305 /++ 306 Returns the subset of the object-tree which is not represented in `node`. 307 If a leaf is represented but has a different value then it will be included 308 in the return value. 309 Returned value has ASDF format and its leaves are set to `null`. 310 +/ 311 Asdf removed(AsdfNode node) 312 { 313 auto serializer = asdfSerializer(); 314 removedImpl(serializer, node); 315 serializer.flush; 316 return serializer.app.result; 317 } 318 319 /// 320 unittest 321 { 322 import asdf; 323 auto text1 = `{"inner":{"a":true,"b":false,"d":null}}`; 324 auto text2 = `{"foo":"bar","inner":{"a":false,"b":false,"c":"32323","d":null,"e":{}}}`; 325 auto node1 = AsdfNode(text1.parseJson); 326 auto node2 = AsdfNode(text2.parseJson); 327 auto diff = AsdfNode(node2.removed(node1)); 328 assert(diff == AsdfNode(`{"foo":null,"inner":{"a":null,"c":null,"e":null}}`.parseJson)); 329 } 330 331 void addedImpl(ref AsdfSerializer serializer, AsdfNode node) 332 { 333 import std.exception : enforce; 334 enforce(!isLeaf); 335 enforce(!node.isLeaf); 336 auto state = serializer.structBegin; 337 foreach(key, ref value; node.children) 338 { 339 auto nodePtr = key in children; 340 if(nodePtr && *nodePtr == value) 341 continue; 342 serializer.putKey(key); 343 if(nodePtr && !nodePtr.isLeaf && !value.isLeaf) 344 nodePtr.addedImpl(serializer, value); 345 else 346 value.serialize(serializer); 347 } 348 serializer.structEnd(state); 349 } 350 351 /++ 352 Returns the subset of the node which is not represented in the object-tree. 353 If a leaf is represented but has a different value then it will be included 354 in the return value. 355 Returned value has ASDF format. 356 +/ 357 Asdf added(AsdfNode node) 358 { 359 auto serializer = asdfSerializer(); 360 addedImpl(serializer, node); 361 serializer.flush; 362 return serializer.app.result; 363 } 364 365 /// 366 unittest 367 { 368 import asdf; 369 auto text1 = `{"foo":"bar","inner":{"a":false,"b":false,"c":"32323","d":null,"e":{}}}`; 370 auto text2 = `{"inner":{"a":true,"b":false,"d":null}}`; 371 auto node1 = AsdfNode(text1.parseJson); 372 auto node2 = AsdfNode(text2.parseJson); 373 auto diff = AsdfNode(node2.added(node1)); 374 assert(diff == AsdfNode(`{"foo":"bar","inner":{"a":false,"c":"32323","e":{}}}`.parseJson)); 375 } 376 }