Hide keyboard shortcuts

Hot-keys on this page

r m x p   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

1""" 

2>>> obj = {"a":"b","c":{"d":"e"}} 

3>>> tgt = obj["c"] 

4>>> JsonFind.to_jsonpointer(JsonFind.find_eq(obj, tgt)) 

5'/c' 

6>>> JsonFind.to_jsonpath(JsonFind.find_eq(obj, tgt)) 

7'c' 

8""" 

9import re 

10import fnmatch 

11from logging import getLogger 

12import jsonpointer 

13import jsonpath 

14try: 

15 import pyjq 

16except (ModuleNotFoundError, ImportError): 

17 pyjq = None 

18try: 

19 import jsonselect 

20except (ModuleNotFoundError, ImportError): 

21 jsonselect = None 

22 

23log = getLogger(__name__) 

24 

25 

26def EQ(a, b): 

27 """ 

28 >>> EQ(1, 2) 

29 False 

30 >>> EQ(1, 1) 

31 True 

32 >>> EQ("abc", "abc") 

33 True 

34 >>> EQ("abc", "def") 

35 False 

36 >>> EQ({"a":"b"}, {"a":"b"}) 

37 True 

38 >>> EQ({"a":"b"}, {"a":"c"}) 

39 False 

40 """ 

41 return a == b 

42 

43 

44def IS(a, b): 

45 """ 

46 >>> IS(1, 2) 

47 False 

48 >>> IS(1, 1) 

49 True 

50 >>> IS("abc", "abc") 

51 True 

52 >>> IS("abc", "def") 

53 False 

54 >>> IS({"a":"b"}, {"a":"b"}) 

55 False 

56 >>> IS({"a":"b"}, {"a":"c"}) 

57 False 

58 """ 

59 return a is b 

60 

61 

62def IN1(a, b): 

63 """ 

64 >>> IN1(1, [1,2,3]) 

65 True 

66 >>> IN1([1,2,3], 1) 

67 False 

68 >>> IN1(1, [2,3]) 

69 False 

70 >>> IN1("abc", "hello abc world") 

71 True 

72 >>> IN1("xyz", "hello abc world") 

73 False 

74 """ 

75 try: 

76 return a in b 

77 except TypeError: 

78 return False 

79 

80 

81def IN2(a, b): 

82 """ 

83 >>> IN2(1, [1,2,3]) 

84 False 

85 >>> IN2([1,2,3], 1) 

86 True 

87 >>> IN2([2,3], 1) 

88 False 

89 >>> IN2("hello abc world", "abc") 

90 True 

91 >>> IN2("hello abc world", "xyz") 

92 False 

93 """ 

94 try: 

95 return b in a 

96 except TypeError: 

97 return False 

98 

99 

100def compare_regexp(a, b): 

101 """ 

102 >>> compare_regexp("abcde", "bcd") 

103 False 

104 >>> compare_regexp("abcde", "a[bcd]{3}e") 

105 True 

106 >>> compare_regexp("a,bcde", "[abcde,]*") 

107 True 

108 >>> compare_regexp("abcde", re.compile("bcd[ef]")) 

109 False 

110 >>> compare_regexp({"b":"abcde"}, "bcd[ef]") 

111 False 

112 """ 

113 log.debug("compare(regexp) %s %s", a, b) 

114 if isinstance(a, str): 

115 if hasattr(b, "fullmatch"): 

116 return b.fullmatch(a) is not None 

117 elif isinstance(b, str): 

118 return re.fullmatch(b, a) is not None 

119 return EQ(a, b) 

120 

121 

122def compare_regexp_substr(a, b): 

123 """ 

124 >>> compare_regexp_substr("abcde", "bcd") 

125 True 

126 >>> compare_regexp_substr("abcde", "bcdf") 

127 False 

128 >>> compare_regexp_substr("abcde", "bcd[ef]") 

129 True 

130 >>> compare_regexp_substr("abcde", re.compile("bcd[ef]")) 

131 True 

132 >>> compare_regexp_substr({"b":"abcde"}, "bcd[ef]") 

133 False 

134 """ 

135 if isinstance(a, str): 

136 if hasattr(b, "search"): 

137 return b.search(a) is not None 

138 elif isinstance(b, str): 

139 return re.search(b, a) is not None 

140 return EQ(a, b) 

141 

142 

