├── README.org ├── json-to-org-table.el └── json_table.py /README.org: -------------------------------------------------------------------------------- 1 | #+TITLE: json-to-org-table 2 | 3 | * Overview 4 | 5 | =json-to-org-table= is an elisp function to turn json strings org tables while maintaining the linkage of nested structures. 6 | 7 | This was created to two primary use cases: 8 | - Human readability of API results 9 | - Exportablity of API results 10 | 11 | * Use 12 | Use it by calling 13 | #+begin_src emacs-lisp 14 | (json-to-org-table-parse-json-string ) 15 | (json-to-org-table-parse-json ) 16 | #+end_src 17 | 18 | Example: 19 | #+begin_src emacs-lisp 20 | (json-to-org-table-parse-json-string "{\"glossary\": {\"title\": \"example glossary\",\"GlossDiv\": {\"title\": \"S\",\"GlossList\": {\"GlossEntry\": {\"ID\": \"SGML\",\"SortAs\": \"SGML\",\"GlossTerm\": \"Standard Generalized Markup Language\",\"Acronym\": \"SGML\",\"Abbrev\": \"ISO 8879:1986\",\"GlossDef\": {\"para\": \"A meta-markup language, used to create markup languages such as DocBook.\",\"GlossSeeAlso\": [\"GML\", \"XML\"]},\"GlossSee\": \"markup\"}}}}}") 21 | #+end_src 22 | 23 | #+begin_src org 24 | | key | value | 25 | |----------+----------| 26 | | glossary | [[glossary]] | 27 | 28 | ,#+name: glossary 29 | | key | value | 30 | |----------+-------------------| 31 | | title | example glossary | 32 | | GlossDiv | [[GlossDiv_glossary]] | 33 | 34 | ,#+name: GlossDiv_glossary 35 | | key | value | 36 | |-----------+-----------------------------| 37 | | title | S | 38 | | GlossList | [[GlossList_GlossDiv_glossary]] | 39 | 40 | ,#+name: GlossList_GlossDiv_glossary 41 | | key | value | 42 | |------------+----------------------------------------| 43 | | GlossEntry | [[GlossEntry_GlossList_GlossDiv_glossary]] | 44 | 45 | ,#+name: GlossEntry_GlossList_GlossDiv_glossary 46 | | key | value | 47 | |-----------+-------------------------------------------------| 48 | | ID | SGML | 49 | | SortAs | SGML | 50 | | GlossTerm | Standard Generalized Markup Language | 51 | | Acronym | SGML | 52 | | Abbrev | ISO 8879:1986 | 53 | | GlossDef | [[GlossDef_GlossEntry_GlossList_GlossDiv_glossary]] | 54 | | GlossSee | markup | 55 | 56 | ,#+name: GlossDef_GlossEntry_GlossList_GlossDiv_glossary 57 | | key | value | 58 | |--------------+--------------------------------------------------------------------------| 59 | | para | A meta-markup language, used to create markup languages such as DocBook. | 60 | | GlossSeeAlso | [[GlossSeeAlso_GlossDef_GlossEntry_GlossList_GlossDiv_glossary]] | 61 | 62 | ,#+name: GlossSeeAlso_GlossDef_GlossEntry_GlossList_GlossDiv_glossary 63 | | GlossSeeAlso_GlossDef_GlossEntry_GlossList_GlossDiv_glossary | 64 | |--------------------------------------------------------------| 65 | | GML | 66 | | XML | 67 | #+end_src 68 | 69 | 70 | * Structures 71 | ** Hashmap 72 | Json 73 | #+begin_src json 74 | {"Celestial Body": "Luna", 75 | "Inhabitantants": "Loonies", 76 | "Government": null } 77 | #+end_src 78 | 79 | ...becomes 80 | #+begin_src org 81 | | key | value | 82 | |----------------+---------| 83 | | Celestial Body | Luna | 84 | | Inhabitants | Loonies | 85 | | Government | | 86 | #+end_src 87 | 88 | ** List of Hashmaps 89 | json 90 | #+begin_src json 91 | [{"type": "human", "name": "Manuel", "nickname": "Mannie"}, 92 | {"type": "AI", "name": "HOLMES IV", "nickname": "Mike"}, 93 | {"type": "human", "name": "Bernardo de la Paz", "nickname": "The Professor"}] 94 | #+end_src 95 | 96 | ...becomes 97 | #+begin_src org 98 | | type | name | nickname | 99 | |-------+--------------------+---------------| 100 | | human | Manuel | Mannie | 101 | | AI | HOLMES IV | Mike | 102 | | human | Bernardo de la Paz | The Professor | 103 | 104 | #+end_src 105 | 106 | ** A List of Lists 107 | json 108 | #+begin_src json 109 | [[3345246207 "launch" "hit"], 110 | [3345286207 "launch" "critical hit"], 111 | [3345946207 "launch" "hit"]] 112 | #+end_src 113 | 114 | ...becomes 115 | #+begin_src org 116 | | 3345246207 | launch | hit | 117 | | 3345286207 | launch | critical hit | 118 | | 3345946207 | launch | hit | 119 | #+end_src 120 | 121 | ** A List of none of the above 122 | json 123 | #+begin_src json 124 | ["The Dinkum Thinkum", "A Rabble in Arms", "TANSTAAFL"] 125 | #+end_src 126 | 127 | ...becomes 128 | #+begin_src org 129 | | The Dinkum Thinkum | 130 | | A Rabble in Arms | 131 | | TANSTAAFL | 132 | #+end_src 133 | 134 | ** The linkages are maintained between nested objects 135 | json 136 | #+begin_src json 137 | {"genre": "Science Fiction", 138 | "author": "Robert Heinlein", 139 | "main-characters": ["Mannie", 140 | "Wyoh", 141 | "Professor Bernardo de la Paz", 142 | "Mike", 143 | "Stu", 144 | "Hazel Meade"]} 145 | #+end_src 146 | 147 | ...becomes 148 | #+begin_src org 149 | | genre | Science Fiction | 150 | | author | Robert Heinlein | 151 | | main-characters | [[characters]] | 152 | 153 | ,#+name: characters 154 | | characters | 155 | |------------------------------| 156 | | Mannie | 157 | | Wyoh | 158 | | Professor Bernardo de la Paz | 159 | | Mike | 160 | | Stu | 161 | | Hazel Meade | 162 | #+end_src 163 | 164 | 165 | * WIP 166 | Org bable post processing with =json-to-org-table= 167 | 168 | #+begin_src org 169 | ,#+name: requester 170 | ,#+begin_src bash :results drawer 171 | curl -XGET https://jsonplaceholder.typicode.com/posts/1 172 | ,#+end_src 173 | 174 | ,#+RESULTS: requester 175 | :results: 176 | { 177 | "userId": 1, 178 | "id": 1, 179 | "title": "sunt aut facere repellat provident occaecati excepturi optio reprehenderit", 180 | "body": "quia et suscipit\nsuscipit recusandae consequuntur expedita et cum\nreprehenderit molestiae ut ut quas totam\nnostrum rerum est autem sunt rem eveniet architecto" 181 | } 182 | :end: 183 | 184 | ,#+name: to-json-table 185 | ,#+begin_src emacs-lisp :var str=requester() :results raw 186 | (json-to-org-table-parse-json-string str) 187 | ,#+end_src 188 | 189 | ,#+RESULTS: to-json-table 190 | | key | value | 191 | |--------+-------------------------------------------------------------------------------------------------------------------------------------------------------------| 192 | | userId | 1 | 193 | | id | 1 | 194 | | title | sunt aut facere repellat provident occaecati excepturi optio reprehenderit | 195 | | body | quia et suscipitsuscipit recusandae consequuntur expedita et cumreprehenderit molestiae ut ut quas totamnostrum rerum est autem sunt rem eveniet architecto | 196 | 197 | #+end_src 198 | -------------------------------------------------------------------------------- /json-to-org-table.el: -------------------------------------------------------------------------------- 1 | ;;; json-to-org-table.el --- Converts json string to linked org table -*- lexical-binding: t; -*- 2 | ;; 3 | ;; Copyright (C) 2020 Joshua Person 4 | ;; 5 | ;; Author: Joshua Person 6 | ;; Maintainer: Joshua Person 7 | ;; Created: December 06, 2020 8 | ;; Modified: December 06, 2020 9 | ;; Version: 0.0.1 10 | ;; Keywords: 11 | ;; Homepage: https://github.com/noonker/json-to-org-table 12 | ;; Package-Requires: ((emacs 27.1)) 13 | ;; 14 | ;; This file is not part of GNU Emacs. 15 | ;; 16 | ;;; Commentary: 17 | ;; 18 | ;; Converts json string to linked org table 19 | ;; 20 | ;;; Code: 21 | ;;; TODO: Better Examples 22 | 23 | (defvar j2t-debug nil) 24 | 25 | (defvar j2t-cs-map [("\r" "") 26 | ("\n" "")] 27 | "Map of characters to replace in json string.") 28 | 29 | (defun j2t-cs (str) 30 | "Clean String. 31 | Replace any string org mode wouldn't like according to the j2t-cs-map 32 | STR: String to be replaced 33 | RETURNS: A string with problematic characters returned" 34 | (seq-reduce 35 | (lambda (s m) (replace-regexp-in-string (car m) (cadr m) s)) 36 | j2t-cs-map (format "%s" str))) 37 | 38 | (defun j2t-lf (key &optional ref i) 39 | "Convert to link Link Format based on available args. 40 | KEY: String or Symbol that becomes the name of the table 41 | REF: If there is a Reference that becomes a subkey of the link 42 | I: Is the Index for links in vectors" 43 | (cond (i (format "[[%s_%s%s]]" key ref (number-to-string i))) 44 | (ref (format "[[%s_%s]]" key ref)) 45 | (t (format "[[%s]]" key)))) 46 | 47 | (defun j2t-hp (key value) 48 | "Hashmap Print prints a hashmap key-value table row. 49 | KEY: Hashmap key column 50 | VALUE: Hashmap value column" 51 | (format "|%s|%s|\n" (j2t-cs key) (j2t-cs value))) 52 | 53 | (defmacro j2t-c+ (&rest str) 54 | "HACK: Concatenates all args and update the value of cur with new STR. 55 | There's probably a better way to do this but this keeps things as clean 56 | as possible in the =tablify= function." 57 | `(setq cur (concat cur (concat ,@str )))) 58 | 59 | (defun j2t-parse-vector-vector (elt) 60 | "The row parser for a vector of vectors. 61 | ELT: Is a vector to be turned into a table row 62 | RETURNS: A table row representing the values of a vector" 63 | (let ((cur "")) 64 | (j2t-c+ "|") 65 | (mapc (lambda (x) (j2t-c+ (j2t-cs (format "%s" x)) "|" )) elt) 66 | (j2t-c+ "\n") 67 | cur 68 | ) 69 | ) 70 | 71 | (defun j2t-parse-hashmap-vector (elt &optional ref i) 72 | "A row parser for a vector element composed of hashmaps. 73 | ELT: A dotted pair cons representing a json hashmap 74 | REF: Reference if this is a linked table 75 | I: Optional Index for multiple linked tables 76 | RETURNS: The table row representing values of a hashmap and a 77 | list of subtables to create if applicable 78 | EXAMPLE: ((a . (b . 2)) (c . d) (e . f)) -> '(\"|[[a]]|d|f|]\" '(a (b .2) 1))" 79 | (let ((cur "") 80 | (keys (mapcar #'car elt)) 81 | (nex '())) 82 | (mapcar (lambda (key) 83 | (let ((value (alist-get key elt))) 84 | (if (consp value) 85 | (progn 86 | (j2t-c+ (j2t-lf key ref i) "|") 87 | (setq nex (append nex '('(key value i))))) 88 | (j2t-c+ (j2t-cs value) "|" ))) 89 | ) keys) 90 | `(,cur ,nex) 91 | )) 92 | 93 | 94 | (defun j2t-parse-hash-element (elt &optional ref) 95 | "A row parser for elements of a hash map. 96 | ELT: A dotted pair cons representing a json hashmap 97 | REF: Reference if this is a linked table 98 | RETURNS: Return an object who's first element is the generated string 99 | and the second element is the key if a new table is required. 100 | EXAMPLE: (a . b) -> '(\"|a|b|\n\" '())" 101 | (let ((key (car elt)) 102 | (val (cdr elt))) 103 | (cond ((not val) `(,(j2t-hp key "") nil)) 104 | ((vectorp val) `(,(j2t-hp key (j2t-lf key ref)) ,key)) 105 | ((consp val) `(,(j2t-hp key (j2t-lf key ref)) ,key)) 106 | (t `(,(j2t-hp key (format "%s" val)) nil)) 107 | ))) 108 | 109 | (defun j2t-tablify (elt &optional ref) 110 | "Function to be called recusrively to build an table. 111 | ELT: a json object 112 | REF: a reference is this is a linked table" 113 | (let ((cur "") 114 | (nex '())) 115 | (if j2t-debug (message (format "Got here! I was called with:\n elt: %s\n ref: %s\n" elt ref))) 116 | (if ref (j2t-c+ (format "#+name: %s\n" ref))) ;; If there's a reference add a name block to establish the linkage 117 | 118 | (cond 119 | ;; ----- Element is an empty vector ----- 120 | ((and (vectorp elt) 121 | (= (length elt) 0)) 122 | (j2t-c+ "| |\n")) 123 | 124 | ;; ----- Element is a hash-map ----- 125 | ((consp elt) 126 | (progn 127 | (j2t-c+ "|key|value|\n|-\n") ;; Add headers for hashmap table 128 | ;; For each element in the hashmap either add the value or add a link to the table of values 129 | (mapc (lambda (x) (let ((parsed (j2t-parse-hash-element x ref))) 130 | (format "x: %s\nparsed: %s" x parsed) 131 | (j2t-c+ (car parsed)) 132 | (if (cadr parsed) (setq nex (append (cdr parsed) nex))))) elt) 133 | (j2t-c+ "\n") 134 | ;; Recursively call this function to create any subtables 135 | (mapc (lambda (x) (progn (if j2t-debug (message (format "\nThe symbol I'm going to look up is: %s\n it's type is: %s\n and the value is: %s" x (type-of x) (alist-get x elt)))) 136 | (if ref 137 | (j2t-c+ (j2t-tablify (alist-get x elt) (format "%s_%s" x ref))) 138 | (j2t-c+ (j2t-tablify (alist-get x elt) (format "%s" x)))))) nex) 139 | )) 140 | 141 | ;; ----- Element is a vector and is a vector of hash-maps ----- 142 | ((and (vectorp elt) 143 | (consp (aref elt 0))) 144 | (let ((keys (mapc #'car (aref elt 0))) 145 | ) 146 | (j2t-c+ (format "|%s|\n" (string-join (mapcar (lambda (x) (format "%s" (car x))) keys) "|"))) 147 | (j2t-c+ "|-\n") 148 | (seq-map-indexed 149 | (lambda (elt idx) 150 | (let ((parsed (j2t-parse-hashmap-vector elt ref idx))) 151 | (j2t-c+ "|") 152 | (j2t-c+ (car parsed)) 153 | (j2t-c+ "\n") 154 | (if (cadr parsed) (setq nex (append (cdr parsed) nex)))) 155 | ) elt) 156 | ) 157 | 158 | ;; Recursively call this function to create any subtables 159 | (mapc (lambda (x) (let ((key (nth 0 x)) 160 | (value (nth 1 x)) 161 | (i (nth 2 x))) 162 | (j2t-c+ (j2t-tablify value (format "%s_%s%s" key ref (format "%s" i)) )))) nex) 163 | ) 164 | 165 | ;; ----- Element is a vector of vectors ----- 166 | ((and (vectorp elt) 167 | (vectorp (aref elt 0))) 168 | (let ((a nil)) 169 | (mapc (lambda (x) (j2t-c+ (j2t-parse-vector-vector x))) elt) 170 | (j2t-c+ "\n") 171 | )) 172 | 173 | ;; ----- Element is a vector of strings ----- 174 | ((vectorp elt) 175 | (j2t-c+ (format "|%s|\n|-\n" ref)) 176 | (mapc (lambda (x) (j2t-c+ "|" (j2t-cs x) "|" "\n")) elt) 177 | ) 178 | ) 179 | cur 180 | ) 181 | ) 182 | 183 | (defun json-to-org-table-parse-json-string (str) 184 | "Read a json string, parse it, and return a tablified string. 185 | STR: json string" 186 | (j2t-tablify (json-read-from-string str))) 187 | 188 | (defun json-to-org-table-parse-json (js) 189 | "Read an Emacs json object, parse it, and return a tablified string. 190 | The json should be in the format: 191 | - lists -> vectors 192 | - hashmaps -> alist cons 193 | - null -> \"\" 194 | - bool -> :json-true / :json-false 195 | JS: json object" 196 | (j2t-tablify js)) 197 | 198 | (provide 'json-to-org-table) 199 | 200 | ;;; json-to-org-table.el ends here 201 | -------------------------------------------------------------------------------- /json_table.py: -------------------------------------------------------------------------------- 1 | def cs(string): 2 | """Replaces any string org mode wouldn't like""" 3 | # string = string.strip("\'") 4 | string = string.replace("\r","") 5 | string = string.replace("\n","") 6 | return string 7 | 8 | def keys_to_headers(keys): 9 | pass 10 | 11 | def lf(key, ref=None, i=None): 12 | """Link format""" 13 | if i: 14 | return "[[{}_{}{}]]".format(key, ref, str(i)) 15 | elif ref: 16 | return "[[{}_{}]]".format(key, ref) 17 | else: 18 | return "[[{}]]".format(key) 19 | 20 | def dp(key, value): 21 | """Inputs a dict and outputs a table""" 22 | return "|{}|{}|\n".format(cs(key),cs(value)) 23 | 24 | def sp(key): 25 | return "|{}|\n".format(cs(key)) 26 | 27 | def tablify(elt, ref=None): 28 | # TODO Short List of Dicts 29 | # TODO List of Lists 30 | # All named go in drawer 31 | # Clean up spaces 32 | cur = "" 33 | nex = [] 34 | 35 | if isinstance(elt, dict): 36 | # Add the headers 37 | # If the reference is none this top level 38 | if ref == "": 39 | cur += "" 40 | elif ref: 41 | #cur += "* {}\n".format(ref) 42 | cur += "#+name: {}\n".format(ref) 43 | 44 | cur += "|key|value|\n|-\n" 45 | 46 | # Iterate over the keys 47 | for key in elt.keys(): 48 | value = elt[key] 49 | # If the value is None add empty string 50 | if not value: 51 | cur += dp(key, "") 52 | # If the value is a list create a new table 53 | elif isinstance(value, list): 54 | cur += dp(key, lf(key, ref)) 55 | nex.append(key) 56 | # If the value is a large dict- create a new table 57 | elif isinstance(value, dict): 58 | cur += dp(key, lf(key, ref)) 59 | nex.append(key) 60 | else: 61 | cur += dp(key, str(value)) 62 | 63 | if not ref: 64 | pass 65 | # cur += ":EXTRA:\n" 66 | for key in nex: 67 | if ref: 68 | cur += tablify(elt[key], key + "_" + ref) 69 | else: 70 | cur += tablify(elt[key], key) 71 | 72 | elif isinstance(elt, list) and isinstance(elt[0], dict): 73 | # Add the headers 74 | # If the reference is none this top level 75 | if ref == "": 76 | cur += "" 77 | elif ref: 78 | #cur += "* {}\n".format(ref) 79 | cur += "#+name: {}\n".format(ref) 80 | else: 81 | ref = "" 82 | keys = elt[0].keys() 83 | cur += "|" + "|".join(keys) + "|\n" 84 | cur += "|-\n" 85 | for i, list_item in enumerate(elt, start=1): 86 | cur += "|" 87 | for key in keys: 88 | value = list_item[key] 89 | if isinstance(value, dict): 90 | cur += lf(key, ref, i) 91 | cur += "|" 92 | nex.append((key, value, i,)) 93 | else: 94 | cur += cs(str(list_item[key])) + "|" 95 | cur += "\n" 96 | for key, value, i in nex: 97 | cur += tablify(value, "{}_{}{}".format(key, ref, str(i)) ) 98 | 99 | elif isinstance(elt, list): 100 | # Add the headers 101 | # If the reference is none this top level 102 | if ref == "": 103 | cur += "" 104 | elif ref: 105 | #cur += "* {}\n".format(ref) 106 | cur += "#+name: {}\n".format(ref) 107 | else: 108 | ref = "" 109 | 110 | for i, list_item in enumerate(elt, start=1): 111 | # If it's a dict with a single item just make a normal table 112 | #elif isinstance(list_item, dict) and len(elt) == 1: 113 | # cur += tablify(elt[0], ref="") 114 | 115 | # If it's a list of lists make a table with no headers 116 | if isinstance(list_item, list): 117 | cur += "|" 118 | for entry in list_item: 119 | cur += cs(str(list_item[key])) + "|" 120 | cur += "\n" 121 | 122 | # If it's a list of strings create a 1 column table with the key as the header 123 | else: 124 | if i == 1: 125 | cur += "|{}|\n|-\n".format(ref) 126 | cur += "|{}|".format(cs(str(list_item))) 127 | cur += "\n" 128 | if not ref: 129 | pass 130 | #cur += ":END:" 131 | else: 132 | cur += "\n" 133 | return cur 134 | --------------------------------------------------------------------------------