There is a rule in Python programming called "it is Easier to Ask for Forgiveness than for Permission" (in short: EAFP). It means that you should catch exceptions instead of checking values for validity.
Thus, try the following:
try:
qByUser = byUsrUrlObj.read()
qUserData = json.loads(qByUser).decode('utf-8')
questionSubjs = qUserData["all"]["questions"]
except ValueError: # includes simplejson.decoder.JSONDecodeError
print('Decoding JSON has failed')
EDIT: Since simplejson.decoder.JSONDecodeError actually inherits from ValueError (proof here), I simplified the catch statement by just using ValueError.
There is a rule in Python programming called "it is Easier to Ask for Forgiveness than for Permission" (in short: EAFP). It means that you should catch exceptions instead of checking values for validity.
Thus, try the following:
try:
qByUser = byUsrUrlObj.read()
qUserData = json.loads(qByUser).decode('utf-8')
questionSubjs = qUserData["all"]["questions"]
except ValueError: # includes simplejson.decoder.JSONDecodeError
print('Decoding JSON has failed')
EDIT: Since simplejson.decoder.JSONDecodeError actually inherits from ValueError (proof here), I simplified the catch statement by just using ValueError.
If you don't mind importing the json module, then the best way to handle it is through json.JSONDecodeError (or json.decoder.JSONDecodeError as they are the same) as using default errors like ValueError could catch also other exceptions not necessarily connected to the json decode one.
from json.decoder import JSONDecodeError
try:
qByUser = byUsrUrlObj.read()
qUserData = json.loads(qByUser).decode('utf-8')
questionSubjs = qUserData["all"]["questions"]
except JSONDecodeError as e:
# do whatever you want
//EDIT (Oct 2020):
As @Jacob Lee noted in the comment, there could be the basic common TypeError raised when the JSON object is not a str, bytes, or bytearray. Your question is about JSONDecodeError, but still it is worth mentioning here as a note; to handle also this situation, but differentiate between different issues, the following could be used:
from json.decoder import JSONDecodeError
try:
qByUser = byUsrUrlObj.read()
qUserData = json.loads(qByUser).decode('utf-8')
questionSubjs = qUserData["all"]["questions"]
except JSONDecodeError as e:
# do whatever you want
except TypeError as e:
# do whatever you want in this case
JSON doc:
{
"Data" : [
"input1",
"input2",
"input3",
"input4"
]
}Python code:
import json
with open("data.json", 'r') as f:
json_data = json.load(f)
print(json_data)Error message:
sh-5.1$ /bin/python /home/USER/Documents/Python/learning
Traceback (most recent call last):
File "/home/USER/Documents/Python/learning", line 27, in <module>
json_data = json.load(f)
File "/usr/lib/python3.9/json/__init__.py", line 293, in load
return loads(fp.read(),
File "/usr/lib/python3.9/json/__init__.py", line 346, in loads
return _default_decoder.decode(s)
File "/usr/lib/python3.9/json/decoder.py", line 337, in decode
obj, end = self.raw_decode(s, idx=_w(s, 0).end())
File "/usr/lib/python3.9/json/decoder.py", line 355, in raw_decode
raise JSONDecodeError("Expecting value", s, err.value) from None
json.decoder.JSONDecodeError: Expecting value: line 1 column 1 (char 0)Disproved theories: Improper syntax (python), document not located, bad formatting (json), accidental empty space in json.
I get this error each time I try to use "json.load" so long as it has a valid parameter input. Whether read or write; error. I'm using Visual Studio Code on Linux Mint in case that matters.
Thanks for your time! I'll be happy to answer questions in the morning when I wake up.
EDIT: Clarity, and reddit dislikes my use of backticks. Manually tabbed code.
EDIT: This code SHOULD work. It does on other computers. But it doesn't on mine, and I don't know why nor how to fix it.
[This answer is outdated. See other answers for modern python versions]
Scanning the json/decoder.py source code, we can see that the decoder's error messages are constructed using the errmsg function:
Copydef errmsg(msg, doc, pos, end=None):
# Note that this function is called from _json
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)
Since this is a pure-python module, it's easy to wrap this function with a custom one. This process is known as monkey patching:
Copyimport json
original_errmsg= json.decoder.errmsg
def our_errmsg(msg, doc, pos, end=None):
json.last_error_position= json.decoder.linecol(doc, pos)
return original_errmsg(msg, doc, pos, end)
json.decoder.errmsg= our_errmsg
try:
data = json.loads('{1:}')
except ValueError as e:
print("error at", json.last_error_position)
Obviously, this solution is not ideal, since the implementation may change at any time, although it's still better than relying on the message. You should check if errmsg exists before patching (and possibly if there's no other arguments, or use varargs).
If you use simplejson library, you get a well qualified JSONDecodeError:
Copyclass 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)
"""
Hopefully, this will be merged into stdlib soon.
User input sucks. You can't trust those users to get anything right, and so you've got to handle all kinds of special cases that make your life difficult. Having said that, we can minimize the difficulty with general principles.
Validate early, not often
Check input for validity as soon as it read into your program. If you read in a string that should be a number, convert it into a number right away and complain to the user if it isn't a number. Any rogue data you don't verify at input will make its way into the rest of the program and produce bugs.
Now, you can't always do this. There will be cases where you can't verify the correct properties right away, and you'll have to verify them during later processing. But you want as much verification to happen as early as possible so that you have your special cases around input logic centralized to one location as much as possible.
Use Schemas
Let's consider a function that parses some json.
def parse_student(text):
try:
data = json.parse(text)
except ValueError as error:
raise ParseError(error)
if not isinstance(data, dict):
raise ParseError("Expected an object!")
try:
name = data['name']
except KeyError:
raise ParseError('Expected a name')
if not isinstance(name, dict):
raise ParseError("Expected an object for name")
try:
first = name['first']
except KeyError:
raise ParseError("Expected a first name")
if not isinstance(first, basestring):
raise ParseError("Expected first name to be a string")
if first == '':
raise ParseError("Expected non-empty first name")
That was a lot of work just to extract the first name, let alone any other attributes. We can make this a lot better if we can use a json-schema. See: http://json-schema.org/.
I can describe what my student object looks like:
{
"type": "object",
"properties": {
"name": {
"type": "object",
"properties": {
"first" : {
"type" : "string"
}
},
"required": "first"
},
}
"required": ["name"]
}
When I parse I then do something like:
def parse_student(text):
try:
data = json.parse(text)
except ValueError as error:
raise ParseError(error)
try:
validate(data, STUDENT_SCHEMA)
except ValidationError as error:
raise ParseError(error)
first = data['name']['first']
Checking against the schema verifies most of the structure that I need. If the user input does not match the schema, the schema validator will produce a nice error message explaining exactly what was wrong. It will do so far more consistently and correctly then if I wrote the checking code by hand. Once the validation has been passed, I can just grab data out of the json object, because I know that it will have the correct structure.
Now, you probably aren't parsing JSON. But you may find that you can do something similar for your format that lets you reuse the basic validation logic across the different pieces of information that you fetch.
You can probably simplify your code by centralising the try/except validation of function args, and the conversion of exceptions to your exception class, into one or two decorators, which you apply to each of your methods and functions. google for python decorators for exceptions, and python decorators to validate args and you'll find stackoverflow examples like this and this.
You often don't need to explicitly validate method arguments as your code is going to cause exceptions naturally, and this might be good enough, as you cannot test for all eventualities.
Remember when writing a script, that often that your code will be even more useful if someone can include it as a library module, so make the main be specific to what a user would like from the command line, but in the library part don't try too hard to obscure where an exception stems from and so on.
The correct answer is: stop NOT wanting to catch the ValueError.
Example Python script returns a boolean if a string is valid json:
import json
def is_json(myjson):
try:
json_object = json.loads(myjson)
except ValueError as e:
return False
return True
print(is_json('{}')) # prints True
print(is_json('{asdf}')) # prints False
print(is_json('{"age":100}')) # prints True
print(is_json('{'age':100 }')) # prints False
print(is_json('{"age":100 }')) # prints True
To verify the string would require parsing it - so if you checked then converted it would literally take twice as long. Catching the exception is the best way. Interestingly, you can still use an if-else style expression:
try:
json_object = json.loads(json_string)
except ValueError as e:
pass # invalid json
else:
pass # valid json