json: improve handling of JSON floating-point types#86956
Conversation
|
Hello @cjwinklhofer, and thank you very much for your first pull request to the Zephyr project! |
EricNRS
left a comment
There was a problem hiding this comment.
Thank for the PR! Everything looks good to me, just some very minor comments.
Suggest minor edit for the commit message in 5809b83:
json: improve parsing and serializing of 'float' and 'double'
Up to now, the handling of type float was uploaded to the users of the
JSON utility, with the token JSON_TOK_OPAQUE_FLOAT.
...
I would suggest changing it to:
json: improve parsing and serializing of 'float' and 'double'
Up to now, the handling of type float was offloaded to the users of the
JSON utility, with the token JSON_TOK_OPAQUE_FLOAT (renamed from JSON_TOK_FLOAT).
...
| static int float_encode(const float *num, json_append_bytes_t append_bytes, | ||
| void *data) | ||
| { | ||
| char buf[17]; |
There was a problem hiding this comment.
Instead of using a magic number, you may want to use sizeof("-3.40282347e+38") (which would be 16-characters consisting of 15 characters plus a null). The compiler will evaluate that at compile time and just replace it with the number and it has the benefit of showing your sizing intentions better.
Same comment for double.
There was a problem hiding this comment.
Hi @EricNRS,
thanks for the review! I adapted the commit message (also added a note for the requirements) and replaced the magic-numbers for the buffer size.
Thanks
Christoph
There was a problem hiding this comment.
@cjwinklhofer Code looks good to me. Just one minor change of uploaded -> offloaded in the commit message:
Up to now, the handling of type float was
uploadedoffloaded to the users of the
JSON utility, with the token JSON_TOK_OPAQUE_FLOAT (renamed from
JSON_TOK_FLOAT)
@ofirshe Could you do a quick once-over of this change since you reported the original issue?
5809b83 to
90bbadb
Compare
andyross
left a comment
There was a problem hiding this comment.
I do note this now creates a hard dependency between the json code and the libc strtod(), which may not be small (the presence of printk floating point generation support is already kconfig'able). Have you looked at code size effects? Basically there are a lot of size-constrained deployments that might want json but not to pay for floating point.
90bbadb to
cabc447
Compare
@andyross - would that be |
Thanks, yes that is a good point! I did a quick comparison with the 'helloworld' sample on a STM32F3 board, the flash size reported by the build output increases from 18428 Bytes to 27676 Bytes, with the floating-point changes (CONFIG_CBPRINTF_FP_SUPPORT and usage strtod from picolib). Moreover, it will not compile when built with minimal libc, due to the missing strtof and strtod definitions: Rework of PRI will rework this PR and make the floating-point handling in the JSON module optional. Probably by adding a new Kconfig option, similar to CONFIG_CBPRINTF_FP_SUPPORT: By default, the JSON module will work as before (float handling offloaded to the client), with the benefit that no migration is required (the JSON_TOK_OPAQUE_FLOAT will disappear) and FP support can be enabled on demand (utilizing snprintk with %g and strtod). |
The issue with that approach is that if you are using both the LWM2M library and other code that requires floating point, then the LWM2M library would have an issue again. Another option would be to keep the original |
cabc447 to
61ca01f
Compare
|
Encoding and decoding support is now only included on demand with the Kconfig option 'CONFIG_JSON_LIBRARY_FP_SUPPORT'. |
|
The change looks great. Thank you! |
61ca01f to
b956db8
Compare
|
There was an issue with my mail address: the author (mail) and the sign-off did not match, hence the new push. |
|
One additional remark for NaN, Infinity and -Infinity: Currently, this implementation returns an EINVAL, which I would like to change and also allow them, like in other JSON parsers (Python JSON or boost-json). A serialized JSON output would look like:
I would use this as the default behavior, instead of the EINVAL error - probably it makes sense to add a Kconfig option but I am a bit skeptical if this makes things more complicated than needed. Best |
I think that is fine to add as the default behavior and no reason to add a Kconfig for it. Once those changes are done, I will test here on my branch and then we can rattle some cages to get this change merged. |
b956db8 to
f309119
Compare
|
Thanks @EricNRS! I added the NaN and Infinity serialization and updated also the description of the pull-request. |
f309119 to
cd0af38
Compare
d3zd3z
left a comment
There was a problem hiding this comment.
I think this now looks good. However, I think we should probably have tests to at least exercise the behavior across numeric types (given js's float-only specification).
Just to clarify, are you just asking to just test the float and double parsing with some integers (0, -0, 1234, -1234, etc)? |
EricNRS
left a comment
There was a problem hiding this comment.
I retested for my use case using doubles and everything works as expected.
| return -ENOMEM; | ||
| } | ||
|
|
||
| return append_bytes(buf, (size_t)ret, data); |
There was a problem hiding this comment.
Note that I get a warning of buf being uninitialized when CONFIG_JSON_LIBRARY_FP_SUPPORT=n. Looks like it is a false positive. The easiest fix may be to add str[0] = '\0'; in print_double() at line 1231.
zephyr/lib/utils/json.c: In function 'double_encode':
zephyr/lib/utils/json.c:1274:16: warning: 'buf' may be used uninitialized [-Wmaybe-uninitialized]
1271 | return append_bytes(buf, (size_t)ret, data);Same warning for fload_encode() as well.
There was a problem hiding this comment.
Thanks, I also think that it is a false positive, will add the explicit initialization. It occurs with CONFIG_NO_OPITIMIZATIONS=n and CONFIG_DEBUG=y.
Thanks! I will add a test to parse JSON numbers in different formats. |
cd0af38 to
8400aeb
Compare
Add support to decode floating point numbers in scientific notation, e.g. 3.40282347e+38. Only the lower-case specifier 'e' is allowed. Signed-off-by: Christoph Winklhofer <cj.winklhofer@gmail.com>
Add support to decode the special floating point values NaN, Infinity
and -Infinity. For example:
{"nan_val":NaN,"inf_pos":Infinity,"inf_neg":-Infinity}
Note that this commit is a preparation for the built-in support of
floating point values and these are only accepted when compiled with
the flag -DCONFIG_JSON_LIBRARY_FP_SUPPORT.
Signed-off-by: Christoph Winklhofer <cj.winklhofer@gmail.com>
8400aeb to
1ed309f
Compare
|
I had to rebase this branch to the current main, due to a CI error:
|
Up to now, the handling of type float was offloaded to the users of the
JSON utility, with the token JSON_TOK_FLOAT.
Improve handling of floating point types and support the types 'float'
and 'double' in a built-in way so that they can be directly parsed to
and serialized from variables (of type float or double).
The types are serialized in the shortest representation, either as a
decimal number or in scientific notation:
* float (with JSON_TOK_FLOAT_FP): encoded with maximal 9 digits
* double (with JSON_TOK_DOUBLE_FP): encoded with maximal 16 digits
* NaN, Infinity, -Infinity: encoded and decoded as:
{"nan_val":NaN,"inf_pos":Infinity,"inf_neg":-Infinity}
Enable the floating point functionality with the Kconfig option:
JSON_LIBRARY_FP_SUPPORT=y
It requires a libc implementation with support for floating point
functions: strtof(), strtod(), isnan() and isinf().
Fixes: zephyrproject-rtos#59412
Signed-off-by: Christoph Winklhofer <cj.winklhofer@gmail.com>
1ed309f to
f50914a
Compare
As well as parsing integers that are given as integer-valued floating point numbers. (1234.0). JS is weird. Obviously, this won't work if floating point is not enabled, but this is allowed by json. |
Sorry, did not got it: You mean adding additional tests for the integer types (JSON_TOK_NUMBER, JSON_TOK_UINT64 and JSON_TOK_INT64) - parsing a JSON number in float format (1234.0) into an integer-type, which would result in an EINVAL? |
We should open another issue for that since this PR is just for issue #59412 which is for floating-point support. Currently, if the format is specified as an integer type (e.g. |
|
Also, parsing as double prevents a json user from being able to use the full range of a 64-bit integer. I think it is reasonable to not support numbers with decimals as integers. |
|
@cjwinklhofer and all the reviewers, thanks for this PR and #87580! |
Improve parsing and serializing of 'float' and 'double'
Up to now, the handling of type float was offloaded to the users of the JSON utility, with the token JSON_TOK_FLOAT, which is still possible.
Improve handling of floating point types and support the types 'float' and 'double' in a built-in way so that they can be directly parsed into variables (of type float or double) and are also directly serialized as a JSON number.
The types are serialized in the shortest representation, either as adecimal number or in scientific notation:
{"nan_val":NaN,"inf_pos":Infinity,"inf_neg":-Infinity}Enable the floating point functionality with the Kconfig option: JSON_LIBRARY_FP_SUPPORT=y. It requires a libc implementation with support for floating point functions: strtof(), strtod(), isnan() and isinf().
Fixes: #59412
Remark
This pull-request emerged from the pull-request #86800. Thanks @EricNRS!