How to create json, sorted on keys, using gson? - java

I need to create constant json string or a json sorted on keys. What do I mean by constant json string? Please look into following code sample, which I created.
My Code 1:
public class GsonTest
{
class DataObject {
private int data1 = 100;
private String data2 = "hello";
}
public static void main(String[] args)
{
GsonTest obj=new GsonTest();
DataObject obj2 = obj.new DataObject();
Gson gson = new Gson();
String json = gson.toJson(obj2);
System.out.println(json);
}
}
Output 1:
{"data1":100,"data2":"hello"}
My Code 2:
public class GsonTest
{
class DataObject {
private String data2 = "hello";
private int data1 = 100;
}
public static void main(String[] args)
{
GsonTest obj=new GsonTest();
DataObject obj2 = obj.new DataObject();
Gson gson = new Gson();
String json = gson.toJson(obj2);
System.out.println(json);
}
}
Output 2:
{"data2":"hello","data1":100}
If you see, if I switch variables (data1 & data2 in DataObject class), I get different json. My objective to get same json, even if somebody changes position of the class variables. I get it when somebody adds new variables, json would change. But json shouldn't change when variables are moved around. So, my objective is to get standard json, possibly in sorted keys order for same class. If there is nested json, then it should be sorted in the nested structure.
Expected output on run of both the codes:
{"data1":100,"data2":"hello"} //sorted on keys!! Here keys are data1 & data2
I understand, I need to change something in String json = gson.toJson(obj2); line, but what do I have to do?
Why I need them to be order?
I need to encode the json string and then pass it to another function. If I change the order of keys, even though value remain intact, the encoded value will change. I want to avoid that.

First of all, the keys of a json object are unordered by definition, see http://json.org/.
If you merely want a json string with ordered keys, you can try deserializing your json into a sorted map, and then serialize the map in order to get the sorted-by-key json string.
GsonTest obj=new GsonTest();
DataObject obj2 = new DataObject();
Gson gson = new Gson();
String json = gson.toJson(obj2);
TreeMap<String, Object> map = gson.fromJson(json, TreeMap.class);
String sortedJson = gson.toJson(map);

Like others have mentioned that by design JSON is not supposed to have sorted keys in itself. You can also come up with a recursive solution to do it. I won't say my solution is very efficient but it does the intended job. Please have a look at the following piece of code.
private static JsonObject sortAndGet(JsonObject jsonObject) {
List<String> keySet = jsonObject.keySet().stream().sorted().collect(Collectors.toList());
JsonObject temp = new JsonObject();
for (String key : keySet) {
JsonElement ele = jsonObject.get(key);
if (ele.isJsonObject()) {
ele = sortAndGet(ele.getAsJsonObject());
temp.add(key, ele);
} else if (ele.isJsonArray()) {
temp.add(key, ele.getAsJsonArray());
} else
temp.add(key, ele.getAsJsonPrimitive());
}
return temp;
}
Input:
{"c":"dhoni","a":"mahendra","b":"singh","d":{"c":"tendulkar","b":"ramesh","a":"sachin"}}
Output:
{"a":"mahendra","b":"singh","c":"dhoni","d":{"a":"sachin","b":"ramesh","c":"tendulkar"}}

Perhaps a work around is for your class wrap a TreeMap which maintains sort order of the keys. You can add getters and setters for convenience. When you gson the TreeMap, you'll get ordered keys.

Related

Create a json response containing array of array

Response required
{
"data" : [[1234, 55],[1264,45],[1334, 56]]
}
Model
Class Timeseries{
private List<List<Integer>> data;
}
I don't feel List of List is appropriate to achive the json response required.. But I am unable to replace it with List, where CustomObject will contain 2 integer member variables. As it will change the format of the response and send the response as data containing list of objects of type CustomObject instead of list of list..
Please suggest an alternate approch
The easiest way to reach your needed output is
class data extends ArrayList<List<Integer>> {
}
and use this code for serilization with Jackson JSON
data ts = new data();
ts.addAll(Arrays.asList(Arrays.asList(1234, 55), Arrays.asList(1264, 45), Arrays.asList(1334, 56)));
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.enable(SerializationFeature.WRAP_ROOT_VALUE);
String result = objectMapper.writeValueAsString(ts);
System.out.println(result);
The output string will be as you need {"data":[[1234,55],[1264,45],[1334,56]]}
But, seriously, the right way here is to implement for
class Timeseries {
private List<List<Integer>> data;
}
your own com.fasterxml.jackson.databind.ser.std.StdSerializer<T> successor for Timeseries class.
UPDATE:
Just find out the easiest way to reach your needed string for class
class Timeseries {
public List<List<Integer>> data;
}
Note data field has to be either public or have a getter.
And then code
Timeseries ts = new Timeseries();
ts.data = Arrays.asList(Arrays.asList(1234, 55), Arrays.asList(1264, 45), Arrays.asList(1334, 56));
ObjectMapper objectMapper = new ObjectMapper();
String result = objectMapper.writeValueAsString(ts);
System.out.println(result);
will print {"data":[[1234,55],[1264,45],[1334,56]]}
You can try like this,
class CustomObject {
private int data1;
private int data2;
// getters & setters
}
Then you can modify your Timeseries as below,
private List<CustomObject> data;
You can use a list of Arrays of size 2.

