Jackson Deserialization of dynamic data fails, - java

public class Baseproperties
{
#JsonProperty("id")
private String id ;
private Integer ccode;
//...set and geters
}
public class Person
{
#JsonProperty("name")
private String name ;
private Integer age;
#JsonProperty("props")
private Baseproperties bprop;
//...set and geters
}
public class Cars
{
#JsonProperty("model")
private String Model ;
private Integer yearOfMake;
#JsonProperty("props")
private Baseproperties bprop;
//...set and geters
}
public MessageWrapper
{
#JsonProperty("ct")
private String classType;
private Object data;
//...set and geters
}
I need to serialise MessageWrapper class to json, but the approach fails due to unable to desearialize the Object data;
here i am reading the classType and desearializing it to either Person or CarType
//Person
{
"name": "arnold",
"age": 21
}
//car
{
"model": "Moriz",
"yearOfMake": 1892
}
//example MessageWrapper
String s= "{
"ct": "<packagename>.car",
"data": {
"model": "Moriz",
"yearOfMake": 1892
"props":{
"id" : "12312",
"ccode" :33
}
}
}"
mapper.configure(DeserializationFeature.UNWRAP_ROOT_VALUE, true);
MessageWrapper mw = mapper.readValue(s, MessageWrapper.class);
if(mw.getclassType().toString().equals("<packagename>.car"))
Cars cw = mapper.readValue(mw.getData(), Cars.class);
but cw is wrong // serialise fails.

This is because there is no ObjectMapper::readValue method that takes Object as first argument.
By default with your approach Jackson will deserialize your data field to LinkedHashMap because you have given it Object type.
To then deserialize this value manually you will have to use ObjectMapper::convertValue and passing Cars.class as argument :
Cars cw = mapper.convertValue(mw.getData(), Cars.class);
And also get rid of :
mapper.configure(DeserializationFeature.UNWRAP_ROOT_VALUE, true);
as it does not seem to be needed here.
And just to add, I am not sure that approach with such dynamic data is good because, if you will be creating more and more types of objects you will end up with a tower of ifs or colosal switch statement.

Related

How deserialize plain String to Json using Jackson in Java?

I have a simple class as property of mage:
// getter/setter omitted for brevity
public class Magic() {
String Spell;
int strength;
}
public class Mage() {
String name;
Magic magic;
}
I need to deserialize JSON from 2 different source strings:
{
"name" : "Sauron",
"magic" : {
"spell" : "Tamador",
"strenght" : 10
}
}
and
{
"name" : "Gandalf",
"magic" : "You shall not pass"
}
or even "You shall not pass" -> Magic object
I thought going with #JsonDeserialize(using = MagicDeserializer.class) would be the way to go with Jackson, but the Parser barfs with "Unrecognized token". Is there a way I can intercept the loading to do my own parsing?
The idea of a custom deserializer is correct, you can extends the StdDeserializer class and in its deserialize method convert the json to a JsonNode separating the two Stringand Object distinct values associated to the magic key in the json:
public class MagicDeserializer extends StdDeserializer<Magic> {
public MagicDeserializer() {
super(Magic.class);
}
#Override
public Magic deserialize(JsonParser jp, DeserializationContext dc) throws IOException, JsonProcessingException {
final ObjectCodec codec = jp.getCodec();
JsonNode root = codec.readTree(jp);
Magic magic = new Magic();
if (root.isTextual()) { //<- magic is a string
magic.setSpell(root.textValue());
return magic;
}
//ok, so magic is an Magic object
return codec.treeToValue(root, Magic.class);
}
}
Then if you annotate your Magic field you can deserialize both the jsons:
#Data
public class Mage {
private String name;
#JsonDeserialize(using = MagicDeserializer.class)
private Magic magic;
}
#Data
public class Magic {
private String Spell;
private int strength;
}
Mage sauron = mapper.readValue(json1, Mage.class);
System.out.println(mapper.writeValueAsString(sauron));
Mage gandalf = mapper.readValue(json2, Mage.class);
System.out.println(mapper.writeValueAsString(gandalf));