143def compare_fnmatch(a, b): 

144 """ 

145 >>> compare_fnmatch("world", "worl?") 

146 True 

147 >>> compare_fnmatch("world", "word*") 

148 False 

149 >>> compare_fnmatch("world", "wor*d") 

150 True 

151 >>> compare_fnmatch("world", "?or??") 

152 True 

153 >>> compare_fnmatch("world", "?and?") 

154 False 

155 >>> compare_fnmatch(1, 2) 

156 False 

157 """ 

158 if isinstance(a, str) and isinstance(b, str): 

159 return fnmatch.fnmatch(a, b) 

160 return EQ(a, b) 

161 

162 

163def compare_range(a, b): 

164 """ 

165 >>> compare_range(1, "-10") 

166 True 

167 >>> compare_range(1, "10-") 

168 False 

169 >>> compare_range(20, "-10") 

170 False 

171 >>> compare_range(1, "10-20") 

172 False 

173 >>> compare_range(1.0, "0-1.0") 

174 True 

175 >>> compare_range(100, "-") 

176 True 

177 >>> compare_range("b", "a-z") 

178 True 

179 >>> compare_range("b", "b") 

180 True 

181 >>> compare_range("b", "a") 

182 False 

183 """ 

184 if "-" not in b: 

185 return a == type(a)(b) 

186 bmin, bmax = b.split("-", 1) 

187 if bmin not in (None, "") and type(a)(bmin) > a: 

188 return False 

189 if bmax not in (None, "") and type(a)(bmax) < a: 

190 return False 

191 return True 

192 

193 

194def compare_eval(a, b): 

195 """ 

196 >>> compare_eval(1, "x%2==1") 

197 True 

198 >>> compare_eval(1, "0<x/3 and x/3 <=1.0") 

199 True 

200 >>> compare_eval("xyz", "len(x)==3") 

201 True 

202 >>> compare_eval("xyz", 'hash(x)!=hash("xyz")') 

203 False 

204 >>> compare_eval("xyz", 'x[2]=="z"') 

205 True 

206 >>> compare_eval("xyzxyz", 'x[:int(len(x)/2)]==x[int(len(x)/2):]') 

207 True 

208 >>> compare_eval("xyzxyz123123", 'x[:int(len(x)/2)]==x[int(len(x)/2):]') 

209 False 

210 """ 

211 return eval(b, {}, {"x": a}) 

212 

213 

214def compare_subset(a, b, key_fn=EQ, val_fn=EQ): 

215 """ 

216 >>> compare_subset({"a":"b", "c":{"d":"e"}}, {"a":"b"}) 

217 True 

218 >>> compare_subset({"a":"b", "c":{"d":"e"}}, {"a":"c"}) 

219 False 

220 >>> compare_subset({"a":"b", "c":{"d":"e"}}, {"a":"b", "c":"d"}) 

221 False 

222 >>> compare_subset({"a":"b", "c":{"d":"e"}}, {}) 

223 True 

224 >>> compare_subset([1,2,3], [1,3,5,2,4]) 

225 False 

226 >>> compare_subset([1,2,3], [1,3]) 

227 True 

228 """ 

229 if isinstance(b, (list, tuple)) and isinstance(a, (list, tuple)): 

230 for ib in b: 

231 if not any(compare_subset(ia, ib, key_fn, val_fn) for ia in a): 

232 return False 

233 return True 

234 elif isinstance(b, dict) and isinstance(a, dict): 

235 for kb, vb in b.items(): 

236 flag = False 

237 for ka, va in filter(lambda f: key_fn(f[0], kb), a.items()): 

238 if compare_subset(va, vb, key_fn, val_fn): 

239 flag = True 

240 break 

241 if not flag: 

242 return False 

243 return True 

244 return val_fn(a, b) 

245 

246 

247def compare_superset(a, b, key_fn=EQ, val_fn=EQ): 

248 """ 

249 >>> compare_superset({"a":"b", "c":{"d":"e"}}, {"a":"b", "c":{"d":"e", "f":"g"}}) 

250 True 

251 >>> compare_superset({"a":"b", "c":{"d":"e"}}, {"a":"b"}) 

252 False 

253 >>> compare_superset({"a":"b", "c":{"d":"e"}}, {"a":"b", "c":"d"}) 

254 False 

255 >>> compare_superset({"a":"b", "c":{"d":"e"}}, {}) 

256 False 

257 >>> compare_superset({"a":"b"}, {"a":"b"}) 

258 True 

259 >>> compare_superset({"a":"b"}, {"a":"b", "c":"d"}) 

260 True 

261 >>> compare_superset([1,2,3], [1,3,5,2,4]) 

262 True 

263 >>> compare_superset([1,2,3], [1,3]) 

264 False 

265 """ 

