├── pyjsonq ├── __init__.py ├── matcher.py └── query.py ├── setup.cfg ├── .gitignore ├── examples ├── at.py ├── sum.py ├── group_by.py ├── sort_by.py ├── chunk.py ├── where.py └── data.json ├── LICENSE ├── setup.py └── readme.md /pyjsonq/__init__.py: -------------------------------------------------------------------------------- 1 | from .query import JsonQ 2 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [metadata] 2 | description-file = README.md 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .vscode 2 | *.pyc 3 | __pycache__ 4 | .DS_Store 5 | build 6 | dist 7 | *.egg-info/ 8 | -------------------------------------------------------------------------------- /examples/at.py: -------------------------------------------------------------------------------- 1 | """ 2 | Example of at() 3 | """ 4 | from pyjsonq import JsonQ 5 | 6 | qe = JsonQ("./data.json").at("users").where("id", "<", 3).get() 7 | 8 | print("result", qe) 9 | -------------------------------------------------------------------------------- /examples/sum.py: -------------------------------------------------------------------------------- 1 | """ 2 | Example of sum() 3 | """ 4 | from pyjsonq import JsonQ 5 | 6 | e1 = JsonQ("./data.json").at("users.5.visits").sum("year") 7 | 8 | print("result", e1) 9 | -------------------------------------------------------------------------------- /examples/group_by.py: -------------------------------------------------------------------------------- 1 | """ 2 | Example of group_by() 3 | """ 4 | from pyjsonq import JsonQ 5 | 6 | e1 = JsonQ("./data.json").at("products").group_by("price").get() 7 | 8 | print("result", e1) 9 | -------------------------------------------------------------------------------- /examples/sort_by.py: -------------------------------------------------------------------------------- 1 | """ 2 | Example of sort_by() 3 | """ 4 | from pyjsonq import JsonQ 5 | 6 | e1 = JsonQ("./data.json").at("products").sort_by("id", "desc").get() 7 | 8 | print("result", e1) 9 | -------------------------------------------------------------------------------- /examples/chunk.py: -------------------------------------------------------------------------------- 1 | """ 2 | Example of chunk() 3 | """ 4 | from pyjsonq import JsonQ 5 | 6 | e1 = JsonQ("./data.json").at("users")\ 7 | .where("location", "=", "Barisal")\ 8 | .chunk(2) 9 | 10 | print("result", e1) 11 | -------------------------------------------------------------------------------- /examples/where.py: -------------------------------------------------------------------------------- 1 | """ 2 | Example of where() 3 | """ 4 | from pyjsonq import JsonQ 5 | 6 | e1 = JsonQ("./data.json").at("users")\ 7 | .where("id", ">", 3)\ 8 | .where("location", "=", "Barisal")\ 9 | .get() 10 | 11 | print("result", e1) 12 | 13 | e2 = JsonQ("./data.json").at("users")\ 14 | .where("id", ">", 3)\ 15 | .where("location", "=", "Barisal")\ 16 | .get() 17 | 18 | print("result", e2) 19 | 20 | e3 = JsonQ("./data.json").at("users")\ 21 | .where("id", ">", 3)\ 22 | .where("location", "=", "barisal", True)\ 23 | .get() 24 | 25 | print("result", e3) 26 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Shaon Shaonty 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /examples/data.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "products", 3 | "description": "Features product list", 4 | "vendor":{ 5 | "name": "Computer Source BD", 6 | "email": "info@example.com", 7 | "website":"www.example.com" 8 | }, 9 | "users":[ 10 | {"id":1, "name":"Johura Akter Sumi", "location": "Barisal"}, 11 | {"id":2, "name":"Mehedi Hasan Nahid", "location": "Barisal"}, 12 | {"id":3, "name":"Ariful Islam", "location": "Barisal"}, 13 | {"id":4, "name":"Suhel Ahmed", "location": "Sylhet"}, 14 | {"id":5, "name":"Firoz Serniabat", "location": "Gournodi"}, 15 | {"id":6, "name":"Musa Jewel", "location": "Barisal", "visits": [ 16 | {"name": "Sylhet", "year": 2011}, 17 | {"name": "Cox's Bazar", "year": 2012}, 18 | {"name": "Bandarbar", "year": 2014} 19 | ]} 20 | ], 21 | "products": [ 22 | {"id":1, "user_id": 2, "city": "bsl", "name":"iPhone", "cat":1, "price": 80000}, 23 | {"id":2, "user_id": 2, "city": null, "name":"macbook pro", "cat": 2, "price": 150000}, 24 | {"id":3, "user_id": 2, "city": "dhk", "name":"Redmi 3S Prime", "cat": 1, "price": 12000}, 25 | {"id":4, "user_id": 1, "city": null, "name":"Redmi 4X", "cat":1, "price": 15000}, 26 | {"id":5, "user_id": 1, "city": "bsl", "name":"macbook air", "cat": 2, "price": 110000}, 27 | {"id":6, "user_id": 2, "city": null, "name":"macbook air 1", "cat": 2, "price": 81000} 28 | ] 29 | } 30 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | """Run "python setup.py install" to install pyjsonq.""" 2 | 3 | from setuptools import setup 4 | 5 | try: 6 | with open('README.md') as f: 7 | long_description = f.read() 8 | 9 | except Exception: 10 | long_description = """ 11 | `pyjsonq` is a simple, elegant Python package to Query over any 12 | type of JSON Data. It'll make your life easier by giving the 13 | flavour of an ORM-like query on your JSON. 14 | 15 | More information at: https://github.com/s1s1ty/py-jsonq/. 16 | """ 17 | 18 | 19 | setup(name="pyjsonq", 20 | packages=['pyjsonq'], 21 | version='1.0.2', 22 | description="Query over Json file", 23 | long_description=long_description, 24 | long_description_content_type='text/markdown', 25 | classifiers=[ 26 | 'Development Status :: 4 - Beta', 27 | 'Intended Audience :: Developers', 28 | 'Natural Language :: English', 29 | 'Operating System :: OS Independent', 30 | 'Topic :: Software Development :: Libraries :: ' 31 | 'Python Modules', 32 | 'Programming Language :: Python :: 2', 33 | 'Programming Language :: Python :: 3', 34 | ], 35 | author='Shaonty Dutta', 36 | author_email='shaonty.dutta@gmail.com', 37 | license='MIT', 38 | url="https://github.com/s1s1ty/py-jsonq/", 39 | keywords=['Python', 'plugin'], 40 | include_package_data=True, 41 | zip_safe=False, 42 | setup_requires=['setuptools>=38.6.0'], 43 | ) 44 | -------------------------------------------------------------------------------- /pyjsonq/matcher.py: -------------------------------------------------------------------------------- 1 | class Matcher(object): 2 | """docstring for Helper.""" 3 | def __init__(self): 4 | self.condition_mapper = { 5 | '=': '_is_equal', 6 | 'eq': '_is_equal', 7 | '!=': '_is_not_equal', 8 | 'neq': '_is_not_equal', 9 | '>': '_is_greater', 10 | 'gt': '_is_greater', 11 | '<': '_is_smaller', 12 | 'lt': '_is_smaller', 13 | '>=': '_is_greater_equal', 14 | 'gte': '_is_greater_equal', 15 | '<=': '_is_smaller_equal', 16 | 'lte': '_is_smaller_equal', 17 | 'in': '_is_in', 18 | 'notin': '_is_not_in', 19 | 'null': '_is_null', 20 | 'notnull': '_is_not_null', 21 | 'startswith': '_is_starts_with', 22 | 'endswith': '_is_ends_with', 23 | 'contains': '_is_contain' 24 | } 25 | 26 | def _is_equal(self, x, y): 27 | """Checks the given values are equal 28 | 29 | :@param x, y 30 | :@type x, y: mixed 31 | 32 | :@return bool 33 | """ 34 | return x == y 35 | 36 | def _is_not_equal(self, x, y): 37 | """Checks the given values are not equal 38 | 39 | :@param x, y 40 | :@type x, y: mixed 41 | 42 | :@return bool 43 | """ 44 | return x != y 45 | 46 | def _is_greater(self, x, y): 47 | """Checks the given value `x` is greater than the given value `y` 48 | 49 | :@param x, y 50 | :@type x, y: mixed 51 | 52 | :@return bool 53 | """ 54 | return x > y 55 | 56 | def _is_smaller(self, x, y): 57 | """Checks the given value `x` is less than the given value `y` 58 | 59 | :@param x, y 60 | :@type x, y: mixed 61 | 62 | :@return bool 63 | """ 64 | return x < y 65 | 66 | def _is_greater_equal(self, x, y): 67 | """Checks the given value `x` is greater than or equal the given value `y` 68 | 69 | :@param x, y 70 | :@type x, y: mixed 71 | 72 | :@return bool 73 | """ 74 | return x >= y 75 | 76 | def _is_smaller_equal(self, x, y): 77 | """Checks the given value `x` is less than or equal the given value `y` 78 | 79 | :@param x, y 80 | :@type x, y: mixed 81 | 82 | :@return bool 83 | """ 84 | return x <= y 85 | 86 | def _is_in(self, key, arr): 87 | """Checks the given `key` is exists in the given `list` 88 | 89 | :@param key, arr 90 | :@type key: mixed 91 | :type arr: list 92 | 93 | :@return bool 94 | """ 95 | 96 | return isinstance(arr, list) and \ 97 | bool(len(([k for k in key if k in arr] 98 | if isinstance(key, list) else key in arr))) 99 | 100 | def _is_not_in(self, key, arr): 101 | """Checks the given `key` is not exists in the given `arr` 102 | 103 | :@param x, y 104 | :@type x, y: mixed 105 | 106 | :@return bool 107 | """ 108 | return isinstance(arr, list) and (key not in arr) 109 | 110 | def _is_null(self, x, y=None): 111 | """Checks the given value `x` is None 112 | 113 | :@param x, y 114 | :@type x, y: mixed 115 | 116 | :@return bool 117 | """ 118 | return x is None 119 | 120 | def _is_not_null(self, x, y=None): 121 | """Checks the given value `x` is not None 122 | 123 | :@param x, y 124 | :@type x, y: mixed 125 | 126 | :@return bool 127 | """ 128 | return x is not None 129 | 130 | def _is_starts_with(self, data, val): 131 | """Checks the given string `data` starts with the given string `val` 132 | 133 | :@param data 134 | :@param val 135 | :@type data: string 136 | :@type val: string 137 | 138 | :@return bool 139 | """ 140 | return data.startswith(val) 141 | 142 | def _is_ends_with(self, data, val): 143 | """Checks the given string `data` ends with the given string `val` 144 | 145 | :@param data 146 | :@param val 147 | :@type data: string 148 | :@type val: string 149 | 150 | :@return bool 151 | """ 152 | return data.endswith(val) 153 | 154 | def _is_contain(self, str, val): 155 | """Checks the given `val` is exists in the given `string` 156 | 157 | :@param str, val 158 | :@type: string/list 159 | :@type val: string 160 | 161 | :@return bool 162 | """ 163 | return val in str 164 | 165 | def _to_lower(self, x, y): 166 | """Convert val to lower case 167 | 168 | :@param x, y 169 | :@type x, y: mixed 170 | 171 | :@return x, y 172 | """ 173 | return [[v.lower() if isinstance(v, str) else v for v in val] 174 | if isinstance(val, list) else val.lower() 175 | if isinstance(val, str) else val 176 | for val in [x, y]] 177 | 178 | def _match(self, x, op, y, case_insensitive): 179 | """Compare the given `x` and `y` based on `op` 180 | 181 | :@param x, y, op, case_insensitive 182 | :@type x, y: mixed 183 | :@type op: string 184 | :@type case_insensitive: bool 185 | 186 | :@return bool 187 | :@throws ValueError 188 | """ 189 | if (op not in self.condition_mapper): 190 | raise ValueError('Invalid where condition given') 191 | 192 | if case_insensitive: 193 | x, y = self._to_lower(x, y) 194 | 195 | func = getattr(self, self.condition_mapper.get(op)) 196 | return func(x, y) 197 | -------------------------------------------------------------------------------- /pyjsonq/query.py: -------------------------------------------------------------------------------- 1 | import json 2 | import os 3 | import copy 4 | import math 5 | 6 | from .matcher import Matcher 7 | 8 | 9 | class JsonQ(object): 10 | """Query over Json file""" 11 | 12 | def __init__(self, file_path="", data={}): 13 | """ 14 | :@param file_path: Set main json file path 15 | :@type file_path: string 16 | """ 17 | if file_path != "": 18 | self.from_file(file_path) 19 | 20 | if data: 21 | self.__parse_json_data(data) 22 | 23 | self.__reset_queries() 24 | self._matcher = Matcher() 25 | 26 | def __reset_queries(self): 27 | """Reset previous query data""" 28 | 29 | self._queries = [] 30 | self._current_query_index = 0 31 | 32 | def __parse_json_data(self, data): 33 | """Process Json data 34 | 35 | :@param data 36 | :@type data: json/dict 37 | 38 | :throws TypeError 39 | """ 40 | if isinstance(data, dict) or isinstance(data, list): 41 | self._raw_data = data 42 | self._json_data = copy.deepcopy(self._raw_data) 43 | else: 44 | raise TypeError("Provided Data is not json") 45 | 46 | def __parse_json_file(self, file_path): 47 | """Process Json file data 48 | 49 | :@param file_path 50 | :@type file_path: string 51 | 52 | :@throws IOError 53 | """ 54 | if file_path == '' or os.path.splitext(file_path)[1] != '.json': 55 | raise IOError('Invalid Json file') 56 | 57 | with open(file_path) as json_file: 58 | self._raw_data = json.load(json_file) 59 | 60 | self._json_data = copy.deepcopy(self._raw_data) 61 | 62 | def __get_value_from_data(self, key, data): 63 | """Find value from json data 64 | 65 | :@pram key 66 | :@type: string 67 | 68 | :@pram data 69 | :@type data: dict 70 | 71 | :@return object 72 | :@throws KeyError 73 | """ 74 | if key.isdigit(): 75 | return data[int(key)] 76 | 77 | if key not in data: 78 | raise KeyError("Key not exists") 79 | 80 | return data.get(key) 81 | 82 | def get(self): 83 | """Getting prepared data 84 | 85 | :@return object 86 | """ 87 | self.__prepare() 88 | return self._json_data 89 | 90 | def from_file(self, file_path): 91 | """Set main json file path 92 | 93 | :@param file_path 94 | :@type file_path: string 95 | 96 | :@throws FileNotFoundError 97 | """ 98 | self.__parse_json_file(file_path) 99 | return self 100 | 101 | def at(self, root): 102 | """Set root where PyJsonq start to prepare 103 | 104 | :@param root 105 | :@type root: string 106 | 107 | :@return self 108 | :@throws KeyError 109 | """ 110 | leafs = root.strip(" ").split('.') 111 | for leaf in leafs: 112 | if leaf: 113 | self._json_data = self.__get_value_from_data(leaf, self._json_data) 114 | return self 115 | 116 | def clone(self): 117 | """Clone the exact same copy of the current object instance.""" 118 | return copy.deepcopy(self._json_data) 119 | 120 | def reset(self, data={}): 121 | """JsonQuery object cen be reset to new data 122 | 123 | according to given data or previously given raw Json data 124 | 125 | :@param data: {} 126 | :@type data: json/dict 127 | 128 | :@return self 129 | """ 130 | if data and (isinstance(data, dict) or isinstance(data, list)): 131 | self._json_data = data 132 | else: 133 | self._json_data = copy.deepcopy(self._raw_data) 134 | 135 | self.__reset_queries() 136 | return self 137 | 138 | def __store_query(self, query_items): 139 | """Make where clause 140 | 141 | :@param query_items 142 | :@type query_items: dict 143 | """ 144 | temp_index = self._current_query_index 145 | if len(self._queries) - 1 < temp_index: 146 | self._queries.append([]) 147 | 148 | self._queries[temp_index].append(query_items) 149 | 150 | def __prepare(self): 151 | """Prepare query result""" 152 | 153 | if len(self._queries) > 0: 154 | self.__execute_queries() 155 | self.__reset_queries() 156 | 157 | def __execute_queries(self): 158 | """Execute all condition and filter result data""" 159 | 160 | def func(item): 161 | or_check = False 162 | for queries in self._queries: 163 | and_check = True 164 | for query in queries: 165 | and_check &= self._matcher._match( 166 | item.get(query.get('key'), None), 167 | query.get('operator'), 168 | query.get('value'), 169 | query.get('case_insensitive') 170 | ) 171 | or_check |= and_check 172 | return or_check 173 | 174 | self._json_data = list(filter(lambda item: func(item), self._json_data)) 175 | 176 | # ---------- Query Methods ------------- # 177 | 178 | def where(self, key, operator, value, case_insensitive=False): 179 | """Make where clause 180 | 181 | :@param key 182 | :@param operator 183 | :@param value 184 | :@type key,operator,value: string 185 | 186 | :@param case_insensitive 187 | :@type case_insensitive: bool 188 | 189 | :@return self 190 | """ 191 | self.__store_query({ 192 | "key": key, 193 | "operator": operator, 194 | "value": value, 195 | "case_insensitive": case_insensitive 196 | }) 197 | 198 | return self 199 | 200 | def or_where(self, key, operator, value): 201 | """Make or_where clause 202 | 203 | :@param key 204 | :@param operator 205 | :@param value 206 | :@type key, operator, value: string 207 | 208 | :@return self 209 | """ 210 | if len(self._queries) > 0: 211 | self._current_query_index += 1 212 | self.__store_query({"key": key, "operator": operator, "value": value}) 213 | return self 214 | 215 | def where_in(self, key, value): 216 | """Make where_in clause 217 | 218 | :@param key 219 | :@param value 220 | :@type key, value: string 221 | 222 | :@return self 223 | """ 224 | self.where(key, 'in', value) 225 | return self 226 | 227 | def where_not_in(self, key, value): 228 | """Make where_not_in clause 229 | 230 | :@param key 231 | :@param value 232 | :@type key, value: string 233 | 234 | :@return self 235 | """ 236 | self.where(key, 'notin', value) 237 | return self 238 | 239 | def where_null(self, key): 240 | """Make where_null clause 241 | 242 | :@param key 243 | :@type key: string 244 | 245 | :@return self 246 | """ 247 | self.where(key, '=', 'None') 248 | return self 249 | 250 | def where_not_null(self, key): 251 | """Make where_not_null clause 252 | 253 | :@param key 254 | :@type key: string 255 | 256 | :@return self 257 | """ 258 | self.where(key, '!=', 'None') 259 | return self 260 | 261 | def where_start_with(self, key, value): 262 | """Make where_start_with clause 263 | 264 | :@param key 265 | :@param value 266 | :@type key,value: string 267 | 268 | :@return self 269 | """ 270 | self.where(key, 'startswith', value) 271 | return self 272 | 273 | def where_end_with(self, key, value): 274 | """Make where_ends_with clause. 275 | 276 | :@param key 277 | :@param value 278 | :@type key,value: string 279 | 280 | :@return self 281 | """ 282 | self.where(key, 'endswith', value) 283 | return self 284 | 285 | def where_contains(self, key, value): 286 | """Make where_contains clause. 287 | 288 | :@param key 289 | :@param value 290 | :@type key,value: string 291 | 292 | :@return self 293 | """ 294 | self.where(key, 'contains', value) 295 | return self 296 | 297 | # ---------- Aggregate Methods ------------- # 298 | 299 | def count(self): 300 | """Getting the size of the collection 301 | 302 | :@return int 303 | """ 304 | self.__prepare() 305 | return len(self._json_data) 306 | 307 | def size(self): 308 | """Getting the size of the collection 309 | 310 | :@return int 311 | """ 312 | self.__prepare() 313 | return len(self._json_data) 314 | 315 | def first(self): 316 | """Getting the first element of the collection otherwise None 317 | 318 | :@return object 319 | """ 320 | self.__prepare() 321 | return self._json_data[0] if self.count() > 0 else None 322 | 323 | def last(self): 324 | """Getting the last element of the collection otherwise None 325 | 326 | :@return object 327 | """ 328 | self.__prepare() 329 | return self._json_data[-1] if self.count() > 0 else None 330 | 331 | def nth(self, index): 332 | """Getting the nth element of the collection 333 | 334 | :@param index 335 | :@type index: int 336 | 337 | :@return object 338 | """ 339 | self.__prepare() 340 | return None if self.count() < math.fabs(index) else self._json_data[index] 341 | 342 | def sum(self, property): 343 | """Getting the sum according to the given property 344 | 345 | :@param property 346 | :@type property: string 347 | 348 | :@return int/float 349 | """ 350 | self.__prepare() 351 | total = 0 352 | for i in self._json_data: 353 | total += i.get(property) 354 | 355 | return total 356 | 357 | def max(self, property): 358 | """Getting the maximum value from the prepared data 359 | 360 | :@param property 361 | :@type property: string 362 | 363 | :@return object 364 | :@throws KeyError 365 | """ 366 | self.__prepare() 367 | try: 368 | return max(self._json_data, key=lambda x: x[property]).get(property) 369 | except KeyError: 370 | raise KeyError("Key is not exists") 371 | 372 | def min(self, property): 373 | """Getting the minimum value from the prepared data 374 | 375 | :@param property 376 | :@type property: string 377 | 378 | :@return object 379 | :@throws KeyError 380 | """ 381 | self.__prepare() 382 | try: 383 | return min(self._json_data, key=lambda x: x[property]).get(property) 384 | except KeyError: 385 | raise KeyError("Key is not exists") 386 | 387 | def avg(self, property): 388 | """Getting average according to given property 389 | 390 | :@param property 391 | :@type property: string 392 | 393 | :@return average: int/float 394 | """ 395 | self.__prepare() 396 | return self.sum(property) / self.count() 397 | 398 | def chunk(self, size=0): 399 | """Group the resulted collection to multiple chunk 400 | 401 | :@param size: 0 402 | :@type size: integer 403 | 404 | :@return Chunked List 405 | """ 406 | 407 | if size == 0: 408 | raise ValueError('Invalid chunk size') 409 | 410 | self.__prepare() 411 | _new_content = [] 412 | 413 | while(len(self._json_data) > 0): 414 | _new_content.append(self._json_data[0:size]) 415 | self._json_data = self._json_data[size:] 416 | 417 | self._json_data = _new_content 418 | 419 | return self._json_data 420 | 421 | def group_by(self, property): 422 | """Getting the grouped result by the given property 423 | 424 | :@param property 425 | :@type property: string 426 | 427 | :@return self 428 | """ 429 | self.__prepare() 430 | group_data = {} 431 | for data in self._json_data: 432 | if data[property] not in group_data: 433 | group_data[data[property]] = [] 434 | group_data[data[property]].append(data) 435 | self._json_data = group_data 436 | 437 | return self 438 | 439 | def sort(self, order="asc"): 440 | """Getting the sorted result of the given list 441 | 442 | :@param order: "asc" 443 | :@type order: string 444 | 445 | :@return self 446 | """ 447 | self.__prepare() 448 | if isinstance(self._json_data, list): 449 | if order == "asc": 450 | self._json_data = sorted(self._json_data) 451 | else: 452 | self._json_data = sorted(self._json_data, reverse=True) 453 | 454 | return self 455 | 456 | def sort_by(self, property, order="asc"): 457 | """Getting the sorted result by the given property 458 | 459 | :@param property, order: "asc" 460 | :@type property, order: string 461 | 462 | :@return self 463 | """ 464 | self.__prepare() 465 | if isinstance(self._json_data, list): 466 | if order == "asc": 467 | self._json_data = sorted( 468 | self._json_data, 469 | key=lambda x: x.get(property) 470 | ) 471 | else: 472 | self._json_data = sorted( 473 | self._json_data, 474 | key=lambda x: x.get(property), 475 | reverse=True 476 | ) 477 | 478 | return self 479 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # py-jsonq 2 | 3 | **py-jsonq** is a simple, elegant Python package to Query over any type of JSON Data. It'll make your life easier by giving the flavour of an ORM-like query on your JSON. 4 | 5 | ## Installation 6 | 7 | ``` 8 | pip install pyjsonq 9 | ``` 10 | 11 | ## Usage 12 | 13 | Just import the package before start using it. 14 | 15 | As a Python Package: 16 | 17 | ```python 18 | from pyjsonq import JsonQ 19 | ``` 20 | 21 | You can start using this package right away by importing your Json data from a file: 22 | 23 | ```Python 24 | JsonQ('data.json') 25 | ``` 26 | or 27 | 28 | ```Python 29 | JsonQ(data={"id": 1, "name": "shaonty"}) # must assign data if you want to pass data instead of file_path 30 | ``` 31 | 32 | You can start Query your data using the various query methods such as **where**, **or_where**, **where_in**, **where_not_in**, **where_starts_with**, **where_ends_with**, **where_contains** and so on. Also you can aggregate your data after query using **sum**, **count**, **group_by**, **sort_by**, **max**, **min** etc. 33 | 34 | Let's see a quick example: 35 | 36 | ```python 37 | # sample Json data 38 | json_object = { 39 | products: [ 40 | { 41 | id: 1, 42 | city: 'bsl', 43 | name: 'iPhone', 44 | cat: 1, 45 | price: 80000.5 46 | }, 47 | { 48 | id: 2, 49 | city: null, 50 | name: 'macbook pro', 51 | cat: 1, 52 | price: 150000 53 | }, 54 | { 55 | id: 3, 56 | city: 'dhk', 57 | name: 'Redmi 3S Prime', 58 | cat: 2, 59 | price: 12000 60 | }, 61 | { 62 | id: 4, 63 | city: 'bsl', 64 | name: 'macbook air', 65 | cat: 2, 66 | price: 110000 67 | } 68 | ] 69 | }; 70 | 71 | qe = JsonQ(file_path) 72 | res = qe.at('products').where('cat', '=', 2).get() 73 | print(res) 74 | 75 | """This will print 76 | 77 | [ 78 | { 79 | id: 3, 80 | city: 'dhk', 81 | name: 'Redmi 3S Prime', 82 | cat: 2, 83 | price: 12000 84 | }, 85 | { 86 | id: 4, 87 | city: 'bsl', 88 | name: 'macbook air', 89 | cat: 2, 90 | price: 110000 91 | } 92 | ] 93 | """ 94 | ``` 95 | 96 | Let's say we want to get the Summation of _price_ of the Queried result. We can do it easily by calling the **sum()** method instead of **get()**: 97 | 98 | ```Python 99 | res = qe.at('products').where('cat', '=', 2).sum('price') 100 | print(res) 101 | 102 | """It will print: 103 | 104 | 122000 105 | 106 | """ 107 | ``` 108 | 109 | Let's explore the full API to see what else magic this library can do for you. 110 | Shall we? 111 | 112 | ## API 113 | 114 | Following API examples are shown based on the sample JSON data given [here](examples/data.json). To get a better idea of the examples see that JSON data first. Also detailed examples of each API can be found [here](examples/). 115 | 116 | **List of API:** 117 | 118 | * [get](#get) 119 | * [from_path](#from_filefile_path) 120 | * [at](#atpath) 121 | * [where](#wherekey-operator-value) 122 | * [or_where](#orwherekey-operator-value) 123 | * [where_in](#where_inkey-value) 124 | * [where_not_in](#where_not_inkey-value) 125 | * [where_null](#where_nullkey) 126 | * [where_not_null](#where_not_nullkey) 127 | * [where_starts_with](#where_starts_withkey-value) 128 | * [where_ends_with](#where_ends_withkey-value) 129 | * [where_contains](#where_containskey-value) 130 | * [sum](#sumproperty) 131 | * [count](#count) 132 | * [size](#size) 133 | * [max](#maxproperty) 134 | * [min](#minproperty) 135 | * [avg](#avgproperty) 136 | * [first](#first) 137 | * [last](#last) 138 | * [nth](#nthindex) 139 | * [group_by](#group_byproperty) 140 | * [sort](#sortorder) 141 | * [sortBy](#sortbyproperty-order) 142 | * [reset](#resetdata) 143 | * [clone](#clone) 144 | * [chunk](#chunksize) 145 | 146 | ### `get()` 147 | 148 | This method will execute queries and will return the resulted data. You need to call it finally after using some query methods. Details can be found in other API examples. 149 | 150 | ### `from_file(file_path)` 151 | 152 | This method is the alternative of set json file path. Details can be found in other API examples. 153 | 154 | **example:** 155 | 156 | Let's say you have a file named `data.json`. You can set path like this: 157 | 158 | ```Python 159 | qu = JsonQ().from_file('data.json').at('users').where('id', '=', 1).get() 160 | ``` 161 | 162 | ### `at(path)` 163 | 164 | * `path` -- the path hierarchy of the data you want to start query from. 165 | 166 | By default, query would be started from the root of the JSON Data you've given. If you want to first move to a nested path hierarchy of the data from where you want to start your query, you would use this method. Skipping the `path` parameter or giving **'.'** as parameter will also start query from the root Data. 167 | 168 | 169 | **example:** 170 | 171 | Let's say you want to start query over the values of _'users'_ property of your Json Data. You can do it like this: 172 | 173 | ```Python 174 | qu = JsonQ(file_path).at('users').where('id', '=', 1).get() 175 | ``` 176 | 177 | If you want to traverse to more deep in hierarchy, you can do it like: 178 | 179 | ```Python 180 | qe = JsonQ(file_path).at('users.5.visits').where('year', '=', 2011).get() 181 | ``` 182 | 183 | See a detail example [here](examples/at.py). 184 | 185 | ### `where(key, operator, value, case_insensitive)` 186 | 187 | * `key` -- the property name of the data. Or you can pass a Function here to group multiple query inside it. See details in [example](examples/where.py) 188 | * `value` -- value to be matched with. It can be a _int_, _string_, _bool_ or even _float_ - depending on the `operator`. 189 | * `operator` -- operand to be used for matching. The following operands are available to use: 190 | 191 | * `=` : For equality matching 192 | * `eq` : Same as `=` 193 | * `!=` : For weak not equality matching 194 | * `neq` : Same as `!=` 195 | * `>` : Check if value of given **key** in data is Greater than **value** 196 | * `gt` : Same as `>` 197 | * `<` : Check if value of given **key** in data is Less than **value** 198 | * `lt` : Same as `<` 199 | * `>=` : Check if value of given **key** in data is Greater than or Equal of **value** 200 | * `gte` : Same as `>=` 201 | * `<=` : Check if value of given **key** in data is Less than or Equal of **value** 202 | * `lte` : Same as `<=` 203 | * `null` : Check if the value of given **key** in data is **null** (`value` parameter in `where()` can be omitted for this `operator`) 204 | * `notnull` : Check if the value of given **key** in data is **not null** (`value` parameter in `where()` can be omitted for this `operator`) 205 | * `in` : Check if the value of given **key** in data is exists in given **value**. **value** should be a plain _List_. **key** can be a plain _List_. 206 | * `notin` : Check if the value of given **key** in data is not exists in given **val**. **val** should be a plain _List_. 207 | * `startswith` : Check if the value of given **key** in data starts with (has a prefix of) the given **value**. This would only works for _String_ type data. 208 | * `endswith` : Check if the value of given **key** in data ends with (has a suffix of) the given **value**. This would only works for _String_ type data. 209 | * `contains` : Same as `in` 210 | 211 | * `case_insensitive` -- if `True`, the search will be case insensitive, `False` is default. 212 | 213 | **example:** 214 | 215 | Let's say you want to find the _'users'_ who has _id_ of `1`. You can do it like this: 216 | 217 | ```Python 218 | qu = JsonQ(file_path).at('users').where('id', '=', 1).get() 219 | ``` 220 | 221 | You can add multiple _where_ conditions. It'll give the result by AND-ing between these multiple where conditions. 222 | 223 | ```Python 224 | qe = JsonQ(file_path).at('users').where('id', '=', 1).where('location', '=', 'Sylhet').get() 225 | ``` 226 | 227 | ```Python 228 | qi = JsonQ(file_path).at('users').where('id', '=', 1).where('location', '=', 'sylhet', True).get() 229 | ``` 230 | 231 | See a detail example [here](examples/where.py). 232 | 233 | ### `or_where(key, operator, value)` 234 | 235 | Parameters of `or_where()` are the same as `where()`. The only difference between `where()` and `or_where()` is: condition given by the `or_where()` method will OR-ed the result with other conditions. 236 | 237 | For example, if you want to find the users with _id_ of `1` or `2`, you can do it like this: 238 | 239 | ```Python 240 | re = JsonQ(file_path).at('users').where('id', '=', 1).or_where('id', '=', 2).get() 241 | ``` 242 | 243 | See detail example [here](examples/or_where.py). 244 | 245 | ### `where_in(key, value)` 246 | 247 | * `key` -- the property name of the data 248 | * `value` -- it should be an **List** 249 | 250 | This method will behave like `where(key, 'in', value)` method call. 251 | 252 | ### `where_not_in(key, value)` 253 | 254 | * `key` -- the property name of the data 255 | * `value` -- it should be an **List** 256 | 257 | This method will behave like `where(key, 'notin', value)` method call. 258 | 259 | ### `where_null(key)` 260 | 261 | * `key` -- the property name of the data 262 | 263 | This method will behave like `where(key, '=', 'None')` method call. 264 | 265 | ### `where_not_null(key)` 266 | 267 | * `key` -- the property name of the data 268 | 269 | This method will behave like `where(key, '!=', 'None')` method call. 270 | 271 | ### `where_starts_with(key, value)` 272 | 273 | * `key` -- the property name of the data 274 | * `value` -- it should be a String 275 | 276 | This method will behave like `where(key, 'startswith', value)` method call. 277 | 278 | ### `where_ends_with(key, value)` 279 | 280 | * `key` -- the property name of the data 281 | * `value` -- it should be a String 282 | 283 | This method will behave like `where(key, 'endswith', value)` method call. 284 | 285 | ### `where_contains(key, val)` 286 | 287 | * `key` -- the property name of the data 288 | * `value` -- it should be a String or List 289 | 290 | This method will behave like `where(key, 'contains', val)` method call. 291 | 292 | ### `sum(property)` 293 | 294 | * `property` -- the property name of the data 295 | 296 | **example:** 297 | 298 | Let's say you want to find the sum of the _'price'_ of the _'products'_. You can do it like this: 299 | 300 | ```Python 301 | qe = JsonQ(file_path).at('products').sum('price') 302 | ``` 303 | 304 | If the data you are aggregating is plain list, you don't need to pass the 'property' parameter. 305 | See detail example [here](examples/sum.py) 306 | 307 | ### `count()` 308 | 309 | It will return the number of elements in the collection. 310 | 311 | **example:** 312 | 313 | Let's say you want to find how many elements are in the _'products'_ property. You can do it like: 314 | 315 | ```Python 316 | qe = JsonQ(file_path).at('products').count() 317 | ``` 318 | 319 | See detail example [here](examples/count.py). 320 | 321 | ### `size()` 322 | 323 | This is an alias method of `count()`. 324 | 325 | ### `max(property)` 326 | 327 | * `property` -- the property name of the data 328 | 329 | **example:** 330 | 331 | Let's say you want to find the maximum of the _'price'_ of the _'products'_. You can do it like this: 332 | 333 | ```Python 334 | qu = JsonQ(file_path).at('products').max('price') 335 | ``` 336 | 337 | If the data you are querying is plain array, you don't need to pass the 'property' parameter. 338 | See detail example [here](examples/max.py) 339 | 340 | ### `min(property)` 341 | 342 | * `property` -- the property name of the data 343 | 344 | **example:** 345 | 346 | Let's say you want to find the minimum of the _'price'_ of the _'products'_. You can do it like this: 347 | 348 | ```Python 349 | qe = JsonQ(file_path).at('products').min('price') 350 | ``` 351 | 352 | If the data you are querying is plain array, you don't need to pass the 'property' parameter. 353 | See detail example [here](examples/min.py) 354 | 355 | ### `avg(property)` 356 | 357 | * `property` -- the property name of the data 358 | 359 | **example:** 360 | 361 | Let's say you want to find the average of the _'price'_ of the _'products'_. You can do it like this: 362 | 363 | ```Python 364 | qe = JsonQ(file_path).at('products').avg('price') 365 | ``` 366 | 367 | If the data you are querying is plain array, you don't need to pass the 'property' parameter. 368 | See detail example [here](examples/avg.py) 369 | 370 | ### `first()` 371 | 372 | It will return the first element of the collection. 373 | 374 | **example:** 375 | 376 | ```Python 377 | qe = JsonQ(file_path).at('products').first() 378 | ``` 379 | 380 | See detail example [here](examples/first.py). 381 | 382 | ### `last()` 383 | 384 | It will return the last element of the collection. 385 | 386 | **example:** 387 | 388 | ```Python 389 | qe = JsonQ(file_path).at('products').last() 390 | ``` 391 | 392 | See detail example [here](examples/last.py). 393 | 394 | ### `nth(index)` 395 | 396 | * `index` -- index of the element to be returned. 397 | 398 | It will return the nth(n starts from 0) element of the collection. If the given index is a **positive** value, it will return the nth element from the beginning. If the given index is a **negative** value, it will return the nth element from the end. 399 | 400 | **example:** 401 | 402 | ```Python 403 | qe = JsonQ(file_path).at('products').nth(2) 404 | ``` 405 | 406 | See detail example [here](examples/nth.py). 407 | 408 | 409 | ### `group_by(property)` 410 | 411 | * `property` -- The property by which you want to group the collection. 412 | 413 | **example:** 414 | 415 | Let's say you want to group the _'users'_ data based on the _'location'_ property. You can do it like: 416 | 417 | ```Python 418 | qe = JsonQ(file_path).at('users').group_by('location').get() 419 | ``` 420 | 421 | See detail example [here](examples/group_by.py). 422 | 423 | ### `sort(order)` 424 | 425 | * `order` -- If you skip the _'order'_ property the data will be by default ordered as **ascending**. You need to pass **'desc'** as the _'order'_ parameter to sort the data in **descending** order. Also, you can pass a compare function in _'order'_ parameter to define your own logic to order the data. 426 | 427 | **Note:** This method should be used for plain Array. If you want to sort an Array of Objects you should use the **sortBy()** method described later. 428 | 429 | **example:** 430 | 431 | Let's say you want to sort the _'arr'_ data. You can do it like: 432 | 433 | ```Python 434 | qe = JsonQ(file_path).at('arr').sort().get() 435 | ``` 436 | 437 | See detail example [here](examples/sort.py). 438 | 439 | ### `sort_by(property, order)` 440 | 441 | * `property` -- You need to pass the property name on which the sorting will be done. 442 | * `order` -- If you skip the _'order'_ property the data will be by default ordered as **ascending**. You need to pass **'desc'** as the _'order'_ parameter to sort the data in **descending** order. Also, you can pass a compare function in _'order'_ parameter to define your own logic to order the data. 443 | 444 | **Note:** This method should be used for Array of Objects. If you want to sort a plain Array you should use the **sort()** method described earlier. 445 | 446 | **example:** 447 | 448 | Let's say you want to sort the _'price'_ data of _'products'_. You can do it like: 449 | 450 | ```Python 451 | qe = JsonQ(file_path).at('products').sort_by('price').get() 452 | ``` 453 | 454 | See detail example [here](examples/sort_by.py). 455 | 456 | ### `reset(data)` 457 | 458 | * `data` -- can be a JSON file path, or a JSON string or a JSON Object. If no data passed in the `data` parameter, the `JsonQ` Object instance will be reset to previously initialised data. 459 | 460 | At any point, you might want to reset the Object instance to a completely different set of data and then query over it. You can use this method in that case. 461 | 462 | See a detail example [here](examples/reset.py). 463 | 464 | ### `clone()` 465 | 466 | It will return a complete clone of the Object instance. 467 | 468 | See a detail example [here](examples/clone.py). 469 | 470 | ### `chunk(size)` 471 | It will return a complete new array after chunking your array with specific size. 472 | 473 | See a detail example [here](examples/chunk.py). 474 | 475 | 476 | ## Bugs and Issues 477 | 478 | If you encounter any bugs or issues, feel free to [open an issue at 479 | github](https://github.com/s1s1ty/py-jsonq/issues). 480 | 481 | Also, you can shoot me an email to 482 | for suggestion or bugs. 483 | 484 | ## Credit 485 | 486 | Speical thanks to [Nahid Bin Azhar](https://github.com/nahid) for the inspiration and guidance for the package. 487 | 488 | ## Others Platform 489 | - [php-jsonq](https://github.com/nahid/jsonq) 490 | - [js-jsonq](https://github.com/me-shaon/js-jsonq) 491 | --------------------------------------------------------------------------------