I have a custom deserializer for my class as shown below:
private class HolderDeserializer implements JsonDeserializer<Holder> {
#Override
public Holder deserialize(JsonElement json, Type type, JsonDeserializationContext context)
throws JsonParseException {
Type mapType = new TypeToken<Map<String, String>>() {}.getType();
// in the below data map, I want value to be stored in lowercase
// how can I do that?
Map<String, String> data = context.deserialize(json, mapType);
return new Holder(data);
}
}
And this is how I register my deserializer when creating the Gson object:
GsonBuilder gsonBuilder = new GsonBuilder();
gsonBuilder.registerTypeAdapter(Holder.class, new HolderDeserializer());
Gson gson = gsonBuilder.create();
And finally, parsing my JSON like this:
Type responseType = new TypeToken<Map<String, Holder>>() {}.getType();
Map<String, Holder> response = gson.fromJson(jsonLine, responseType);
In my deserialize method, value of json is coming as like this {"linkedTo":"COUNT"} and then it get loaded into data map as {linkedTo=COUNT}. I wanted to see if there is any way by which all the value of data map can be lowercase so instead of this {linkedTo=COUNT}, it should get stored like this {linkedTo=count} in data map automatically?
Is there any way to do this in Gson itself automatically?
Update:
Below is my JSON content:
{
"abc": {
"linkedTo": "COUNT",
// possibly more data...
},
"plmtq": {
"linkedTo": "TITLE",
"decode": "TRUE",
// possibly more data...
}
}
Firstly, it is suggested to use Gson TypeAdapter instead of JsonDeserializer. So I'm going to answer your question with it:
New applications should prefer TypeAdapter, whose streaming API is
more efficient than this interface's tree API.
More information.
Question: How can we modify the json content before deserialization ?
One of the solutions: Preprocess the json content before deserialization and modify some of its contents.
How can we achive this with TypeAdapter: Define a custom TypeAdapter, get the json content at its read method (which is called just before the deserialization) and modify the content.
Code sample:
Define a TypeAdapterFactory and a TypeAdapter;
TypeAdapterFactory myCustomTypeAdapterFactory = new TypeAdapterFactory() {
#Override
public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> type) {
final TypeAdapter<JsonElement> elementAdapter = gson.getAdapter(JsonElement.class);
final TypeAdapter<T> delegate = gson.getDelegateAdapter(this, type); //
return new TypeAdapter<T>() {
public void write(JsonWriter out, T value) throws IOException {
JsonElement tree = delegate.toJsonTree(value);
beforeWrite(value, tree);
elementAdapter.write(out, tree);
}
public T read(JsonReader in) throws IOException {
JsonElement tree = elementAdapter.read(in);
afterRead(tree);
return delegate.fromJsonTree(tree);
}
/**
* Modify {#code toSerialize} before it is written to
* the outgoing JSON stream.
*/
protected void beforeWrite(T source, JsonElement toSerialize) {
}
/**
* Modify {#code deserialized} before it is parsed
*/
protected void afterRead(JsonElement deserialized) {
if(deserialized instanceof JsonObject) {
JsonObject jsonObject = ((JsonObject)deserialized);
Set<Map.Entry<String, JsonElement>> entrySet = jsonObject.entrySet();
for(Map.Entry<String,JsonElement> entry : entrySet){
if(entry.getValue() instanceof JsonPrimitive) {
if(entry.getKey().equalsIgnoreCase("linkedTo")) {
String val = jsonObject.get(entry.getKey()).toString();
jsonObject.addProperty(entry.getKey(), val.toLowerCase());
}
} else {
afterRead(entry.getValue());
}
}
}
}
};
}
};
We've added an extra process before deserialization. We get the entrySet from json content and updated linkedTo key's value.
Working sample:
String jsonContent = "{\"abc\":{\"linkedTo\":\"COUNT\"},\"plmtq\":{\"linkedTo\":\"TITLE\",\"decode\":\"TRUE\"}}";
GsonBuilder gsonBuilder = new GsonBuilder();
gsonBuilder.registerTypeAdapterFactory(myCustomTypeAdapterFactory);
Gson gson = gsonBuilder.create();
Map mapDeserialized = gson.fromJson(jsonContent, Map.class);
Output:
This is the similar answer for your question.
Related
I have a JSON string that is parsed using the GSON library into a Map like so:
static Type type = new TypeToken<Map<String, String>>() {}.getType();
// note the trailing/leading white spaces
String data = "{'employee.name':'Bob ','employee.country':' Spain '}";
Map<String, String> parsedData = gson.fromJson(data, type);
The problem I have is, my JSON attribute values have trailing/leading whitespaces that needs to be trimmed. Ideally, I want this to be done when the data is parsed to the Map using GSON. Is something like this possible?
You need to implement custom com.google.gson.JsonDeserializer deserializer which trims String values:
class StringTrimJsonDeserializer implements JsonDeserializer<String> {
#Override
public String deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
final String value = json.getAsString();
return value == null ? null : value.trim();
}
}
And, you need to register it:
Gson gson = new GsonBuilder()
.registerTypeAdapter(String.class, new StringTrimJsonDeserializer())
.create();
I'm trying to deserialize the following structure
{ meta: { keywords: [a, b, c, d]} ... }
other valid structures are
{ meta: { keywords: "a,b,c,d"} ... }
and
{ meta: {keywords: "a"} ...}
I have this classes
public class Data {
#PropertyName("meta")
MetaData meta;
...
}
public class MetaData {
List<String> keywords;
...
}
and a custom deserializer
public static class CustomDeserilizer implements JsonDeserializer<MetaData>{
#Override
public MetaData deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
List<String> keywords = null;
Gson gson = new Gson();
MetaData metaData = gson.fromJson(json, AppMetaData.class);
JsonObject jsonObject = json.getAsJsonObject();
if (jsonObject.has("keywords")) {
JsonElement elem = jsonObject.get("keywords");
if (elem != null && !elem.isJsonNull()) {
if (jsonObject.get("keywords").isJsonArray()) {
keywords = gson.fromJson(jsonObject.get("keywords"), new TypeToken<List<String>>() {
}.getType());
} else {
String keywordString = gson.fromJson(jsonObject.get("keywords"), String.class);
keywords = new ArrayList<String>();
list.addAll(Arrays.asList(keywordString.split(",")));
}
}
}
metaData.setKeywords(keywords);
}
Then I try to apply the deserilizer:
Gson gson = new GsonBuilder()
.registerTypeAdapter(Data.class,new CustomDeserilizer())
.create();
But I get a parsing error , because is trying to deserialize Data instead of MetaData, how can I apply this deserializer to make it work right?
I solved it creating a deserializer for my class Data.
public static class DataDeserilizer implements JsonDeserializer {
#Override
public Data deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
Gson gson = new Gson();
Data data = gson.fromJson(json, Data.class);
JsonObject jsonObject = json.getAsJsonObject();
if (jsonObject.has("meta")) {
JsonElement elem = jsonObject.get("meta");
if (elem != null && !elem.isJsonNull()) {
Gson gsonDeserializer = new GsonBuilder()
.registerTypeAdapter(MetaData.class, new CustomDeserilizer())
.create();
gsonDeserializer.fromJson(jsonObject.get("meta"), Data.class);
}
}
return data;
}
}
And
Gson gson = new GsonBuilder()
.registerTypeAdapter(Data.class,new DataDeserilizer())
.create();
Pretty obvious, but is there a more elegant solution?
Firstly, rename your class to meta instead of metadata and make keywords String instead of List.Then use the following to map your JSonString into your object.
Gson gson = new GsonBuilder().create();
Meta meta = gson.from(yourJsonString,Meta.class);
In order to get keywords only, you need this.
JsonObject jsonObject = new JsonObject(yourJSonString);
String data = jsonObject.getJsonObject("meta").getString("keywords");
keywords is a JsonObject not an JsonArray so you can't directly map it
onto List. You can split the string to get keywords in an array.
String keywords[] = data.split(",");
Here's a concise solution that leverages Java inheritance to represent the nested structure; and therefore does not need to provide any actual instance member fields (mappings, etc) for capturing the nested String data that GSON maps.
Step 1: For readability, create an empty object to represent the nested mapping
public class StateRegionCitiesMap extends HashMap<String, List<String>> {
}
Step 2: Add the one line of actual code to do the mapping; no other serialize/deserialize logic to manage
protected void loadContent(JsonObject stateRegionsJsonObject) {
HashMap<String, StateRegionCitiesMap> stateRegionCitiesMap =
mGson.fromJson(
stateRegionsJsonObject,
new TypeToken<HashMap<String, StateRegionCitiesMap>>() {
}.getType()
);
}
Alternatively, you can skip the wrapper class altogether and just directly put <String, List<String>> in the GSON call. However, I find an explicit object helps to inform/remind whoever is reading the code, what the purpose is.
Example JSON:
The class StateRegionCitiesMap represents a multi-tier map structure for say:
[US State] -> [State-Region Key] -> [Sub-Region Key] -> CitiesArray[]
"CA": {
"Central CA": {
"Central Valley": [
"FRESNO",
"VISALIA"
],
"Sacramento Area": [
"SACRAMENTO",
"EL DORADO HILLS"
]
},
This suppose to achieve what you want easily. You should define an inner static class. You can keep nesting classes to define keywords as class Keywords, etc. Just remember to have a field in the containing class, i.e.
in your inner class have private Keywords keywords;
In your Main class:
Gson gson = new Gson();
Data data = gson.fromJson(SOME_JSON_STRING, Data.class);
In a class called Data:
public class Data {
private Meta meta;
static class Meta{
private String[] keywords;
}
}
I have a translation JSON object that maps locales to messages and takes the following form:
{"en_US" : "English Text", "sp": "Spanish Text", "fr" : "French Text", ... }
Is there a way for me to map the JSON object as a list of the following class using gson?
class Translation {
String locale, text;
}
I know I can parse it first as a map and then going through the map elements to create the Translation objects, but I'm not sure if there's a "gson" way of doing that.
There are two options. If you need to serialize and deserialize the data, you could write a custom TypeAdapter.
class TranslationTypeAdapter extends TypeAdapter<List<Translation>> {
#Override
public void write(JsonWriter out, List<Translation> list) throws IOException {
out.beginObject();
for(Translation t : list) {
out.name(t.locale).value(t.text);
}
out.endObject();
}
#Override
public List<Translation> read(JsonReader in) throws IOException {
List<Translation> list = new ArrayList<>();
in.beginObject();
while(in.hasNext()) {
list.add(new Translation(in.nextName(), in.nextString()));
}
in.endObject();
return list;
}
}
and then:
TypeToken typeToken = new TypeToken<List<Translation>>(){};
Type type = typeToken.getType();
Gson gson = new GsonBuilder().registerTypeAdapter(type, new TranslationTypeAdapter()).create();
List<Translation> list = gson.fromJson(new FileReader(new File("json")),type);
which outputs:
[Translation{locale='en_US', text='English Text'}, Translation{locale='sp', text='Spanish Text'}, Translation{locale='fr', text='French Text'}]
If, however, you only need to deserialize the data, you can just write a custom deserializer:
class TranslationDeserializer implements JsonDeserializer<List<Translation>> {
#Override
public List<Translation> deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
List<Translation> list = new ArrayList<>();
for(Map.Entry<String, JsonElement> entry : json.getAsJsonObject().entrySet()) {
list.add(new Translation(entry.getKey(), entry.getValue().getAsString()));
}
return list;
}
}
You register this deserializer with the GsonBuilder as in the first example. This yield the same output of course.
I'm using Gson to parse my REST API calls to Java objects.
I want to filter out null objects in an array, e.g.
{
list: [
{"key":"value1"},
null,
{"key":"value2"}
]
}
should result in a List<SomeObject> with 2 items.
How can you do this with Gson?
Answer: The Custom Serializer
You can add a custom serializer for List.class which would look like:
package com.dominikangerer.q27637811;
import java.lang.reflect.Type;
import java.util.Collections;
import java.util.List;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonSerializationContext;
import com.google.gson.JsonSerializer;
public class RemoveNullListSerializer<T> implements JsonSerializer<List<T>> {
#Override
public JsonElement serialize(List<T> src, Type typeOfSrc,
JsonSerializationContext context) {
// remove all null values
src.removeAll(Collections.singleton(null));
// create json Result
JsonArray result = new JsonArray();
for(T item : src){
result.add(context.serialize(item));
}
return result;
}
}
This will remove the null values from the list using Collections.singleton(null) and removeAll().
Register your Custom Serializer
Now all you have to do is register it to your Gson instance like:
g = new GsonBuilder().registerTypeAdapter(List.class, new RemoveNullListSerializer()).create();
Downloadable & executable Example
You can find this answer and the exact example in my github stackoverflow answers repo:
Gson CustomSerializer to remove Null from List by DominikAngerer
See also
Gson User Guide - Custom serializers and deserializers
To remove all the null values from a list regardless of their structure, first we need to register a de-serializer with the gson like this
Gson gson = new GsonBuilder().registerTypeAdapter(List.class, new RemoveNullListDeserializer()).create();
Then the custom de-serializer would remove the null like this
/**
* <p>
* Deserializer that helps remove all <code>null</code> values form the <code>JsonArray</code> .
*/
public class RemoveNullListDeserializer<T> implements JsonDeserializer<List<T>>
{
/**
* {#inheritDoc}
*/
#Override
public List<T> deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException
{
JsonArray jsonArray = new JsonArray();
for(final JsonElement jsonElement : json.getAsJsonArray())
{
if(jsonElement.isJsonNull())
{
continue;
}
jsonArray.add(jsonElement);
}
Gson gson = new GsonBuilder().create();
List<?> list = gson.fromJson(jsonArray, typeOfT);
return (List<T>) list;
}
}
Hope this help others, who want to remove null values from a json array, regardless of the incoming json structure
My answer maybe late but I expect it works. This question can be solved by removing all the null elements in the Java object when deserialize the json string. So first, we define the custom JsonDeserializer for type List
public class RemoveNullListDeserializer<T> implements JsonDeserializer<List<T>> {
#Override
public List<T> deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
//TODO
}
}
And then, remove all null elements in the JsonElement. Here we use recursive to handle these potential null elements.
private void removeNullEleInArray(JsonElement json) {
if (json.isJsonArray()) {
JsonArray jsonArray = json.getAsJsonArray();
for (int i = 0; i < jsonArray.size(); i++) {
JsonElement ele = jsonArray.get(i);
if (ele.isJsonNull()) {
jsonArray.remove(i);
i--;
continue;
}
removeNullEleInArray(ele);
}
} else if (json.isJsonObject()) {
JsonObject jsonObject = json.getAsJsonObject();
for (String key : jsonObject.keySet()) {
JsonElement jsonElement = jsonObject.get(key);
if (jsonElement.isJsonArray() || jsonElement.isJsonObject()) {
removeNullEleInArray(jsonElement);
}
}
}
}
It's worth noting that only remove the null elements in top class is not enough.
And next step, transfer this method when deserialize.
public class RemoveNullListDeserializer<T> implements JsonDeserializer<List<T>> {
#Override
public List<T> deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
removeNullEleInArray(json)
return Gson().fromJson(json, typeOfT)
}
}
Finally, register the adapter for creating your Gson:
Gson gson = new GsonBuilder()
.registerTypeAdapter(List.class, new RemoveNullListDeserializer())
.create();
Just Over!
I have this structure of my JSON response string:
{
"1":{
"data1":"1","data2":"test1", ...
},
"2":{
"data1":"6","data2":"test2", ...
},
...
}
And I want to get the values to put into an ArrayList<MyItem>. I use GSON and normally I can do it in this way:
ArrayList<MyItem> items =
gson.fromJson(jsonString, new TypeToken<ArrayList<MyItem>>() {}.getType());
The problem is, that it does not work, because my JSON String has numbers as keys, but I only want to get the values to put into the ArrayList (unfortunately, the JSON string can not be changed by myself). How can I do this efficiently?
I'd probably deserialize the JSON into a java.util.Map, get the values from the Map as a Collection using the Map.values() method, and then create a new ArrayList using the constructor that takes a Collection.
Write a custom deserializer.
class MyItem
{
String data1;
String data2;
// ...
}
class MyJSONList extends ArrayList<MyItem> {}
class MyDeserializer implements JsonDeserializer<MyJSONList>
{
public MyJSONList deserialize(JsonElement je, Type type, JsonDeserializationContext jdc)
throws JsonParseException
{
MyJSONList list = new MyJSONList();
for (Entry<String, JsonElement> e : je.getAsJsonObject().entrySet())
{
list.add((MyItem)jdc.deserialize(e.getValue(), MyItem.class));
}
return list;
}
}
Example:
String json = "{\"1\":{\"data1\":\"1\",\"data2\":\"test1\"},\"2\":{\"data1\":\"6\",\"data2\":\"test2\"}}";
Gson g = new GsonBuilder()
.registerTypeAdapter(MyJSONList.class, new MyDeserializer())
.create();
MyJSONList l = g.fromJson(json, MyJSONList.class);
for (MyItem i : l)
{
System.out.println(i.data2);
}
Output:
test1test2