- Notifications
You must be signed in to change notification settings - Fork 2.6k
JSONObject and JSONArray initialization:#153
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
Uh oh!
There was an error while loading. Please reload this page.
Conversation
JSONObject(Map<String, ?> map) allows to initialize the JSONObject with a Map<String, String> JSONArray(Collection<?> collection) allows to initialize a JSONArray with a Collection<JSONObject>
stleary commented Oct 5, 2015
Thanks for the proposed code change and for including unit tests. This looks like an enhancement to some of the ctors to convert a map param to JSONObject and a list param to JSONArray, rather than to process them as beans. I don't call it a bug fix, since this may have been the intended behavior, although it is possible that it was just a matter of generics not being available at the time. Any code that results in behavior changes has a high bar for acceptance. We only want to allow changes that are clearly needed, and won't cause problems to current users who expect the pre-change behavior. When possible, it is better to enhance the API with new methods. I don't think we can accept this, but will leave the pull request open for a few days to get more input. |
johnjaylward commented Oct 5, 2015
I don't think this is changing any functionality. It appears that it is just allowing you to have more generic inputs placed into the ctor without needing a conversion on the call Before: Collection<Integer> myC = Collections.singleton(Integer.TEN); JSONArrayja = newJSONArray((Collection<Object>)myC);After: Collection<Integer> myC = Collections.singleton(Integer.TEN); JSONArrayja = newJSONArray(myC);both calls should result in the same generated JSONArray. |
stleary commented Oct 5, 2015
The proposed change passed unit tests, so it does not introduce regressions. Agreed, users who have up til now had to cast their params will benefit. But it seems to me that some users may be counting on the bean behavior. In that case, could it not break their applications? |
johnjaylward commented Oct 5, 2015
I'm not sure any user would want a Map translated as a Bean. It seems like an odd thing to depend on the internals of a Collection or a Map to convert those to Json. Personally I implemented this change years ago in my own code upon the switch to java 5 or 6. |
johnjaylward commented Oct 5, 2015
Actually, in mine, I took the Map 1 step further and made both the key and the value generic: publicJSONObject(finalMap<?, ?> map) throwsJSONException{if (LOG.isTraceEnabled()){this.map = newTreeMap<>()} else{this.map = newHashMap<>()} if (map != null){for (finalEntry<?, ?> e : map.entrySet()){finalObjectvalue = e.getValue(); if (value != null){this.map.put(String.valueOf(e.getKey()), wrap(value))} } } }I used a treemap so when debugging I could see the keys in an ordered fashion |
treyerl commented Oct 5, 2015
I recently switched to maven in one of my projects, and now I depend on json-java. Before that I was using the old json.org library as a jar. Actually it was these generics breaking the code for me. If you think it should be general practice to cast everything to Map<String, Object> and Collection I can adapt of course. I'm just not sure if that's good practice. |
stleary commented Oct 10, 2015
We see a similar issue with Enum, which is handled by the JSONObject and JSONArray ctors as a bean. I think a reasonable argument could made that, although the library has been brought to 1.8 compatibility, it would benefit from a rewrite to incorporate new features like enum and "?" parameters. On the other hand, the API clearly advertises what types it supports, and within those limits works correctly, so far as I have been able to determine, and as evidenced by the unit tests. So far as I know, the author of this lib has no plans for a non-backwards-compatible "release 2.0" which explicitly supports the latest Java features (but please correct me if I am wrong). I think the proposed changes have merit. But I don't think we can accept them at this time. If you think you can rework the changes as an enhancement, please do so, as long as it does not break backwards compatibility with existing apps. Or if you think I am totally off-base, please let me know as well :) |
treyerl commented Oct 10, 2015
I thought that I clearly pointed out that the process of bringing the lib to 1.8 DID break backwards compatibility. I don't see why "?-Parameters" (wildcard generic types) would break the code. Could you provide an example for this? Or in other words: why do you think my code is no enhancement? What else would I need to provide to convince you that this is an enhancement? As shown by the comments people start to create their own versions of this library. You would argue that they create their own enhancements. For me it's bugfixing to bring back the capabilities we had with the original version. Here's why: http://docs.oracle.com/javase/tutorial/extra/generics/wildcards.html |
stleary commented Oct 10, 2015
I think the point being made in this pull request and in #155 is that upgrading to the latest Maven release is breaking some applications. Re-opening for discussion of this change as a bug fix instead of an enhancement. If your previously working application broke because you upgraded to (Java 1.8 compatible) 20150729, please continue the discussion here. So far I see an issue with the JSONObject(Map<String, Object>) ctor and the JSONArray(Collection<Object>) ctor. |
ghost commented Oct 10, 2015
wildcard generic types, e.g. Collection<?> instead of Collection<Object>. This was proposed by other pull requests (#111, #112) already. Consider this commit as merge with #111 and #112. JSONArray: - put(Collection<?> value){...} - put(Map<String, ?> value){...} - put(int index, Collection<?> value) throws JSONException{...} - put(int index, Map<String, ?> value) throws JSONException{...} JSONObject: - put(String key, Collection<?> value) throws JSONException{...} - put(String key, Map<String, ?> value) throws JSONException{...} Changed all code affected by new JSONObject and JSONArray constructors: JSONObject: - valueToString(Object value) throws JSONException{- value instanceof Map - value instanceof Collection } - wrap(Object object){- value instanceof Map - value instanceof Collection } - writeValue(Writer writer, Object value, int indentFactor, int indent){- value instanceof Map - value instanceof Collection }
treyerl commented Oct 11, 2015
Thank you @bguedas for adding your comments and pull requests. I included them in my latest commit. |
changed Iterator to foreach loop in JSONArray ctor JSONArray(Collection<?> collection) and JSONObject ctor JSONObject(Map<?,?> map)
stleary commented Oct 11, 2015
The various ctors are not core functionality, in my opinion. It is sometimes convenient for the lib to automatically populate JSONObject and JSONArray instances. The API advertises what types it does and does not support. However, it is easy for a user to misinterpret the API, e.g. to expect a Map<String, String> to be handled by the JSONObject<String, Object> ctor. Also, it seems that some users have experienced incompatibilities when upgrading to the 20150829 release. It is probably best to avoid mixing bug fixes with enhancements or redesigns. At work, I shamelessly push enhancements while fixing random bugs, but try to hold to a higher standard here. For now, the goal is to fix the incompatibility while minimizing changes to the code and avoiding any change to intended behavior. Reverting to the previous ctor API should probably be on the table, too. Any proposed solution will need to be rigorously tested to avoid regressions or unintended behavior. Let me know if you see the problem statement and/or proposed scope of the fix differently |
johnjaylward commented Oct 12, 2015
(sorry was logged in as the wrong account) publicJSONObject(finalMap<?, ?> map) throwsJSONException{this.map = newHashMap<>(); if (map != null){for (finalEntry<?, ?> e : map.entrySet()){finalObjectvalue = e.getValue(); if (value != null){this.map.put(String.valueOf(e.getKey()), wrap(value))} } } }The old ctor was not converting the key here, but instead at output time. (see https://github.com/douglascrockford/JSON-java/blob/34f327e6d070568256b314479be158589d391891/JSONObject.java#L250 and https://github.com/douglascrockford/JSON-java/blob/34f327e6d070568256b314479be158589d391891/JSONObject.java#L1604) The As for JSONArray, the implementation in this pull request should be functionally equivalent to the original pre-java8 ctor The new |
treyerl commented Oct 12, 2015
BUG: as stated in the oracle tutorial: Collection<Object>, which, as we've just demonstrated, is not a supertype of all kinds of collections! Collection<?> is the supertype. Various constructors? This pull request fixes the affected JSONArray constructor and the affected JSONObject constructor as well as all the code that is calling those constructors. The only question that remains: should we throw an exception upon null keys in a map? I think we should have it behave as close to the existing code as possible. I will think about it. Again, this pull request is no enhancement. It fixes a small bug introduced with generics. |
johnjaylward commented Oct 12, 2015
Yes, I'm not sure I was clear in my previous comments, but this is a bug fix. When the "Java8" (a9a0762) was committed, @douglascrockford improperly (if trying to maintain backwards compatability) used exact typing for the parameter to the ctors in JSONArray and JSONObject. The correct syntax for full backwards compatibility would be to use the |
johnjaylward commented Oct 12, 2015
@treyerl would you also be able to update the "put", "wrap", "writeValue", and "valueToString" methods in this pull request:
JSONObject:
|
johnjaylward commented Oct 12, 2015
Wow, I need to get better at reading diffs. It looks like all the conversions are already handled properly. Thanks @treyerl ! |
johnjaylward commented Oct 12, 2015
@stleary I'm trying to run the test cases but am failing one that this pull request would fix. Would you like me to file an issue in the Test project? Specifically it's the jsonObjectPut() test, and it fails because the expected value is a JSONArray, but the actual value is ArrayList. The comparison is comparing the |
treyerl commented Oct 12, 2015
I think
|
johnjaylward commented Oct 12, 2015
@treyerl what package error messages? |
treyerl commented Oct 12, 2015
package error messages: to test the lib I have to copy all classes into a org/json/ folder in a different eclipse project, I assume. I verified my assumptions with such a test. I will also remove the "Unnecessary @SuppressWarnings("unchecked")" warnings. |
johnjaylward commented Oct 12, 2015
I wrapped them into a common gradle project and used git submodules. See https://github.com/johnjaylward/Json-Java-Combined It's a little clunky using the sub modules, but I can do things like You can see the project layout from my project. Forking it may or may not be useful for you since the submodules point to my own forks. |
treyerl commented Oct 12, 2015
Thanks @johnjaylward, for pointing me at it. If I need to create another pull request I will try to follow this ;-) |
johnjaylward commented Oct 14, 2015
It is the same use case as the constructors. If we want to maintain backwards compatibility, then these need to be updated. |
stleary commented Oct 14, 2015
We need to be able to justify each change. A case can be made for the ctors because there is evidence of existing apps using String objects breaking on upgrade. But I don't have a reason why the map key should not be required to be String, nor why the put methods should change. What will break, or not work as intended, if those changes are not made? |
johnjaylward commented Oct 14, 2015
I guess the best way to check would be to create some test cases of what we expect to happen vs what does happen. What I expect to happen: (I did not compile or run these at all, may need tweeking) Test the ctors: Collection<Integer> myC = Collections.singleton(Integer.TEN); JSONArrayja = newJSONArray(myC); @SuppressWarnings("rawtypes") CollectionmyRawC = Collections.singleton(Integer.TEN); JSONArrayja2 = newJSONArray(myRawC); assertTrue("The RAW Collection should give me the same type as the Typed Collection", ja.similar(ja2) );Map<String,String> myC = Collections.singletonMap("Key","Value"); JSONObjectjo = newJSONObject(myC); @SuppressWarnings("rawtypes") CollectionmyRawC = Collections.singletonMap("Key","Value"); JSONObjectjo2 = newJSONObject(myRawC); assertTrue("The RAW Collection should give me the same type as the Typed Collection", jo.similar(jo2) );Test the put methods: Collection<Integer> myC = Collections.singleton(Integer.TEN); JSONArrayja = newJSONArray(); ja.put(myC); @SuppressWarnings("rawtypes") CollectionmyRawC = Collections.singleton(Integer.TEN); JSONArrayja2 = newJSONArray(); ja2.put(myRawC); assertTrue("The RAW Collection should give me the same type as the Typed Collection", ja.similar(ja2) );Map<String,String> myC = Collections.singletonMap("Key","Value"); JSONObjectjo = newJSONObject(); jo.put("someKey",myC); @SuppressWarnings("rawtypes") CollectionmyRawC = Collections.singletonMap("Key","Value"); JSONObjectjo2 = newJSONObject(); jo2.put("someKey",myRawC); assertTrue("The RAW Collection should give me the same type as the Typed Collection", jo.similar(jo2) );We'd do this for each ctor and put method affected. |
johnjaylward commented Oct 14, 2015
If the existing code does not pass the tests, then changes to the put methods would be needed. |
stleary commented Oct 14, 2015
johnjaylward commented Oct 14, 2015
I added test cases for the regression testing as well as directions on how to run them here: stleary/JSON-Java-unit-test#28 You will see that everything in this PR is needed when run against all 3 commits (baseline, master, and this PR) |
stleary commented Oct 16, 2015
Think I need to reopen in order to pull to a local branch. |
johnjaylward commented Oct 16, 2015
it's probably best to leave it open until all testing is completed. Then it shows as a known issue and will hopefully prevent more duplicates from being posted |
stleary commented Oct 17, 2015
Conceded that the code both addresses the bug fix and is consistent with https://github.com/douglascrockford/JSON-java/tree/JSON-java-1.4, which makes for a nice implementation. Why do you think the 1.8 commit used <String, Object>, instead of <Object, Object>? |
johnjaylward commented Oct 18, 2015
It was likely just an oversight. As @douglascrockford said before, he hasn't used the library in a while, so it probably just passed an initial test using a Map<String, Object> and not a thorough test using different types of maps. |
treyerl commented Oct 18, 2015
@stleary: did you mean "<String, Object> instead of <?,?>" ? |
stleary commented Oct 19, 2015
I am not explaining my thoughts well, let me try to rephrase: The API provides a mechanism via the put() methods for users to populate a JSONOjbect. I can see the benefit of using <?,?> in the case where a non-String key class easily converts to String, but what about the user whose key class has no useful toString() method? Do we want to provide an API method that does not always work, where we cannot tell if it is not working? It seems to me that the prudent thing to do is to open the API to allow all map values, but keep the restriction on String map keys. |
johnjaylward commented Oct 20, 2015
I see, so you'd like to change the (java1.4) functionality to now restrict all keys as strings. I'm not sure that is the best idea. I like being able to pass in a |
stleary commented Oct 21, 2015
What problem does this code solve? Root cause analysis Mitigation
Risks
Will this result in changes to the API?
Changes to how the code behaves?
Does it break the unit tests? Will this require a new release? Should the documentation be updated? |
johnjaylward commented Oct 21, 2015
Just noticed a typo in the last comment under the JSONObject API changes: |
stleary commented Oct 21, 2015
Fixed, thanks. |
stleary commented Oct 29, 2015
Merging now, but will wait a week or so to cut a release and update Maven, in case any users report problems. |
JSONObject and JSONArray initialization for Map<?,?> and Collection<?>
Test cases for PR stleary#153 in JSON-Java
JSONObject(Map<String, ?> map) allows to initialize the JSONObject with
a Map<String, String>
JSONArray(Collection<?> collection) allows to initialize a JSONArray
with a Collection<JSONObject>
tests: