Repository navigation
fix: coerce string quote_style values into QuoteStyle members - #141
Conversation
Passing a QuoteStyle member's own value (e.g. 'always_double') instead of
the member itself would never match the 'in' checks against the members,
so strings were emitted with the wrong quote style, or worse, with raw
unescaped quotes that json5.loads() itself rejects:
>>> json5.dumps("it's", quote_style='always_double')
"'it's'" # single-quoted, unescaped, cannot be parsed back
Coerce plain-string quote_style values into QuoteStyle members in
JSON5Encoder.__init__, and raise a ValueError listing the valid values
for strings that do not correspond to a member.
Co-authored-by: rahul05ranjan <11793455+rahul05ranjan@users.noreply.github.com>
dpranke
left a comment
There was a problem hiding this comment.
As noted in a comment on the bug, I think I'd rather have this just fail rather accept the coercion. Does that make sense, and do you feel like changing things accordingly?
…rings Per review feedback, reject any value that is not a QuoteStyle member (including strings that name or equal a member's value) instead of coercing them, per the documented signature. Co-authored-by: rahul05ranjan <11793455+rahul05ranjan@users.noreply.github.com>
|
That makes sense — done in acdbaf2. The coercion is gone: any value that is not a |
| self.trailing_commas = trailing_commas | ||
| self.allow_duplicate_keys = allow_duplicate_keys | ||
|
|
||
| # `quote_style` must be a `QuoteStyle` member. A plain string |
There was a problem hiding this comment.
I don't think we need this comment; it's clear enough from the code what it's doing.
As for the error message, I appreciate the effort, but it's more detailed than I would like.
I'd probably use something like ValueError('quote_style argument must be a json5.QuoteStyle value'). We don't need to list each individual value
| # pass the value where the member itself is meant; that is a bug | ||
| # and must fail loudly rather than silently produce | ||
| # wrongly-quoted output. | ||
| with self.assertRaises(ValueError): |
There was a problem hiding this comment.
I think these tests are more exhaustive than they need to be; just test that a json5.QuoteStyle value works and something that isn't a QuoteStyle doesn't. If you really want to be paranoid, you could have two failing tests, one for a 'always_double' and one for 1 or some other value.
You also don't need the tests on lines 1161-1168; working values are covered by other existing tests (specifically test_string_quote_styles.
So, I'd either move these tests inside that method, or create a new method that is immediately next to that method, and call it test_invalid_quote_styles; your name is more specific than it needs to be as well (since we might want to test for other non-strings).
dpranke
left a comment
There was a problem hiding this comment.
Thanks for the updates! I have some comments about the wording and testing, but otherwise things are close :).
- Drop the explanatory comment and the valid-value listing; the error
is now a plain 'must be a json5.QuoteStyle value'.
- Replace the exhaustive rejection tests with test_invalid_quote_styles,
placed next to test_string_quote_styles, covering a member's value
string ('always_double') and a non-string value (1).
Co-authored-by: rahul05ranjan <11793455+rahul05ranjan@users.noreply.github.com>
|
Thanks for the re-review! Addressed in d7f9f1c:
Full suite passes locally (74/74). |
|
Appreciate the merge on #141 — nice to see "fix: coerce string quote_style values into QuoteStyle members" ship. On a personal note: I'm on the lookout for my next role (Full Stack (PHP, NodeJs, Python) / Data Science / Applied AI / Devtools, based in India). Happy to chat if there's an opening on your side or one you'd recommend: |
Sure :). It's been nice working with you. I've got nothing for you at the moment, but I'll try to keep you in mind if I hear of anything. |
|
Thanks Dirk, really appreciate it — and same here, this was a genuinely fun one to work on. Your reviews were quick, specific, and easy to act on, which made the whole loop a pleasure. Zero pressure on the role front: if something crosses your radar I'd love a pointer, and if not, the collaboration was well worth it on its own. 🙂 All the best with pyjson5! |
Fixes #140.
Problem
dumps()documentsquote_styleas aQuoteStyleenum member, but every member's value is a plain string ('always_double','always_single','prefer_double','prefer_single'), so passing the value instead of the member is an easy mistake — and it is accepted silently. Because_encode_quoted_str()comparesquote_styleagainst the members within/==, a raw string matches none of them:quote_style='always_double'emits a single-quoted string with no quotes escaped;quote_style='always_single'emits a single-quoted string in which single quotes are not escaped;quote_style='prefer_double'/'prefer_single'silently never apply their preference (the flip/compare logic never matches).The escaping guess and the delimiter can therefore contradict each other and produce output that
json5.loads()itself rejects:Fix
In
JSON5Encoder.__init__, coerce plain-string values intoQuoteStylemembers (QuoteStyle('always_double')→QuoteStyle.ALWAYS_DOUBLE) and raise aValueErrorlisting the valid values for strings that do not correspond to a member:This mirrors what
json5/tool.pyalready does with itsQUOTE_STYLESmapping, and leaves non-string custom values untouched for backwards compatibility.Verification
test_quote_style_string_is_coercedasserts, for all four members, that passingmember.valueproduces byte-identical output to passingmember(including a string containing both quote kinds), that both'always_double'and'always_single'now round-trip throughloads(), and that an invalid string raisesValueError.python3 -m pytest tests/lib_test.py→ 74 passed (73 pre-existing + 1 new).tests/lib_test.py.newandtests/lib_test.py.new.2look like accidentally committed editor leftovers on main — happy to remove them in a separate cleanup PR if wanted.