How to get Jackson mixin to handle an included type?

I'm using Jackson mixins to only serialize out specific fields.
My ObjectMapper is configured like so:
ObjectMapper mapper = new ObjectMapper();
mapper.setVisibility(PropertyAccessor.ALL, Visibility.NONE);
mapper.setSerializationInclusion(Include.NON_NULL);
mapper.addMixIn(Person.class, SyncPerson.class);
mapper.addMixIn(TransactionLog.class, TransactionLogExport.class);
Here are the model classes paired with the JSON mixin objects that I'd like to export:
// Model class
public class Person {
private Long id;
private String email;
private String firstName;
private String lastName;
}
// Desired JSON format. Excludes 'id' field
public interface SyncPerson {
#JsonProperty("firstName")
String getFirstName();
#JsonProperty("lastName")
String getLastName();
#JsonProperty("email")
String getEmail();
}
// Model class
public class TransactionLog {
private long id;
private Integer version;
private Person person;
private Date date;
private EntityAction action;
}
// Desired JSON format. Excludes 'id' field, 'version', 'date'
public interface TransactionLogExport {
#JsonProperty("id")
String getId();
#JsonProperty("person")
Person person();
#JsonProperty("action")
EntityAction getAction();
}
Yet, my tests are showing that the person attribute of the TransactionLog isn't coming through.
#Test
public void testWriteValue() throws Exception {
Person person = new Person();
person.setEmail("a#c.com");
person.setFirstName("A");
person.setLastName("C");
TransactionLog log = new TransactionLog();
log.setId(0L);
log.setAction(EntityAction.CREATE);
log.setPerson(person);
log.setStartValue("start");
log.setEndValue("end");
log.setChanges("change");
String prettyJson = mapper.writerWithDefaultPrettyPrinter().writeValueAsString(log);
System.out.println(prettyJson);
// Prints:
// {
// "id" : 0,
// "action" : "CREATE",
}
}
If I try the same test with a regular ObjectMapper mapper = new ObjectMapper(); instead of the mixin, then I see the full object exported, including the Person with email, names, etc. So something must be wrong with how I've configured the mixin... or else I'm misunderstanding something.
So can anyone help indicate what I could do to export out the subtype 'person' in my mixin?
Thanks!
Finally figured out the issue. The test now prints what we want:
{
“id” : 0,
“person” : {
“email” : “a#c.com”,
“firstName” : “A”,
“lastName” : “C”
},
“action” : “CREATE”
}
The mistake was in TransactionLogExport. It needs to say:
#JsonProperty("person")
Person getPerson();
Instead of:
#JsonProperty("person")
Person person();
I.e. the method needs to start with 'get'.

Convert JSON object to Java object with different format using GSON

