This link from the Gson project seems to indicate that I would have to do something like the following for serializing a typed Map to JSON:
public static class NumberTypeAdapter
implements JsonSerializer<Number>, JsonDeserializer<Number>,
InstanceCreator<Number> {
public JsonElement serialize(Number src, Type typeOfSrc, JsonSerializationContext
context) {
return new JsonPrimitive(src);
}
public Number deserialize(JsonElement json, Type typeOfT,
JsonDeserializationContext context)
throws JsonParseException {
JsonPrimitive jsonPrimitive = json.getAsJsonPrimitive();
if (jsonPrimitive.isNumber()) {
return jsonPrimitive.getAsNumber();
} else {
throw new IllegalStateException("Expected a number field, but was " + json);
}
}
public Number createInstance(Type type) {
return 1L;
}
}
public static void main(String[] args) {
Map<String, Number> map = new HashMap<String, Number>();
map.put("int", 123);
map.put("long", 1234567890123456789L);
map.put("double", 1234.5678D);
map.put("float", 1.2345F);
Type mapType = new TypeToken<Map<String, Number>>() {}.getType();
Gson gson = new GsonBuilder().registerTypeAdapter(Number.class, new
NumberTypeAdapter()).create();
String json = gson.toJson(map, mapType);
System.out.println(json);
Map<String, Number> deserializedMap = gson.fromJson(json, mapType);
System.out.println(deserializedMap);
}
Cool and that works, but it seems like so much overhead (a whole Type Adapter class?). I have used other JSON libraries like JSONLib and they let you build a map in the following way:
JSONObject json = new JSONObject();
for(Entry<String,Integer> entry : map.entrySet()){
json.put(entry.getKey(), entry.getValue());
}
Or if I have a custom class something like the following:
JSONObject json = new JSONObject();
for(Entry<String,MyClass> entry : map.entrySet()){
JSONObject myClassJson = JSONObject.fromObject(entry.getValue());
json.put(entry.getKey(), myClassJson);
}
The process is more manual, but requires way less code and doesn't have the overhead of haivng to create a custom Type Adapter for Number or in most cases for my own custom class.
Is this the only way to serialize a map with Gson, or has anyone found a way that beats out what Gson recommends in the link above.
Only the TypeToken part is neccesary (when there are Generics involved).
Map<String, String> myMap = new HashMap<String, String>();
myMap.put("one", "hello");
myMap.put("two", "world");
Gson gson = new GsonBuilder().create();
String json = gson.toJson(myMap);
System.out.println(json);
Type typeOfHashMap = new TypeToken<Map<String, String>>() { }.getType();
Map<String, String> newMap = gson.fromJson(json, typeOfHashMap); // This type must match TypeToken
System.out.println(newMap.get("one"));
System.out.println(newMap.get("two"));
Output:
{"two":"world","one":"hello"}
hello
world
Default
The default Gson implementation of Map serialization uses toString() on the key:
Gson gson = new GsonBuilder()
.setPrettyPrinting().create();
Map<Point, String> original = new HashMap<>();
original.put(new Point(1, 2), "a");
original.put(new Point(3, 4), "b");
System.out.println(gson.toJson(original));
Will give:
{
"java.awt.Point[x\u003d1,y\u003d2]": "a",
"java.awt.Point[x\u003d3,y\u003d4]": "b"
}
Using enableComplexMapKeySerialization
If you want the Map Key to be serialized according to default Gson rules you can use enableComplexMapKeySerialization. This will return an array of arrays of key-value pairs:
Gson gson = new GsonBuilder().enableComplexMapKeySerialization()
.setPrettyPrinting().create();
Map<Point, String> original = new HashMap<>();
original.put(new Point(1, 2), "a");
original.put(new Point(3, 4), "b");
System.out.println(gson.toJson(original));
Will return:
[
[
{
"x": 1,
"y": 2
},
"a"
],
[
{
"x": 3,
"y": 4
},
"b"
]
]
More details can be found here.
In Gson 2.7.2 it's as easy as
Gson gson = new Gson();
String serialized = gson.toJson(map);
I'm pretty sure GSON serializes/deserializes Maps and multiple-nested Maps (i.e. Map<String, Map<String, Object>>) just fine by default. The example provided I believe is nothing more than just a starting point if you need to do something more complex.
Check out the MapTypeAdapterFactory class in the GSON source: http://code.google.com/p/google-gson/source/browse/trunk/gson/src/main/java/com/google/gson/internal/bind/MapTypeAdapterFactory.java
So long as the types of the keys and values can be serialized into JSON strings (and you can create your own serializers/deserializers for these custom objects) you shouldn't have any issues.
Map<String, Object> config = gson.fromJson(reader, Map.class);
Related
I'm very sorry if the title is a little misleading... I really don't know what is exactly the terms but don't worry I have provided my code just in case.
I have a JSON string called json_string, this is its value:
{
"record": {
"sample": "Hello World",
"sample_2": "Hello World_2"
},
"metadata": {
"id": "value",
"private": "true"
}
}
I can convert this JSON string to HashMap using this line of code:
myHashMap = new Gson().fromJson(json_string, new TypeToken<HashMap<String, Object>>(){}.getType());
Now I can get the contents of the key record and pass it to string called final_output using this code:
String final_output = myHashMap.get("record").toString();
the string final_output is:
{ sample=Hello World, sample_2=Hello World_2 }
How can I make it return this format again:
{
"sample": "Hello World",
"sample_2": "Hello World_2"
}
?
Thanks for answering
GSON has a built-in method for pretty printing
.setPrettyPrinting()
To enable Gson pretty print, you must configure the Gson instance using the setPrettyPrinting() method of GsonBuilder class and this method configures Gson to output JSON that fits in a page for pretty printing.
For your case:
Gson gson = new GsonBuilder().setPrettyPrinting().create();
myhashmap = gson.fromJson(json_string, new TypeToken<HashMap<String, Object>>(){}.getType());
Update:
You can get the prettified json Object from a key as a String. (here 'record')
String output = gson.toJson(myHashMap.get("record")).toString();
Maybe this example helps you:
SortedMap<String, String> elements = new TreeMap();
elements.put("Key1", "Value1");
elements.put("Key2", "Value2");
elements.put("Key3", "Value3");
Gson gson = new Gson();
Type gsonType = new TypeToken<HashMap>(){}.getType();
String gsonString = gson.toJson(elements,gsonType);
System.out.println(gsonString);
Output:
{"Key1":"Value1","Key2":"Value2","Key3":"Value3"}
For your case:
Gson gson = new Gson();
Type gsonType = new TypeToken<HashMap>(){}.getType();
String final_output = gson.toJson(myHashMap.get("record"),gsonType);
System.out.println(final_output);
I have a Json String representing a object which further has another nested objects. Also, I have a list of keys which I need to remove from this Json String. These keys can be at any nested level of object inside this string. Finally I need to compare this edited Json string to another string and output the differences. I need to remove those key-value pairs from first Json string because I need to ignore those keys during comparison. Currently, I am converting the Json String to LinkedTreeMap provided by Gson API and then doing Map.difference() to compare. Please suggest a solution to this.
I did it by traversing recursively inside the Nested LinkedTreeMap till I find the field and remove it if it exists. The full path of Key needs to be provide to get the exact key-value location inside the Object (like "objects.desc" in the below Json Sample to remove desc from the Json String)
Json Sample:
{
"message": "MSG",
"code": "COD001",
"objects": [
{
"resource": "Student",
"field": "StudentId",
"desc": "Student Description"
}
]
}
Code Sample:
public MapDifference<String, Object> getMapDifference(String jsonString1, String jsonString2) {
MapDifference<String, Object> mapDifference = null;
Gson gson = new Gson();
Type mapType = new TypeToken<Map<String, Object>>() {
}.getType();
Map<String, Object> firstMap = gson.fromJson(jsonString1, mapType);
Map<String, Object> secondMap = gson.fromJson(jsonString2, mapType);
firstMap = CollectionUtils.isEmpty(firstMap) ? new HashMap<>() : firstMap;
secondMap = CollectionUtils.isEmpty(secondMap) ? new HashMap<>() : secondMap;
//This contains the List of keys that is required to be filtered out from Json Strings before comparision like {"message", "objects.desc"}
List<String> firstIgnoreList = getIgnoreList1();
List<String> secondIgnoreList = getIgnoreList2();
filterKeys(firstMap, firstIgnoreList);
filterKeys(secondMap, secondIgnoreList);
mapDifference = Maps.difference(firstMap, secondMap);
return mapDifference;
}
private void filterKeys(Map<String, Object> keyMap, List<String> ignoreList) {
if (!(CollectionUtils.isEmpty(keyMap) || CollectionUtils.isEmpty(ignoreList))) {
ignoreList.stream().parallel().forEach(key -> recursiveRemove(keyMap, key));
}
}
private static void recursiveRemove(Map<String, Object> keyMap, String key) {
List<String> path = Arrays.asList(StringUtils.split(key.trim(), "."));
int size = path.size();
int index = 0;
List<LinkedTreeMap> treeMapList = new ArrayList<LinkedTreeMap>();
treeMapList.add((LinkedTreeMap) keyMap);
while (index != size - 1) {
int i = index++;
List<LinkedTreeMap> treeMapListTemp = new ArrayList<LinkedTreeMap>();
treeMapList.stream().parallel().forEach(treeMap -> {
Object obj = treeMap.get(path.get(i));
if (obj instanceof List) {
treeMapListTemp.addAll((List<LinkedTreeMap>) obj);
} else if (obj instanceof LinkedTreeMap) {
treeMapListTemp.add((LinkedTreeMap) obj);
}
});
treeMapList = treeMapListTemp;
}
treeMapList.stream().parallel().forEach(treeMap -> treeMap.remove(path.get(size - 1)));
}
I use this list:
List<HashMap<Map<String, Object>, Map<String, Object>>>
And I need to save it to a text file.
So with the List#toString method, I obtain a text like that:
[{{key=value, key=value, key=value}={key=value, key=value}}, {{key=value, key=value, key=value}={key=value, key=value}}, {{key=value, key=value, key=value}={key=value, key=value}}]
How can I convert it back to a List?
Thanks!
You are better off using a format like JSON or YAML. Usng toString() means there is too many corner cases like a = { } or , appearing in a key or value.
ObjectMapper mapper = new ObjectMapper();
Map<String, Object> map1=new HashMap<>();
map1.put("key","value");
map1.put("key1","value");
map1.put("key2","value");
map1.put("key3","value");
HashMap<Map<String, Object>, Map<String, Object>> hash=new HashMap<>();
List<HashMap<Map<String, Object>, Map<String, Object>>> lists=new ArrayList<>();
hash.put(map1, map1);
lists.add(hash);
System.out.println(mapper.writeValueAsString(lists));
The toString() method produces a format that is intended for logging and debugging, not for serialization and deserialization.
I recommend to convert your object into json format (other formats like xml can work just as well) and use a json library such as "Gson" for conversions.
Example how you can convert your object into a json string:
Gson gson = new Gson();
String json = gson.toJson(myObject);
Example how you can convert the json string back into an object, see:
creating Hashmap from a JSON String
Update: Here's a complete example of the first part:
List<HashMap<Map<String, Object>, Map<String, Object>>> myObject = new ArrayList<>();
HashMap<Map<String, Object>, Map<String, Object>> hashMap1 = new HashMap<>();
myObject.add(hashMap1);
Map<String, Object> key1 = new HashMap<>();
key1.put("key1", "keyValue1");
Map<String, Object> value1 = new HashMap<>();
value1.put("value1", "valueValue1");
hashMap1.put(key1, value1);
Gson gson = new GsonBuilder().disableHtmlEscaping().create();
String json = gson.toJson(myObject);
System.out.println(json);
This example prints:
[{"{key1=keyValue1}":{"value1":"valueValue1"}}]
Update2 Use Pair (from apache commons lang3) instead of HashMap. That makes a lot more sense.
List<Pair<Map<String, Object>, Map<String, Object>>> myObject = new ArrayList<>();
MutablePair<Map<String, Object>, Map<String, Object>> pair1 = new MutablePair<>();
myObject.add(pair1);
Map<String, Object> key1 = new HashMap<>();
key1.put("key1", "keyValue1");
Map<String, Object> value1 = new HashMap<>();
value1.put("value1", "valueValue1");
pair1.setLeft(key1);
pair1.setRight(value1);
Gson gson = new Gson();
String json = gson.toJson(myObject);
System.out.println(json);
Prints:
[{"left":{"key1":"keyValue1"},"right":{"value1":"valueValue1"}}]
I'm parsing simple JSON object with Gson. I want it to throw some error when key name is duplicated. E.g.
{
a: 2,
a: 3
}
In my case, Gson parses such JSON and sets a to 3. I want it to throw some exception.
I know I can parse JSON as map, and then Gson throws exception in such case, but only if the duplicated key is not nested in the map. If I have e.g. JSON like this:
{
a: 2,
b: {
dup: 1,
dup: 2
}
}
Still, it is parsed without any exception and I have only one "dup" with value 2.
Can I somehow setup Gson to throw error in such case? Or to have duplicated entries in JsonObject instance, so that I can detect it myself (but I doubt that, as it would be invalid JsonObject)
Reproducible example
String json = "{\"a\":2, \"a\":3}";
Gson gson = new Gson();
JsonObject jsonObject = gson.fromJson(json, JsonObject.class);
System.out.println(jsonObject);
prints out
{"a":3}
1) You may edit the source of gson a little bit. This is just a suggestion to understand how things work. I don't advice you to use this on a real/production environment.
Gson uses com.google.gson.internal.LinkedTreeMap while parsing a json string to a JsonObject. For testing issues you can copy that class into your project with the same name and package name. And edit its put method to not allow duplicate keys.
#Override
public V put(K key, V value) {
if (key == null) {
throw new NullPointerException("key == null");
}
// my edit here
if(find(key, false) != null) {
throw new IllegalArgumentException("'" + key.toString() + "' is duplicate key for json!");
}
Node<K, V> created = find(key, true);
V result = created.value;
created.value = value;
return result;
}
2) Another clean solution is to define custom classes which are going to map to your json strings. Then write their custom TypeAdapters
3) Do it by using a Deserializer? I don't think it is possible. If you try to use it you'll see that you already have a jsonObject there which your duplicate keys are handled as one.
You can try this way:
String json = "{\"a\":2, \"a\":3}";
Gson gson = new Gson();
Type mapType = new TypeToken<Map<String, String>>() {}.getType();
Map<String, String> map = gson.fromJson(json, mapType);
And if json is more complex than JsonObject can be used as map value type:
Type mapType = new TypeToken<Map<String, JsonObject>>() {}.getType();
I have a JSONObject with some attributes that I want to convert into a Map<String, Object>
Is there something that I can use from the json.org or ObjectMapper?
You can use Gson() (com.google.gson) library if you find any difficulty using Jackson.
//changed yourJsonObject.toString() to yourJsonObject as suggested by Martin Meeser
HashMap<String, Object> yourHashMap = new Gson().fromJson(yourJsonObject, HashMap.class);
use Jackson (https://github.com/FasterXML/jackson) from http://json.org/
HashMap<String,Object> result =
new ObjectMapper().readValue(<JSON_OBJECT>, HashMap.class);
This is what worked for me:
public static Map<String, Object> toMap(JSONObject jsonobj) throws JSONException {
Map<String, Object> map = new HashMap<String, Object>();
Iterator<String> keys = jsonobj.keys();
while(keys.hasNext()) {
String key = keys.next();
Object value = jsonobj.get(key);
if (value instanceof JSONArray) {
value = toList((JSONArray) value);
} else if (value instanceof JSONObject) {
value = toMap((JSONObject) value);
}
map.put(key, value);
} return map;
}
public static List<Object> toList(JSONArray array) throws JSONException {
List<Object> list = new ArrayList<Object>();
for(int i = 0; i < array.length(); i++) {
Object value = array.get(i);
if (value instanceof JSONArray) {
value = toList((JSONArray) value);
}
else if (value instanceof JSONObject) {
value = toMap((JSONObject) value);
}
list.add(value);
} return list;
}
Most of this is from this question: How to convert JSONObject to new Map for all its keys using iterator java
The best way to convert it to HashMap<String, Object> is this:
HashMap<String, Object> result = new ObjectMapper().readValue(jsonString, new TypeReference<Map<String, Object>>(){}));
Note to the above solution (from A Paul):
The solution doesn't work, cause it doesn't reconstructs back a HashMap< String, Object > - instead it creates a HashMap< String, LinkedHashMap >.
Reason why is because during demarshalling, each Object (JSON marshalled as a LinkedHashMap) is used as-is, it takes 1-on-1 the LinkedHashMap (instead of converting the LinkedHashMap back to its proper Object).
If you had a HashMap< String, MyOwnObject > then proper demarshalling was possible - see following example:
ObjectMapper mapper = new ObjectMapper();
TypeFactory typeFactory = mapper.getTypeFactory();
MapType mapType = typeFactory.constructMapType(HashMap.class, String.class, MyOwnObject.class);
HashMap<String, MyOwnObject> map = mapper.readValue(new StringReader(hashTable.toString()), mapType);
The JSONObject has a method toMap which returns Map<String,Object>.
The Maven dependency used in pom.xml:
<dependency>
<groupId>org.json</groupId>
<artifactId>json</artifactId>
<version>current-version</version>
</dependency>
You can find the current-version here.
Found out these problems can be addressed by using
ObjectMapper#convertValue(Object fromValue, Class<T> toValueType)
As a result, the origal quuestion can be solved in a 2-step converison:
Demarshall the JSON back to an object - in which the Map<String, Object> is demarshalled as a HashMap<String, LinkedHashMap>, by using bjectMapper#readValue().
Convert inner LinkedHashMaps back to proper objects
ObjectMapper mapper = new ObjectMapper();
Class clazz = (Class) Class.forName(classType);
MyOwnObject value = mapper.convertValue(value, clazz);
To prevent the 'classType' has to be known in advance, I enforced during marshalling an extra Map was added, containing <key, classNameString> pairs. So at unmarshalling time, the classType can be extracted dynamically.
This is how I did it in Kotlin:
mutableMapOf<String, Any>().apply {
jsonObj.keys().forEach { put(it, jsonObj[it]) }
}
import com.alibaba.fastjson.JSONObject;
Map<String, Object> map = new HashMap<String, Object>();
JSONObject rec = JSONObject.parseObject(<JSONString>);
map.put(rec.get("code").toString(), rec.get("value").toString());