JSONObject and JSONArrayThe LabKey server-side source includes widely used JSONObject and JSONArray classes that were forked from org.json sources 15+ years ago. We are replacing this old implementation with the current, maintained json.org JSON library, however, these libraries share the same package and the new version is not a drop-in replacement. To allow both libraries to co-exist for a transition time we have repackaged the old library to org.json.old.* and have added the new library which lives in the org.json.* package. We have begun migrating from old to new in the LabKey-managed repositories, but any code outside these repositories that use JSONObject or JSONArray will likely need changes.
There are some significant differences between the old and new libraries, requiring you to review and revise existing uses. Key differences are summarized below.
The new JSONObject has some significant differences:
Map-like interface (with put(), get(), remove(), clear(), isEmpty(), etc.) but it no longer implements Map. Instead of treating myJsonObject as a Map, you'll need to call toMap() to generate a new Map. Note that the generated Map does NOT hold JSONObject and JSONArray values, as you used to find in the old map-like JSONObject; instead, it provides compound values as generic Maps and Lists. When working with a converted Map, you may need to update casts and instanceof checks (or switch to operating directly on the JSONObject with typed getters).get() method has changed. At some point, we modified our forked implementation to return null if the key was not found; the new version always throws if the key is not found! The typed getters (getString(), getInt(), getJSONArray(), getJSONObject(), etc.) also throw for not found, but their behavior hasn't changed. get*() methods should be used only for properties that are guaranteed to be present. Use opt*() methods for optional properties (but be aware of the default values they return).opt() method returns the value or null if no value is associated with the key. There are also typed versions optString(), optInteger(), optBoolean(), etc. that return and cast the value OR return a default value if no value exists. WARNING: optString() returns an empty String by default... which is not expected or helpful (IMO). All of these methods have a variant where you can specify the default value; in most cases, we use optString("myProp", null) instead of optString("myProp").get*() and opt*() instead of using get() and opt() and casting the result.{"propName": null}, in keeping with HashMap behavior. Constructing a new JSONObject from a Map has similar behavior; removing any entries with a null value. To put a null value into a JSONObject, use JSONObject.NULL or JSONObject.wrap(value). Also see JsonUtil.toJsonPreserveNulls() to convert a Map<String, Object> to a JSONObject while preserving null values.myJsonObject.containsKey() -> myJsonObject.has()putAll() method: myJsonObject.putAll(map) -> map.forEach(myJsonObject::put)myJsonObject.getJavaScriptFragment(indentFactor) -> JspBase.json(myJsonObject, indentFactor)SafeToRender but JSONObjects will still render directly from JSPs (i.e., <%=myJsonObject%> is legal). (LabKeyJspWriter special cases this class as it does with Number and Boolean.)equals() method, so avoid directly comparing these objects with each other. There is a similar() method that could be a good comparison option. Comparing the results of JSONObject.toMap() is an option as well, though likely more expensive.JsonUtil.stripComments() can be used to remove these comments before parsing with new JSONObject(String) or similar.JSONObject is more aggressive when serializing Java beans compared with the old class. The new class will drill into more getters, often yielding more verbose output and, in some cases, recursion and cyclic definition exceptions. Annotate problematic getters with the @JSONPropertyIgnore annotation to avoid these problems. Or, if generating a serialization map via BeanObjectFactory, annotate with @Transient.In short, the method and class changes are fairly obvious and simple; the get() / opt() behavior, null value handling, and the new non-Map implementation are the changes that require careful attention.
The new JSONArray is very similar to the old JSONArray, but there are some differences to note:
JSONArray.collector() -> LabKeyCollectors.toJSONArray()myJsonArray.getJavaScriptFragment(indentFactor) -> JspBase.json(myJsonArray, indentFactor)SafeToRender but JSONArray objects will still render directly from JSPs (i.e., <%=myJsonArray%> is legal). (LabKeyJspWriter special cases this class as it does with Number and Boolean.)equals() or hashCode(). Avoid comparing JSONArrays or putting them in hash-based data structures. There is a similar() method that could be a good comparison option.toJSONObjectArray() and toMapList() are now available as JsonUtil static methods.JSONArray.toArray() -> JSONArray.toList()JSONArray.getString() throws if the value is not a String; old version was simply get().toString().JsonUtil.stripComments() can be used to remove these comments before parsing with new JSONArray(String) or similar.1 Related Topics