-
Notifications
You must be signed in to change notification settings - Fork 2.6k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Fixes #438 - Corrections to BigDecimal consistency #440
Conversation
forgot to update JSONArray. I'll do that quick now. |
cdb3bfb
to
6661986
Compare
46e7e2f
to
afcd9ab
Compare
* Performance: Updates JSONWriter to use a regex to decide if writing as a number is best.
afcd9ab
to
30c1bd1
Compare
Accepted, starting 3 day comment window. Thanks @johnjaylward, changes look good. |
@stleary can you review this latest commit too. fine if you want to restart the 3 day window. I was making sure we didn't have more cases of inconsistencies with numbers and decided to unify the Number methods a little more. |
No worries, changes look reasonable, will verify the unit tests tonight. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Another drawback of this code clean up is, say you have the following Json, a number represented as String which I have seen in some APIs:
{ "a" : "3"}
because native calls to Xxx.parseXxx
were removed for the following case an intermediary instance of Number
will always be created generating more garbage to be GCed, in other words; you are always "boxing" to Number
and unboxing to the request native value.
The values are all stored in a Map object, which means they are all boxed anyway. I don't think I added any extra boxing in that wasn't present before. Are there any areas you can point out specifically? |
In the example I put In the old code String Please note that I'm talking about the Json {"a" : "3"} and not {"a" : 3}, the 1st example will store a String in the Map, the 2nd example which is the standard and most common it will be indeed a subclass of Number, because number as a String is supported by this API it is only natural to take this case into consideration as well. |
Thanks, I'll take a look. That was probably an over site on my part. |
@guidomedina , I looked closer at the full implementation and I believe the reason I made for going this route was as follows:
The only way we would see the conversion happening in the get/opt and making extra Objects for GC, would be something like these cases: String xml = "<root><key>3</key></root>";
JSONObject jo = XML.ToJSONObject(xml, true); // keep all values as strings
jo.getInt(key); // creates the intermediate Number which then calls Number.intValue() Custom built up JSONObject/array: JSONObject jo = new JSONObject();
jo.put("key", "3"); // explicitly put the value as a string
jo.getInt("key"); // creates the intermediate Number which then calls Number.intValue() The first one could be avoided by pre-processing the string using The second one seems totally avoidable altogether if the coder had planed out ahead of time what types go into the keys instead of lazily putting in a |
This is not always the case, in the utility industry in general IoT providers come in many flavors, we cannot control how a third party provides us their Json. Creating an intermediary instance of any of the See,
|
I double checked the implementation, and I misspoke. A call like this I'll look into updating the calls to get the extra Number instances removed. I don't really like so much copy/paste code, but I'm not seeing a more generic way of doing it. |
There isn't really a generic way to do this unfortunately, see this other implementation for example: https://github.com/eclipse-vertx/vert.x/blob/master/src/main/java/io/vertx/core/json/JsonObject.java |
@guidomedina I finally remembered my final reasoning for changing the functions to always use the get/optNumber functions. Previously, the parser for get/optInt would call jo = new JSONObject("{\"key\":\"3.14\"}");
jo.optInt("key", 0); // before the change, `0`, after the change `3`
jo.optLong("key", 0); // before the change, `0`, after the change `3`
jo.optBigInteger("key", 0); // before the change, `3`, after the change `3` The end result was that we ended up with more GC, but more consistent functionality. |
Would you prefer the The changes in this PR aren't quite what I mentioned above (it was a few PRs to get where the current code is). This one, the Personally, I feel that the opt/get methods should use the same type conversions in the background, so using the same example |
What problem does this code solve?
get<NumberType>
operations to return the result ofgetNumber
for consistency and less copy/paste of codeopt<NumberType>
operations to return the result ofoptNumber
for consistency and less copy/paste of codeRisks
Due to the incorrect behavior of getBigDecimal, some code may now return different values than previously. The new behavior tries to keep the underlying value as close as possible, same as the optBigDecimal implementation. This means that values like
1.234f
will no longer return asnew BigDecimal("1.234")
and will instead return asnew BigDecimal(1.234f)
. The caller of get/optBigDecimal can then choose the number of significant digits they need by using theBigDecimal.setScale
and choosing a rounding mode.Changes to the API?
no.
Will this require a new release?
yes.
Should the documentation be updated?
Updated JavaDoc to reflect the possible inconsistencies callers may see when using get/optBigDecimal
Does it break the unit tests?
No. There was no test case that covered verifying that opt/getBigDecimal values matched. New test cases were added. See stleary/JSON-Java-unit-test#88
Was any code refactored in this commit?
Yes,
JSONObject.writeValue
andJSONWriter.valueToString
. I updated how numbers are validated before outputting them. We now use a regex that properly matches JSON Numbers instead of theBigDecimal
constructor. Performance testing between the two implementations shows the regex offers significant speed gains over the BigDecimal constructor in most use cases. (see the new test case in the test case PR). For the most common cases, the RegEx offers about 80% less runtime (9355ms for BigDecimal vs 1902ms RegEx on my machine). Adding in the worst case scenario (an extremely long string that looks like a number until the last digit) all the test cases ran at approximately 12417ms for BigDecimal and 10039ms for the RegEx. The savings are still there, but not nearly as significant.get<numberType>
andopt<number type>
methods have been changed to use the commongetNumber
andoptNumber
under the hood. Functionality should be identical, but there is now less duplicated code and a single (two really...) place where numbers are possibly coerced from other types instead of it happening in many code points.Review status
ACCEPTED