my JSON String :
{
"categorySetName": "TSN",
"rows": [
{
"date": 201750,
"rank1": {
"label": "\uBC30\uACBD",
"search_keyword": "\uBC30\uACBD",
"frequency": 3975,
"score": 69.27185
},
"rank2": {
"label": "\uBC30\uACBD\uD654\uBA74",
"search_keyword": "\uBC30\uACBD\uD654\uBA74",
"frequency": 3736,
"score": 109.83768
},
"rank3": {
"label": "\uC544\uC774\uD3F0x",
"search_keyword": "\uC544\uC774\uD3F0x",
"frequency": 3382,
"score": 62.057728
},
.
.
.
.
"rank500": {
"label": "\uC544\uC774\uD3F0x",
"search_keyword": "\uC544\uC774\uD3F0x",
"frequency": 1572,
"score": 68.057728
}
}
}
my Deserializer.Java
#SuppressWarnings("serial")
public class AssociationTopRowDeserializer extends StdDeserializer<AssociationTopRow> {
public static final String DATE = "date";
public static final List<String> knownFieldNames = Arrays.asList(DATE);
public AssociationTopRowDeserializer() {
this(null);
}
public AssociationTopRowDeserializer(Class<AssociationTopRow> c) {
super(c);
}
#Override
#SuppressWarnings("unchecked")
public AssociationTopRow deserialize(JsonParser jsonParser, DeserializationContext desContext) throws IOException {
AssociationTopRow row = new AssociationTopRow();
JsonNode jsonNode = jsonParser.getCodec().readTree(jsonParser);
ObjectMapper objectMapper = new ObjectMapper();
row.setDate(jsonNode.get(DATE).asText());
String unknownField = getUnknownField(jsonNode.fieldNames());
if (unknownField != null) {
Map<String, AssociationTopRowDetail> map = new HashMap<String, AssociationTopRowDetail>();
map.put(unknownField, (AssociationTopRowDetail) objectMapper.convertValue(jsonNode.get(unknownField),
AssociationTopRowDetail.class));
row.setTransMap(map);
}
return row;
}
public String getUnknownField(Iterator<String> fieldNames) {
while (fieldNames.hasNext()) {
String next = fieldNames.next();
if (!knownFieldNames.contains(next))
return next;
}
return null;
}
}
This shows me only information about only 'rank1'
You can use #JsonAnySetter annotation instead of a custom deserializer:
public class AssociationTopRow {
private String date;
private final Map<String, AssociationTopRowDetail> details = new LinkedHashMap<>();
// getter & setter for date
#JsonAnySetter
public void addDetail(String key, AssociationTopRowDetail detail) {
details.put(key, detail);
}
#JsonAnyGetter
public Map<String, AssociationTopRowDetail> getDetails() {
return details;
}
}
Related
I'm sending an HTTP request using Retrofit 2, I've done everything regularly, however, the situation is "not standard". I have a problem that one of the objects to receive in the response class is once sent as an object and in the other as a list. So I don't know whether to initialize it in the response class as an object or as an array.
Here is my full JSON response:
{
"data": [
{
"name": "John",
"surname": "Smith",
"nicname": "Joe",
"age": "32",
"description": "Example",
"state": "Nevada",
"city": "Las Vegas",
"job": "Security",
"adress": "Some adress 1",
"postcode": "412421",
"details": {
"intro": {
"title": "Mr.",
"married": "No",
"children": "2"
},
"rest": {
"pitctures":"24",
"chats": "7",
"lastChat": "12-01-2016",
"lastVisited": "07-04-2017",
}
}
},
{
"name": "John",
"surname": "Smith",
"nicname": "Joe",
"age": "32",
"description": "Example",
"state": "Nevada",
"city": "Las Vegas",
"job": "Security",
"adress": "Some adress 1",
"postcode": "412421",
"details": {
"intro": {
"title": "Mr.",
"married": "No",
"children": "No"
},
"rest": []
}
}
],
"errors": false,
"update_notifications": {
"message": [],
"friend_request": [],
"code": "IzS0hivN1cyHBdygpeWv"
}
}
Details.java class:
public class Details {
#SerializedName("intro")
#Expose
private Intro intro;
#SerializedName("rest")
#Expose
private Rest restObject;
private ArrayList<Rest> restList;
public Details(Intro intro, Rest restObject) {
this.intro = intro;
this.restObject = restObject;
}
public Details(Intro intro, ArrayList<Rest> restList) {
this.intro = intro;
this.restList = restList;
}
public Intro getIntro() {
return intro;
}
public void setIntro(Intro intro) {
this.intro = intro;
}
public Rest getRestObject() {
return restObject;
}
public void setRestObject(Rest restObject) {
this.restObject = restObject;
}
public ArrayList<Rest> getRestList() {
return restList;
}
public void setRestList(ArrayList<Rest> restList) {
this.restList = restList;
}
}
And here is my CustomDeserializer.java (rest array neeed to be empty, maybe that's a problem) based on #Farid's answer:
public class CustomDeserializer implements JsonDeserializer<Details> {
#Override
public Details deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
JsonObject detailsObject = json.getAsJsonObject().get("details").getAsJsonObject();
Details details;
JsonObject introObject = detailsObject.get("intro").getAsJsonObject();
String title = introObject.get("title").getAsString();
String married = introObject.get("married").getAsString();
String children = introObject.get("children").getAsString();
try {
JsonObject restObject = detailsObject.get("rest").getAsJsonObject();
String pitctures = restObject.get("pitctures ").getAsString();
String chats = restObject.get("chats ").getAsString();
String lastChat = restObject.get("lastChat ").getAsString();
String lastVisited = restObject.get("lastVisited ").getAsString();
details = new Details(new Intro(title, married, children),
new Rest(pitctures, chats, lastChat, lastVisited));
}catch (IllegalStateException e){
JsonArray restArray = detailsObject.get("rest").getAsJsonArray();
ArrayList<Rest> restList = new ArrayList<>();
details = new Details(new Intro(title, married, children), restList);
}
return details;
}
}
In MainActivity based on #Farid's answer:
Gson gsonConverter = new GsonBuilder().registerTypeAdapter(Details.class, new CustomDeserializer()).create();
Retrofit retrofit = new Retrofit.Builder()
.baseUrl(myUrl)
.addConverterFactory(GsonConverterFactory.create(gsonConverter))
.build();
service1 = retrofit.create(MyAPI.class);
final ProgressDialog progressDialog = new ProgressDialog(this);
progressDialog.setCancelable(false);
progressDialog.setMessage("Please wait!");
progressDialog.show();
final MyRequest myRequest = new MyRequest();
myRequest.setPin(pin);
final Call<MyResponse> myResponseCall = service1.get (code, myRequest);
myResponseCall.enqueue(new Callback<MyResponse>() {
#Override
public void onResponse(Call<MyResponse> call, Response<MyResponse> response) {
}
});
It's possible. You'll have to create a custom Deserializer. As you can see there is JsonDeserializer<Details> whose type parameter is Details that means any time GSON tries to deserialize Details object, it will call CustomDeserializer to deserialize it. Anything inside Details object should be deserialized manually as seen in the CustomDeserializer class whereas all other types outside Details class (e.g. Class, String, int) will be handled by GSON seamlessly
CustomDeserializer.java
public class CustomDeserializer implements JsonDeserializer<Details> {
#Override
public Details deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
JsonObject detailsObject = json.getAsJsonObject();
Details details;
Intro intro = new Intro(detailsObject.get("intro").getAsJsonObject().get("example").getAsString());
try {
JsonObject restObject = detailsObject.get("rest").getAsJsonObject();
String example = restObject.get("example").getAsString();
details = new Details(intro, new Rest(example));
}catch (IllegalStateException e){ // In case rest is ArrayList
JsonArray restArray = detailsObject.get("rest").getAsJsonArray();
ArrayList<Rest> resList = new ArrayList<>();
for (JsonElement element: restArray){
JsonObject restObject = element.getAsJsonObject();
String example = restObject.get("example").getAsString();
resList.add(new Rest(example));
}
details = new Details(intro, resList);
}
return details;
}
}
Then you have to register CustomDeserializer as TypeAdapter as below:
Gson gsonConverter = GsonBuilder().registerTypeAdapter(Details::class.java,
CustomDeserializer()).create();
Retrofit retrofit = new Retrofit.Builder()
.addConverterFactory(GsonConverterFactory.create(gsonConverter))
///Rest of Retrofit builder code
If you want to know more about custom deserialization, search for "gson deserialization" and find a tutorial that appeals to you most.
Below code snippets are OP specific classes
Response.java
public class Response {
private List<DataItem> data = null;
private Boolean errors;
private String example;
public List<DataItem> getData() {
return data;
}
public void setData(List<DataItem> data) {
this.data = data;
}
public Boolean getErrors() {
return errors;
}
public void setErrors(Boolean errors) {
this.errors = errors;
}
public String getExample() {
return example;
}
public void setExample(String example) {
this.example = example;
}
}
Data.java
public class DataItem {
private String example;
private Details details;
public String getExample() {
return example;
}
public void setExample(String example) {
this.example = example;
}
public Details getDetails() {
return details;
}
public void setDetails(Details details) {
this.details = details;
}
}
Details.java
public class Details {
private Intro intro;
private Rest restObject;
private ArrayList<Rest> restList;
public Details(Intro intro, ArrayList<Rest> restList) {
this.intro = intro;
this.restList = restList;
}
public Details(Intro intro, Rest restObject) {
this.intro = intro;
this.restObject = restObject;
}
public Rest getRestObject() {
return restObject;
}
public ArrayList<Rest> getRestList() {
return restList;
}
public Intro getIntro() {
return intro;
}
public void setIntro(Intro intro) {
this.intro = intro;
}
}
Intro.java
public class Intro {
public Intro(String example) {
this.example = example;
}
private String example;
public String getExample() {
return example;
}
public void setExample(String example) {
this.example = example;
}
}
Rest.java
public class Rest {
private String example;
public Rest(String example) {
this.example = example;
}
public String getExample() {
return example;
}
}
I am fairly new to Jackson. I am trying to map the following json to a POJO using Jackson for deserialization.
{
"data": [
{
"customerName": "abc",
"varaible_Key1": {
"p1": "text data",
"p2": "textarea data",
........
}
},
{
"customerName": "bbc",
"varaible_Key2": {
"p1": "text",
"p2": "textarea"
......
}
},
{
"customerName": "xyz",
"varaible_Key3": {
"p1": "xyz text",
"p2": "xyz textarea"
......
}
}
///////more customername / variable_keys
]
}
The problem I am facing is with dynamic / variable keys in the json.
I have tried using #JsonAnySetter in the POJO as shown below.
public class Foo {
#JsonProperty("customerName")
private String name;
private Map<String, DataObject> properties;
#JsonAnyGetter
public Map<String, DataObject> getProperties() {
return properties;
}
#JsonAnySetter
public void add(String key, DataObject value) {
properties.put(key, value);
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
where DataObject contains the fields like p1,p2 and so on.
public class DataObject {
#JsonProperty("p1")
private String firstValue;
#JsonProperty("p2")
private String secondValue;
#JsonProperty("p3")
private String thirdValue;
#JsonProperty("p4")
private String fourthValue;
public String getFirstValue() {
return firstValue;
}
public void setFirstValue(String firstValue) {
this.firstValue = firstValue;
}
public String getSecondValue() {
return secondValue;
}
public void setSecondValue(String secondValue) {
this.secondValue = secondValue;
}
public String getThirdValue() {
return thirdValue;
}
public void setThirdValue(String thirdValue) {
this.thirdValue = thirdValue;
}
public String getFourthValue() {
return fourthValue;
}
public void setFourthValue(String fourthValue) {
this.fourthValue = fourthValue;
}
}
I keep getting the below error. Any help on this is appreciated.
com.fasterxml.jackson.databind.JsonMappingException: N/A (through
reference chain:
com.epic.customer.dto.DataField["data"]->java.util.ArrayList[0]-> com.epic.customer.dto.Foo["varaible_Key1"])
at
com.fasterxml.jackson.databind.deser.SettableAnyProperty._throwAsIOE(SettableAnyProperty.java:214)
at
com.fasterxml.jackson.databind.deser.SettableAnyProperty.set(SettableAnyProperty.java:179)
at
com.fasterxml.jackson.databind.deser.SettableAnyProperty.deserializeAndSet(SettableAnyProperty.java:134)
at
com.fasterxml.jackson.databind.deser.BeanDeserializerBase.handleUnknownVanilla(BeanDeserializerBase.java:1539)
at
com.fasterxml.jackson.databind.deser.BeanDeserializer.vanillaDeserialize(BeanDeserializer.java:293)
at
com.fasterxml.jackson.databind.deser.BeanDeserializer.deserialize(BeanDeserializer.java:151)
at
com.fasterxml.jackson.databind.deser.std.CollectionDeserializer.deserialize(CollectionDeserializer.java:285)
at
com.fasterxml.jackson.databind.deser.std.CollectionDeserializer.deserialize(CollectionDeserializer.java:244)
at
com.fasterxml.jackson.databind.deser.std.CollectionDeserializer.deserialize(CollectionDeserializer.java:27)
at
com.fasterxml.jackson.databind.deser.impl.MethodProperty.deserializeAndSet(MethodProperty.java:127)
at
com.fasterxml.jackson.databind.deser.BeanDeserializer.vanillaDeserialize(BeanDeserializer.java:287)
at
com.fasterxml.jackson.databind.deser.BeanDeserializer.deserialize(BeanDeserializer.java:151)
at
com.fasterxml.jackson.databind.ObjectMapper._readMapAndClose(ObjectMapper.java:4001)
You just need to create 2 setters, for specific property:
class Foo {
private DataObject dataObject;
public DataObject getDataObject() {
return dataObject;
}
public void setVaraible_Key1(DataObject dataObject) {
this.dataObject = dataObject;
}
public void setVaraible_Key2(DataObject dataObject) {
this.dataObject = dataObject;
}
}
Further usages refers here.
I think you have to give it a hint on the type of object you want back:
ObjectMapper mapper = new ObjectMapper();
Map<String, DataObject> test = mapper.readValue("Insert Data in here", mapper.getTypeFactory().constructMapLikeType(HashMap.class, String.class, DataObject.class));
I have these type of JSON object which I'm getting from gridx filter expression:
{
"op": "or",
"data": [
{
"op": "contain",
"data": [
{
"op": "string",
"data": "id",
"isCol": true
},
{
"op": "string",
"data": "sdfv"
}
]
},
{
"op": "contain",
"data": [
{
"op": "string",
"data": "post",
"isCol": true
},
{
"op": "string",
"data": "sdfv"
}
]
},
{
"op": "contain",
"data": [
{
"op": "string",
"data": "birthday",
"isCol": true
},
{
"op": "string",
"data": "sdfv"
}
]
}
]
}
How I can map this to a Java object and then deserialize using Gson?
I've made these two classes:
package dto.Filter;
public class FilterData extends FilterExpression {
private String op;
private boolean isCol;
private String data;
public String getOp() {
return op;
}
public void setOp(String op) {
this.op = op;
}
public boolean isCol() {
return isCol;
}
public void setCol(boolean col) {
isCol = col;
}
public String getData() {
return data;
}
public void setData(String data) {
this.data = data;
}
}
package dto.Filter;
import java.util.List;
public class FilterExpression {
private List<FilterData> filters;
private String op;
public List<FilterData> getFilters() {
return filters;
}
public void setFilters(List<FilterData> filters) {
this.filters = filters;
}
public String getOp() {
return op;
}
public void setOp(String op) {
this.op = op;
}
}
The problem is that I have both data as Object and String type. Do I need to use my custom TypeAdapter?
Make your data structure as
class DataStructure {
private String op;
private String data;
private String isCol;
public DataStructure(){
op="";
data="";
isCol="";
}
public String getOp() {
return op;
}
public void setOp(String op) {
this.op = op;
}
public String getData() {
return data;
}
public void setData(String data) {
this.data = data;
}
public String isCol() {
return isCol;
}
public void setCol(String isCol) {
this.isCol = isCol;
}
#Override
public String toString() {
return "DataStructure [op=" + op + ", data=" + data + ", isCol=" + isCol + "]";
}
}
I have parse the JSON file using google GSON library. Here is maven repository.
Note: to read using GSON library add '[' at starting and ']' at ending.
I have read the JSON file and store the data in ArrayList. Hope after getting array list you can do serialization.
public class FilterData {
private static Gson gson = new Gson();
private static JsonParser parser = new JsonParser();
public static List<DataStructure> getData(List<DataStructure> datas){
List<DataStructure> result = new ArrayList<>();
for (DataStructure data : datas) {
for (JsonElement object : parser.parse(data.getData()).getAsJsonArray()) {
DataStructure dataStructure = new DataStructure();
JsonObject jObject = gson.fromJson(object, JsonObject.class);
dataStructure.setOp(jObject.get("op").toString());
dataStructure.setData(jObject.get("data").toString());
if (jObject.has("isCol"))
dataStructure.setData(jObject.get("isCol").toString());
System.out.println(dataStructure);
result.add(dataStructure);
}
}
return result;
}
public static void main(String[] args) throws FileNotFoundException {
JsonReader reader = new JsonReader(new InputStreamReader(new FileInputStream("input.json")));
List<DataStructure> datas = new ArrayList<>();
JsonArray jArray = parser.parse(reader).getAsJsonArray();
for (JsonElement object : jArray) {
DataStructure dataStructure = new DataStructure();
JsonObject jObject = gson.fromJson(object, JsonObject.class);
dataStructure.setOp(jObject.get("op").toString());
dataStructure.setData(jObject.get("data").toString());
if (jObject.has("isCol"))
dataStructure.setData(jObject.get("isCol").toString());
System.out.println(dataStructure);
datas.add(dataStructure);
}
List<DataStructure> insideData = getData(datas);
List<DataStructure> inside2Data = getData(insideData);
}
}
I am parsing a JSON data using GSON library and the issue what I am facing is, a part of my json data keeps changing below is how my JSON data looks.
{
"body": [{
"result": [
{
"EndTime": "1411495899000",
"StartTime": "1411495360000"
},
{
"EndTime": "1411495359000",
"StartTime": "1411494784000"
}],
"rule": {
"ruleid": "73B5EEB4"
}
},
{
"result": [
{
"noOfErrors": "5",
"severity": "high"
},
{
"noOfErrors": "4",
"severity": "low"
}],
"rule": {
"ruleid": "35B5EEB4"
}
}
],
"header": {
"contentver": "5.5"
}
}
So in the above JSON data the result array content keeps changing based on the ruleid and I want to choose the java bean for result content at runtime based on the ruleid. Any idea?
-Regards
Well, this is going to be a long answer ...
You could use a custom JsonDeserializer to deserialize the variable part of the json string based on the ruleid.
public class MessageAdapter implements JsonDeserializer<Message> {
private Map<String, Class<? extends Result>> ruleToResultClassMap;
public MessageAdapter() {
this.ruleToResultClassMap = new HashMap<String, Class<? extends Result>>();
ruleToResultClassMap.put("73B5EEB4", DurationResults.class);
ruleToResultClassMap.put("35B5EEB4", ErrorResults.class);
}
#java.lang.Override
public Message deserialize(JsonElement json, java.lang.reflect.Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
JsonObject messageObject = json.getAsJsonObject();
JsonArray bodyArray = messageObject.getAsJsonArray("body");
List<Result> results = new ArrayList<Result>();
for (JsonElement bodyElement : bodyArray) {
JsonObject resultObject = bodyElement.getAsJsonObject();
JsonObject ruleObject = resultObject.getAsJsonObject("rule");
String ruleId = ruleObject.getAsJsonPrimitive("ruleid").getAsString();
Class<? extends Result> resultClass = ruleToResultClassMap.get(ruleId);
if (resultClass != null) {
Result result = context.deserialize(resultObject, resultClass);
results.add(result);
} else {
throw new IllegalArgumentException("Illegal ruleId: " + ruleId);
}
}
return new Message(results, context.<Header>deserialize(messageObject.getAsJsonObject("header"), Header.class));
}
}
You need to register the custom deserializer with GsonBuilder:
GsonBuilder gsonBuilder = new GsonBuilder();
gsonBuilder.registerTypeAdapter(Message.class, new MessageAdapter());
Gson gson = gsonBuilder.create();
String jsonString = null; // your json string
Message message = gson.fromJson(jsonString, Message.class);
Here Message is the root POJO of the json string, you probably have your own. For full reference, I include all classes here:
public class Message {
private List<? extends Result> body;
private Header header;
public Message(List<? extends Result> body, Header header) {
this.body = body;
this.header = header;
}
public List<? extends Result> getBody() {
return body;
}
public Header getHeader() {
return header;
}
}
public class Header {
private String contentver;
public Header(String contentVer) {
this.contentver = contentVer;
}
public String getContentVer() {
return contentver;
}
}
public interface Result {
public Rule getRule();
}
public final class Rule {
private String ruleid;
public String getRuleid() {
return ruleid;
}
}
public class DurationResults implements Result {
private Duration[] result;
private Rule rule;
public Duration[] getResult() {
return result;
}
#Override
public Rule getRule() {
return rule;
}
public static final class Duration {
private long EndTime;
private long StartTime;
public long getStartTime() {
return StartTime;
}
public long getEndTime() {
return EndTime;
}
}
}
public class ErrorResults implements Result {
private Error[] result;
private Rule rule;
public Error[] getResult() {
return result;
}
#Override
public Rule getRule() {
return rule;
}
public static final class Error {
private int noOfErrors;
private String severity;
public int getNoOfErrors() {
return noOfErrors;
}
public String getSeverity() {
return severity;
}
}
}
I think you have to parse your json if your result change.
Replace first "result" to "FirstResult" , and replace second "result" to "SecondResult".
Your json have to look like this:
{
"body": [{
"FirstResult": [
{
"EndTime": "1411495899000",
"StartTime": "1411495360000"
},
{
"EndTime": "1411495359000",
"StartTime": "1411494784000"
}],
"rule": {
"ruleid": "73B5EEB4"
}
},
{
"SecondResult": [
{
"noOfErrors": "5",
"severity": "high"
},
{
"noOfErrors": "4",
"severity": "low"
}],
"rule": {
"ruleid": "35B5EEB4"
}
}
],
"header": {
"contentver": "5.5"
}
}
And you can parse json to Java Objects. If you have first result parse to FirstResultObject.java , if you have second result parse to SecondResult.java
SecondResult.java
public class SecondResult
{
private String noOfErrors;
private String severity;
public String getNoOfErrors ()
{
return noOfErrors;
}
public void setNoOfErrors (String noOfErrors)
{
this.noOfErrors = noOfErrors;
}
public String getSeverity ()
{
return severity;
}
public void setSeverity (String severity)
{
this.severity = severity;
}
}
FirstResult.java
public class FirstResult
{
private String EndTime;
private String StartTime;
public String getEndTime ()
{
return EndTime;
}
public void setEndTime (String EndTime)
{
this.EndTime = EndTime;
}
public String getStartTime ()
{
return StartTime;
}
public void setStartTime (String StartTime)
{
this.StartTime = StartTime;
}
}
I would like deserialize my custom serialized objects. My objects are basically consisting a simple Pair implementation.
class School{
Integer id;
String schoolName;
}
class Student{
Integer id;
Integer schoolId;
String studentName;
}
#JsonSerialize(using=PairSerializer.class)
public class Pair<V,K>{
V v;
K k;
}
Here is the result
[
{
"v":{
"id":1,
"schoolId":3,
"studentName":"O. Bas"
},
"k":{
"id":3,
"schoolName":"School 3"
}
},
{
"v":{
"id":2,
"schoolId":3,
"studentName":"C. Koc"
},
"k":{
"id":3,
"schoolName":"School 3"
}
}
]
v and k as field name in json is pretty ugly. That is why I have written a custom serializer as this:
#Override
public void serialize(Pair pair, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException, JsonProcessingException {
jsonGenerator.writeStartObject();
jsonGenerator.writeObjectField(CaseFormat.UPPER_CAMEL.to(CaseFormat.LOWER_CAMEL,pair.getK().getClass().getSimpleName() ), pair.getK());
jsonGenerator.writeObjectField(CaseFormat.UPPER_CAMEL.to(CaseFormat.LOWER_CAMEL,pair.getV().getClass().getSimpleName() ), pair.getV());
jsonGenerator.writeEndObject();
}
The result is exactly what I want. v and k field names are replaced by their class names.
[
{
"school":{
"id":3,
"schoolName":"School 3"
},
"student":{
"id":1,
"schoolId":3,
"studentName":"O. Bas"
}
},
{
"school":{
"id":3,
"schoolName":"School 3"
},
"student":{
"id":2,
"schoolId":3,
"studentName":"C. Koc"
}
}
]
Here is the my question. How can I deserialize my json string to List<Pair<V, K> ? The real problem is that V and K are depends on the deserialized context it might vary as Student, School or another pair implementation.
public class PairDeserializer extends JsonDeserializer<Pair> {
public PairDeserializer() {
}
#Override
public Pair deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException, JsonProcessingException {
// I need to Deserialized generic type information of Pair
}
}
I think, you should create your own PropertyNamingStrategy. For example see my simple implementation:
class MapTransformNamingStrategy extends LowerCaseWithUnderscoresStrategy {
private static final long serialVersionUID = 1L;
private Map<String, String> mapping;
public MapTransformNamingStrategy(Map<String, String> mapping) {
this.mapping = mapping;
}
#Override
public String translate(String property) {
if (mapping.containsKey(property)) {
return mapping.get(property);
}
return property;
}
}
Now you can use it in this way:
Map<String, String> mapping = new HashMap<String, String>();
mapping.put("k", "student");
mapping.put("v", "school");
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.setPropertyNamingStrategy(new MapTransformNamingStrategy(mapping));
//etc
Example JSON output:
{ "school" : { "id" : 1,
"schoolName" : "The Best School in the world"
},
"student" : { "id" : 1,
"schoolId" : 1,
"studentName" : "Arnold Shwarz"
}
}
EDIT
Because my answer is not clear for everyone I present full example source code which serialize Java POJO objects into JSON and "vice versa".
import java.io.StringWriter;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import com.fasterxml.jackson.core.JsonFactory;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.PropertyNamingStrategy.LowerCaseWithUnderscoresStrategy;
public class JacksonProgram {
#SuppressWarnings("unchecked")
public static void main(String[] args) throws Exception {
List<Pair<Student, School>> pairs = createDataForSerialization();
Map<String, String> mapping = createSchoolStudentMapping();
JsonConverter jsonConverter = new JsonConverter(mapping);
String json = jsonConverter.toJson(pairs);
System.out.println("JSON which represents list of pairs:");
System.out.println(json);
List<Pair<Student, School>> value = jsonConverter.fromJson(json, List.class);
System.out.println("----");
System.out.println("Deserialized version:");
System.out.println(value);
}
private static Map<String, String> createSchoolStudentMapping() {
Map<String, String> mapping = new HashMap<String, String>();
mapping.put("k", "student");
mapping.put("v", "school");
return mapping;
}
private static List<Pair<Student, School>> createDataForSerialization() {
List<Pair<Student, School>> pairs = new ArrayList<Pair<Student, School>>();
pairs.add(new Pair<Student, School>(new Student(1, 3, "O. Bas"), new School(3, "School 3")));
pairs.add(new Pair<Student, School>(new Student(2, 4, "C. Koc"), new School(4, "School 4")));
return pairs;
}
}
class JsonConverter {
private Map<String, String> mapping;
private ObjectMapper objectMapper;
private JsonFactory jsonFactory;
public JsonConverter(Map<String, String> mapping) {
this.mapping = mapping;
initJsonObjects();
}
private void initJsonObjects() {
objectMapper = new ObjectMapper();
objectMapper.setPropertyNamingStrategy(new MapTransformNamingStrategy(mapping));
jsonFactory = new JsonFactory();
}
public String toJson(Object object) throws Exception {
StringWriter stringWriter = new StringWriter();
JsonGenerator jsonGenerator = jsonFactory.createGenerator(stringWriter);
objectMapper.writeValue(jsonGenerator, object);
return stringWriter.toString();
}
public <T> T fromJson(String json, Class<T> expectedType) throws Exception {
JsonParser jsonParser = jsonFactory.createJsonParser(json);
return objectMapper.readValue(jsonParser, expectedType);
}
}
class MapTransformNamingStrategy extends LowerCaseWithUnderscoresStrategy {
private static final long serialVersionUID = 1L;
private Map<String, String> mapping;
public MapTransformNamingStrategy(Map<String, String> mapping) {
this.mapping = mapping;
}
#Override
public String translate(String property) {
if (mapping.containsKey(property)) {
return mapping.get(property);
}
return property;
}
}
class School {
private Integer id;
private String schoolName;
public School() {
}
public School(Integer id, String schoolName) {
this.id = id;
this.schoolName = schoolName;
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getSchoolName() {
return schoolName;
}
public void setSchoolName(String schoolName) {
this.schoolName = schoolName;
}
#Override
public String toString() {
return "School [id=" + id + ", schoolName=" + schoolName + "]";
}
}
class Student {
private Integer id;
private Integer schoolId;
private String studentName;
public Student() {
}
public Student(Integer id, Integer schoolId, String studentName) {
this.id = id;
this.schoolId = schoolId;
this.studentName = studentName;
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public Integer getSchoolId() {
return schoolId;
}
public void setSchoolId(Integer schoolId) {
this.schoolId = schoolId;
}
public String getStudentName() {
return studentName;
}
public void setStudentName(String studentName) {
this.studentName = studentName;
}
#Override
public String toString() {
return "Student [id=" + id + ", schoolId=" + schoolId + ", studentName=" + studentName
+ "]";
}
}
class Pair<V, K> {
private V v;
private K k;
public Pair() {
}
public Pair(V v, K k) {
this.v = v;
this.k = k;
}
public V getV() {
return v;
}
public void setV(V v) {
this.v = v;
}
public K getK() {
return k;
}
public void setK(K k) {
this.k = k;
}
#Override
public String toString() {
return "Pair [v=" + v + ", k=" + k + "]";
}
}
The full output log:
JSON which represents list of pairs:
[{"school":{"id":1,"schoolId":3,"studentName":"O. Bas"},"student":{"id":3,"schoolName":"School 3"}},{"school":{"id":2,"schoolId":4,"studentName":"C. Koc"},"student":{"id":4,"schoolName":"School 4"}}]
----
Deserialized version:
[{school={id=1, schoolId=3, studentName=O. Bas}, student={id=3, schoolName=School 3}}, {school={id=2, schoolId=4, studentName=C. Koc}, student={id=4, schoolName=School 4}}]
Because the output JSON is not formatted I present it in more understandable version:
[
{
"school":{
"id":1,
"schoolId":3,
"studentName":"O. Bas"
},
"student":{
"id":3,
"schoolName":"School 3"
}
},
{
"school":{
"id":2,
"schoolId":4,
"studentName":"C. Koc"
},
"student":{
"id":4,
"schoolName":"School 4"
}
}
]
As you can see, we create new JsonConverter object with definition of mapping between Pair property names and which names we want to see in JSON string representation. Now if you have for example Pair<School, Room> you can create mapping Map in this way:
private static Map<String, String> createSchoolRoomMapping() {
Map<String, String> mapping = new HashMap<String, String>();
mapping.put("k", "school");
mapping.put("v", "room");
return mapping;
}
I was going for an answer with some annotation (JsonTypeInfo and JsonUnwrapped), but those two don't work well together apparently (see this issue). That would of handled both the serialization and deserialization part of your problem, without relying on custom de/serializer. Instead, you'll need a custom deserializer, which does something along those line:
class PairDeserializer extends JsonDeserializer<Pair>{
static Map<String, Class> MAPPINGS = new HashMap<String, Class>();
#Override
public Pair deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException, JsonProcessingException {
Object key = deserializeField(jp);
Object value = deserializeField(jp);
Pair pair = new Pair();
pair.k = key;
pair.v = value;
jp.nextToken();
return pair;
}
private Object deserializeField(JsonParser jp) throws IOException, JsonParseException, JsonProcessingException {
jp.nextValue();
String className = jp.getCurrentName();
return jp.readValueAs(MAPPINGS.get(className));
}
}
Then you only need to register the mappings you need