266 if isinstance(a, (list, tuple)) and isinstance(b, (list, tuple)): 

267 for ia in a: 

268 if not any(compare_superset(ia, ib, key_fn, val_fn) for ib in b): 

269 return False 

270 return True 

271 elif isinstance(a, dict) and isinstance(b, dict): 

272 for ka, va in a.items(): 

273 flag = False 

274 for kb, vb in filter(lambda f: key_fn(ka, f[0]), b.items()): 

275 if compare_superset(va, vb, key_fn, val_fn): 

276 flag = True 

277 break 

278 if not flag: 

279 return False 

280 return True 

281 return val_fn(a, b) 

282 

283 

284def compare_set(a, b, key_fn=EQ, val_fn=EQ): 

285 """ 

286 >>> compare_set({"a":"b", "c":"d"}, {"a":"b", "c":"d"}) 

287 True 

288 >>> compare_set({"a":"b", "c":"d"}, {"a":"b"}) 

289 False 

290 >>> compare_set({"a":"b"}, {"a":"b", "c":"d"}) 

291 False 

292 """ 

293 return compare_subset(a, b, key_fn, val_fn) and compare_superset(a, b, key_fn, val_fn) 

294 

295 

296class JsonFind: 

297 

298 @classmethod 

299 def get_children(cls, obj): 

300 if isinstance(obj, dict): 

301 return obj.items() 

302 elif isinstance(obj, (tuple, list)): 

303 return enumerate(obj) 

304 return [] 

305 

306 @classmethod 

307 def get_children_attr(cls, obj): 

308 """ 

309 >>> class A: hello="world" 

310 >>> list(filter(lambda f: not f[0].startswith("_"), JsonFind.get_children_attr(A))) 

311 [('hello', 'world')] 

312 """ 

313 return obj.__dict__.items() 

314 

315 @classmethod 

316 def issubset(cls, obj, target): 

317 if isinstance(target, dict) and isinstance(obj, dict): 

318 if obj.items() >= target.items(): 

319 return True 

320 elif isinstance(target, (list, tuple)) and isinstance(obj, (list, tuple)): 

321 if all(x in obj for x in target): 

322 return True 

323 return False 

324 

325 @classmethod 

326 def filter_subset(cls, obj, target): 

327 if cls.issubset(obj, target): 

328 yield [] 

329 return 

330 for k, v in cls.get_children(obj): 

331 for chld in cls.filter_subset(v, target): 

332 yield [k, *chld] 

333 return 

334 

335 @classmethod 

336 def filter_eq(cls, obj, target): 

337 if obj == target: 

338 yield [] 

339 return 

340 for k, v in cls.get_children(obj): 

341 for chld in cls.filter_eq(v, target): 

342 yield [k, *chld] 

343 return 

344 

345 @classmethod 

346 def filter_is(cls, obj, target): 

347 if obj is target: 

348 yield [] 

349 return 

350 for k, v in cls.get_children(obj): 

351 for chld in cls.filter_is(v, target): 

352 yield [k, *chld] 

353 return 

354 

355 @classmethod 

356 def filter_compare(cls, obj, target, key_fn=IS, val_fn=IS): 

357 if compare_set(obj, target, key_fn, val_fn): 

358 log.debug("found %s %s", obj, target) 

359 yield [] 

360 return 

361 log.debug("not-found %s %s", obj, target) 

362 for k, v in cls.get_children(obj): 

363 for chld in cls.filter_compare(v, target, key_fn, val_fn): 

364 yield [k, *chld] 

365 return 

366 

367 @classmethod 

368 def filter_compare_subset(cls, obj, target, key_fn=IS, val_fn=IS): 

369 if compare_subset(obj, target, key_fn, val_fn): 

370 log.debug("found %s %s", obj, target) 

371 yield [] 

372 return 

373 log.debug("not-found %s %s", obj, target) 

374 for k, v in cls.get_children(obj): 

