Is there a way to clone an instance of org.json.JSONObject without stringifying it and reparsing the result?
A shallow copy would be acceptable.
Easiest (and incredibly slow and inefficient) way to do it
JSONObject clone = new JSONObject(original.toString());
Use the public JSONObject(JSONObject jo, java.lang.String[] names) constructor and the public static java.lang.String[] getNames(JSONObject jo) method.
JSONObject copy = new JSONObject(original, JSONObject.getNames(original));
Cause $JSONObject.getNames(original) not accessible in android,
you can do it with:
public JSONObject shallowCopy(JSONObject original) {
JSONObject copy = new JSONObject();
for ( Iterator<String> iterator = original.keys(); iterator.hasNext(); ) {
String key = iterator.next();
JSONObject value = original.optJSONObject(key);
try {
copy.put(key, value);
} catch ( JSONException e ) {
//TODO process exception
}
}
return copy;
}
But remember it is not deep copy.
For Android developers, the simplest solution without using .getNames is:
JSONObject copy = new JSONObject();
for (Object key : original.keySet()) {
Object value = original.get(key);
copy.put(key, value);
}
Note: This is a only a shallow copy
the fastest + minimal way I found is this. it does deep copy.
JSONObject clone= new JSONObject(original.toMap());
I know the asker said
A shallow copy would be acceptable.
but I think that does not rule out if the solution will do deep copy.
Update: the toMap() function is not available in Android. but the org.json library available on maven under groupId org.json has it: https://search.maven.org/artifact/org.json/json/20210307/bundle
Couldn't find an existing deep clone method for com.google.gwt.json.client.JSONObject but the implementation should be few lines of code, something like:
public static JSONValue deepClone(JSONValue jsonValue){
JSONString string = jsonValue.isString();
if (string != null){return new JSONString(string.stringValue());}
JSONBoolean aBoolean = jsonValue.isBoolean();
if (aBoolean != null){return JSONBoolean.getInstance(aBoolean.booleanValue());}
JSONNull aNull = jsonValue.isNull();
if (aNull != null){return JSONNull.getInstance();}
JSONNumber number = jsonValue.isNumber();
if (number!=null){return new JSONNumber(number.doubleValue());}
JSONObject jsonObject = jsonValue.isObject();
if (jsonObject!=null){
JSONObject clonedObject = new JSONObject();
for (String key : jsonObject.keySet()){
clonedObject.put(key, deepClone(jsonObject.get(key)));
}
return clonedObject;
}
JSONArray array = jsonValue.isArray();
if (array != null){
JSONArray clonedArray = new JSONArray();
for (int i=0 ; i < array.size() ; ++i){
clonedArray.set(i, deepClone(array.get(i)));
}
return clonedArray;
}
throw new IllegalStateException();
}
*Note:*I haven't tested it yet!
In case anyone comes here looking for a deep clone for org.google.gson, since they don't expose their deepClone() method this is what I came up with...
public static JsonElement deepClone(JsonElement el){
if (el.isJsonPrimitive() || el.isJsonNull())
return el;
if (el.isJsonArray()) {
JsonArray array = new JsonArray();
for(JsonElement arrayEl: el.getAsJsonArray())
array.add(deepClone(arrayEl));
return array;
}
if(el.isJsonObject()) {
JsonObject obj = new JsonObject();
for (Map.Entry<String, JsonElement> entry : el.getAsJsonObject().entrySet()) {
obj.add(entry.getKey(), deepClone(entry.getValue()));
}
return obj;
}
throw new IllegalArgumentException("JsonElement type " + el.getClass().getName());
}
And here are a few methods to merge two JsonObject's
public static JsonObject merge(String overrideJson, JsonObject defaultObj) {
return mergeInto((JsonObject)new JsonParser().parse(overrideJson), defaultObj);
}
public static JsonObject merge(JsonObject overrideObj, JsonObject defaultObj) {
return mergeOverride((JsonObject)deepClone(defaultObj), overrideObj);
}
public static JsonObject mergeOverride(JsonObject targetObj, JsonObject overrideObj) {
for (Map.Entry<String, JsonElement> entry : overrideObj.entrySet())
targetObj.add(entry.getKey(), deepClone(entry.getValue()));
return targetObj;
}
public static JsonObject mergeInto(JsonObject targetObj, JsonObject defaultObj) {
for (Map.Entry<String, JsonElement> entry : defaultObj.entrySet()) {
if (targetObj.has(entry.getKey()) == false)
targetObj.add(entry.getKey(), deepClone(entry.getValue()));
}
return targetObj;
}
Related
I am trying to build query dynamically from json object using StringBuilder and Iterator.
method:
public static void main(String[] args) {
//JSONArray jsonArray = array of json
//for(int i=0;i<jsonArray.length();i++){
//JSONObject jsonObject=jsonArray.get(i);
//for sample json
JSONObject jsonObject=new JSONObject("{\"SchemaId\":\"186f134a-e65c-4e2d-92c6-04afe6951dd6\",\"SchemaName\":\"CIR\",\"Schema\":\"{}\",\"Parent\":null,\"Application\":\"CA\",\"ModifiedDateTime\":\"2020-08-31T04:21:46.403\",\"Version\":\"3\",\"PageType\":\"Line\",\"favorite\":false,\"CategoryType\":\"bits\",\"IsDeleted\":true}");
createPreparedStmtQuery(jsonObject,"mytable");
//}
}
public static String createPreparedStmtQuery(JSONObject record,String tableName) throws JSONException {
record = modifyRecord(record);
Iterator<String> keys = record.keys();
StringBuilder builder = new StringBuilder();
builder.append("insert or replace into ");
builder.append(tableName);
builder.append(" (");
while(keys.hasNext()){
builder.append(keys.next()).append(",");
}
builder.setLength(builder.length() - 1);
builder.append(") values(");
keys = record.keys();
JSArray valuesArray = new JSArray();
while(keys.hasNext()){
String key = keys.next();
if( record.get(key) != null && record.get(key) instanceof String) {
}
builder.append("?").append(",");
valuesArray.put(record.get(key));
}
builder.deleteCharAt(builder.length() - 1);
builder.append(");");
JSONObject ret = new JSONObject();
ret.put("query",builder.toString());
ret.put("values",valuesArray);
return ret.toString();
}
private static JSONObject modifyRecord(JSONObject record) throws JSONException {
if (record.has("ValidFrom")) {
record.remove("ValidFrom");
}
if (record.has("ValidTo")) {
record.remove("ValidTo");
}
Iterator<String> keys = record.keys();
while(keys.hasNext()){
String key = keys.next();
if (record.get(key) instanceof JSONObject || record.get(key) instanceof List) {
record.put(key,((JSObject)record.get(key)).toString());
}
}
return record;
}
And an Error receiving like this:
Caused by: java.lang.StackOverflowError: stack size 1040KB at java.lang.AbstractStringBuilder.append(AbstractStringBuilder.java:649)
Is there any issue in the above methods?How to solve it?
Thanks.
You could try to increase the stack size of your JVM. In your case, the size seems to be 1040KB. You can set it to another value (e.g. 48 MB) like this:
java -Xss48m
For more details and examples see https://www.baeldung.com/jvm-configure-stack-sizes or the official reference https://www.oracle.com/java/technologies/javase/vmoptions-jsp.html
Code used:
jObj = new JSONObject(json);
newJSONString = jObj.getString("payload");
JArray = new JSONArray(newJSONString);
This is what JArray looks like:
[{"06:30:00":{"color":"grey","time_color":"black"},"06:45:00":{"color":"grey","time_color":"black"}}]
Now I want to loop through the received times and print their color, how to do this?
What I've tried:
for (int i = 0; i < JArray.length(); ++i) {
JSONObject rec = null;
try {
rec = JArray.getJSONObject(i);
} catch (JSONException e) {
e.printStackTrace();
}
android.util.Log.e("print row:", String.valueOf(rec));
}
This just gives me this output:
{"06:30:00":{"color":"grey","time_color":"black"},"06:45:00":{"color":"grey","time_color":"black"}}
You are getting this output since your JSON array contains only one JSON object which is - {"06:30:00":{"color":"grey","time_color":"black"},"06:45:00":{"color":"grey","time_color":"black"}}
Before answering your question, I would recommend you to go through JSON syntax. It will help you understand your question and answer effectively.
Coming back to your question, in order to get "color" field from your nested JSON:
Traverse through keys in your JSON object. In your case these are -
"06:30:00" , "06:45:00". You can google out solution to traverse
through keys in JSON object in java.
Get nested object associated with given key(time) - you can use
getJSONObject() method provided by Json library for this.
Get "color" field from json object - you can use optString() or
getString() methods provided by Json library for this- depending
upon whether your string is mandatory or optional.
Here is working solution in java for your problem:
public static void getColor(JSONObject payloadObject) {
try {
JSONArray keys = payloadObject.names();
for (int i = 0; i < keys.length(); i++) {
String key = keys.getString(i); // Here's your key
JSONObject value = payloadObject.getJSONObject(key); // Here's your value - nested JSON object
String color = value.getString("color");
System.out.println(color);
}
} catch (JSONException e) {
e.printStackTrace();
}
}
Please note, it is considered that object you receive as payload is a JSON object.
Hope this helps.
Thanks.
Use Keys() method which return Iterator<String> so that it will be easy for iterating every nested JSON
for (int i = 0; i < JArray.length(); ++i) {
try {
JSONObject rec = JArray.getJSONObject(i);
Iterator<String> keys = rec.keys();
while(keys.hasNext()) {
String key1 = keys.next();
JSONObject nested = rec.getJSONObject(key1); //{"color":"grey","time_color":"black"}
//now again same procedure
Iterator<String> nestedKeys = nested.keys();
while(nestedKeys.hasNext()) {
String key2 = nestedKeys.next();
System.out.println("key"+"..."+key2+"..."+"value"+"..."+nested.getString(key2);
}
}
} catch (JSONException e) {
e.printStackTrace();
}
android.util.Log.e("print row:", String.valueOf(rec));
}
Following is my code can it be optimized for java 8 and can it be more efficient?
public String LanguageString(Set<Locale> languageSet) throws Exception {
JSONObject json = new JSONObject();
JSONObject tempj = new JSONObject();
JSONArray jArr = new JSONArray();
try {
for (Locale locale : languageSet) {
if (locale != null) {
tempj = new JSONObject();
tempj.put("lcode", locale.toLanguageTag());
tempj.put("ldisplay", locale.getDisplayName());
jArr.put(tempj);
}
}
json.put("root", jArr);
} catch (JSONException e) {
//
}
return json.toString();
}
If you want to use Java 8 and Stream API you can use stream, map and reduce to create your final JSONObject, e.g.
public static String languageStringUsingStream(Set<Locale> locales) {
return new JSONObject()
.put("root", locales.stream()
.map(locale -> new JSONObject()
.put("lcode", locale.toLanguageTag())
.put("ldisplay", locale.getDisplayName(Locale.ENGLISH))
)
.reduce(new JSONArray(), JSONArray::put, (a, b) -> a))
.toString();
}
Here you can find a complete example:
https://gist.github.com/wololock/27bd296fc894f6f4594f997057218fb3
So I'm kind of stuck. I'm experiencing with JSon and I'm trying to get all the total_price of different orders from a JSON file. I can read the file, but I'm having issue getting that specific information then sum it all up.
Here's the code I have so far.
public static void main(String[] args) {
JSONParser parser = new JSONParser();
try {
Object obj = parser.parse(new FileReader(
"/orders.json"));
JSONObject jsonObject = (JSONObject) obj;
JSONArray orderList = (JSONArray) jsonObject.get("orders");
System.out.println("\norders");
Iterator<String> iterator = orderList.iterator();
while (iterator.hasNext()) {
System.out.println(iterator.next());
}
obj = parser.parse(sCurrentLine);
JSONArray jsonArray = (JSONArray) obj;
for(obj : jsonArray){
JSONObject jsonObject = (JSONObject)obj;
JSONObject realTitle = (JSONObject)jsonObject.get("0");
String name = (String) realTitle.get("title");
}
} catch (Exception e) {
e.printStackTrace();
}
}
For the JSON file:
{
"orders":[
{
"id":4251070723,
"email":"heathcote_amya#gmail.com",
"closed_at":null,
"created_at":"2016-12-05T23:16:40-05:00",
"updated_at":"2016-12-05T23:16:40-05:00",
"number":137,
"note":null,
"token":"c460f260f4f38b8a2b1f78e6efd0140e",
"gateway":"",
"test":false,
"total_price":"179.04",
"default":true
},
{
"id":4251070787,
"email":"napoleon.batz#gmail.com",
"closed_at":null,
"created_at":"2016-12-05T23:16:40-05:00",
"updated_at":"2016-12-05T23:16:41-05:00",
"number":138,
"note":null,
"token":"54c4f1735cfec8f98ad16ae5e9a161cd",
"gateway":"",
"test":false,
"total_price":"14.62"
}
]
}
The error I get is for(obj : jsonArray) is causing me issue, but honestly, I don't think I'm even on the right path.
Thanks a lot for your help in advance!
Your code has obvious compile time errors. With in the for loop, you are again trying to do a get(0), which is incorrect.
I have refined the code.
Try this. Its giving proper result as below
The price data of each order --{4251070723=179.04, 4251070787=14.62}.
The total Price of all order --193.66
public static void main(String[] args) {
JSONParser parser = new JSONParser();
Map<String, String> priceData = new HashMap<>();
BigDecimal totalPrice = BigDecimal.ZERO;
String orderId = null;
String orderPrice = null;
try {
String sCurrentLine = null;
Object obj = parser.parse(new FileReader(
"orders.json"));
JSONObject jsonObject = (JSONObject) obj;
JSONArray orderList = (JSONArray)(jsonObject.get("orders"));
System.out.println("Complete order list --"+orderList.toString());
for(Object singleOrder : orderList) {
JSONObject singleArrayData = (JSONObject) singleOrder;
orderId = singleArrayData.get("id").toString();
orderPrice = singleArrayData.get("total_price").toString();
priceData.put(orderId, orderPrice);
totalPrice = totalPrice.add(new BigDecimal(orderPrice));
}
System.out.println("The price data of each order --"+priceData.toString());
System.out.println("The total Price of all order --"+totalPrice);
} catch (Exception e) {
e.printStackTrace();
}
}
The following is my code. It works fine on the emulator. But when I run it on my android device, it crashes with java.lang.ClassCastException: java.lang.String at the line marked below. I can't find the place where the casting has gone wrong. Any help would be greatly appreciated! :)
public static MatrixCursor getRespAsCursor(String query) {
String url = (String)(API_URL + "?api_key=" + API_KEY + "&" + query);
System.out.println(url);
String resp = (String)getAPIResp(url);
JSONObject reply;
MatrixCursor emptyCursor = new MatrixCursor(new String[0]);
try {
reply = (JSONObject) new JSONTokener(resp).nextValue();//CRASHES HERE
String statusCode = (String)reply.getString("statusCode");
if(!statusCode.equals("200")) {
return emptyCursor;
}
int count = Integer.parseInt(reply.getString("count"));
if(count < 1) {
return emptyCursor;
}
JSONArray data = reply.getJSONArray("data");
//Get keys in String[] format
Iterator<String> keys = data.optJSONObject(0).keys();
List<String> copy = new ArrayList<String>();
//Add "_id" as adapters need it for traversal
copy.add("_id");
//copy rest of the keys
while (keys.hasNext())
copy.add(keys.next());
String[] sKeys = new String[copy.size()];
copy.toArray(sKeys);
int len = data.length();
//Create Cursor with JSON keys as columns
MatrixCursor resultCursor = new MatrixCursor(sKeys, len);
//Add rows to Cursor
for(int i = 0; i < len; i++) {
JSONObject d = data.optJSONObject(i);
List<Object> values = new ArrayList<Object>();
for(String key: copy) {
if(key.equals("_id")) {
values.add(Integer.toString(i));
}
else {
values.add(d.opt(key));
}
}
resultCursor.addRow(values);
}
return resultCursor;
}
catch(JSONException e) {
return emptyCursor;
}
}
You've already shown the place the casting goes wrong - it's here:
reply = (JSONObject) new JSONTokener(resp).nextValue();
nextValue() is returning a String, and you're casting it to JSONObject.
I suspect the JSON isn't as you expect it to be - it's not a statusCode = "value" pair; it's just the value.
You should check the JSON that you're returning, and either change to cast to a string, or if you want to be able to handle both forms of JSON, check the result with instanceof before casting:
Object reply = new JSONTokener(resp).nextValue();
if (reply instanceof String) {
...
} else if (reply instanceof JSONObject) {
...
}
You might wanna have a look at JSONTokener javadoc
The doc says nextValue()
Get the next value. The value can be a Boolean, Double, Integer,
JSONArray, JSONObject, Long, or String, or the JSONObject.NULL object.
You are probably getting the ClassCastException as your line of code
reply = (JSONObject) new JSONTokener(resp).nextValue();//CRASHES HERE
does not return JSONObject.
You might want to check instance type of the returned object before typecasting it.
For example,
Object obj = new JSONTokener(resp).nextValue();
if(obj instanceof JSONObject){
reply = (JSONObject)obj;
} else {
//Handle otherwise,
}
reply = (JSONObject) new JSONTokener(resp).nextValue();//CRASHES HERE
If you are getting a ClassCastException is because the Object returned by nextValue() is not a JSONObject (probably a String instance)