I am looking to serialize my class using gson but I would like to omit the hashmap name. Is this possible with gson?
I have tried writing my own TypeAdapter but the map name is still written as the parent object.
I have a class which looks like
public class myClass {
#Expose
public Long timestamp;
#Expose
public String id;
#Expose
public HashMap<String, someOtherClass> myMap = new HashMap<>();
#Override
public String toString() {
Gson gson = new GsonBuilder()
.excludeFieldsWithoutExposeAnnotation()
.create();
return gson.toJson(this);
}
}
current output :
{
"timestamp": 1517245340000,
"id": "01",
"myMap": {
"mapKey1": {
"otherClassId": "100", // works as expected
}
"mapKey2": {
"otherClassId": "101", // works as expected
}
}
}
What I am hoping to get :
{
"timestamp": 1517245340000,
"id": "01",
"mapKey1": {
"otherClassId": "100", // works as expected
},
"mapKey2": {
"otherClassId": "100", // works as expected
}
}
Write your own TypeAdapter. See javadoc for example.
Specify it with #JsonAdapter annotation, or register it with GsonBuilder.
#JsonAdapter(MyClassAdapter.class)
public class MyClass {
public Long timestamp;
public String id;
public HashMap<String, SomeOtherClass> myMap = new HashMap<>();
}
public class MyClassAdapter extends TypeAdapter<MyClass> {
#Override public void write(JsonWriter out, MyClass myClass) throws IOException {
// implement the write method
}
#Override public MyClass read(JsonReader in) throws IOException {
// implement the read method
return ...;
}
}
Related
I'm trying to deserialize a JSON result of a search API. The search API (and I have simplified here) has a main object with metadata about the search and then has a list of whatever the searched object is. I'm trying to achieve this using a list of generic objects
I have tested these classes without the and everything works exactly as I would want it to.
TestSearch.java
public class TestSearch<T> {
private String count;
private List<T> results;
public String getCount() {
return count;
}
public void setCount(String count) {
this.count = count;
}
public List<T> getResults() {
return results;
}
public void setResults(List<T> results) {
this.results = results;
}
}
TestResult.java
public class TestResult {
private String name;
private String description;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
}
TestController.java
#RequestMapping("/test/json")
public String testGetJson() {
return "{\"count\":3,\"results\":[{\"name\":\"result 1\",\"description\":\"this is the first result\",\"extra\":\"property\"},{\"name\":\"result 2\",\"description\":\"tqbf\"},{\"name\":\"result 3\",\"description\":\"jotlz\"}]}";
}
#RequestMapping("/test/testserialize")
public List<TestResult> testSerialize() {
TestSearch<TestResult> testSearch = new RestTemplate().getForObject("http://localhost:8957/test/json", new TestSearch<TestResult>().getClass());
List<TestResult> results = testSearch.getResults();
results.forEach(result -> {
result.setName("new value");
});
return results;
}
JSON (just to make things more readable)
{
"count": 3,
"results": [
{
"name": "result 1",
"description": "this is the first result",
"extra": "property"
},
{
"name": "result 2",
"description": "tqbf"
},
{
"name": "result 3",
"description": "jotlz"
}
]
}
After calling the endpoint /test/testserialize my application throws this error
java.lang.ClassCastException: java.util.LinkedHashMap cannot be cast
to com.testapp.entity.TestResult
Now, if I go back and update the TestSearch class to:
public class TestSearch {
private String count;
private List<TestResult> results;
public String getCount() {
return count;
}
public void setCount(String count) {
this.count = count;
}
public List<TestResult> getResults() {
return results;
}
public void setResults(List<TestResult> results) {
this.results = results;
}
}
I get the results I'm expecting:
[
{
"name": "new value",
"description": "this is the first result"
},
{
"name": "new value",
"description": "tqbf"
},
{
"name": "new value",
"description": "jotlz"
}
]
The getClass() method inside of the getForObject function does not see the type
To solve this, use a ParameterizedTypeReference
TestSearch<TestResult> testSearch = new RestTemplate().exchange(
"http://localhost:8957/test/json",
HttpMethod.GET,
null,
new ParameterizedTypeReference<TestSearch<TestResult>>() {}).getBody();
I finally tracked down a solution that happens to be 6 years old:
Generics with Spring RESTTemplate
You need to create a TypeReference object for each generic type you use and use that for deserialization. For example,
ObjectMapper mapper = new ObjectMapper();
Map<String, Object> map = new HashMap<String, Object>();
// convert JSON string to Map
map = mapper.readValue(json, new TypeReference<Map<String, String>>(){});
In your case you will have to create the type reference for
public List<T> getResults()
Please let me know if you need additional help in it.
I am trying to play with the Baidu Push Notification RESTFUL API, however, I failed to figure out how to serialize and deserialize IOS Message object by Jackson with annotation.
Json Example of Target IOS Message
{
"aps": {
"alert":"Message From Baidu Cloud Push-Service",
"sound":"", //可选
"badge":0, //可选
},
"key1":"value1",
"key2":"value2"
}
"key1":"value1" and "key2":"value2" comes from a Map.
My IosApsMessage object
import com.fasterxml.jackson.annotation.JsonProperty;
public class IosApsMessage {
#JsonProperty("alert")
private String alert; //REQUIRED
#JsonProperty("sound")
private String sound;
#JsonProperty("badge")
private Integer badge;
public String getAlert() {
return alert;
}
public void setAlert(String alert) {
this.alert = alert;
}
public String getSound() {
return sound;
}
public void setSound(String sound) {
this.sound = sound;
}
public Integer getBadge() {
return badge;
}
public void setBadge(Integer badge) {
this.badge = badge;
}
}
import java.util.Map;
import com.fasterxml.jackson.annotation.JsonProperty;
public class IosNotificationMessage {
#JsonProperty("aps")
private IosApsMessage aps;
#JsonProperty("custom_content")
private Map<String, Object> customContent;
public IosApsMessage getAps() {
return aps;
}
public void setAps(IosApsMessage aps) {
this.aps = aps;
}
public Map<String, Object> getCustomContent() {
return customContent;
}
public void setCustomContent(Map<String, Object> customContent) {
this.customContent = customContent;
}
}
My serialize result json
{
"aps": {
"alert": "alert",
"sound": "sound",
"badge": 1
},
"custom_content": {
"category": "freetrail",
"type": "state-change",
"status": "rescheduled"
}
}
What I want :
{
"aps": {
"alert": "alert",
"sound": "sound",
"badge": 1
},
"category": "freetrail",
"type": "state-change",
"status": "rescheduled"
}
I don't want the custom_content to be displayed, but I want attribute in custom_content. how can I solve this problem?
I was able to achieve the requested output through a custom serializer. The details will follow. This solution has pros and cons:
Pros
It gets the required output
It allows total freedom in how you generate the output
Cons
It seems that implementing custom serilalizer overrides all other metadata of the target class. i.e. the custom serilalizer ignores annotations of IosNotificationMessage (for example, property names). so you have to supply all the info in the code.
Here is the custom serilalizer:
public static class IosNotificationMessageSerializer extends JsonSerializer<IosNotificationMessage>
{
#Override
public void serialize(IosNotificationMessage msg, JsonGenerator gen, SerializerProvider serializers)
throws IOException, JsonProcessingException
{
gen.writeStartObject();
// serialize IosApsMessage
gen.writeObjectField("aps", msg.getAps());
// serialize map entries sequentially, thus skipping map name
for (Map.Entry<String, Object> customContentEntry : msg.getCustomContent().entrySet()) {
gen.writeObjectField(customContentEntry.getKey(), customContentEntry.getValue());
}
gen.writeEndObject();
}
}
Here is the annotation that associates the custom serilalizer to the application class:
#JsonSerialize(using = IosNotificationMessageSerializer.class)
public class IosNotificationMessage {
...
Calling new ObjectMapper().writeValue(... in the usual manner produces:
{"aps":{"alert":"Message From Baidu Cloud Push-Service","sound":"","badge":0},"key1":"value1","key2":"value2"}
My Json looks something like (and its unmodifiable)
{
....
"Sale": [
{ "SaleLines": {
"SaleLine": [
{
"unitPrice": "190",
"unitQuantity": "1"
}
],
"calcDiscount": "0",
"calcSubtotal": "500"
}
} ]
}
The java POJO code looks like
public static class SaleLines {
#JsonProperty("SaleLine")
private ArrayList<SaleLine> saleLine;
public ArrayList<SaleLine> getSaleLine() { return saleLine; }
public void setSaleLine(ArrayList<SaleLine> saleLine) { this.saleLine = saleLine; }
}
public static class SaleLine {
#JsonProperty("itemID")
private String itemId; //line_item_nk
#JsonProperty("unitPrice")
private String unitPrice;
....
}
#JsonPropertyOrder({"total", "calcSubTotal", "calcDiscount"})
public static class Sale {
private String saleTotal, calcSubtotal, calcDiscount;
private int salesValueWOVat;
#JsonProperty("SaleLines")
SaleLines saleLine;
#JsonCreator
public Sale (#JsonProperty("total")String saleTotal,
#JsonProperty("calcSubtotal")String calcSubtotal,
#JsonProperty("calcDiscount")String calcDiscount,
#JsonProperty("SaleLines")SaleLines saleLine,
) {
this.saleTotal = saleTotal;
this.calcSubtotal = calcSubtotal;
this.calcDiscount = calcDiscount;
this.saleLine = saleLine;
setSalesValueWOVat();
}
// getter and setters
}
#SuppressWarnings({ "rawtypes" })
public static <E, T extends Collection> T readFromJsonAndFillType (
String json,
Modules module,
Class <T> collectionType,
Class <E> elementType)
throws JsonParseException, JsonMappingException, IOException {
ObjectMapper objMapper = new ObjectMapper()
.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
.configure(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY, true);
TypeFactory tf = objMapper.getTypeFactory();
JsonNode node = objMapper.readTree(json).get(module.jsonFieldName);
return objMapper.readValue(node.toString(),
tf.constructCollectionType(collectionType, elementType));
}
In main
ArrayList<Sale> saleList = readFromJsonAndFillType(
saleJSON,
Modules.SALE,
ArrayList.class,
Sale.class);
for (Sale sale: saleList) {
System.out.println(sale.getSaleLines().getSaleLine().size()); //ERROR Null Pointer Exception
System.out.println(sale.toString());
}
So, the problem is that the SaleLine does not get populated as expected
It is possible that your JSON is invalid; e.g. there are commas missing in the latest version in your Question.
If the problem is that your JSON is syntactically invalid, then you will either need to hack the JSON before you parse it or hack the parser to accept invalid JSON.
On the other hand, it is possible that some of your JSON records are missing the SaleLine or SaleLines attributes or have a null instead of one of the values. If that is possible, add some null tests ... and reject the record or cope with the missing data.
My Json looks something like (and its unmodifiable)
{
....
"Sale": [
"SaleLines": {
"SaleLine": [
{
"Item": {
"Prices": {
"ItemPrice": [
{
"amount": "100",
"useType": "Default"
},
{
"amount": "100",
"useType": "MSRP"
}
]
},
}
......
......
}
]
"calcDiscount": "0",
"calcSubtotal": "500",
}
]
}
The java POJO code looks like
public static class SaleLines {
#JsonProperty("SaleLine")
private SaleLineObject[] saleLineObject;
public SaleLineObject[] getSaleLineObject() { return saleLineObject; }
public void setSaleLineObject(SaleLineObject[] saleLineObject) { this.saleLineObject = saleLineObject; }
}
public static class SaleLineObject {
private SaleLine saleLine;
public SaleLine getSaleLine() {
return saleLine;
}
public void setSaleLine(SaleLine saleLine) {
this.saleLine = saleLine;
}
}
public static class SaleLine {
#JsonProperty("itemID")
private String itemId; //line_item_nk
#JsonProperty("unitPrice")
private String unitPrice;
....
}
#JsonPropertyOrder({"total", "calcSubTotal", "calcDiscount"})
public static class Sale {
private String saleTotal, calcSubtotal, calcDiscount;
private int salesValueWOVat;
#JsonProperty("SaleLines")
SaleLines saleLine;
#JsonCreator
public Sale (#JsonProperty("total")String saleTotal,
#JsonProperty("calcSubtotal")String calcSubtotal,
#JsonProperty("calcDiscount")String calcDiscount,
#JsonProperty("SaleLines")SaleLines saleLine,
) {
this.saleTotal = saleTotal;
this.calcSubtotal = calcSubtotal;
this.calcDiscount = calcDiscount;
this.saleLine = saleLine;
setSalesValueWOVat();
}
// getter and setters
}
#SuppressWarnings({ "rawtypes" })
public static <E, T extends Collection> T readFromJsonAndFillType (
String json,
Modules module,
Class <T> collectionType,
Class <E> elementType)
throws JsonParseException, JsonMappingException, IOException {
ObjectMapper objMapper = new ObjectMapper()
.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
TypeFactory tf = objMapper.getTypeFactory();
JsonNode node = objMapper.readTree(json).get(module.jsonFieldName);
return objMapper.readValue(node.toString(),
tf.constructCollectionType(collectionType, elementType));
}
In main
ArrayList<Sale> saleList = readFromJsonAndFillType(
saleJSON,
Modules.SALE,
ArrayList.class,
Sale.class);
for (Sale sale: saleList) {
System.out.println(sale.toString());
}
I know this question has been asked multiple times and even I took help from for eg
Can not deserialize instance of java.util.ArrayList out of START_OBJECT token
But still I cannot get through this error
I know this question has been asked multiple times & everyone getting resolved there problems with different ways. Whenever you find "Can not deserialized instance of out of START_OBJECT token". it's generally occur when you trying to get object which is not actually same in json format (means json starting object is different not as you guys are converting).
For Ex:- Json returning first object is Boolean but unfortunately you are converting is to List<Object> then you will having this error.
I would suggest to have a look to read format using below code than convert it as per the object returning.
ObjectMapper objectMapper = new ObjectMapper();
Map<?,?> empMap = objectMapper.readValue(new FileInputStream("employee.json"),Map.class);
for (Map.Entry<?,?> entry : empMap.entrySet())
{
System.out.println("\n----------------------------\n"+entry.getKey() + "=" + entry.getValue()+"\n");
}
Get the key & convert the value as per the object returning.
For reference:- https://dzone.com/articles/processing-json-with-jackson
I am using Retrofit to make a HTTP request which returns an array of object and I am getting the following errors:
com.google.gson.JsonSyntaxException: java.lang.IllegalStateException: Expected BEGIN_OBJECT but was BEGIN_ARRAY
The response returned is expected to be like this:
[ {key1: "value1", key2: "value2"}, {key1: "value1", key2: "value2"}, ... ]
I have the following class, for serializing the data:
public class data {
private List<element> dataList;
public List<element> getElements() {
return dataList;
}
public class element {
#SerializedName("key1")
private String key1;
#SerializedName("key2")
private String key2;
// Getters and Setters
}
}
Please let me know if you have any ideas. Thanks
The error was actually in my implementation of Retrofit Callback. My implementation was expecting an object when it should be expecting an array in this case. Thanks everyone for the help.
Before
//*****MyData*****//
public class MyData {
private List<Data> dataList;
public List<Data> getElements() {
return dataList;
}
public class Data {
#SerializedName("key1")
private String key1;
#SerializedName("key2")
private String key2;
// Getters and Setters
}
}
//*****Callback Implementation*****//
public class MyDataCallback extends Callback {
public MyDataCallback(MyDataCallbackListener<MyData> myDataCallbackListener) {
super(myDataCallbackListener);
}
#Override
public void success(MyData data, Response response) {
if (myDataCallbackListener != null) {
myDataCallbackListener.onCallbackComplete(true, response, MyDataCallback.CALLBACK_SUCCESS_MESSAGE, data);
}
}
}
After
//*****Data*****//
public class Data {
#SerializedName("key1")
private String key1;
#SerializedName("key2")
private String key2;
// Getters and Setters
}
//*****Callback Implementation*****//
public class MyDataCallback extends Callback {
public MyDataCallback(MyDataCallbackListener<List<Data>> myDataCallbackListener) {
super(myDataCallbackListener);
}
#Override
public void success(List<Data> data, Response response) {
if (myDataCallbackListener != null) {
myDataCallbackListener.onCallbackComplete(true, response, MyDataCallback.CALLBACK_SUCCESS_MESSAGE, data);
}
}
}
As Dave mentioned in his comment, it does seem strange that you have recursion in the class that I am assuming is your response object. (your class "data" has a list of "data" objects).
I would suggest something a little more strait forward such as this:
public class ResponseObject {
private ArrayList<DataObject> mDataObjects;
public ArrayList<DataObject> getDataObjects() {
return mDataObjects;
}
private class DataObject {
private String key1;
private String key2;
public String getKey1() {
return key1;
}
public String getKey2() {
return key2;
}
}
}
or since you are local maybe you can buy Jake a beer :) From his photo, I would check Rouge Ales, 21 Amendment or my favorite last time I was in SF - Magnolia
It's not valid JSON to begin with an array. You need to instead return something like this:
{
dataList: [
{
key1: "value1",
key2: "value2"
},
{
key1: "value3",
key2: "value4"
}
]
}
Then you can use GSON to deserialize that into your data class.