create json using Google Gson

I want make json
records":[ {"MON_PRIORITY":"","MON_ICR_ACCNO":"100000010010","MON_REPORT_DATE":"","MON_STATUS":"",
But my json is
{"MON_PRIORITY":"","MON_ICR_ACCNO":"100000010010","MON_REPORT_DATE":"","MON_STATUS":"",
My jsp code is
HashMap jsonRecordval = (HashMap) hshValues.get("jsonRecord");
String json="";
json = new Gson().toJson(jsonRecordval );
Thanks..
What you're getting is the JSON produced by a Hashmap. e.g. {"key":"value"}. Breaking it down piece by what, your desired json is a representation of an object { with a records field "records" that contains an array [ of the contents of your hashmap {"key":"value"}
To do that, it's easiest to create an object with instance variables corresponding to the fields to expected output. Something like
public class JsonRecords {
private final List<HashMap> records = new ArrayList<>;
public JsonRecords(HashMap recordsVal) {
records.add(recordsVal);
}
//Getters and setters
}
Then use it to build your JSON
HashMap jsonRecordval = (HashMap) hshValues.get("jsonRecord");
String json = new Gson().toJson(new JsonRecords(jsonRecordval));

Deserialize JSON object that has a map using Gson

I've searched the web for a solution, but, the answers I found only help when the JSON file being parsed is solely a map.
I was wondering if there is a way to parse an object that has an HashMap along with other data.
For example:
public class Data
{
String aName;
HashMap<String, Object> objects;
List<String> aExpressions;
}
I can parse the name and expressions but I don't know how to parse the entire JSON object as a whole.
I think it has something to do with type tokens, but that only works if the whole object is a HashMap.
The presence of HashMap in your class does not change how Gson converts it to and from json.
data d = new data();
//set some values
String json = new Gson().toJson(d);
data d2 = new Gson().fromJson( json, data.class);
That's pretty much it.
Since
JSON can represent four primitive types (strings, numbers,
booleans, and null) and two structured types (objects and arrays).
A string is a sequence of zero or more Unicode characters
[UNICODE].
An object is an unordered collection of zero or more name/value
pairs, where a name is a string and a value is a string, number,
boolean, null, object, or array.
An array is an ordered sequence of zero or more values.
Gson acts 2 ways:
you can let Gson deserialize creating a combination of maps, lists and primitives or
you can specify exactly the kind of object you want, and through reflection, it will fill the desidered fields.
You can mix these two approaches and of course you can do a lot more to solve all your parsing cases (like using custom type adapters).
I prepared you a little example using your class that shows how Gson can parse your data using a combination of maps/list/primitives or passing your class (that has a mixed approach).
package stackoverflow.questions;
import java.util.*;
import com.google.gson.Gson;
public class Q20154323 {
public static class Data {
public String aName;
public HashMap<String, Object> objects;
public List<String> aExpressions;
#Override
public String toString() {
return "Data [aName=" + aName + ", objects=" + objects + ", aExpressions=" + aExpressions + "]";
}
}
public static void main(String[] args) {
Data d = new Data();
d.aName = "Test";
d.objects = new HashMap<>();
d.aExpressions = new ArrayList<>();
d.objects.put("key1", 1L);
d.objects.put("key2", new Date());
d.aExpressions.add("Stack");
d.aExpressions.add("Overflow");
Gson g = new Gson();
String json = g.toJson(d);
System.out.println("As JSON: " +json);
Data d2 = g.fromJson(json, Data.class);
System.out.println("As \"casted\" data type: " + d2);
Object o3 = g.fromJson(json, Object.class);
System.out.println("As \"free\" object: " + o3);
}
}
and this is the execution. It shows you two way of parsing the JSON string that I created using your initial class.
As JSON: {"aName":"Test","objects":{"key2":"Nov 23, 2013 1:33:23 AM","key1":1},"aExpressions":["Stack","Overflow"]}
As "casted" data type: Data [aName=Test, objects={key2=Nov 23, 2013 1:33:23 AM, key1=1.0}, aExpressions=[Stack, Overflow]]
As "free" object: {aName=Test, objects={key2=Nov 23, 2013 1:33:23 AM, key1=1.0}, aExpressions=[Stack, Overflow]}
You can use one or other approach as your needs.
About the TypeToken, due to generics erasure, something like this
List<Data> list = new Gson().parse(aJsonString, List<Data>.class)
won't work, you have to do something like
Type listType = new TypeToken<List<Data>>() {}.getType();
List<Data> list = new Gson().parse(aJsonString, listType.class)
but this case applies, pratically, when your JSON is an array and you want to deserialize it into a list of custom classes.

JSONObject from HashMap - Cast to String failed

I need to create a JSONObject from a HashMap of a custom class's toString and a float value. I tried the code below hoping that it would just work:
public class MyClass {
...
public String toString() {
return "a nice string"
}
}
HashMap<MyClass,Float> map = new HashMap<MyClass,Float>();
map.put(...);
JSONObject json = new JSONObject(map);
But I get:
java.lang.ClassCastException: MyClass cannot be cast to java.lang.String
What's the best way to create this JSONObject? Thanks.
You need to change this:
HashMap<MyClass,Float> map = new HashMap<MyClass,Float>();
with
HashMap<String,Float> map = new HashMap<String,Float>();
as you said "HashMap of a custom class's toString and a float value"
You haven't mentioned how are you putting the values into the hashmap.
But if you using toString method of your custom class, then you should put it like :
MyClass m = new MyClass();
map.put(m.toString,123.45f);
Seems like you're using the org.json library. If you take a look at the code of the JSONObject class, apparently they're not using generics.
public JSONObject(Map map) {
this.map = new HashMap();
if (map != null) {
Iterator i = map.entrySet().iterator();
while (i.hasNext()) {
Map.Entry e = (Map.Entry)i.next();
Object value = e.getValue();
if (value != null) {
this.map.put(e.getKey(), wrap(value));
}
}
}
}
This map seems to handle entries with a String key and an Object value by the look of the keyPool map they use to manage unique String keys. In the comments, its also stated that:
This is used by JSONObject.put(string, object).
So it would be correct to assume the keys of the JSON objects are Strings.
Your MyClass type can't be upcasted to String directly (String is not a superclass of MyClass), that's why the constructor is actually complaining about the map, because it needs a map of the form HashMap<String,Object> (Note that there's no problem with Float and Object).
To fix the issue, you have to define a HashMap<String,Float> where you should store a String representation of your MyClass object either by using toString.
If you can't use a String you can consider using an intermediate structure that maps a code represented with a String to a certain MyClass object, so you can retain your MyClass class.
Both Gamb's and Abu's answers are correct and helped me to get to my final result.
I solved my problem like this:
HashMap<MyClass,Float> obj = functionThatReturnsThisStructure();
JSONObject jsonObj = new JSONObject();
for (Entry<MyClass,Float> entry: obj.entrySet()) {
jsonObj.put(entry.getKey().toString(), entry.getValue());
}

Serialize objects with Gson into arbitrary JSON arrays without getting warnings

I need to serialize an object like this:
class A {
int a = 1;
String b = "hello";
boolean isDog = false;
}
into JSON array like this:
[1,"hello",false]
I know one (wrong) way to do this: create an untyped Collection out of object's fields and then Gson it:
class A {
// ...
Collection forGson() {
ArrayList col = new ArrayList();
col.add(a);
col.add(b);
col.add(c);
return col;
}
}
new Gson().toJson(new A().forGson());
But it produces a lot of warnings because of untyped collections usage. So is there any way to serialize objects into an array of arbitrary types without getting any warnings?
This is literally "You're doing it wrong". You don't have an array of random things, you have an (A) object. It has nothing in common with the JSON you want to produce.
That being said, if you really wanted to do this, you supply your own Serializer / Deserializer to Gson:
class ASerializer implements JsonSerializer<A>
{
public JsonElement serialize(A t, Type type, JsonSerializationContext jsc)
{
JsonArray ja = new JsonArray();
ja.add(new JsonPrimitive(t.a));
ja.add(new JsonPrimitive(t.b));
ja.add(new JsonPrimitive(t.isDog));
return ja;
}
}
You'd create a JsonDeserializer that did the reverse, creating a A object from the supplied JSON array.
See: https://sites.google.com/site/gson/gson-user-guide#TOC-Custom-Serialization-and-Deserialization for more info.
Then using GsonBuilder you'd tell Gson to use them:
GsonBuilder builder = new GsonBuilder();
builder.registerTypeAdapter(A.class, new ASerializer());
builder.registerTypeAdapter(A.class, new ADeserializer());
Gson gson = builder.create();
...

Categories

Resources