You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Incorrect serialization of float subclass with custom __str__ method #57
Thanks for reporting the issue. I have reproduced it locally (thanks to the easy repro!). I am a bit surprised that this hasn't been reported before.
It looks like json.dumps(MyFloat(1)) returns 1.0 instead of <1.0>. I'm not immediately sure why that is; it looks like the json module is calling repr(MyFloat(1)) instead of str(MyFloat(1)) (and json5 is doing the latter).
My first reaction is that json5 is right and json is wrong, i.e., we should be calling str here, not repr.
That said, I view matching json's behavior to be much more important than trying to say which one is "right". So, I'll definitely change it, but I'll also poke into this a bit more and see if there's something that explains why json might be correct to use repr.
Also, I'll point out that you shouldn't count on loads(dumps())he to always work in the presence of custom classes, at least not if you want to preserve the fact that the object is a MyFloat and not a str, at least not unless you're using a custom parse_float method. But you probably already know that :).
Is there any reason why using float.__str__(MyFloat(1)) directly to sidestep any customization would cause problems?
I have no idea. Changing the default behavior of any public function can usually cause problems. More importantly, reaching deep into a class's private methods doesn't feel great to me.
I think I can probably get equivalent behavior by actually calling JSONEncoder().encode(MyFloat(1)) and that would provide a stronger guarantee that json5's behavior matches json's. I'll probably code that up and see if it flies.
I forgot that back in 10e4e82 I removed (the last) dependency on json.
I don't remember why I didn't want the dependency, exactly: it might have been just for aesthetics, or it might
have been by request from someone.
At any rate, in order to not reintroduce the dependency on json now, I'll call float.repr and int.repr directly
as you suggested instead.
Good question. You could use json.dumps(mylist, cls=CustomEncoder) to get something close, but if you wanted the output to be json5 in a good style (i.e., keys as identifiers instead of strings), you'd basically have to reimplement all of the logic in json5.dumps(), at which point you might as well not use dumps() at all and just use your own code.
I would not want to revert the bugfix, because matching json's behavior by default is a primary goal.
However, currently the cls argument to dumps() isn't supported, and in thinking about it, I don't think there's a particularly good reason not to support it. I think I could refactor the code in dumps() to use a class that was easily subclassed, the way json does. So, I'll look into that.
[ This is unlike loads(), where I don't think there's an easy way to restructure the code to support a subclassable-class, as the parsing algorithm is totally different and not easily amenable to custom parsing. ]
Would supporting a custom encoder where you'd have to write something like:
class HexintEncoder(json5.JSON5Encoder):
def encode(self, o):
if isinstance(o, hexint):
return format(o, '#x')
return super().encode(o)
json5.dumps(mylist, cls=HexintDecoder)
work for you? There could possibly be a way to do this w/ a custom encoder function (as opposed to a class) as well, but I'd have to add a new keyword arg to specify the function, and I'd prefer to avoid that if possible.
If I have mydict = {"a": 100, "b": 200,
"c":[hexint(10),hexint(20),hexint(30)]}, I'm assuming that only the hexints
would be affected, and the 100 and 200 would be printed in decimal. If
that is the case, then this would work for me.
Thanks.
On Wed, Nov 20, 2024 at 2:05 PM Dirk Pranke ***@***.***> wrote:
Good question. You could use json.dumps(mylist, cls=CustomEncoder) to get
something close, but if you wanted the output to be json5 in a good style
(i.e., keys as identifiers instead of strings), you'd basically have to
reimplement all of the logic in json5.dumps(), at which point you might
as well not use dumps() at all and just use your own code.
I would not want to revert the bugfix, because matching json's behavior
by default is a primary goal.
However, currently the cls argument to dumps() isn't supported, and in
thinking about it, I don't think there's a particularly good reason not to
support it. I think I could refactor the code in dumps() to use a class
that was easily subclassed, the way json does. So, I'll look into that.
[ This is unlike loads(), where I don't think there's an easy way to
restructure the code to support a subclassable-class, as the parsing
algorithm is totally different and not easily amenable to custom parsing. ]
Would supporting a custom encoder where you'd have to write something like:
class HexintEncoder(json5.JSON5Encoder):
def encode(self, o):
if isinstance(o, hexint):
return format(o, '#x')
return super().encode(o)
json5.dumps(mylist, cls=HexintDecoder)
work for you? There could possibly be a way to do this w/ a custom encoder
function (as opposed to a class) as well, but I'd have to add a new keyword
arg to specify the function, and I'd prefer to avoid that if possible.
—
Reply to this email directly, view it on GitHub
<#57 (comment)>,
or unsubscribe
<https://github.com/notifications/unsubscribe-auth/AQBWUQFVDOA4GYNMY6VZVET2BTMO3AVCNFSM6AAAAABSEYIEWCVHI2DSMVQWIX3LMV43OSLTON2WKQ3PNVWWK3TUHMZDIOBZGM2DGMJTHE>
.
You are receiving this because you commented.Message ID:
***@***.***>
I've started working on this and have an initial version that I think should work. I still have to add tests, and I'm debating
whether I want to add an iterencode() method to mirror the json.JSONEncoder implementation.
Okay, that took substantially more work than I thought it would, but I think I have everything ready to go. See #88 and, if you have a chance take a look and see if looks like it'll work for you. I'll likely merge it in tomorrow.
By "try it out tomorrow" did you mean you want to look at the changes and/or try them before I merge the PR? Or did you want me to (or not care if) I merged things first?
I thought you wanted me to try the changes first.
Unfortunately, another project is making big demands on my time, and I'm
not going to be able to test your changes today.
I think you should just go ahead and merge.
I do plan to use your changes, as I need to be able to dump lists of
hex-formatted numbers.
I just can't get back to that other project for maybe two days.
Thank you for the changes.
Lisa Bahler
Sent from Gmail Mobile
Never mind. I found my problem. Could not use the initial code you sent,
Needed to add the seen, level, and as_key arguments to my encode function.
The fix works for me.
Thanks.
Lisa Bahler
On Wed, Dec 11, 2024 at 9:06 AM Lisa Bahler ***@***.***> wrote:
I just tried the fix you put in, using the code sample you had sent me.
>>> class HexintEncoder(json5.JSON5Encoder):
... def encode(self, o):
... if isinstance(o, hexint):
... return format(o, '#x')
... return super().encode(o)
...
>>> json5.dumps([10,20,30],cls=HexintEncoder)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "/home/lbahler/.local/lib/python3.8/site-packages/json5/lib.py",
line 396, in dumps
return enc.encode(obj, seen=set(), level=0, as_key=False)
On Tue, Nov 26, 2024 at 2:58 PM Dirk Pranke ***@***.***>
wrote:
> Fixed in #88 <#88>, tagged as
> v0.10.0, and released to PyPI.
>
> —
> Reply to this email directly, view it on GitHub
> <#57 (comment)>,
> or unsubscribe
> <https://github.com/notifications/unsubscribe-auth/AQBWUQAA7CDVSU7VKA2ZQ3T2CTHFXAVCNFSM6AAAAABSEYIEWCVHI2DSMVQWIX3LMV43OSLTON2WKQ3PNVWWK3TUHMZDKMBRHAYDMMJSGI>
> .
> You are receiving this because you commented.Message ID:
> ***@***.***>
>
Activity
dpranke commentedon Jul 31, 2022
Thanks for reporting the issue. I have reproduced it locally (thanks to the easy repro!). I am a bit surprised that this hasn't been reported before.
It looks like
json.dumps(MyFloat(1))returns1.0instead of<1.0>. I'm not immediately sure why that is; it looks like thejsonmodule is callingrepr(MyFloat(1))instead ofstr(MyFloat(1))(and json5 is doing the latter).My first reaction is that
json5is right andjsonis wrong, i.e., we should be callingstrhere, notrepr.That said, I view matching
json's behavior to be much more important than trying to say which one is "right". So, I'll definitely change it, but I'll also poke into this a bit more and see if there's something that explains whyjsonmight be correct to userepr.Also, I'll point out that you shouldn't count on
loads(dumps())he to always work in the presence of custom classes, at least not if you want to preserve the fact that the object is aMyFloatand not astr, at least not unless you're using a customparse_floatmethod. But you probably already know that :).ryanmarquardt commentedon Jul 31, 2022
Thanks for taking a look at this so quick. It looks like the same issue crops up for
intsubclasses too.I did some searching, and found that
strandreprused to be different forfloatbefore around python 3.2, according to https://bugs.python.org/issue9337.reprwas more precise, which is probably why it was used whenjsonwas originally written. Either way, the methods are identical now, so it doesn't make any difference.Is there any reason why using
float.__str__(MyFloat(1))directly to sidestep any customization would cause problems?dpranke commentedon Jul 31, 2022
Thanks for those leads! :).
I have no idea. Changing the default behavior of any public function can usually cause problems. More importantly, reaching deep into a class's private methods doesn't feel great to me.
I think I can probably get equivalent behavior by actually calling
JSONEncoder().encode(MyFloat(1))and that would provide a stronger guarantee that json5's behavior matches json's. I'll probably code that up and see if it flies.dpranke commentedon Aug 1, 2022
I forgot that back in 10e4e82 I removed (the last) dependency on
json.I don't remember why I didn't want the dependency, exactly: it might have been just for aesthetics, or it might
have been by request from someone.
At any rate, in order to not reintroduce the dependency on
jsonnow, I'll call float.repr and int.repr directlyas you suggested instead.
Fixed serialization for custom ints and floats.
dpranke commentedon Aug 1, 2022
Okay, I've fixed this in 1f7f806 and pushed that to PyPI as v0.9.9.
Please let me know if you find any issues, and thanks again!
L-A-F-B commentedon Nov 20, 2024
I have a list of integers that I want to serialize in hex format. This fix now makes that impossible.
With an older version of the json5 package, I was able to do this:
Now this does not work. I get '[10, 20, 30]' instead. How do I accomplish what I want now?
dpranke commentedon Nov 20, 2024
Good question. You could use
json.dumps(mylist, cls=CustomEncoder)to get something close, but if you wanted the output to be json5 in a good style (i.e., keys as identifiers instead of strings), you'd basically have to reimplement all of the logic injson5.dumps(), at which point you might as well not usedumps()at all and just use your own code.I would not want to revert the bugfix, because matching
json's behavior by default is a primary goal.However, currently the
clsargument todumps()isn't supported, and in thinking about it, I don't think there's a particularly good reason not to support it. I think I could refactor the code indumps()to use a class that was easily subclassed, the wayjsondoes. So, I'll look into that.[ This is unlike
loads(), where I don't think there's an easy way to restructure the code to support a subclassable-class, as the parsing algorithm is totally different and not easily amenable to custom parsing. ]Would supporting a custom encoder where you'd have to write something like:
work for you? There could possibly be a way to do this w/ a custom encoder function (as opposed to a class) as well, but I'd have to add a new keyword arg to specify the function, and I'd prefer to avoid that if possible.
L-A-F-B commentedon Nov 20, 2024
dpranke commentedon Nov 20, 2024
Yes, that would be the idea.
dpranke commentedon Nov 21, 2024
I've started working on this and have an initial version that I think should work. I still have to add tests, and I'm debating
whether I want to add an
iterencode()method to mirror thejson.JSONEncoderimplementation.dpranke commentedon Nov 26, 2024
Okay, that took substantially more work than I thought it would, but I think I have everything ready to go. See #88 and, if you have a chance take a look and see if looks like it'll work for you. I'll likely merge it in tomorrow.
dpranke commentedon Nov 26, 2024
(Also, I did not add an
iterencodemethod.)L-A-F-B commentedon Nov 26, 2024
dpranke commentedon Nov 26, 2024
By "try it out tomorrow" did you mean you want to look at the changes and/or try them before I merge the PR? Or did you want me to (or not care if) I merged things first?
L-A-F-B commentedon Nov 26, 2024
dpranke commentedon Nov 26, 2024
Fixed in #88, tagged as v0.10.0, and released to PyPI.
L-A-F-B commentedon Dec 11, 2024
L-A-F-B commentedon Dec 11, 2024
dpranke commentedon Dec 12, 2024
Thanks for confirming. You had me worried for a second :).