Compare commits

...

21 Commits
master ... st3

Author SHA1 Message Date
TheSecEng b53fc1137f
update: messages 2020-04-28 15:48:29 -04:00
TheSecEng f48297e391
implement: updated Simple JSON
implement: JSON Lines Formatting
2020-04-23 14:24:05 -04:00
TheSecEng 8fa26e86ef
implement: changelog 2020-04-22 12:23:44 -04:00
TheSecEng c5fcf50f2d
implement: get_selection_from_region 2020-04-19 16:31:11 -04:00
TheSecEng cab4d367fb
implement: JSNO Lines formatting 2020-04-19 14:14:04 -04:00
TheSecEng 2c80b93b76
fix: Incorrect Status Message when validating JSON 2020-04-18 15:03:20 -04:00
Terminal 8abbb888dc
Merge pull request #114 from dzhibas/fix-st3/issue-43
Fix st3/issue 45
2020-04-17 22:46:32 -04:00
TheSecEng 3f2841d109
implement: jq path setting 2020-04-17 22:43:01 -04:00
Terminal 394353d5bf
Merge pull request #113 from dzhibas/fix-st3/issue-110
fix: implement duplicate key detection
2020-04-17 18:51:09 -04:00
TheSecEng 6c52f846b5
fix: implement duplicate key detection 2020-04-17 18:46:26 -04:00
Terminal 6fd83df6b8
Merge pull request #112 from dzhibas/fix/st3/issue-109
Fix/st3/issue 109
2020-04-17 18:39:48 -04:00
TheSecEng 9d17ced7f5
implement: remaining fix for #109
remove: no-sublime-package
2020-04-17 17:22:19 -04:00
Terminal 59c9f9babd
Merge pull request #88 from Silwing/keep-arrays-single-line-fix
Replacing more than wanted in array handling
2020-04-17 15:42:47 -04:00
Terminal 4d199f3b93
Merge branch 'st3' into keep-arrays-single-line-fix 2020-04-17 15:42:32 -04:00
Terminal 7cf04726d2
Merge pull request #81 from yukixz/fix-one-line-array
Sort matches before replacment on post process
2020-04-17 15:40:24 -04:00
Terminal 966d66eb02
Merge pull request #80 from tadashi-aikawa/master
Fix issue #62
2020-04-17 15:38:43 -04:00
TheSecEng a261db2026
fix: issue #109 2020-04-17 15:33:46 -04:00
TheSecEng 1431afd3d7
implement: .gitattributes 2020-04-17 15:11:21 -04:00
Randi Katrine Hillerøe 742b26b920
Issue with replacing more than wanted
When finding array matches only the content of the array is replaced. This content can potentially be present in multiple places and will then break later array replacements. The below example can reproduce the issue:
{
  "test": [3, 4],
  "test2": [1, 2, 3, 4],
  "test3": [3, 4]
}

With setting "keep_arrays_single_line": true

Basically the issue occurs for two arrays A, B where A is a suffix of B and A occurs earlier in the JSON document than B.
The fix is to match the whole array, not just the content. Then remove the brackets to split and join and re-add the brackets afterwards.
If the same array occurs multiple times then this will replace all instances during the first replace, however that should not cause any issues.
The fix prevents issues with replacing arrays that are subsets of other arrays.
2017-11-15 12:46:02 +01:00
Dazzy Ding 6e769bd62c Sort matches before replacment on post process 2017-02-18 19:41:53 +08:00
tadashi-aikawa 391ebd8a8c Fix issue #62 2016-12-18 21:14:12 +09:00
21 changed files with 758 additions and 448 deletions

1
.gitattributes vendored Normal file
View File

@ -0,0 +1 @@
/tests export-ignore

View File

View File

@ -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"

View File

@ -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"
}

View File

@ -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):

9
messages.json Normal file
View File

@ -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"
}

5
messages/1.0.0.txt Normal file
View File

@ -0,0 +1,5 @@
Version 1.0.4 (April 17, 2020)
-------------------------------
* Added `.gitattributes` to stop
shipping `/tests` with releases

11
messages/1.0.1.txt Normal file
View File

@ -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

6
messages/1.0.2.txt Normal file
View File

@ -0,0 +1,6 @@
Version 1.0.2 (April 17, 2020)
-------------------------------
* Implement the following PR's
- #114 - implement configurable
jq path

4
messages/1.0.3.txt Normal file
View File

@ -0,0 +1,4 @@
Version 1.0.3 (April 17, 2020)
-------------------------------
* Fix incorrect message for Valid JSON

7
messages/1.0.4.txt Normal file
View File

@ -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

5
messages/1.0.5.txt Normal file
View File

@ -0,0 +1,5 @@
Version 1.0.5 (April 28, 2020)
-------------------------------
* Sublime Text 3 Users
- Update to latest Simple Json

103
messages/install.txt Normal file
View File

@ -0,0 +1,103 @@
[![Build Status](https://travis-ci.org/dzhibas/SublimePrettyJson.svg)](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:
[![Demo](http://i.imgur.com/sw7Hrsp.gif?1)](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

View File

@ -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]

View File

@ -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())

View File

@ -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. &amp;) 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

51
simplejson/errors.py Normal file
View File

@ -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)

View File

@ -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

8
simplejson/raw_json.py Normal file
View File

@ -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

View File

@ -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

View File

@ -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()