Compare commits
21 Commits
Author | SHA1 | Date |
---|---|---|
![]() |
b53fc1137f | |
![]() |
f48297e391 | |
![]() |
8fa26e86ef | |
![]() |
c5fcf50f2d | |
![]() |
cab4d367fb | |
![]() |
2c80b93b76 | |
![]() |
8abbb888dc | |
![]() |
3f2841d109 | |
![]() |
394353d5bf | |
![]() |
6c52f846b5 | |
![]() |
6fd83df6b8 | |
![]() |
9d17ced7f5 | |
![]() |
59c9f9babd | |
![]() |
4d199f3b93 | |
![]() |
7cf04726d2 | |
![]() |
966d66eb02 | |
![]() |
a261db2026 | |
![]() |
1431afd3d7 | |
![]() |
742b26b920 | |
![]() |
6e769bd62c | |
![]() |
391ebd8a8c |
|
@ -0,0 +1 @@
|
|||
/tests export-ignore
|
|
@ -7,6 +7,10 @@
|
|||
"caption": "Pretty JSON: Format and Sort JSON",
|
||||
"command": "pretty_json_and_sort"
|
||||
},
|
||||
{
|
||||
"caption": "Pretty JSON: Format JSON Lines",
|
||||
"command": "pretty_json_lines"
|
||||
},
|
||||
{
|
||||
"caption": "Pretty JSON: Minify (compress) JSON",
|
||||
"command": "un_pretty_json"
|
||||
|
|
|
@ -8,5 +8,6 @@
|
|||
"keep_arrays_single_line": false,
|
||||
"max_arrays_line_length": 120,
|
||||
"pretty_on_save": false,
|
||||
"validate_on_save": true
|
||||
"validate_on_save": true,
|
||||
"jq_binary": "jq"
|
||||
}
|
367
PrettyJson.py
367
PrettyJson.py
|
@ -1,16 +1,18 @@
|
|||
import sublime
|
||||
import sublime_plugin
|
||||
import decimal
|
||||
import sys
|
||||
import os
|
||||
import re
|
||||
from xml.etree import ElementTree
|
||||
import subprocess
|
||||
import sys
|
||||
from xml.dom import minidom
|
||||
from xml.etree import ElementTree
|
||||
|
||||
import sublime
|
||||
import sublime_plugin
|
||||
|
||||
try:
|
||||
basestring
|
||||
basestring
|
||||
except NameError:
|
||||
basestring = str
|
||||
basestring = str
|
||||
|
||||
try:
|
||||
# python 3 / Sublime Text 3
|
||||
|
@ -22,31 +24,35 @@ except ValueError:
|
|||
from simplejson import OrderedDict
|
||||
|
||||
SUBLIME_MAJOR_VERSION = int(sublime.version()) / 1000
|
||||
s = sublime.load_settings("Pretty JSON.sublime-settings")
|
||||
|
||||
jq_exits = False
|
||||
jq_init = False
|
||||
|
||||
import subprocess
|
||||
jq_path = str()
|
||||
|
||||
""" for OSX we need to manually add brew bin path so jq can be found """
|
||||
if sys.platform != 'win32' and '/usr/local/bin' not in os.environ['PATH']:
|
||||
os.environ["PATH"] += os.pathsep + '/usr/local/bin'
|
||||
if sys.platform != "win32" and "/usr/local/bin" not in os.environ["PATH"]:
|
||||
os.environ["PATH"] += os.pathsep + "/usr/local/bin"
|
||||
|
||||
""" defer jq presence check until the user tries to use it, include Package "Fix Mac Path" to resolve
|
||||
all homebrew issues (https://github.com/int3h/SublimeFixMacPath) """
|
||||
|
||||
|
||||
def check_jq():
|
||||
global jq_exits
|
||||
global jq_init
|
||||
global jq_exits, jq_init, jq_path
|
||||
|
||||
if not jq_init:
|
||||
jq_init = True
|
||||
jq_path = s.get("jq_binary", "jq")
|
||||
try:
|
||||
# checking if ./jq tool is available so we can use it
|
||||
s = subprocess.Popen(["jq", "--version"],
|
||||
stdin=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE,
|
||||
stdout=subprocess.PIPE)
|
||||
out, err = s.communicate()
|
||||
jq = subprocess.Popen(
|
||||
[jq_path, "--version"],
|
||||
stdin=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE,
|
||||
stdout=subprocess.PIPE,
|
||||
)
|
||||
out, err = jq.communicate()
|
||||
jq_exits = True
|
||||
except OSError:
|
||||
os_exception = sys.exc_info()[1]
|
||||
|
@ -54,18 +60,15 @@ def check_jq():
|
|||
jq_exits = False
|
||||
|
||||
|
||||
s = sublime.load_settings("Pretty JSON.sublime-settings")
|
||||
|
||||
|
||||
class PrettyJsonBaseCommand:
|
||||
json_error_matcher = re.compile(r"line (\d+)")
|
||||
force_sorting = False
|
||||
|
||||
@staticmethod
|
||||
def json_loads(selection):
|
||||
return json.loads(selection,
|
||||
object_pairs_hook=OrderedDict,
|
||||
parse_float=decimal.Decimal)
|
||||
return json.loads(
|
||||
selection, object_pairs_hook=OrderedDict, parse_float=decimal.Decimal
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def json_dumps(obj):
|
||||
|
@ -77,23 +80,27 @@ class PrettyJsonBaseCommand:
|
|||
line_separator = s.get("line_separator", ",")
|
||||
value_separator = s.get("value_separator", ": ")
|
||||
|
||||
output_json = json.dumps(obj,
|
||||
indent=s.get("indent", 2),
|
||||
ensure_ascii=s.get("ensure_ascii", False),
|
||||
sort_keys=sort_keys,
|
||||
separators=(line_separator, value_separator),
|
||||
use_decimal=True)
|
||||
output_json = json.dumps(
|
||||
obj,
|
||||
indent=s.get("indent", 2),
|
||||
ensure_ascii=s.get("ensure_ascii", False),
|
||||
sort_keys=sort_keys,
|
||||
separators=(line_separator, value_separator),
|
||||
use_decimal=True,
|
||||
)
|
||||
|
||||
# do we need try and shuffle things around ?
|
||||
post_process = s.get("keep_arrays_single_line", False)
|
||||
|
||||
if post_process:
|
||||
# find all array matches
|
||||
matches = re.findall(r"\[([^\[\]]+?)\]", output_json)
|
||||
matches = re.findall(r"(\[[^\[\]]+?\])", output_json)
|
||||
matches.sort(key=len, reverse=True)
|
||||
join_separator = line_separator.ljust(2)
|
||||
for m in matches:
|
||||
items = [a.strip() for a in m.split(line_separator.strip())]
|
||||
replacement = join_separator.join(items)
|
||||
content = m[1:-1]
|
||||
items = [a.strip() for a in content.split(line_separator.strip())]
|
||||
replacement = "[" + join_separator.join(items) + "]"
|
||||
# if line not gets too long, replace with single line
|
||||
if len(replacement) <= s.get("max_arrays_line_length", 120):
|
||||
output_json = output_json.replace(m, replacement, 1)
|
||||
|
@ -111,16 +118,32 @@ class PrettyJsonBaseCommand:
|
|||
if PrettyJsonBaseCommand.force_sorting:
|
||||
sort_keys = True
|
||||
|
||||
return json.dumps(obj,
|
||||
ensure_ascii=s.get("ensure_ascii", False),
|
||||
sort_keys=sort_keys,
|
||||
separators=(line_separator.strip(), value_separator.strip()),
|
||||
use_decimal=True)
|
||||
return json.dumps(
|
||||
obj,
|
||||
ensure_ascii=s.get("ensure_ascii", False),
|
||||
sort_keys=sort_keys,
|
||||
separators=(line_separator.strip(), value_separator.strip()),
|
||||
use_decimal=True,
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def get_selection_from_region(
|
||||
region: sublime.Region, regions_length: int, view: sublime.View
|
||||
) -> sublime.Region:
|
||||
selected_entire_file = False
|
||||
if region.empty() and regions_length > 1:
|
||||
return None, None
|
||||
elif region.empty() and s.get("use_entire_file_if_no_selection", True):
|
||||
selection = sublime.Region(0, view.size())
|
||||
selected_entire_file = True
|
||||
else:
|
||||
selection = region
|
||||
return selection, selected_entire_file
|
||||
|
||||
def highlight_error(self, message):
|
||||
|
||||
self.view.erase_regions('json_errors')
|
||||
self.view.erase_status('json_errors')
|
||||
self.view.erase_regions("json_errors")
|
||||
self.view.erase_status("json_errors")
|
||||
|
||||
m = self.json_error_matcher.search(message)
|
||||
if m:
|
||||
|
@ -128,22 +151,33 @@ class PrettyJsonBaseCommand:
|
|||
|
||||
# sometime we need to highlight one line above
|
||||
if "','" in message and "delimiter" in message:
|
||||
line_content = self.view.substr(self.view.full_line(self.view.text_point(line - 1, 0)))
|
||||
if line_content.strip()[-1] != ',' and line_content.strip() != '{' and line_content.strip() != '}':
|
||||
line_content = self.view.substr(
|
||||
self.view.full_line(self.view.text_point(line - 1, 0))
|
||||
)
|
||||
if (
|
||||
line_content.strip()[-1] != ","
|
||||
and line_content.strip() != "{"
|
||||
and line_content.strip() != "}"
|
||||
):
|
||||
line -= 1
|
||||
|
||||
if "control character '\\n'" in message:
|
||||
line_content = self.view.substr(self.view.full_line(self.view.text_point(line - 1, 0)))
|
||||
line_content = self.view.substr(
|
||||
self.view.full_line(self.view.text_point(line - 1, 0))
|
||||
)
|
||||
quotes = re.findall(r"\"", line_content)
|
||||
if len(quotes) % 2 != 0 and len(quotes) != 0:
|
||||
line -= 1
|
||||
|
||||
regions = [self.view.full_line(self.view.text_point(line, 0)), ]
|
||||
regions = [
|
||||
self.view.full_line(self.view.text_point(line, 0)),
|
||||
]
|
||||
|
||||
self.view.add_regions('json_errors', regions, 'invalid', 'dot',
|
||||
sublime.DRAW_OUTLINED)
|
||||
self.view.add_regions(
|
||||
"json_errors", regions, "invalid", "dot", sublime.DRAW_OUTLINED
|
||||
)
|
||||
self.view.show(regions[0])
|
||||
self.view.set_status('json_errors', message)
|
||||
self.view.set_status("json_errors", message)
|
||||
|
||||
def show_exception(self):
|
||||
exc = sys.exc_info()[1]
|
||||
|
@ -152,44 +186,66 @@ class PrettyJsonBaseCommand:
|
|||
|
||||
def change_syntax(self):
|
||||
""" Changes syntax to JSON if its in plain text """
|
||||
if "Plain text" in self.view.settings().get('syntax'):
|
||||
if "Plain text" in self.view.settings().get("syntax"):
|
||||
self.view.set_syntax_file("Packages/JavaScript/JSON.tmLanguage")
|
||||
|
||||
|
||||
class PrettyJsonValidate(PrettyJsonBaseCommand, sublime_plugin.TextCommand):
|
||||
def run(self, edit):
|
||||
self.view.erase_regions('json_errors')
|
||||
for region in self.view.sel():
|
||||
# If no selection, use the entire file as the selection
|
||||
if region.empty() and s.get("use_entire_file_if_no_selection", True):
|
||||
selection = sublime.Region(0, self.view.size())
|
||||
selected_entire_file = True
|
||||
else:
|
||||
selection = region
|
||||
self.view.erase_regions("json_errors")
|
||||
regions = self.view.sel()
|
||||
for region in regions:
|
||||
(
|
||||
selection,
|
||||
selected_entire_file,
|
||||
) = PrettyJsonBaseCommand.get_selection_from_region(
|
||||
region=region, regions_length=len(regions), view=self.view
|
||||
)
|
||||
if selection is None:
|
||||
continue
|
||||
|
||||
try:
|
||||
obj = self.json_loads(self.view.substr(selection))
|
||||
sublime.message_dialog("JSON is Valid")
|
||||
self.json_loads(self.view.substr(selection))
|
||||
except Exception:
|
||||
self.show_exception()
|
||||
sublime.message_dialog("Invalid JSON")
|
||||
return
|
||||
|
||||
try:
|
||||
decoder = json.JSONDecoder(object_pairs_hook=self.duplicate_key_hook)
|
||||
decoder.decode(self.view.substr(selection))
|
||||
except Exception:
|
||||
self.show_exception()
|
||||
sublime.message_dialog("Invalid JSON")
|
||||
return
|
||||
|
||||
sublime.message_dialog("Valid JSON")
|
||||
|
||||
def duplicate_key_hook(self, pairs):
|
||||
result = dict()
|
||||
for key, val in pairs:
|
||||
if key in result:
|
||||
raise KeyError("Duplicate key specified: %s" % key)
|
||||
result[key] = val
|
||||
return result
|
||||
|
||||
|
||||
class PrettyJsonCommand(PrettyJsonBaseCommand, sublime_plugin.TextCommand):
|
||||
|
||||
""" Pretty Print JSON """
|
||||
|
||||
def run(self, edit):
|
||||
self.view.erase_regions('json_errors')
|
||||
for region in self.view.sel():
|
||||
|
||||
selected_entire_file = False
|
||||
|
||||
# If no selection, use the entire file as the selection
|
||||
if region.empty() and s.get("use_entire_file_if_no_selection", True):
|
||||
selection = sublime.Region(0, self.view.size())
|
||||
selected_entire_file = True
|
||||
else:
|
||||
selection = region
|
||||
self.view.erase_regions("json_errors")
|
||||
regions = self.view.sel()
|
||||
for region in regions:
|
||||
(
|
||||
selection,
|
||||
selected_entire_file,
|
||||
) = PrettyJsonBaseCommand.get_selection_from_region(
|
||||
region=region, regions_length=len(regions), view=self.view
|
||||
)
|
||||
if selection is None:
|
||||
continue
|
||||
|
||||
try:
|
||||
selection_text = self.view.substr(selection)
|
||||
|
@ -200,44 +256,113 @@ class PrettyJsonCommand(PrettyJsonBaseCommand, sublime_plugin.TextCommand):
|
|||
self.change_syntax()
|
||||
|
||||
except Exception:
|
||||
amount_of_single_quotes = re.findall(r"(\'[^\']+\'?)", selection_text)
|
||||
amount_of_double_quotes = re.findall(r"(\"[^\"]+\"?)", selection_text)
|
||||
try:
|
||||
amount_of_single_quotes = re.findall(
|
||||
r"(\'[^\']+\'?)", selection_text
|
||||
)
|
||||
amount_of_double_quotes = re.findall(
|
||||
r"(\"[^\"]+\"?)", selection_text
|
||||
)
|
||||
|
||||
if len(amount_of_single_quotes) >= len(amount_of_double_quotes):
|
||||
selection_text_modified = re.sub(r"(?:\'([^\']+)\'?)", r'"\1"', selection_text)
|
||||
obj = self.json_loads(selection_text_modified)
|
||||
self.view.replace(edit, selection, self.json_dumps(obj))
|
||||
if len(amount_of_single_quotes) >= len(amount_of_double_quotes):
|
||||
selection_text_modified = re.sub(
|
||||
r"(?:\'([^\']+)\'?)", r'"\1"', selection_text
|
||||
)
|
||||
obj = self.json_loads(selection_text_modified)
|
||||
self.view.replace(edit, selection, self.json_dumps(obj))
|
||||
|
||||
if selected_entire_file:
|
||||
self.change_syntax()
|
||||
else:
|
||||
if selected_entire_file:
|
||||
self.change_syntax()
|
||||
else:
|
||||
self.show_exception()
|
||||
except:
|
||||
self.show_exception()
|
||||
|
||||
|
||||
class PrettyJsonAndSortCommand(PrettyJsonCommand, sublime_plugin.TextCommand):
|
||||
|
||||
""" Pretty print json with forced sorting """
|
||||
|
||||
def run(self, edit):
|
||||
PrettyJsonBaseCommand.force_sorting = True
|
||||
PrettyJsonCommand.run(self, edit)
|
||||
PrettyJsonBaseCommand.force_sorting = False
|
||||
|
||||
|
||||
class PrettyJsonLinesCommand(PrettyJsonCommand, sublime_plugin.TextCommand):
|
||||
|
||||
"""
|
||||
Description: Pretty print json lines https://jsonlines.org
|
||||
"""
|
||||
|
||||
def run(self, edit):
|
||||
self.view.erase_regions("json_errors")
|
||||
regions = self.view.sel()
|
||||
for region in regions:
|
||||
(
|
||||
selection,
|
||||
selected_entire_file,
|
||||
) = self.get_selection_from_region(
|
||||
region=region, regions_length=len(regions), view=self.view
|
||||
)
|
||||
if selection is None:
|
||||
continue
|
||||
|
||||
for jsonl in sorted(self.view.split_by_newlines(selection), reverse=True):
|
||||
if self.view.substr(jsonl).strip() == "":
|
||||
continue
|
||||
|
||||
if jsonl.empty() and len(jsonl) > 1:
|
||||
continue
|
||||
|
||||
try:
|
||||
selection_text = self.view.substr(jsonl)
|
||||
obj = self.json_loads(selection_text)
|
||||
self.view.replace(edit, jsonl, self.json_dumps(obj))
|
||||
|
||||
if selected_entire_file:
|
||||
self.change_syntax()
|
||||
|
||||
except Exception:
|
||||
try:
|
||||
amount_of_single_quotes = re.findall(
|
||||
r"(\'[^\']+\'?)", selection_text
|
||||
)
|
||||
amount_of_double_quotes = re.findall(
|
||||
r"(\"[^\"]+\"?)", selection_text
|
||||
)
|
||||
|
||||
if len(amount_of_single_quotes) >= len(amount_of_double_quotes):
|
||||
selection_text_modified = re.sub(
|
||||
r"(?:\'([^\']+)\'?)", r'"\1"', selection_text
|
||||
)
|
||||
obj = self.json_loads(selection_text_modified)
|
||||
self.view.replace(edit, jsonl, self.json_dumps(obj))
|
||||
|
||||
if selected_entire_file:
|
||||
self.change_syntax()
|
||||
else:
|
||||
self.show_exception()
|
||||
except:
|
||||
self.show_exception()
|
||||
|
||||
|
||||
class UnPrettyJsonCommand(PrettyJsonBaseCommand, sublime_plugin.TextCommand):
|
||||
|
||||
""" Compress/minify JSON - it makes json as one-liner """
|
||||
|
||||
def run(self, edit):
|
||||
self.view.erase_regions('json_errors')
|
||||
for region in self.view.sel():
|
||||
|
||||
selected_entire_file = False
|
||||
|
||||
# If no selection, use the entire file as the selection
|
||||
if region.empty() and s.get("use_entire_file_if_no_selection", True):
|
||||
selection = sublime.Region(0, self.view.size())
|
||||
selected_entire_file = True
|
||||
else:
|
||||
selection = region
|
||||
self.view.erase_regions("json_errors")
|
||||
regions = self.view.sel()
|
||||
for region in regions:
|
||||
(
|
||||
selection,
|
||||
selected_entire_file,
|
||||
) = PrettyJsonBaseCommand.get_selection_from_region(
|
||||
region=region, regions_length=len(regions), view=self.view
|
||||
)
|
||||
if selection is None:
|
||||
continue
|
||||
|
||||
try:
|
||||
obj = self.json_loads(self.view.substr(selection))
|
||||
|
@ -254,43 +379,50 @@ class JqPrettyJson(sublime_plugin.WindowCommand):
|
|||
"""
|
||||
Allows work with ./jq
|
||||
"""
|
||||
|
||||
def run(self):
|
||||
check_jq()
|
||||
if jq_exits:
|
||||
self.window.show_input_panel("Enter ./jq filter expression", ".",
|
||||
self.done, None, None)
|
||||
self.window.show_input_panel(
|
||||
"Enter ./jq filter expression", ".", self.done, None, None
|
||||
)
|
||||
else:
|
||||
sublime.status_message('./jq tool is not available on your system. http://stedolan.github.io/jq')
|
||||
sublime.status_message(
|
||||
"./jq tool is not available on your system. http://stedolan.github.io/jq"
|
||||
)
|
||||
|
||||
def get_content(self):
|
||||
""" returns content of active view or selected region """
|
||||
view = self.window.active_view()
|
||||
selection = ""
|
||||
for region in view.sel():
|
||||
# If no selection, use the entire file as the selection
|
||||
if region.empty():
|
||||
selection = sublime.Region(0, view.size())
|
||||
else:
|
||||
selection = region
|
||||
regions = view.sel()
|
||||
for region in regions:
|
||||
(selection, _,) = PrettyJsonBaseCommand.get_selection_from_region(
|
||||
region=region, regions_length=len(regions), view=view
|
||||
)
|
||||
if selection is None:
|
||||
continue
|
||||
return view.substr(selection)
|
||||
|
||||
def done(self, query):
|
||||
try:
|
||||
p = subprocess.Popen(["jq", query],
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE,
|
||||
stdin=subprocess.PIPE)
|
||||
p = subprocess.Popen(
|
||||
[jq_path, query],
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE,
|
||||
stdin=subprocess.PIPE,
|
||||
)
|
||||
|
||||
raw_json = self.get_content()
|
||||
|
||||
if SUBLIME_MAJOR_VERSION < 3:
|
||||
if sys.platform != 'win32':
|
||||
if sys.platform != "win32":
|
||||
out, err = p.communicate(bytes(raw_json))
|
||||
else:
|
||||
out, err = p.communicate(unicode(raw_json).encode('utf-8'))
|
||||
out, err = p.communicate(unicode(raw_json).encode("utf-8"))
|
||||
else:
|
||||
out, err = p.communicate(bytes(raw_json, "utf-8"))
|
||||
output = out.decode("UTF-8").strip()
|
||||
output = out.decode("UTF-8").replace(os.linesep, "\n").strip()
|
||||
if output:
|
||||
view = self.window.new_file()
|
||||
view.run_command("jq_pretty_json_out", {"jq_output": output})
|
||||
|
@ -305,9 +437,10 @@ class JsonToXml(PrettyJsonBaseCommand, sublime_plugin.TextCommand):
|
|||
"""
|
||||
converts Json to XML
|
||||
"""
|
||||
|
||||
def run(self, edit):
|
||||
""" overwriting base class run function to remove intent """
|
||||
self.view.erase_regions('json_errors')
|
||||
self.view.erase_regions("json_errors")
|
||||
for region in self.view.sel():
|
||||
|
||||
selected_entire_file = False
|
||||
|
@ -339,7 +472,9 @@ class JsonToXml(PrettyJsonBaseCommand, sublime_plugin.TextCommand):
|
|||
# for some reason python 2.6 shipped with ST2
|
||||
# does not have pyexpat
|
||||
if SUBLIME_MAJOR_VERSION >= 3:
|
||||
xml_string = minidom.parseString(xml_string).toprettyxml(encoding="UTF-8")
|
||||
xml_string = minidom.parseString(xml_string).toprettyxml(
|
||||
encoding="UTF-8"
|
||||
)
|
||||
|
||||
if type(xml_string) is bytes:
|
||||
xml_string = xml_string.decode("utf-8")
|
||||
|
@ -354,14 +489,14 @@ class JsonToXml(PrettyJsonBaseCommand, sublime_plugin.TextCommand):
|
|||
|
||||
def indent_for_26(self, elem, level=0):
|
||||
""" intent of ElementTree in case it's py26 without minidom/pyexpat """
|
||||
i = "\n" + level*" "
|
||||
i = "\n" + level * " "
|
||||
if len(elem):
|
||||
if not elem.text or not elem.text.strip():
|
||||
elem.text = i + " "
|
||||
if not elem.tail or not elem.tail.strip():
|
||||
elem.tail = i
|
||||
for elem in elem:
|
||||
self.indent_for_26(elem, level+1)
|
||||
self.indent_for_26(elem, level + 1)
|
||||
if not elem.tail or not elem.tail.strip():
|
||||
elem.tail = i
|
||||
else:
|
||||
|
@ -375,12 +510,12 @@ class JsonToXml(PrettyJsonBaseCommand, sublime_plugin.TextCommand):
|
|||
e = ElementTree.Element(i)
|
||||
element.append(self.traverse(e, json_dict[i]))
|
||||
elif type(json_dict) is list:
|
||||
e_items = ElementTree.Element('items')
|
||||
e_items = ElementTree.Element("items")
|
||||
for i in json_dict:
|
||||
e_items.append(self.traverse(ElementTree.Element('item'), i))
|
||||
e_items.append(self.traverse(ElementTree.Element("item"), i))
|
||||
element.append(e_items)
|
||||
else:
|
||||
element.set('value', str(json_dict))
|
||||
element.set("value", str(json_dict))
|
||||
|
||||
return element
|
||||
|
||||
|
@ -390,7 +525,7 @@ class JsonToXml(PrettyJsonBaseCommand, sublime_plugin.TextCommand):
|
|||
|
||||
|
||||
class JqPrettyJsonOut(sublime_plugin.TextCommand):
|
||||
def run(self, edit, jq_output=''):
|
||||
def run(self, edit, jq_output=""):
|
||||
self.view.insert(edit, 0, jq_output)
|
||||
|
||||
|
||||
|
@ -403,7 +538,7 @@ class PrettyJsonGotoSymbolCommand(PrettyJsonBaseCommand, sublime_plugin.TextComm
|
|||
|
||||
try:
|
||||
json_data = self.json_loads(content)
|
||||
self.generate_items(json_data, '')
|
||||
self.generate_items(json_data, "")
|
||||
sublime.active_window().show_quick_panel(self.items, self.goto)
|
||||
except Exception:
|
||||
self.show_exception()
|
||||
|
@ -411,14 +546,14 @@ class PrettyJsonGotoSymbolCommand(PrettyJsonBaseCommand, sublime_plugin.TextComm
|
|||
def generate_items(self, json_data, root_key):
|
||||
if isinstance(json_data, OrderedDict):
|
||||
for key in json_data:
|
||||
new_key_name = root_key + '.' + key
|
||||
self.items.append('%s' % new_key_name)
|
||||
new_key_name = root_key + "." + key
|
||||
self.items.append("%s" % new_key_name)
|
||||
self.goto_items.append('"%s"' % key)
|
||||
self.generate_items(json_data[key], new_key_name)
|
||||
elif isinstance(json_data, list):
|
||||
for index, item in enumerate(json_data):
|
||||
if isinstance(item, basestring):
|
||||
self.items.append('%s' % root_key + '.' + item)
|
||||
self.items.append("%s" % root_key + "." + item)
|
||||
self.goto_items.append('"%s"' % item)
|
||||
|
||||
def goto(self, pos):
|
||||
|
|
|
@ -0,0 +1,9 @@
|
|||
{
|
||||
"install": "messages/install.txt",
|
||||
"1.0.0": "messages/1.0.0.txt",
|
||||
"1.0.1": "messages/1.0.1.txt",
|
||||
"1.0.2": "messages/1.0.2.txt",
|
||||
"1.0.3": "messages/1.0.3.txt",
|
||||
"1.0.4": "messages/1.0.4.txt"
|
||||
"1.0.5": "messages/1.0.5.txt"
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
Version 1.0.4 (April 17, 2020)
|
||||
-------------------------------
|
||||
|
||||
* Added `.gitattributes` to stop
|
||||
shipping `/tests` with releases
|
|
@ -0,0 +1,11 @@
|
|||
Version 1.0.1 (April 17, 2020)
|
||||
-------------------------------
|
||||
|
||||
* Implement the following PR's
|
||||
- #80 - Fixes issues with incorrect
|
||||
line returns
|
||||
- #81 - Fixes sorting arrays
|
||||
- #88 - Fixes array sorting
|
||||
- #112 - Remove `.no-sublime-package`
|
||||
Broken logic for empty regions and entire file
|
||||
- #113 - Duplicate key detection
|
|
@ -0,0 +1,6 @@
|
|||
Version 1.0.2 (April 17, 2020)
|
||||
-------------------------------
|
||||
|
||||
* Implement the following PR's
|
||||
- #114 - implement configurable
|
||||
jq path
|
|
@ -0,0 +1,4 @@
|
|||
Version 1.0.3 (April 17, 2020)
|
||||
-------------------------------
|
||||
|
||||
* Fix incorrect message for Valid JSON
|
|
@ -0,0 +1,7 @@
|
|||
Version 1.0.4 (April 22, 2020)
|
||||
-------------------------------
|
||||
|
||||
* Sublime Text 3 Users
|
||||
If upgrading from any version prior to 1.0.0
|
||||
such as `.no-sublime-package` release
|
||||
please full restart Sublime Text
|
|
@ -0,0 +1,5 @@
|
|||
Version 1.0.5 (April 28, 2020)
|
||||
-------------------------------
|
||||
|
||||
* Sublime Text 3 Users
|
||||
- Update to latest Simple Json
|
|
@ -0,0 +1,103 @@
|
|||
[](https://travis-ci.org/dzhibas/SublimePrettyJson)
|
||||
|
||||
Prettify/Minify/Query/Goto/Validate/Lint JSON plugin for Sublime Text 2 & 3
|
||||
|
||||
## Installation
|
||||
|
||||
Install this sublime text 2/3 package via [Package Control](https://sublime.wbond.net) search for package: "[**Pretty JSON**](https://sublime.wbond.net/packages/Pretty%20JSON)"
|
||||
|
||||
### or manually install
|
||||
|
||||
- `cd <Packages directory>` (for example on Mac it is `~/Library/Application\ Support/Sublime\ Text\ 2/Packages` or `~/Library/Application\ Support/Sublime\ Text\ 3/Packages`)
|
||||
- `git clone https://github.com/dzhibas/SublimePrettyJson.git`
|
||||
|
||||
## Usage
|
||||
|
||||
To prettify JSON, make selection of json (or else it will try to use full view buffer) and press keys:
|
||||
|
||||
- Linux: <kbd>ctrl+alt+j</kbd>
|
||||
- Windows: <kbd>ctrl+alt+j</kbd>
|
||||
- OS X: <kbd>cmd+ctrl+j</kbd>
|
||||
|
||||
or through Command Palette <kbd>Ctrl+Shift+P</kbd> find "Pretty JSON: Format (Pretty Print) JSON" (you can search for part of it like 'pretty format')
|
||||
|
||||
If selection is empty and configuration entry **use_entire_file_if_no_selection** is true, tries to prettify whole file
|
||||
|
||||
If JSON is not valid it will be displayed in status bar of Sublime Text
|
||||
|
||||
### Validate JSON
|
||||
|
||||
Using Command Palette <kbd>Ctrl+Shift+P</kbd> find "Pretty JSON: Validate" (you can search for partial string 'validate') this will validate selection or full file and will show in dialog if it's valid or invalid. In case of found errors view will jump to error and will highlight it
|
||||
|
||||
### Compress / Minify JSON
|
||||
|
||||
Using Command Palette <kbd>Ctrl+Shift+P</kbd> find "Pretty JSON: Minify (compress) JSON" (you can search for part of it like 'json minify') this will make selection or full buffer as single line JSON which later you can use in command lines (curl/httpie) or somewhere else...
|
||||
|
||||
To map a key combination like <kbd>Ctrl+Alt+M</kbd> to the Minify command, you can add a setting like this to your .sublime-keymap file (eg: `Packages/User/Default (Windows).sublime-keymap`):
|
||||
|
||||
```
|
||||
{ "keys": [ "ctrl+alt+m" ], "command": "un_pretty_json" }
|
||||
```
|
||||
|
||||
### Convert JSON to XML
|
||||
|
||||
Using Command Palette <kbd>Ctrl+Shift+P</kbd> search fo "Pretty JSON: JSON 2 XML" (you can search for part of it like '2XML') this will convert your selected JSON of full buffer to XML and replace syntax and buffer to XML output
|
||||
|
||||
## ./jQ query/filter usage
|
||||
|
||||
Demo:
|
||||
|
||||
[](http://i.imgur.com/sw7Hrsp.gif?1)
|
||||
|
||||
If on your machine "[./jq](http://stedolan.github.io/jq/)" tool is available with <kdb>ctrl+atl+shift+j</kdb> you can run against your json. output will be opened in new view so you can once again apply jq on new buffer
|
||||
|
||||
You can find instructions of tool here:
|
||||
|
||||
http://stedolan.github.io/jq/
|
||||
|
||||
## Default configuration
|
||||
|
||||
**use_entire_file_if_no_selection** - true
|
||||
|
||||
**indent** - 2
|
||||
`int used for how many spaces to use for indent, replace it with value "\t" and tabs will be used instead`
|
||||
|
||||
**sort_keys** - false
|
||||
|
||||
**ensure_ascii** - false
|
||||
|
||||
**line_separator** - ","
|
||||
|
||||
**value_separator** - ": "
|
||||
`value separator in config, so if you need to get rid of extra space you can remove it with this param`
|
||||
|
||||
**keep_arrays_single_line** - false
|
||||
`if we need to re-structure arrays and make them single-line`
|
||||
|
||||
**max_arrays_line_length** - 120
|
||||
`if array for example '["a", "b", 123213, ....]' length will reach max it will be kept multi-line (for beauty)`
|
||||
|
||||
**pretty_on_save** - false
|
||||
`do we need to automatically Pretty JSON on save`
|
||||
|
||||
**validate_on_save** - true
|
||||
`do we need validate JSON files on each save`
|
||||
|
||||
## Using tabs for indentation
|
||||
|
||||
You can change configuration key **indent** to string value "\t" or any other string
|
||||
|
||||
```
|
||||
"indent" : "\t",
|
||||
```
|
||||
|
||||
Be sure "Indent Using Spaces" is unchecked otherwise you will not see effect and ST2/3 will convert it back to spaces
|
||||
|
||||
## Thanks
|
||||
|
||||
- @the3rdhbtaylor https://github.com/the3rdhbtaylor
|
||||
- @crcastle https://github.com/crcastle
|
||||
|
||||
## Others
|
||||
|
||||
If you YAMLing then maybe you interested in this plugin: https://github.com/aukaost/SublimePrettyYAML
|
|
@ -5,39 +5,30 @@ if sys.version_info[0] < 3:
|
|||
PY3 = False
|
||||
def b(s):
|
||||
return s
|
||||
def u(s):
|
||||
return unicode(s, 'unicode_escape')
|
||||
import cStringIO as StringIO
|
||||
StringIO = BytesIO = StringIO.StringIO
|
||||
try:
|
||||
from cStringIO import StringIO
|
||||
except ImportError:
|
||||
from StringIO import StringIO
|
||||
BytesIO = StringIO
|
||||
text_type = unicode
|
||||
binary_type = str
|
||||
string_types = (basestring,)
|
||||
integer_types = (int, long)
|
||||
unichr = unichr
|
||||
reload_module = reload
|
||||
def fromhex(s):
|
||||
return s.decode('hex')
|
||||
|
||||
else:
|
||||
PY3 = True
|
||||
from imp import reload as reload_module
|
||||
import codecs
|
||||
if sys.version_info[:2] >= (3, 4):
|
||||
from importlib import reload as reload_module
|
||||
else:
|
||||
from imp import reload as reload_module
|
||||
def b(s):
|
||||
return codecs.latin_1_encode(s)[0]
|
||||
def u(s):
|
||||
return s
|
||||
import io
|
||||
StringIO = io.StringIO
|
||||
BytesIO = io.BytesIO
|
||||
return bytes(s, 'latin1')
|
||||
from io import StringIO, BytesIO
|
||||
text_type = str
|
||||
binary_type = bytes
|
||||
string_types = (str,)
|
||||
integer_types = (int,)
|
||||
unichr = chr
|
||||
|
||||
def unichr(s):
|
||||
return u(chr(s))
|
||||
|
||||
def fromhex(s):
|
||||
return bytes.fromhex(s)
|
||||
|
||||
long_type = integer_types[-1]
|
||||
long_type = integer_types[-1]
|
|
@ -4,8 +4,9 @@ from __future__ import absolute_import
|
|||
import re
|
||||
import sys
|
||||
import struct
|
||||
from .compat import fromhex, b, u, text_type, binary_type, PY3, unichr
|
||||
from .scanner import make_scanner
|
||||
from .compat import PY3, unichr
|
||||
from .scanner import make_scanner, JSONDecodeError
|
||||
|
||||
def _import_c_scanstring():
|
||||
try:
|
||||
from ._speedups import scanstring
|
||||
|
@ -14,72 +15,23 @@ def _import_c_scanstring():
|
|||
return None
|
||||
c_scanstring = _import_c_scanstring()
|
||||
|
||||
# NOTE (3.1.0): JSONDecodeError may still be imported from this module for
|
||||
# compatibility, but it was never in the __all__
|
||||
__all__ = ['JSONDecoder']
|
||||
|
||||
FLAGS = re.VERBOSE | re.MULTILINE | re.DOTALL
|
||||
|
||||
def _floatconstants():
|
||||
_BYTES = fromhex('7FF80000000000007FF0000000000000')
|
||||
# The struct module in Python 2.4 would get frexp() out of range here
|
||||
# when an endian is specified in the format string. Fixed in Python 2.5+
|
||||
if sys.byteorder != 'big':
|
||||
_BYTES = _BYTES[:8][::-1] + _BYTES[8:][::-1]
|
||||
nan, inf = struct.unpack('dd', _BYTES)
|
||||
if sys.version_info < (2, 6):
|
||||
_BYTES = '7FF80000000000007FF0000000000000'.decode('hex')
|
||||
nan, inf = struct.unpack('>dd', _BYTES)
|
||||
else:
|
||||
nan = float('nan')
|
||||
inf = float('inf')
|
||||
return nan, inf, -inf
|
||||
|
||||
NaN, PosInf, NegInf = _floatconstants()
|
||||
|
||||
|
||||
class JSONDecodeError(ValueError):
|
||||
"""Subclass of ValueError with the following additional properties:
|
||||
|
||||
msg: The unformatted error message
|
||||
doc: The JSON document being parsed
|
||||
pos: The start index of doc where parsing failed
|
||||
end: The end index of doc where parsing failed (may be None)
|
||||
lineno: The line corresponding to pos
|
||||
colno: The column corresponding to pos
|
||||
endlineno: The line corresponding to end (may be None)
|
||||
endcolno: The column corresponding to end (may be None)
|
||||
|
||||
"""
|
||||
def __init__(self, msg, doc, pos, end=None):
|
||||
ValueError.__init__(self, errmsg(msg, doc, pos, end=end))
|
||||
self.msg = msg
|
||||
self.doc = doc
|
||||
self.pos = pos
|
||||
self.end = end
|
||||
self.lineno, self.colno = linecol(doc, pos)
|
||||
if end is not None:
|
||||
self.endlineno, self.endcolno = linecol(doc, end)
|
||||
else:
|
||||
self.endlineno, self.endcolno = None, None
|
||||
|
||||
|
||||
def linecol(doc, pos):
|
||||
lineno = doc.count('\n', 0, pos) + 1
|
||||
if lineno == 1:
|
||||
colno = pos
|
||||
else:
|
||||
colno = pos - doc.rindex('\n', 0, pos)
|
||||
return lineno, colno
|
||||
|
||||
|
||||
def errmsg(msg, doc, pos, end=None):
|
||||
# Note that this function is called from _speedups
|
||||
lineno, colno = linecol(doc, pos)
|
||||
if end is None:
|
||||
#fmt = '{0}: line {1} column {2} (char {3})'
|
||||
#return fmt.format(msg, lineno, colno, pos)
|
||||
fmt = '%s: line %d column %d (char %d)'
|
||||
return fmt % (msg, lineno, colno, pos)
|
||||
endlineno, endcolno = linecol(doc, end)
|
||||
#fmt = '{0}: line {1} column {2} - line {3} column {4} (char {5} - {6})'
|
||||
#return fmt.format(msg, lineno, colno, endlineno, endcolno, pos, end)
|
||||
fmt = '%s: line %d column %d - line %d column %d (char %d - %d)'
|
||||
return fmt % (msg, lineno, colno, endlineno, endcolno, pos, end)
|
||||
|
||||
|
||||
_CONSTANTS = {
|
||||
'-Infinity': NegInf,
|
||||
'Infinity': PosInf,
|
||||
|
@ -88,21 +40,20 @@ _CONSTANTS = {
|
|||
|
||||
STRINGCHUNK = re.compile(r'(.*?)(["\\\x00-\x1f])', FLAGS)
|
||||
BACKSLASH = {
|
||||
'"': u('"'), '\\': u('\u005c'), '/': u('/'),
|
||||
'b': u('\b'), 'f': u('\f'), 'n': u('\n'), 'r': u('\r'), 't': u('\t'),
|
||||
'"': u'"', '\\': u'\\', '/': u'/',
|
||||
'b': u'\b', 'f': u'\f', 'n': u'\n', 'r': u'\r', 't': u'\t',
|
||||
}
|
||||
|
||||
DEFAULT_ENCODING = "utf-8"
|
||||
|
||||
def py_scanstring(s, end, encoding=None, strict=True,
|
||||
_b=BACKSLASH, _m=STRINGCHUNK.match, _join=u('').join,
|
||||
_b=BACKSLASH, _m=STRINGCHUNK.match, _join=u''.join,
|
||||
_PY3=PY3, _maxunicode=sys.maxunicode):
|
||||
"""Scan the string s for a JSON string. End is the index of the
|
||||
character in s after the quote that started the JSON string.
|
||||
Unescapes all valid JSON string escape sequences and raises ValueError
|
||||
on attempt to decode an invalid string. If strict is False then literal
|
||||
control characters are allowed in the string.
|
||||
|
||||
Returns a tuple of the decoded string and the index of the character in s
|
||||
after the end quote."""
|
||||
if encoding is None:
|
||||
|
@ -119,8 +70,8 @@ def py_scanstring(s, end, encoding=None, strict=True,
|
|||
content, terminator = chunk.groups()
|
||||
# Content is contains zero or more unescaped string characters
|
||||
if content:
|
||||
if not _PY3 and not isinstance(content, text_type):
|
||||
content = text_type(content, encoding)
|
||||
if not _PY3 and not isinstance(content, unicode):
|
||||
content = unicode(content, encoding)
|
||||
_append(content)
|
||||
# Terminator is the end of string, a literal control character,
|
||||
# or a backslash denoting that an escape sequence follows
|
||||
|
@ -128,8 +79,7 @@ def py_scanstring(s, end, encoding=None, strict=True,
|
|||
break
|
||||
elif terminator != '\\':
|
||||
if strict:
|
||||
msg = "Invalid control character %r at" % (terminator,)
|
||||
#msg = "Invalid control character {0!r} at".format(terminator)
|
||||
msg = "Invalid control character %r at"
|
||||
raise JSONDecodeError(msg, s, end)
|
||||
else:
|
||||
_append(terminator)
|
||||
|
@ -144,45 +94,39 @@ def py_scanstring(s, end, encoding=None, strict=True,
|
|||
try:
|
||||
char = _b[esc]
|
||||
except KeyError:
|
||||
msg = "Invalid \\escape: " + repr(esc)
|
||||
msg = "Invalid \\X escape sequence %r"
|
||||
raise JSONDecodeError(msg, s, end)
|
||||
end += 1
|
||||
else:
|
||||
# Unicode escape sequence
|
||||
msg = "Invalid \\uXXXX escape sequence"
|
||||
esc = s[end + 1:end + 5]
|
||||
next_end = end + 5
|
||||
if len(esc) != 4:
|
||||
msg = "Invalid \\uXXXX escape"
|
||||
raise JSONDecodeError(msg, s, end)
|
||||
escX = esc[1:2]
|
||||
if len(esc) != 4 or escX == 'x' or escX == 'X':
|
||||
raise JSONDecodeError(msg, s, end - 1)
|
||||
try:
|
||||
uni = int(esc, 16)
|
||||
except ValueError:
|
||||
msg = "Invalid \\uXXXX escape"
|
||||
raise JSONDecodeError(msg, s, end)
|
||||
raise JSONDecodeError(msg, s, end - 1)
|
||||
end += 5
|
||||
# Check for surrogate pair on UCS-4 systems
|
||||
if _maxunicode > 65535:
|
||||
unimask = uni & 0xfc00
|
||||
if unimask == 0xd800:
|
||||
msg = "Invalid \\uXXXX\\uXXXX surrogate pair"
|
||||
if not s[end + 5:end + 7] == '\\u':
|
||||
raise JSONDecodeError(msg, s, end)
|
||||
esc2 = s[end + 7:end + 11]
|
||||
if len(esc2) != 4:
|
||||
raise JSONDecodeError(msg, s, end)
|
||||
# Note that this will join high/low surrogate pairs
|
||||
# but will also pass unpaired surrogates through
|
||||
if (_maxunicode > 65535 and
|
||||
uni & 0xfc00 == 0xd800 and
|
||||
s[end:end + 2] == '\\u'):
|
||||
esc2 = s[end + 2:end + 6]
|
||||
escX = esc2[1:2]
|
||||
if len(esc2) == 4 and not (escX == 'x' or escX == 'X'):
|
||||
try:
|
||||
uni2 = int(esc2, 16)
|
||||
except ValueError:
|
||||
raise JSONDecodeError(msg, s, end)
|
||||
if uni2 & 0xfc00 != 0xdc00:
|
||||
msg = "Unpaired high surrogate"
|
||||
raise JSONDecodeError(msg, s, end)
|
||||
uni = 0x10000 + (((uni - 0xd800) << 10) | (uni2 - 0xdc00))
|
||||
next_end += 6
|
||||
elif unimask == 0xdc00:
|
||||
msg = "Unpaired low surrogate"
|
||||
raise JSONDecodeError(msg, s, end)
|
||||
if uni2 & 0xfc00 == 0xdc00:
|
||||
uni = 0x10000 + (((uni - 0xd800) << 10) |
|
||||
(uni2 - 0xdc00))
|
||||
end += 6
|
||||
char = unichr(uni)
|
||||
end = next_end
|
||||
# Append the unescaped character
|
||||
_append(char)
|
||||
return _join(chunks), end
|
||||
|
@ -246,10 +190,7 @@ def JSONObject(state, encoding, strict, scan_once, object_hook,
|
|||
except IndexError:
|
||||
pass
|
||||
|
||||
try:
|
||||
value, end = scan_once(s, end)
|
||||
except StopIteration:
|
||||
raise JSONDecodeError("Expecting object", s, end)
|
||||
value, end = scan_once(s, end)
|
||||
pairs.append((key, value))
|
||||
|
||||
try:
|
||||
|
@ -264,7 +205,7 @@ def JSONObject(state, encoding, strict, scan_once, object_hook,
|
|||
if nextchar == '}':
|
||||
break
|
||||
elif nextchar != ',':
|
||||
raise JSONDecodeError("Expecting ',' delimiter", s, end - 1)
|
||||
raise JSONDecodeError("Expecting ',' delimiter or '}'", s, end - 1)
|
||||
|
||||
try:
|
||||
nextchar = s[end]
|
||||
|
@ -301,12 +242,11 @@ def JSONArray(state, scan_once, _w=WHITESPACE.match, _ws=WHITESPACE_STR):
|
|||
# Look-ahead for trivial empty array
|
||||
if nextchar == ']':
|
||||
return values, end + 1
|
||||
elif nextchar == '':
|
||||
raise JSONDecodeError("Expecting value or ']'", s, end)
|
||||
_append = values.append
|
||||
while True:
|
||||
try:
|
||||
value, end = scan_once(s, end)
|
||||
except StopIteration:
|
||||
raise JSONDecodeError("Expecting object", s, end)
|
||||
value, end = scan_once(s, end)
|
||||
_append(value)
|
||||
nextchar = s[end:end + 1]
|
||||
if nextchar in _ws:
|
||||
|
@ -316,7 +256,7 @@ def JSONArray(state, scan_once, _w=WHITESPACE.match, _ws=WHITESPACE_STR):
|
|||
if nextchar == ']':
|
||||
break
|
||||
elif nextchar != ',':
|
||||
raise JSONDecodeError("Expecting ',' delimiter", s, end)
|
||||
raise JSONDecodeError("Expecting ',' delimiter or ']'", s, end - 1)
|
||||
|
||||
try:
|
||||
if s[end] in _ws:
|
||||
|
@ -330,9 +270,7 @@ def JSONArray(state, scan_once, _w=WHITESPACE.match, _ws=WHITESPACE_STR):
|
|||
|
||||
class JSONDecoder(object):
|
||||
"""Simple JSON <http://json.org> decoder
|
||||
|
||||
Performs the following translations in decoding by default:
|
||||
|
||||
+---------------+-------------------+
|
||||
| JSON | Python |
|
||||
+===============+===================+
|
||||
|
@ -340,7 +278,7 @@ class JSONDecoder(object):
|
|||
+---------------+-------------------+
|
||||
| array | list |
|
||||
+---------------+-------------------+
|
||||
| string | unicode |
|
||||
| string | str, unicode |
|
||||
+---------------+-------------------+
|
||||
| number (int) | int, long |
|
||||
+---------------+-------------------+
|
||||
|
@ -352,10 +290,8 @@ class JSONDecoder(object):
|
|||
+---------------+-------------------+
|
||||
| null | None |
|
||||
+---------------+-------------------+
|
||||
|
||||
It also understands ``NaN``, ``Infinity``, and ``-Infinity`` as
|
||||
their corresponding ``float`` values, which is outside the JSON spec.
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self, encoding=None, object_hook=None, parse_float=None,
|
||||
|
@ -365,15 +301,12 @@ class JSONDecoder(object):
|
|||
*encoding* determines the encoding used to interpret any
|
||||
:class:`str` objects decoded by this instance (``'utf-8'`` by
|
||||
default). It has no effect when decoding :class:`unicode` objects.
|
||||
|
||||
Note that currently only encodings that are a superset of ASCII work,
|
||||
strings of other encodings should be passed in as :class:`unicode`.
|
||||
|
||||
*object_hook*, if specified, will be called with the result of every
|
||||
JSON object decoded and its return value will be used in place of the
|
||||
given :class:`dict`. This can be used to provide custom
|
||||
deserializations (e.g. to support JSON-RPC class hinting).
|
||||
|
||||
*object_pairs_hook* is an optional function that will be called with
|
||||
the result of any object literal decode with an ordered list of pairs.
|
||||
The return value of *object_pairs_hook* will be used instead of the
|
||||
|
@ -382,27 +315,22 @@ class JSONDecoder(object):
|
|||
example, :func:`collections.OrderedDict` will remember the order of
|
||||
insertion). If *object_hook* is also defined, the *object_pairs_hook*
|
||||
takes priority.
|
||||
|
||||
*parse_float*, if specified, will be called with the string of every
|
||||
JSON float to be decoded. By default, this is equivalent to
|
||||
``float(num_str)``. This can be used to use another datatype or parser
|
||||
for JSON floats (e.g. :class:`decimal.Decimal`).
|
||||
|
||||
*parse_int*, if specified, will be called with the string of every
|
||||
JSON int to be decoded. By default, this is equivalent to
|
||||
``int(num_str)``. This can be used to use another datatype or parser
|
||||
for JSON integers (e.g. :class:`float`).
|
||||
|
||||
*parse_constant*, if specified, will be called with one of the
|
||||
following strings: ``'-Infinity'``, ``'Infinity'``, ``'NaN'``. This
|
||||
can be used to raise an exception if invalid JSON numbers are
|
||||
encountered.
|
||||
|
||||
*strict* controls the parser's behavior when it encounters an
|
||||
invalid control character in a string. The default setting of
|
||||
``True`` means that unescaped control characters are parse errors, if
|
||||
``False`` then control characters will be allowed in strings.
|
||||
|
||||
"""
|
||||
if encoding is None:
|
||||
encoding = DEFAULT_ENCODING
|
||||
|
@ -422,10 +350,9 @@ class JSONDecoder(object):
|
|||
def decode(self, s, _w=WHITESPACE.match, _PY3=PY3):
|
||||
"""Return the Python representation of ``s`` (a ``str`` or ``unicode``
|
||||
instance containing a JSON document)
|
||||
|
||||
"""
|
||||
if _PY3 and isinstance(s, binary_type):
|
||||
s = s.decode(self.encoding)
|
||||
if _PY3 and isinstance(s, bytes):
|
||||
s = str(s, self.encoding)
|
||||
obj, end = self.raw_decode(s)
|
||||
end = _w(s, end).end()
|
||||
if end != len(s):
|
||||
|
@ -438,15 +365,20 @@ class JSONDecoder(object):
|
|||
representation and the index in ``s`` where the document ended.
|
||||
Optionally, ``idx`` can be used to specify an offset in ``s`` where
|
||||
the JSON document begins.
|
||||
|
||||
This can be used to decode a JSON document from a string that may
|
||||
have extraneous data at the end.
|
||||
|
||||
"""
|
||||
if _PY3 and not isinstance(s, text_type):
|
||||
if idx < 0:
|
||||
# Ensure that raw_decode bails on negative indexes, the regex
|
||||
# would otherwise mask this behavior. #98
|
||||
raise JSONDecodeError('Expecting value', s, idx)
|
||||
if _PY3 and not isinstance(s, str):
|
||||
raise TypeError("Input string must be text, not bytes")
|
||||
try:
|
||||
obj, end = self.scan_once(s, idx=_w(s, idx).end())
|
||||
except StopIteration:
|
||||
raise JSONDecodeError("No JSON object could be decoded", s, idx)
|
||||
return obj, end
|
||||
# strip UTF-8 bom
|
||||
if len(s) > idx:
|
||||
ord0 = ord(s[idx])
|
||||
if ord0 == 0xfeff:
|
||||
idx += 1
|
||||
elif ord0 == 0xef and s[idx:idx + 3] == '\xef\xbb\xbf':
|
||||
idx += 3
|
||||
return self.scan_once(s, idx=_w(s, idx).end())
|
|
@ -3,8 +3,9 @@
|
|||
from __future__ import absolute_import
|
||||
import re
|
||||
from operator import itemgetter
|
||||
from decimal import Decimal
|
||||
from .compat import u, unichr, binary_type, string_types, integer_types, PY3
|
||||
# Do not import Decimal directly to avoid reload issues
|
||||
import decimal
|
||||
from .compat import unichr, binary_type, text_type, string_types, integer_types, PY3
|
||||
def _import_speedups():
|
||||
try:
|
||||
from . import _speedups
|
||||
|
@ -14,11 +15,9 @@ def _import_speedups():
|
|||
c_encode_basestring_ascii, c_make_encoder = _import_speedups()
|
||||
|
||||
from .decoder import PosInf
|
||||
from .raw_json import RawJSON
|
||||
|
||||
#ESCAPE = re.compile(ur'[\x00-\x1f\\"\b\f\n\r\t\u2028\u2029]')
|
||||
# This is required because u() will mangle the string and ur'' isn't valid
|
||||
# python3 syntax
|
||||
ESCAPE = re.compile(u'[\\x00-\\x1f\\\\"\\b\\f\\n\\r\\t\u2028\u2029]')
|
||||
ESCAPE = re.compile(r'[\x00-\x1f\\"]')
|
||||
ESCAPE_ASCII = re.compile(r'([\\"]|[^\ -~])')
|
||||
HAS_UTF8 = re.compile(r'[\x80-\xff]')
|
||||
ESCAPE_DCT = {
|
||||
|
@ -33,21 +32,30 @@ ESCAPE_DCT = {
|
|||
for i in range(0x20):
|
||||
#ESCAPE_DCT.setdefault(chr(i), '\\u{0:04x}'.format(i))
|
||||
ESCAPE_DCT.setdefault(chr(i), '\\u%04x' % (i,))
|
||||
for i in [0x2028, 0x2029]:
|
||||
ESCAPE_DCT.setdefault(unichr(i), '\\u%04x' % (i,))
|
||||
|
||||
FLOAT_REPR = repr
|
||||
|
||||
def encode_basestring(s, _PY3=PY3, _q=u('"')):
|
||||
def encode_basestring(s, _PY3=PY3, _q=u'"'):
|
||||
"""Return a JSON representation of a Python string
|
||||
|
||||
"""
|
||||
if _PY3:
|
||||
if isinstance(s, binary_type):
|
||||
s = s.decode('utf-8')
|
||||
if isinstance(s, bytes):
|
||||
s = str(s, 'utf-8')
|
||||
elif type(s) is not str:
|
||||
# convert an str subclass instance to exact str
|
||||
# raise a TypeError otherwise
|
||||
s = str.__str__(s)
|
||||
else:
|
||||
if isinstance(s, str) and HAS_UTF8.search(s) is not None:
|
||||
s = s.decode('utf-8')
|
||||
s = unicode(s, 'utf-8')
|
||||
elif type(s) not in (str, unicode):
|
||||
# convert an str subclass instance to exact str
|
||||
# convert a unicode subclass instance to exact unicode
|
||||
# raise a TypeError otherwise
|
||||
if isinstance(s, str):
|
||||
s = str.__str__(s)
|
||||
else:
|
||||
s = unicode.__getnewargs__(s)[0]
|
||||
def replace(match):
|
||||
return ESCAPE_DCT[match.group(0)]
|
||||
return _q + ESCAPE.sub(replace, s) + _q
|
||||
|
@ -55,14 +63,25 @@ def encode_basestring(s, _PY3=PY3, _q=u('"')):
|
|||
|
||||
def py_encode_basestring_ascii(s, _PY3=PY3):
|
||||
"""Return an ASCII-only JSON representation of a Python string
|
||||
|
||||
"""
|
||||
if _PY3:
|
||||
if isinstance(s, binary_type):
|
||||
s = s.decode('utf-8')
|
||||
if isinstance(s, bytes):
|
||||
s = str(s, 'utf-8')
|
||||
elif type(s) is not str:
|
||||
# convert an str subclass instance to exact str
|
||||
# raise a TypeError otherwise
|
||||
s = str.__str__(s)
|
||||
else:
|
||||
if isinstance(s, str) and HAS_UTF8.search(s) is not None:
|
||||
s = s.decode('utf-8')
|
||||
s = unicode(s, 'utf-8')
|
||||
elif type(s) not in (str, unicode):
|
||||
# convert an str subclass instance to exact str
|
||||
# convert a unicode subclass instance to exact unicode
|
||||
# raise a TypeError otherwise
|
||||
if isinstance(s, str):
|
||||
s = str.__str__(s)
|
||||
else:
|
||||
s = unicode.__getnewargs__(s)[0]
|
||||
def replace(match):
|
||||
s = match.group(0)
|
||||
try:
|
||||
|
@ -87,9 +106,7 @@ encode_basestring_ascii = (
|
|||
|
||||
class JSONEncoder(object):
|
||||
"""Extensible JSON <http://json.org> encoder for Python data structures.
|
||||
|
||||
Supports the following objects and types by default:
|
||||
|
||||
+-------------------+---------------+
|
||||
| Python | JSON |
|
||||
+===================+===============+
|
||||
|
@ -107,81 +124,81 @@ class JSONEncoder(object):
|
|||
+-------------------+---------------+
|
||||
| None | null |
|
||||
+-------------------+---------------+
|
||||
|
||||
To extend this to recognize other objects, subclass and implement a
|
||||
``.default()`` method with another method that returns a serializable
|
||||
object for ``o`` if possible, otherwise it should call the superclass
|
||||
implementation (to raise ``TypeError``).
|
||||
|
||||
"""
|
||||
item_separator = ', '
|
||||
key_separator = ': '
|
||||
def __init__(self, skipkeys=False, ensure_ascii=True,
|
||||
check_circular=True, allow_nan=True, sort_keys=False,
|
||||
indent=None, separators=None, encoding='utf-8', default=None,
|
||||
use_decimal=True, namedtuple_as_object=True,
|
||||
tuple_as_array=True, bigint_as_string=False,
|
||||
item_sort_key=None):
|
||||
"""Constructor for JSONEncoder, with sensible defaults.
|
||||
|
||||
def __init__(self, skipkeys=False, ensure_ascii=True,
|
||||
check_circular=True, allow_nan=True, sort_keys=False,
|
||||
indent=None, separators=None, encoding='utf-8', default=None,
|
||||
use_decimal=True, namedtuple_as_object=True,
|
||||
tuple_as_array=True, bigint_as_string=False,
|
||||
item_sort_key=None, for_json=False, ignore_nan=False,
|
||||
int_as_string_bitcount=None, iterable_as_array=False):
|
||||
"""Constructor for JSONEncoder, with sensible defaults.
|
||||
If skipkeys is false, then it is a TypeError to attempt
|
||||
encoding of keys that are not str, int, long, float or None. If
|
||||
skipkeys is True, such items are simply skipped.
|
||||
|
||||
If ensure_ascii is true, the output is guaranteed to be str
|
||||
objects with all incoming unicode characters escaped. If
|
||||
ensure_ascii is false, the output will be unicode object.
|
||||
|
||||
If check_circular is true, then lists, dicts, and custom encoded
|
||||
objects will be checked for circular references during encoding to
|
||||
prevent an infinite recursion (which would cause an OverflowError).
|
||||
Otherwise, no such check takes place.
|
||||
|
||||
If allow_nan is true, then NaN, Infinity, and -Infinity will be
|
||||
encoded as such. This behavior is not JSON specification compliant,
|
||||
but is consistent with most JavaScript based encoders and decoders.
|
||||
Otherwise, it will be a ValueError to encode such floats.
|
||||
|
||||
If sort_keys is true, then the output of dictionaries will be
|
||||
sorted by key; this is useful for regression tests to ensure
|
||||
that JSON serializations can be compared on a day-to-day basis.
|
||||
|
||||
If indent is a string, then JSON array elements and object members
|
||||
will be pretty-printed with a newline followed by that string repeated
|
||||
for each level of nesting. ``None`` (the default) selects the most compact
|
||||
representation without any newlines. For backwards compatibility with
|
||||
versions of simplejson earlier than 2.1.0, an integer is also accepted
|
||||
and is converted to a string with that many spaces.
|
||||
|
||||
If specified, separators should be a (item_separator, key_separator)
|
||||
tuple. The default is (', ', ': '). To get the most compact JSON
|
||||
representation you should specify (',', ':') to eliminate whitespace.
|
||||
|
||||
If specified, separators should be an (item_separator, key_separator)
|
||||
tuple. The default is (', ', ': ') if *indent* is ``None`` and
|
||||
(',', ': ') otherwise. To get the most compact JSON representation,
|
||||
you should specify (',', ':') to eliminate whitespace.
|
||||
If specified, default is a function that gets called for objects
|
||||
that can't otherwise be serialized. It should return a JSON encodable
|
||||
version of the object or raise a ``TypeError``.
|
||||
|
||||
If encoding is not None, then all input strings will be
|
||||
transformed into unicode using that encoding prior to JSON-encoding.
|
||||
The default is UTF-8.
|
||||
|
||||
If use_decimal is true (not the default), ``decimal.Decimal`` will
|
||||
If use_decimal is true (default: ``True``), ``decimal.Decimal`` will
|
||||
be supported directly by the encoder. For the inverse, decode JSON
|
||||
with ``parse_float=decimal.Decimal``.
|
||||
|
||||
If namedtuple_as_object is true (the default), objects with
|
||||
``_asdict()`` methods will be encoded as JSON objects.
|
||||
|
||||
If tuple_as_array is true (the default), tuple (and subclasses) will
|
||||
be encoded as JSON arrays.
|
||||
|
||||
If *iterable_as_array* is true (default: ``False``),
|
||||
any object not in the above table that implements ``__iter__()``
|
||||
will be encoded as a JSON array.
|
||||
If bigint_as_string is true (not the default), ints 2**53 and higher
|
||||
or lower than -2**53 will be encoded as strings. This is to avoid the
|
||||
rounding that happens in Javascript otherwise.
|
||||
|
||||
If int_as_string_bitcount is a positive number (n), then int of size
|
||||
greater than or equal to 2**n or lower than or equal to -2**n will be
|
||||
encoded as strings.
|
||||
If specified, item_sort_key is a callable used to sort the items in
|
||||
each dictionary. This is useful if you want to sort items other than
|
||||
in alphabetical order by key.
|
||||
If for_json is true (not the default), objects with a ``for_json()``
|
||||
method will use the return value of that method for encoding as JSON
|
||||
instead of the object.
|
||||
If *ignore_nan* is true (default: ``False``), then out of range
|
||||
:class:`float` values (``nan``, ``inf``, ``-inf``) will be serialized
|
||||
as ``null`` in compliance with the ECMA-262 specification. If true,
|
||||
this will override *allow_nan*.
|
||||
"""
|
||||
|
||||
self.skipkeys = skipkeys
|
||||
|
@ -192,8 +209,12 @@ class JSONEncoder(object):
|
|||
self.use_decimal = use_decimal
|
||||
self.namedtuple_as_object = namedtuple_as_object
|
||||
self.tuple_as_array = tuple_as_array
|
||||
self.iterable_as_array = iterable_as_array
|
||||
self.bigint_as_string = bigint_as_string
|
||||
self.item_sort_key = item_sort_key
|
||||
self.for_json = for_json
|
||||
self.ignore_nan = ignore_nan
|
||||
self.int_as_string_bitcount = int_as_string_bitcount
|
||||
if indent is not None and not isinstance(indent, string_types):
|
||||
indent = indent * ' '
|
||||
self.indent = indent
|
||||
|
@ -209,10 +230,8 @@ class JSONEncoder(object):
|
|||
"""Implement this method in a subclass such that it returns
|
||||
a serializable object for ``o``, or calls the base implementation
|
||||
(to raise a ``TypeError``).
|
||||
|
||||
For example, to support arbitrary iterators, you could
|
||||
implement default like this::
|
||||
|
||||
def default(self, o):
|
||||
try:
|
||||
iterable = iter(o)
|
||||
|
@ -221,23 +240,21 @@ class JSONEncoder(object):
|
|||
else:
|
||||
return list(iterable)
|
||||
return JSONEncoder.default(self, o)
|
||||
|
||||
"""
|
||||
raise TypeError(repr(o) + " is not JSON serializable")
|
||||
raise TypeError('Object of type %s is not JSON serializable' %
|
||||
o.__class__.__name__)
|
||||
|
||||
def encode(self, o):
|
||||
"""Return a JSON string representation of a Python data structure.
|
||||
|
||||
>>> from simplejson import JSONEncoder
|
||||
>>> JSONEncoder().encode({"foo": ["bar", "baz"]})
|
||||
'{"foo": ["bar", "baz"]}'
|
||||
|
||||
"""
|
||||
# This is for extremely simple cases and benchmarks.
|
||||
if isinstance(o, binary_type):
|
||||
_encoding = self.encoding
|
||||
if (_encoding is not None and not (_encoding == 'utf-8')):
|
||||
o = o.decode(_encoding)
|
||||
o = text_type(o, _encoding)
|
||||
if isinstance(o, string_types):
|
||||
if self.ensure_ascii:
|
||||
return encode_basestring_ascii(o)
|
||||
|
@ -257,12 +274,9 @@ class JSONEncoder(object):
|
|||
def iterencode(self, o, _one_shot=False):
|
||||
"""Encode the given object and yield each string
|
||||
representation as available.
|
||||
|
||||
For example::
|
||||
|
||||
for chunk in JSONEncoder().iterencode(bigobject):
|
||||
mysocket.write(chunk)
|
||||
|
||||
"""
|
||||
if self.check_circular:
|
||||
markers = {}
|
||||
|
@ -272,13 +286,13 @@ class JSONEncoder(object):
|
|||
_encoder = encode_basestring_ascii
|
||||
else:
|
||||
_encoder = encode_basestring
|
||||
if self.encoding != 'utf-8':
|
||||
if self.encoding != 'utf-8' and self.encoding is not None:
|
||||
def _encoder(o, _orig_encoder=_encoder, _encoding=self.encoding):
|
||||
if isinstance(o, binary_type):
|
||||
o = o.decode(_encoding)
|
||||
o = text_type(o, _encoding)
|
||||
return _orig_encoder(o)
|
||||
|
||||
def floatstr(o, allow_nan=self.allow_nan,
|
||||
def floatstr(o, allow_nan=self.allow_nan, ignore_nan=self.ignore_nan,
|
||||
_repr=FLOAT_REPR, _inf=PosInf, _neginf=-PosInf):
|
||||
# Check for specials. Note that this type of test is processor
|
||||
# and/or platform-specific, so do tests which don't depend on
|
||||
|
@ -291,17 +305,23 @@ class JSONEncoder(object):
|
|||
elif o == _neginf:
|
||||
text = '-Infinity'
|
||||
else:
|
||||
if type(o) != float:
|
||||
# See #118, do not trust custom str/repr
|
||||
o = float(o)
|
||||
return _repr(o)
|
||||
|
||||
if not allow_nan:
|
||||
if ignore_nan:
|
||||
text = 'null'
|
||||
elif not allow_nan:
|
||||
raise ValueError(
|
||||
"Out of range float values are not JSON compliant: " +
|
||||
repr(o))
|
||||
|
||||
return text
|
||||
|
||||
|
||||
key_memo = {}
|
||||
int_as_string_bitcount = (
|
||||
53 if self.bigint_as_string else self.int_as_string_bitcount)
|
||||
if (_one_shot and c_make_encoder is not None
|
||||
and self.indent is None):
|
||||
_iterencode = c_make_encoder(
|
||||
|
@ -309,18 +329,18 @@ class JSONEncoder(object):
|
|||
self.key_separator, self.item_separator, self.sort_keys,
|
||||
self.skipkeys, self.allow_nan, key_memo, self.use_decimal,
|
||||
self.namedtuple_as_object, self.tuple_as_array,
|
||||
self.bigint_as_string, self.item_sort_key,
|
||||
self.encoding,
|
||||
Decimal)
|
||||
int_as_string_bitcount,
|
||||
self.item_sort_key, self.encoding, self.for_json,
|
||||
self.ignore_nan, decimal.Decimal, self.iterable_as_array)
|
||||
else:
|
||||
_iterencode = _make_iterencode(
|
||||
markers, self.default, _encoder, self.indent, floatstr,
|
||||
self.key_separator, self.item_separator, self.sort_keys,
|
||||
self.skipkeys, _one_shot, self.use_decimal,
|
||||
self.namedtuple_as_object, self.tuple_as_array,
|
||||
self.bigint_as_string, self.item_sort_key,
|
||||
self.encoding,
|
||||
Decimal=Decimal)
|
||||
int_as_string_bitcount,
|
||||
self.item_sort_key, self.encoding, self.for_json,
|
||||
self.iterable_as_array, Decimal=decimal.Decimal)
|
||||
try:
|
||||
return _iterencode(o, 0)
|
||||
finally:
|
||||
|
@ -329,11 +349,14 @@ class JSONEncoder(object):
|
|||
|
||||
class JSONEncoderForHTML(JSONEncoder):
|
||||
"""An encoder that produces JSON safe to embed in HTML.
|
||||
|
||||
To embed JSON content in, say, a script tag on a web page, the
|
||||
characters &, < and > should be escaped. They cannot be escaped
|
||||
with the usual entities (e.g. &) because they are not expanded
|
||||
within <script> tags.
|
||||
This class also escapes the line separator and paragraph separator
|
||||
characters U+2028 and U+2029, irrespective of the ensure_ascii setting,
|
||||
as these characters are not valid in JavaScript strings (see
|
||||
http://timelessrepo.com/json-isnt-a-javascript-subset).
|
||||
"""
|
||||
|
||||
def encode(self, o):
|
||||
|
@ -351,18 +374,25 @@ class JSONEncoderForHTML(JSONEncoder):
|
|||
chunk = chunk.replace('&', '\\u0026')
|
||||
chunk = chunk.replace('<', '\\u003c')
|
||||
chunk = chunk.replace('>', '\\u003e')
|
||||
|
||||
if not self.ensure_ascii:
|
||||
chunk = chunk.replace(u'\u2028', '\\u2028')
|
||||
chunk = chunk.replace(u'\u2029', '\\u2029')
|
||||
|
||||
yield chunk
|
||||
|
||||
|
||||
def _make_iterencode(markers, _default, _encoder, _indent, _floatstr,
|
||||
_key_separator, _item_separator, _sort_keys, _skipkeys, _one_shot,
|
||||
_use_decimal, _namedtuple_as_object, _tuple_as_array,
|
||||
_bigint_as_string, _item_sort_key, _encoding,
|
||||
_int_as_string_bitcount, _item_sort_key,
|
||||
_encoding,_for_json,
|
||||
_iterable_as_array,
|
||||
## HACK: hand-optimized bytecode; turn globals into locals
|
||||
_PY3=PY3,
|
||||
ValueError=ValueError,
|
||||
string_types=string_types,
|
||||
Decimal=Decimal,
|
||||
Decimal=None,
|
||||
dict=dict,
|
||||
float=float,
|
||||
id=id,
|
||||
|
@ -371,12 +401,38 @@ def _make_iterencode(markers, _default, _encoder, _indent, _floatstr,
|
|||
list=list,
|
||||
str=str,
|
||||
tuple=tuple,
|
||||
iter=iter,
|
||||
):
|
||||
if _use_decimal and Decimal is None:
|
||||
Decimal = decimal.Decimal
|
||||
if _item_sort_key and not callable(_item_sort_key):
|
||||
raise TypeError("item_sort_key must be None or callable")
|
||||
elif _sort_keys and not _item_sort_key:
|
||||
_item_sort_key = itemgetter(0)
|
||||
|
||||
if (_int_as_string_bitcount is not None and
|
||||
(_int_as_string_bitcount <= 0 or
|
||||
not isinstance(_int_as_string_bitcount, integer_types))):
|
||||
raise TypeError("int_as_string_bitcount must be a positive integer")
|
||||
|
||||
def _encode_int(value):
|
||||
skip_quoting = (
|
||||
_int_as_string_bitcount is None
|
||||
or
|
||||
_int_as_string_bitcount < 1
|
||||
)
|
||||
if type(value) not in integer_types:
|
||||
# See #118, do not trust custom str/repr
|
||||
value = int(value)
|
||||
if (
|
||||
skip_quoting or
|
||||
(-1 << _int_as_string_bitcount)
|
||||
< value <
|
||||
(1 << _int_as_string_bitcount)
|
||||
):
|
||||
return str(value)
|
||||
return '"' + str(value) + '"'
|
||||
|
||||
def _iterencode_list(lst, _current_indent_level):
|
||||
if not lst:
|
||||
yield '[]'
|
||||
|
@ -401,9 +457,12 @@ def _make_iterencode(markers, _default, _encoder, _indent, _floatstr,
|
|||
first = False
|
||||
else:
|
||||
buf = separator
|
||||
if (isinstance(value, string_types) or
|
||||
(_PY3 and isinstance(value, binary_type))):
|
||||
if isinstance(value, string_types):
|
||||
yield buf + _encoder(value)
|
||||
elif _PY3 and isinstance(value, bytes) and _encoding is not None:
|
||||
yield buf + _encoder(value)
|
||||
elif isinstance(value, RawJSON):
|
||||
yield buf + value.encoded_json
|
||||
elif value is None:
|
||||
yield buf + 'null'
|
||||
elif value is True:
|
||||
|
@ -411,17 +470,17 @@ def _make_iterencode(markers, _default, _encoder, _indent, _floatstr,
|
|||
elif value is False:
|
||||
yield buf + 'false'
|
||||
elif isinstance(value, integer_types):
|
||||
yield ((buf + str(value))
|
||||
if (not _bigint_as_string or
|
||||
(-1 << 53) < value < (1 << 53))
|
||||
else (buf + '"' + str(value) + '"'))
|
||||
yield buf + _encode_int(value)
|
||||
elif isinstance(value, float):
|
||||
yield buf + _floatstr(value)
|
||||
elif _use_decimal and isinstance(value, Decimal):
|
||||
yield buf + str(value)
|
||||
else:
|
||||
yield buf
|
||||
if isinstance(value, list):
|
||||
for_json = _for_json and getattr(value, 'for_json', None)
|
||||
if for_json and callable(for_json):
|
||||
chunks = _iterencode(for_json(), _current_indent_level)
|
||||
elif isinstance(value, list):
|
||||
chunks = _iterencode_list(value, _current_indent_level)
|
||||
else:
|
||||
_asdict = _namedtuple_as_object and getattr(value, '_asdict', None)
|
||||
|
@ -436,18 +495,22 @@ def _make_iterencode(markers, _default, _encoder, _indent, _floatstr,
|
|||
chunks = _iterencode(value, _current_indent_level)
|
||||
for chunk in chunks:
|
||||
yield chunk
|
||||
if newline_indent is not None:
|
||||
_current_indent_level -= 1
|
||||
yield '\n' + (_indent * _current_indent_level)
|
||||
yield ']'
|
||||
if first:
|
||||
# iterable_as_array misses the fast path at the top
|
||||
yield '[]'
|
||||
else:
|
||||
if newline_indent is not None:
|
||||
_current_indent_level -= 1
|
||||
yield '\n' + (_indent * _current_indent_level)
|
||||
yield ']'
|
||||
if markers is not None:
|
||||
del markers[markerid]
|
||||
|
||||
def _stringify_key(key):
|
||||
if isinstance(key, string_types): # pragma: no cover
|
||||
pass
|
||||
elif isinstance(key, binary_type):
|
||||
key = key.decode(_encoding)
|
||||
elif _PY3 and isinstance(key, bytes) and _encoding is not None:
|
||||
key = str(key, _encoding)
|
||||
elif isinstance(key, float):
|
||||
key = _floatstr(key)
|
||||
elif key is True:
|
||||
|
@ -457,13 +520,17 @@ def _make_iterencode(markers, _default, _encoder, _indent, _floatstr,
|
|||
elif key is None:
|
||||
key = 'null'
|
||||
elif isinstance(key, integer_types):
|
||||
if type(key) not in integer_types:
|
||||
# See #118, do not trust custom str/repr
|
||||
key = int(key)
|
||||
key = str(key)
|
||||
elif _use_decimal and isinstance(key, Decimal):
|
||||
key = str(key)
|
||||
elif _skipkeys:
|
||||
key = None
|
||||
else:
|
||||
raise TypeError("key " + repr(key) + " is not a string")
|
||||
raise TypeError('keys must be str, int, float, bool or None, '
|
||||
'not %s' % key.__class__.__name__)
|
||||
return key
|
||||
|
||||
def _iterencode_dict(dct, _current_indent_level):
|
||||
|
@ -512,9 +579,12 @@ def _make_iterencode(markers, _default, _encoder, _indent, _floatstr,
|
|||
yield item_separator
|
||||
yield _encoder(key)
|
||||
yield _key_separator
|
||||
if (isinstance(value, string_types) or
|
||||
(_PY3 and isinstance(value, binary_type))):
|
||||
if isinstance(value, string_types):
|
||||
yield _encoder(value)
|
||||
elif _PY3 and isinstance(value, bytes) and _encoding is not None:
|
||||
yield _encoder(value)
|
||||
elif isinstance(value, RawJSON):
|
||||
yield value.encoded_json
|
||||
elif value is None:
|
||||
yield 'null'
|
||||
elif value is True:
|
||||
|
@ -522,16 +592,16 @@ def _make_iterencode(markers, _default, _encoder, _indent, _floatstr,
|
|||
elif value is False:
|
||||
yield 'false'
|
||||
elif isinstance(value, integer_types):
|
||||
yield (str(value)
|
||||
if (not _bigint_as_string or
|
||||
(-1 << 53) < value < (1 << 53))
|
||||
else ('"' + str(value) + '"'))
|
||||
yield _encode_int(value)
|
||||
elif isinstance(value, float):
|
||||
yield _floatstr(value)
|
||||
elif _use_decimal and isinstance(value, Decimal):
|
||||
yield str(value)
|
||||
else:
|
||||
if isinstance(value, list):
|
||||
for_json = _for_json and getattr(value, 'for_json', None)
|
||||
if for_json and callable(for_json):
|
||||
chunks = _iterencode(for_json(), _current_indent_level)
|
||||
elif isinstance(value, list):
|
||||
chunks = _iterencode_list(value, _current_indent_level)
|
||||
else:
|
||||
_asdict = _namedtuple_as_object and getattr(value, '_asdict', None)
|
||||
|
@ -554,9 +624,12 @@ def _make_iterencode(markers, _default, _encoder, _indent, _floatstr,
|
|||
del markers[markerid]
|
||||
|
||||
def _iterencode(o, _current_indent_level):
|
||||
if (isinstance(o, string_types) or
|
||||
(_PY3 and isinstance(o, binary_type))):
|
||||
if isinstance(o, string_types):
|
||||
yield _encoder(o)
|
||||
elif _PY3 and isinstance(o, bytes) and _encoding is not None:
|
||||
yield _encoder(o)
|
||||
elif isinstance(o, RawJSON):
|
||||
yield o.encoded_json
|
||||
elif o is None:
|
||||
yield 'null'
|
||||
elif o is True:
|
||||
|
@ -564,38 +637,51 @@ def _make_iterencode(markers, _default, _encoder, _indent, _floatstr,
|
|||
elif o is False:
|
||||
yield 'false'
|
||||
elif isinstance(o, integer_types):
|
||||
yield (str(o)
|
||||
if (not _bigint_as_string or
|
||||
(-1 << 53) < o < (1 << 53))
|
||||
else ('"' + str(o) + '"'))
|
||||
yield _encode_int(o)
|
||||
elif isinstance(o, float):
|
||||
yield _floatstr(o)
|
||||
elif isinstance(o, list):
|
||||
for chunk in _iterencode_list(o, _current_indent_level):
|
||||
yield chunk
|
||||
else:
|
||||
_asdict = _namedtuple_as_object and getattr(o, '_asdict', None)
|
||||
if _asdict and callable(_asdict):
|
||||
for chunk in _iterencode_dict(_asdict(), _current_indent_level):
|
||||
for_json = _for_json and getattr(o, 'for_json', None)
|
||||
if for_json and callable(for_json):
|
||||
for chunk in _iterencode(for_json(), _current_indent_level):
|
||||
yield chunk
|
||||
elif (_tuple_as_array and isinstance(o, tuple)):
|
||||
elif isinstance(o, list):
|
||||
for chunk in _iterencode_list(o, _current_indent_level):
|
||||
yield chunk
|
||||
elif isinstance(o, dict):
|
||||
for chunk in _iterencode_dict(o, _current_indent_level):
|
||||
yield chunk
|
||||
elif _use_decimal and isinstance(o, Decimal):
|
||||
yield str(o)
|
||||
else:
|
||||
if markers is not None:
|
||||
markerid = id(o)
|
||||
if markerid in markers:
|
||||
raise ValueError("Circular reference detected")
|
||||
markers[markerid] = o
|
||||
o = _default(o)
|
||||
for chunk in _iterencode(o, _current_indent_level):
|
||||
yield chunk
|
||||
if markers is not None:
|
||||
del markers[markerid]
|
||||
_asdict = _namedtuple_as_object and getattr(o, '_asdict', None)
|
||||
if _asdict and callable(_asdict):
|
||||
for chunk in _iterencode_dict(_asdict(),
|
||||
_current_indent_level):
|
||||
yield chunk
|
||||
elif (_tuple_as_array and isinstance(o, tuple)):
|
||||
for chunk in _iterencode_list(o, _current_indent_level):
|
||||
yield chunk
|
||||
elif isinstance(o, dict):
|
||||
for chunk in _iterencode_dict(o, _current_indent_level):
|
||||
yield chunk
|
||||
elif _use_decimal and isinstance(o, Decimal):
|
||||
yield str(o)
|
||||
else:
|
||||
while _iterable_as_array:
|
||||
# Markers are not checked here because it is valid for
|
||||
# an iterable to return self.
|
||||
try:
|
||||
o = iter(o)
|
||||
except TypeError:
|
||||
break
|
||||
for chunk in _iterencode_list(o, _current_indent_level):
|
||||
yield chunk
|
||||
return
|
||||
if markers is not None:
|
||||
markerid = id(o)
|
||||
if markerid in markers:
|
||||
raise ValueError("Circular reference detected")
|
||||
markers[markerid] = o
|
||||
o = _default(o)
|
||||
for chunk in _iterencode(o, _current_indent_level):
|
||||
yield chunk
|
||||
if markers is not None:
|
||||
del markers[markerid]
|
||||
|
||||
return _iterencode
|
||||
return _iterencode
|
|
@ -0,0 +1,51 @@
|
|||
"""Error classes used by simplejson
|
||||
"""
|
||||
__all__ = ['JSONDecodeError']
|
||||
|
||||
|
||||
def linecol(doc, pos):
|
||||
lineno = doc.count('\n', 0, pos) + 1
|
||||
if lineno == 1:
|
||||
colno = pos + 1
|
||||
else:
|
||||
colno = pos - doc.rindex('\n', 0, pos)
|
||||
return lineno, colno
|
||||
|
||||
|
||||
def errmsg(msg, doc, pos, end=None):
|
||||
lineno, colno = linecol(doc, pos)
|
||||
msg = msg.replace('%r', repr(doc[pos:pos + 1]))
|
||||
if end is None:
|
||||
fmt = '%s: line %d column %d (char %d)'
|
||||
return fmt % (msg, lineno, colno, pos)
|
||||
endlineno, endcolno = linecol(doc, end)
|
||||
fmt = '%s: line %d column %d - line %d column %d (char %d - %d)'
|
||||
return fmt % (msg, lineno, colno, endlineno, endcolno, pos, end)
|
||||
|
||||
|
||||
class JSONDecodeError(ValueError):
|
||||
"""Subclass of ValueError with the following additional properties:
|
||||
msg: The unformatted error message
|
||||
doc: The JSON document being parsed
|
||||
pos: The start index of doc where parsing failed
|
||||
end: The end index of doc where parsing failed (may be None)
|
||||
lineno: The line corresponding to pos
|
||||
colno: The column corresponding to pos
|
||||
endlineno: The line corresponding to end (may be None)
|
||||
endcolno: The column corresponding to end (may be None)
|
||||
"""
|
||||
# Note that this exception is used from _speedups
|
||||
def __init__(self, msg, doc, pos, end=None):
|
||||
ValueError.__init__(self, errmsg(msg, doc, pos, end=end))
|
||||
self.msg = msg
|
||||
self.doc = doc
|
||||
self.pos = pos
|
||||
self.end = end
|
||||
self.lineno, self.colno = linecol(doc, pos)
|
||||
if end is not None:
|
||||
self.endlineno, self.endcolno = linecol(doc, end)
|
||||
else:
|
||||
self.endlineno, self.endcolno = None, None
|
||||
|
||||
def __reduce__(self):
|
||||
return self.__class__, (self.msg, self.doc, self.pos, self.end)
|
|
@ -1,21 +1,8 @@
|
|||
"""Drop-in replacement for collections.OrderedDict by Raymond Hettinger
|
||||
|
||||
http://code.activestate.com/recipes/576693/
|
||||
|
||||
"""
|
||||
from UserDict import DictMixin
|
||||
|
||||
# Modified from original to support Python 2.4, see
|
||||
# http://code.google.com/p/simplejson/issues/detail?id=53
|
||||
try:
|
||||
all
|
||||
except NameError:
|
||||
def all(seq):
|
||||
for elem in seq:
|
||||
if not elem:
|
||||
return False
|
||||
return True
|
||||
|
||||
class OrderedDict(dict, DictMixin):
|
||||
|
||||
def __init__(self, *args, **kwds):
|
||||
|
@ -63,12 +50,7 @@ class OrderedDict(dict, DictMixin):
|
|||
def popitem(self, last=True):
|
||||
if not self:
|
||||
raise KeyError('dictionary is empty')
|
||||
# Modified from original to support Python 2.4, see
|
||||
# http://code.google.com/p/simplejson/issues/detail?id=53
|
||||
if last:
|
||||
key = reversed(self).next()
|
||||
else:
|
||||
key = iter(self).next()
|
||||
key = reversed(self).next() if last else iter(self).next()
|
||||
value = self.pop(key)
|
||||
return key, value
|
||||
|
||||
|
@ -116,4 +98,4 @@ class OrderedDict(dict, DictMixin):
|
|||
return dict.__eq__(self, other)
|
||||
|
||||
def __ne__(self, other):
|
||||
return not self == other
|
||||
return not self == other
|
|
@ -0,0 +1,8 @@
|
|||
"""Implementation of RawJSON
|
||||
"""
|
||||
|
||||
class RawJSON(object):
|
||||
"""Wrap an encoded JSON document for direct embedding in the output
|
||||
"""
|
||||
def __init__(self, encoded_json):
|
||||
self.encoded_json = encoded_json
|
|
@ -1,20 +1,22 @@
|
|||
"""JSON token scanner
|
||||
"""
|
||||
import re
|
||||
from .errors import JSONDecodeError
|
||||
def _import_c_make_scanner():
|
||||
try:
|
||||
from simplejson._speedups import make_scanner
|
||||
from ._speedups import make_scanner
|
||||
return make_scanner
|
||||
except ImportError:
|
||||
return None
|
||||
c_make_scanner = _import_c_make_scanner()
|
||||
|
||||
__all__ = ['make_scanner']
|
||||
__all__ = ['make_scanner', 'JSONDecodeError']
|
||||
|
||||
NUMBER_RE = re.compile(
|
||||
r'(-?(?:0|[1-9]\d*))(\.\d+)?([eE][-+]?\d+)?',
|
||||
(re.VERBOSE | re.MULTILINE | re.DOTALL))
|
||||
|
||||
|
||||
def py_make_scanner(context):
|
||||
parse_object = context.parse_object
|
||||
parse_array = context.parse_array
|
||||
|
@ -30,10 +32,11 @@ def py_make_scanner(context):
|
|||
memo = context.memo
|
||||
|
||||
def _scan_once(string, idx):
|
||||
errmsg = 'Expecting value'
|
||||
try:
|
||||
nextchar = string[idx]
|
||||
except IndexError:
|
||||
raise StopIteration
|
||||
raise JSONDecodeError(errmsg, string, idx)
|
||||
|
||||
if nextchar == '"':
|
||||
return parse_string(string, idx + 1, encoding, strict)
|
||||
|
@ -64,9 +67,14 @@ def py_make_scanner(context):
|
|||
elif nextchar == '-' and string[idx:idx + 9] == '-Infinity':
|
||||
return parse_constant('-Infinity'), idx + 9
|
||||
else:
|
||||
raise StopIteration
|
||||
raise JSONDecodeError(errmsg, string, idx)
|
||||
|
||||
def scan_once(string, idx):
|
||||
if idx < 0:
|
||||
# Ensure the same behavior as the C speedup, otherwise
|
||||
# this would work for *some* negative string indices due
|
||||
# to the behavior of __getitem__ for strings. #98
|
||||
raise JSONDecodeError('Expecting value', string, idx)
|
||||
try:
|
||||
return _scan_once(string, idx)
|
||||
finally:
|
||||
|
@ -74,4 +82,4 @@ def py_make_scanner(context):
|
|||
|
||||
return scan_once
|
||||
|
||||
make_scanner = c_make_scanner or py_make_scanner
|
||||
make_scanner = c_make_scanner or py_make_scanner
|
|
@ -1,39 +0,0 @@
|
|||
r"""Command-line tool to validate and pretty-print JSON
|
||||
|
||||
Usage::
|
||||
|
||||
$ echo '{"json":"obj"}' | python -m simplejson.tool
|
||||
{
|
||||
"json": "obj"
|
||||
}
|
||||
$ echo '{ 1.2:3.4}' | python -m simplejson.tool
|
||||
Expecting property name: line 1 column 2 (char 2)
|
||||
|
||||
"""
|
||||
import sys
|
||||
import simplejson as json
|
||||
|
||||
def main():
|
||||
if len(sys.argv) == 1:
|
||||
infile = sys.stdin
|
||||
outfile = sys.stdout
|
||||
elif len(sys.argv) == 2:
|
||||
infile = open(sys.argv[1], 'rb')
|
||||
outfile = sys.stdout
|
||||
elif len(sys.argv) == 3:
|
||||
infile = open(sys.argv[1], 'rb')
|
||||
outfile = open(sys.argv[2], 'wb')
|
||||
else:
|
||||
raise SystemExit(sys.argv[0] + " [infile [outfile]]")
|
||||
try:
|
||||
obj = json.load(infile,
|
||||
object_pairs_hook=json.OrderedDict,
|
||||
use_decimal=True)
|
||||
except ValueError:
|
||||
raise SystemExit(sys.exc_info()[1])
|
||||
json.dump(obj, outfile, sort_keys=True, indent=' ', use_decimal=True)
|
||||
outfile.write('\n')
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
Loading…
Reference in New Issue