375 for chld in cls.filter_compare_subset(v, target, key_fn, val_fn): 

376 yield [k, *chld] 

377 return 

378 

379 @classmethod 

380 def filter_compare_superset(cls, obj, target, key_fn=IS, val_fn=IS): 

381 if compare_superset(obj, target, key_fn, val_fn): 

382 log.debug("found %s %s", obj, target) 

383 yield [] 

384 return 

385 log.debug("not-found %s %s", obj, target) 

386 for k, v in cls.get_children(obj): 

387 for chld in cls.filter_compare_superset(v, target, key_fn, val_fn): 

388 yield [k, *chld] 

389 return 

390 

391 @classmethod 

392 def filter_attr_eq(cls, obj, target): 

393 if obj == target: 

394 yield [] 

395 return 

396 for k, v in cls.get_children_attr(obj): 

397 for chld in cls.filter_attr_eq(v, target): 

398 yield [k, *chld] 

399 return 

400 

401 @classmethod 

402 def filter_attr_is(cls, obj, target): 

403 if obj is target: 

404 yield [] 

405 return 

406 for k, v in cls.get_children_attr(obj): 

407 for chld in cls.filter_attr_is(v, target): 

408 yield [k, *chld] 

409 return 

410 

411 @classmethod 

412 def filter_key(cls, obj, target, prev=[]): 

413 if prev[-len(target):] == target: 

414 yield [] 

415 return 

416 for k, v in cls.get_children(obj): 

417 for chld in cls.filter_key(v, target, [*prev, k]): 

418 yield [k, *chld] 

419 return 

420 

421 @classmethod 

422 def find_eq(cls, obj, target): 

423 return next(cls.filter_eq(obj, target), None) 

424 

425 @classmethod 

426 def find_is(cls, obj, target): 

427 return next(cls.filter_is(obj, target), None) 

428 

429 @classmethod 

430 def find_attr_eq(cls, obj, target): 

431 return next(cls.filter_attr_eq(obj, target), None) 

432 

433 @classmethod 

434 def find_attr_is(cls, obj, target): 

435 return next(cls.filter_attr_is(obj, target), None) 

436 

437 @classmethod 

438 def find_subset(cls, obj, target): 

439 return next(cls.filter_subset(obj, target), None) 

440 

441 @classmethod 

442 def find_superset(cls, obj, target): 

443 return next(cls.filter_superset(obj, target), None) 

444 

445 @classmethod 

446 def find_key(cls, obj, target, prev=[]): 

447 return next(cls.filter_key(obj, target, prev), None) 

448 

449 @classmethod 

450 def to_jsonpath(cls, val): 

451 res = "" 

452 for i in val: 

453 if isinstance(i, str): 

454 res += "." + i 

455 elif isinstance(i, int): 

456 res += "[{}]".format(i) 

457 else: 

458 raise Exception("invalid type: {} ({})".format(i, val)) 

459 return res.lstrip(".") 

460 

461 @classmethod 

462 def escape_jsonptr(cls, s): 

463 if not isinstance(s, str): 

464 return str(s) 

465 s = s.replace("~", "~0") 

466 return s.replace("/", "~1") 

467 

468 @classmethod 

469 def to_jsonpointer(cls, val): 

470 return "/" + "/".join([cls.escape_jsonptr(x) for x in val]) 

471 

472 @classmethod 

473 def format_to(cls, mode, val): 

474 fn = getattr(cls, "to_{}".format(mode)) 

475 return fn(val) 

476 

477 @classmethod 

478 def find_by(cls, mode, obj, path): 

479 if mode == "jsonpointer": 

480 return jsonpointer.resolve_pointer(obj, path) 

481 elif mode == "jsonpath": 

482 return jsonpath.jsonpath(obj, path) 

483 elif pyjq is not None and mode == "jq": 

484 return pyjq.all(path, obj) 

485 elif jsonselect is not None and mode == "jsonselect": 

486 return list(jsonselect.match(path, obj)) 

487 return None 

488 

489 

490format_list = [x.split("_", 1)[-1] 

491 for x in filter(lambda f: f.startswith("to_"), dir(JsonFind))] 

492find_format_list = [*format_list] 

493if pyjq is not None and "jq" not in find_format_list: 

494 find_format_list.append("jq") 

495if jsonselect is not None and "jsonselect" not in find_format_list: 

496 find_format_list.append("jsonselect")