I have a response that returns a json object in following format:
{
"playerId": "001",
"name": "michel",
"age": 21,
"nation": "USA",
"ratings": [
{
"type": "speed",
"score": "0121"
},
{
"type": "accuracy",
"score": "85"
}
],
"teaminfo": {
"teamName": "HON",
"isValid": "true"
}
}
and I have a Java Class as :
public class MyRider {
public String playerId;
public String name;
public int age;
public String speed;
public String accuracy;
public String teamName;
public String isValid;
//getter, setter...
}
I want to map the JSON object into Java object using GSON.
I tried using JsonDeserializationContext deserialize, and it returned null for the nested values in JSON.
Without custom deserializer
If you cannot change the JSON to return exactly what you want, I suggest you create classes to match it:
MyRider:
public class MyRider {
private String playerId;
private String name;
private int age;
private String nation;
private List<Rating> ratings;
private TeamInfo teaminfo;
// getters, setters, toString override
}
Rating:
public class Rating {
private String type;
private String score;
// getters, setters, toString override
}
TeamInfo:
private static class TeamInfo {
private String teamName;
private String isValid;
// getters, setters, toString override
}
Then simply deserialize as normal:
MyRider rider = gson.fromJson(json, MyRider.class);
If you need exactly the fields you've specified in MyRider in your question, consider a transformer class to map the full class above to your needs.
With custom deserializer
It's also possible to do this with a custom deserializer, but slightly pointless as GSON provides the normal mapping for you which you can then adapt.
Here is an example with a deserializer:
public class MyRiderDeserializer implements JsonDeserializer<MyRider> {
#Override
public MyRider deserialize(JsonElement json, Type typeOfT,
JsonDeserializationContext context)
throws JsonParseException {
MyRider rider = new MyRider();
if(json.isJsonObject()) {
JsonObject riderObj = json.getAsJsonObject();
rider.setPlayerId(riderObj.get("playerId").getAsString());
rider.setName(riderObj.get("name").getAsString());
rider.setAge(riderObj.get("age").getAsInt());
JsonArray ratingsArray = riderObj.get("ratings").getAsJsonArray();
for(JsonElement ratingElem : ratingsArray) {
JsonObject ratingObj = ratingElem.getAsJsonObject();
String type = ratingObj.get("type").getAsString();
switch(type) {
case "speed":
rider.setSpeed(ratingObj.get("score").getAsString());
break;
case "accuracy":
rider.setAccuracy(ratingObj.get("score").getAsString());
break;
default:
break;
}
}
JsonObject teamInfo = riderObj.get("teaminfo").getAsJsonObject();
rider.setTeamName(teamInfo.get("teamName").getAsString());
rider.setIsValid(teamInfo.get("isValid").getAsString());
}
return rider;
}
}
Note this does not include any checks to validate whether the properties are actually there and is the simplest possible custom deserializer I could think of. To use it, you must register the type adapter at Gson creation time:
Gson gson = new GsonBuilder()
.registerTypeAdapter(MyRider.class, new MyRiderDeserializer())
.create();
MyRider myRider = gson.fromJson(reader, MyRider.class);

Create Object from set of json properties jackson

Please refer below example.
public class Human {
private String name;
private int age;
}
public class Teacher {
private String school;
private Human human;
}
And JSON looks like :
{
"school": "My School",
"age": 20,
"name": "My Name"
}
I want to create Teacher from JSON string which has Human as inner object but should match to same level of JSON properties.
I'm using Jackson API to create java object from JSON.
You can mark the human field as #JsonUnwrapped:
public class Teacher {
private String school;
#JsonUnwrapped
private Human human;
// constructor / setters
}
public class Human {
private String name;
private int age;
// constructor / setters
}
public class Test {
String str = "{ \"school\": \"My School\", \"age\": 20, \"name\": \"My Name\" }";
System.out.println(new ObjectMapper().readValue(str, Teacher.class));
}
That will de-serialize into the format you're looking for.

Can't serialize with Jackson Json class fields if it extends ArrayList

I'm using Jackson Json. I can't serialize class fields if class extends ArrayList.
Class:
public class DataElement {
private Date date;
private int val1;
private int val2;
// constructor, getters, setters
}
public class DataArray extends ArrayList<DataElement> {
private String info;
private int num;
// constructor, getters, setters
}
Serialization:
ObjectMapper jsonMapper = new ObjectMapper();
jsonMapper.writeValue(new File("path"), dataArray);
Result file contains DataElements only:
[ {
"date" : 1446405540000,
"val1" : 10296,
"val2" : 30365
}, {
"date" : 1446405600000,
"val1" : 40164,
"val2" : 20222
} ]
'num' and 'info' are not saved into file.
How to save full class including its fields?
Jackson will serialize your POJOs according to the JsonFormat.Shape. For an ArrayList object that is ARRAY. You can change the shape to OBJECT with an annotation.
#JsonFormat(shape = JsonFormat.Shape.OBJECT)
public class DataArray extends ArrayList<DataElement> {
Make sure DataArray has a getter that returns an ArrayList for e.g.
public ArrayList<DataElement> getContents() {
return new ArrayList<>(this);
}
When I tried the above code I saw this field at the resulting JSON
"empty":false
You can use #JsonIgnore to prevent that from appearing

Categories