60 — Search — finding items in JSON
A catalog is a list of records — dicts with a name and tags. A search
loop asks for a keyword, walks the list, and keeps every record whose name
matches. This guide loads the catalog from a local file or an HTTP server and
matches text case-insensitively with .lower().
Steps
One record has a
nameand atagslist. The whole catalog is a list of those dicts, saved ascatalog.json:
run it →[ {"name": "Red Apple", "tags": ["fruit", "sweet"]}, {"name": "Red Rose", "tags": ["flower", "garden"]}, {"name": "Green Tea", "tags": ["drink", "warm"]}, {"name": "Blueberry", "tags": ["fruit", "blue"]} ]Load the catalog from the file next to the program, or from a local server when the file is missing.
os.path.existspicks the path; the server branch is theurlopenline from guide 55. Bothjson_loadandloadsreturn the same shape — a list of dicts:
run it →import os use file latest from json import loads from urllib.request import urlopen if os.path.exists("catalog.json"): items = json_load("catalog.json") else: url = "http://localhost:8000/catalog.json" items = loads(urlopen(url).read().decode("utf-8"))"Red" in "Red Apple"is case-sensitive, so a lowercase search would miss it..lower()copies a string in lowercase; on both sides it makes the match case-insensitive. Tags are a list, soword.lower() in item["tags"]checks the whole tag list — the sameinoperator on a string and a list. It printsRed Appletwice:
run it →name = "Red Apple" word = "red" if word.lower() in name.lower(): show name item = {"name": "Red Apple", "tags": ["fruit", "sweet"]} if "sweet" in item["tags"]: show item["name"]A
foundcounter turns "no matches" into a real answer instead of silence:
run it →found = 0 for item in items: if "red" in item["name"].lower(): show f"{item['name']}: {', '.join(item['tags'])}" found = found + 1 if found == 0: show "no matches"The whole program. Save
search.nmenext tocatalog.json:
run it →# search.nme — find items in a JSON catalog. # Run: nme r search # Type search, list, or quit. import os use file latest from json import loads from urllib.request import urlopen # Load the catalog from the local file, or from a local server. if os.path.exists("catalog.json"): items = json_load("catalog.json") else: url = "http://localhost:8000/catalog.json" items = loads(urlopen(url).read().decode("utf-8")) show f"catalog: {len(items)} items" while True: show "Commands: search, list, quit" ask command, "? " if command == "search": ask word, "Keyword? " found = 0 for item in items: # Lowercase both sides, so Red finds red. name = item["name"].lower() if word.lower() in name or word.lower() in item["tags"]: show f"{item['name']}: {', '.join(item['tags'])}" found = found + 1 if found == 0: show "no matches" elif command == "list": for item in items: show item["name"] elif command == "quit": show "Bye!" break else: show "Unknown command"The search checks the name and the tags, so
redfindsRed AppleandRed Roseby name, andbluefindsBlueberrythrough its tag.Run it and feed the commands through a pipe.
searchlooks upred,listprints every name, andquitleaves the loop:printf 'search\nred\nlist\nquit\n' | nme r searchcatalog: 4 items Commands: search, list, quit ? Keyword? Red Apple: fruit, sweet Red Rose: flower, garden Commands: search, list, quit ? Red Apple Red Rose Green Tea Blueberry Commands: search, list, quit ? Bye!A keyword with no matches prints
no matches. To use the server branch, startpython3 -m http.server 8000in the folder, renamecatalog.jsonaway, and the same program fetches the identical list over HTTP.
Try it yourself
Search the tags only — drop the name check and match word.lower() against
each item's tag list. Or print f"{found} matches" after the loop.
What you learned
- A catalog is a list of dicts, each with a
nameandtags. os.path.existschooses betweenjson_loadandloads(urlopen(...))..lower()on both sides makes aninmatch case-insensitive.inworks on both strings (name) and lists (tags).- A
foundcounter distinguishes no matches from an empty loop.