How to deserialize json to an abstract class in spring-boot - json

In my Application i have something like this.
public class Question{}
public class MCQ extends Question{}
public class TrueAndFalse Question{}
public class Match Question{}
and in my RestController i have a service that adds question.
#RequestMapping(value = "/game/question/add", method = RequestMethod.POST)
public Question addQuuestion(#RequestParam("gameId") long id, #RequestBody Question question)
But i get an error when i try to call this service as i send json file with different structures one for MCQ, TrueAndFalse and Match.
so is it possible to deserialize the received json to Question abstract class.
And thanks in advance.

You can create a custom deserializer which will create Question instances based on json payload properties.
For example if the Question class looks like this:
public class Question {
private final String name;
#JsonCreator
Question(#JsonProperty("name") String name) {
this.name = name;
}
public String getName() {
return name;
}
}
And sub-class TrueAndFalse:
public class TrueAndFalse extends Question {
private final String description;
#JsonCreator
TrueAndFalse(#JsonProperty("name") String name,
#JsonProperty("description") String description) {
super(name);
this.description = description;
}
public String getDescription() {
return description;
}
}
Then you can create a deserializer, which will create an instance of TrueAndFale sub-class by checking if it has description property:
public class QuestionDeserializer extends JsonDeserializer<Question> {
#Override
public Question deserialize(JsonParser p, DeserializationContext ctx) throws IOException {
ObjectCodec codec = p.getCodec();
JsonNode tree = codec.readTree(p);
if (tree.has("description")) {
return codec.treeToValue(tree, TrueAndFalse.class);
}
// Other types...
throw new UnsupportedOperationException("Cannot deserialize to a known type");
}
}
And afterwards, make sure to register it on the object mapper:
#Configuration
public class ObjectMapperConfiguration {
#Bean
public ObjectMapper objectMapper() {
SimpleModule module = new SimpleModule();
module.addDeserializer(Question.class, new QuestionDeserializer());
return new ObjectMapper().registerModules(module);
}
}

Related

#JsonAnySetter, #JsonAnyGetter with DSL Json (De)/Serialization not taking values(always null)

Im using #JsonAnySetter and #JsonAnyGetter in my POJO class using my Custom serialization with DSL JSON class, the Map is initialized but the other properties are always null.
My POJO class:
#CompiledJson
public class Name {
String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
Map<String,String> properties = new HashMap<String,String>();
public Name() {
// TODO Auto-generated constructor stub
}
#JsonAnyGetter
public Map<String, String> get() {
return this.properties;
}
#JsonAnySetter
public void set(String key, String value) {
this.properties.put(key, value);
}
De/Serializing using DSLJson serialize() and deserialize() methods. I do not see any error also, but properties remains null in JSON. I doubt if Jackson annotations are supported by DSL Json. :/
Spring Boot App with DSL Json and Jackson Annotations
UPDATE
I want to parse MyClass, which is a part of RootClass:
#Compiledjson
public class RootClass {
private String id;
private List<MyClass> myclass;
private AnotherCLass class2;
//getters and setter here
}
#CompiledJson
public class MyClass implements JsonObject {
private String name;
private Map<String, String> properties; //want this to behave like Jackson's #JsonAnySetter/Getter annotation.
//The implementation of MapConverter serializer you mentioned below.
}
The entire code parses through custom Message reader and writer.
While sending my JSON Body, It'll be like this :
{
"id" : "1234",
"myclass" :
[
{
"name" : "abcd",
//any dynamic properties I want to add will go here
"test" : "test1",
"anything" : "anything"
}
],
"class2" : "test5"
}
Thank you :)
DSL-JSON doesn't support such get()/set(string, string) method pairs.
It does understand Map<String, String> so if you expose properties it will work on that. But not in this kind of setup.
As of v1.1 you have two options for solving such problems, both of them are covered in example project
If you wish to reuse existing converters, your solution can look like this:
public static class MyClass {
private String name;
private Map<String, String> properties;
#JsonConverter(target = MyClass.class)
public static class MyClassConverter {
public static final JsonReader.ReadObject<MyClass> JSON_READER = new JsonReader.ReadObject<MyClass>() {
public MyClass read(JsonReader reader) throws IOException {
Map<String, String> properties = MapConverter.deserialize(reader);
MyClass result = new MyClass();
result.name = properties.get("name");
result.properties = properties;
return result;
}
};
public static final JsonWriter.WriteObject<MyClass> JSON_WRITER = new JsonWriter.WriteObject<MyClass>() {
public void write(JsonWriter writer, MyClass value) {
MapConverter.serialize(value.properties, writer);
}
};
}
}

How to ignore attributes while serializing a class by ObjectMapper

I have a class with lots of attributes which are required for server side logic, but a few of those are required for UI. Now when I am creating a json from the class, all the attributes are written to json. I want to ignore some values only when it is converted to json. I Tried with #JsonIgnore. But it is not working.
My Class Is
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonProperty;
#JsonIgnoreProperties(ignoreUnknown = true)
public class Student {
#JsonProperty("id")
protected Integer id;
#JsonProperty("name")
protected String name;
/**
* This field I want to ignore in json.
* Thus used #JsonIgnore in its getter
*/
#JsonProperty("securityCode")
protected String securityCode;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
#JsonIgnore
public String getSecurityCode() {
return securityCode;
}
public void setSecurityCode(String securityCode) {
this.securityCode = securityCode;
}
}
And I am writing this using
public static StringBuilder convertToJson(Object value){
StringBuilder stringValue = new StringBuilder();
ObjectMapper mapper = new ObjectMapper();
try {
stringValue.append(mapper.writeValueAsString(value));
} catch (JsonProcessingException e) {
logger.error("Error while converting to json>>",e);
}
return stringValue;
}
My Expected json should contain only :
id:1
name:abc
but what I am getting is
id:1
name:abc
securityCode:_gshb_90880..some_value.
What is wrong here, please help
Your #JsonProperty annotation overrides #JsonIgnore annotation. Remove #JsonProperty from securityCode and your desired json output will be produced.
If you want more advanced ignoring / filtering please take a look at:
#JsonView : http://wiki.fasterxml.com/JacksonJsonViews
#JsonFilter : http://wiki.fasterxml.com/JacksonFeatureJsonFilter

Jackson Mapper Serialize/Deserialize ObjectId

My POJO is :
import org.jongo.marshall.jackson.id.Id;
public class User {
#Id
private String id;
private String name;
private int age;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
I get user from mongo database and want to output him into a file with jackson mapper
ObjectMapper mapper = new ObjectMapper();
mapper.writerWithDefaultPrettyPrinter().writeValue(new File("c:/user.txt"), user);
and I get something like this in my file
{
"name" : "John",
"age" : 23,
"_id" : {
"time" : 1358443593000,
"inc" : 660831772,
"machine" : 2028353122,
"new" : false,
"timeSecond" : 1358443593
}
}
I need id field to be stored into a file as string because when i deserialize this object my id field in pojo looks something like this
{
"time":1358443593000,
"inc":660831772,
"machine":2028353122,
"new":false,
"timeSecond":1358443593
}
Any help will be apreciated
Answering my own question. Found solution here Spring 3.2 and Jackson 2: add custom object mapper
I needed custom object mapper and ObjectId serializer.
public class ObjectIdSerializer extends JsonSerializer<ObjectId> {
#Override
public void serialize(ObjectId value, JsonGenerator jgen, SerializerProvider provider) throws IOException, JsonProcessingException {
jgen.writeString(value.toString());
}
}
public class CustomObjectMapper extends ObjectMapper {
public CustomObjectMapper() {
SimpleModule module = new SimpleModule("ObjectIdmodule");
module.addSerializer(ObjectId.class, new ObjectIdSerializer());
this.registerModule(module);
}
}
I found an easy attempt using springboot 2.5.4.
Just by adding a Jackson2ObjectMapperBuilderCustomizer bean will do the trick.
#Configuration
public class JacksonMapperConfiguration
{
#Bean
public Jackson2ObjectMapperBuilderCustomizer jsonCustomizer() {
return builder -> builder.serializerByType(ObjectId.class, new ToStringSerializer());
}
}
Here is a simple solution for serialization if you don't have a model for the object being stored:
client.getDatabase("db").getCollection("collection").find().onEach { it["_id"] = it["_id"].toString() }
"onEach" is a kotlin function. If you use Java, then change it to a simple foreach.
It's not efficient to iterate over the entire list just for the id. Only use it for small lists where performance is less important than short code.

how to handle Json body in post request in jax-rs

I have a project (homework) about JAX-RS. I'm working with NetBeans, Jersey and Tomcat.
This is my "User" class for main object in the system.
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;
#XmlRootElement(name="user")
public class User {
//#XmlElement
//public int id ;
#XmlElement
public String username;
#XmlElement
public String fullname;
#XmlElement
public String gender;
#XmlElement
public String birthDate;
public User(){
}
public User(String username,String fullname, String gender,String birthDate){
//this.id = id;
this.username = username;
this.fullname = fullname;
this.gender = gender;
this.birthDate = birthDate;
}
}
This is my "JAXBContextResolver" Class
import com.sun.jersey.api.json.JSONConfiguration;
import com.sun.jersey.api.json.JSONJAXBContext;
import javax.ws.rs.ext.ContextResolver;
import javax.ws.rs.ext.Provider;
import javax.xml.bind.JAXBContext;
#Provider
public class JAXBContextResolver implements ContextResolver<JAXBContext>{
private JAXBContext context;
private Class[] types = {User.class};
public JAXBContextResolver() throws Exception {
this.context =
new JSONJAXBContext( JSONConfiguration.mapped().build(), types);
}
#Override
public JAXBContext getContext(Class<?> objectType) {
for (Class type : types) {
if (type == objectType) {
return context;
}
}
return null;
}
}
And this is my post method in the "UserService" class
#POST
#Consumes(MediaType.APPLICATION_JSON)
#Produces(MediaType.APPLICATION_JSON)
public List<User> createNewUser(User tUser) {
List<User> list = new ArrayList<User>();
list.add(tUser);
return list;
}
When I am trying a post new user in the localhost with RESTClient (Firefox add-ons) my request body is a json input like that:
{"user":{"username":"blabla","fullname":"blabla","gender":"M","birthDate":"05.01.1978"}}
In the post method (in the UserService class) must the variable "tUser" automatically filled with the coming input ? "tUser" variable shows null elements in it in the debugging mode like that:
If I know wrong could somebody correct me please? Why this values shows null? Must not them shows "blabla" - "blabla" - "M" - "05.01.1878" ? Could you help me please?
I solved this problem; In the JAXBContextResolver class I change the method like that :
public JAXBContextResolver() throws Exception {
this.context =
new JSONJAXBContext( JSONConfiguration.mapped().rootUnwrapping(false).build(), types);
}
The difference with the first one is adding "rootUnwrapping(false)" expression.
#XmlRootElement is not working in your example. Send
{"username":"blabla","fullname":"blabla","gender":"M","birthDate":"05.01.1978"}
instead
EDIT
1)
public List<User> createNewUser(Request tUser)
and class
class Request
{
public User user;
}
2)
public List<User> createNewUser(String tUser)
and convert String to object using google-gson or jackson json processor

Jackson JSON Deserialization of MongoDB ObjectId

Ok, so first off here's the JSON that's returning from my web service. I'm trying to deserialize it into pojos after an asynchronous query in a ResponseHandler in my Android ContentProvider.
{"exampleList" : [{
"locationId" : "00001" ,
"owners" : [
{
"paidID" : { "$oid" : "50a9c951300493f64fbffdb6"} ,
"userID" : { "$oid" : "50a9c951300493f64fbffdb6"}
} ,
{
"paidID" : { "$oid" : "50a9c951300493f64fbffdb7"} ,
"userID" : { "$oid" : "50a9c951300493f64fbffdb7"}
}
]
}]}
At first, I was confused about the problem I was seeing, since I use the same Jackson-annotated beans for my web service as I do in my Android app--but then I realized that the owners object was never getting sent in the sample JSON to my web service (it skips the POJOs on my web service and gets added into the documents in mongoDB through atomic updates from the DAO).
So OK. Up to now, Jackson wasn't having to handle the owners object, and now that it is it is choking on it, namely:
JsonMappingException: Can not deserialize instance of java.lang.String out of
START_OBJECT token at [char position where you can find "userID" and "paidID"]
through reference chain [path to my Jackson bean which contains the owners class]
My Jackson bean has a wrapper, which is what that "exampleList" is all about:
public class Examples extends HashMap<String, ArrayList<Example>> {
}
And then the actual Example class:
#JsonIgnoreProperties(ignoreUnknown = true)
public class Example implements Comparable<Example> {
#ObjectId #Id
private String id;
#JsonProperty(Constants.Example.location)
private String location;
#JsonProperty(Constants.Example.OWNERS)
private List<Owners> owners;
public int compareTo(Example _o) {
return getId().compareTo(_o.getId());
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getLocation() {
return location;
}
public void setLocation(String location) {
this.location = location;
}
public List<Example.Owners> getOwners() {
return owners;
}
public void setOwners(List<Example.Owners> owners) {
this.owners = owners;
}
public Example() {
}
#JsonCreator
public Example(#Id #ObjectId String id) {
this.id = id;
}
#JsonIgnoreProperties(ignoreUnknown = true)
public static class Owners implements Comparable<Owners> {
#JsonProperty(Constants.Example.USERID)
private String userID;
#JsonProperty(Constants.Example.PAIDID)
private String paidID;
public Owners() {
}
public int compareTo(Owners _o) {
return getUserID().compareTo(_o.getUserID());
}
#ObjectId
public String getUserID() {
return userID;
}
#ObjectId
public void setUserID(String userID) {
this.userID = userID;
}
#ObjectId
public String getPaidID() {
return paidID;
}
#ObjectId
public void setPaidID(String paidID) {
this.paidID = paidID;
}
}
}
And finally, the code in the ResponseHandler where this is all failing (the 2nd line produces the JsonMappingException):
objectMapper = MongoJacksonMapperModule.configure(objectMapper);
mExamples = objectMapper.readValue(jsonParser, Examples.class);
I have a feeling the issue is that Jackson still doesn't know how to map those $oid, which are the mongoDB ObjectIds. The MongoJacksonMapper library is supposed to help that by providing the #ObjectId annotation and a way to configure the ObjectMapper to use that library, but it still isn't working. For some reason, it's still looking for the userID or paidID as a String, not an ObjectId. Any ideas?
Another alternative is
com.fasterxml.jackson.databind.ser.std.ToStringSerializer.
#Id
#JsonSerialize(using = ToStringSerializer.class)
private final ObjectId id;
This will result in:
{
"id": "5489f420c8306b6ac8d33897"
}
For future users: Use a custom jackson deserializer to convert $oid back to ObjectId.
public class ObjectIdDeserializer extends JsonDeserializer<ObjectId> {
#Override
public ObjectId deserialize(JsonParser p, DeserializationContext ctxt) throws IOException, JsonProcessingException {
JsonNode oid = ((JsonNode)p.readValueAsTree()).get("$oid");
return new ObjectId(oid.asText());
}
}
How to use:
ObjectMapper mapper = new ObjectMapper();
SimpleModule mod = new SimpleModule("ObjectId", new Version(1, 0, 0, null, null, null));
mod.addDeserializer(ObjectId.class, new ObjectIdDeserializer());
mapper.registerModule(mod);
YourClass obj = mapper.readValue("{your json with $oid}", YourClass.class);
My code had at least two problems that were pretty tough to track down answers to online, so I'll make sure to link here. Basically, child classes need a constructor in the parent class that calls Jackson's readValue() to map the child. As far as mongoDB $oid's go, you should create a separate MongoId class to represent these mongo objects, and follow a similar pattern as with the child class to map the data when it comes in for deserialization. Here's a blog post I found that describes this well and provides some examples.
Jackson does not know how to serialize an ObjectId. I tweaked Arny's code to serialize any ObjectId and provide this working example:
public class SerialiserTest {
private ObjectMapper mapper = new ObjectMapper();
public static class T {
private ObjectId objectId;
public ObjectId getObjectId() {
return objectId;
}
public void setObjectId(ObjectId objectId) {
this.objectId = objectId;
}
}
#Test
public final void serDeser() throws IOException {
T t = new T();
t.setObjectId(new ObjectId());
List<T> ls = Collections.singletonList(t);
String json = mapper.writeValueAsString(ls);
System.out.println(json);
SimpleModule mod = new SimpleModule("ObjectId", new Version(1, 0, 0, null, null, null));
mod.addDeserializer(ObjectId.class, new ObjectIdDeserializer());
mapper.registerModule(mod);
JavaType type = mapper.getTypeFactory().
constructCollectionType(List.class, T.class);
List<?> l = mapper.readValue(json, type);
System.out.println(l);
}
}
public class ObjectIdDeserializer extends JsonDeserializer<ObjectId> {
#Override
public ObjectId deserialize(JsonParser p, DeserializationContext ctxt) throws IOException, JsonProcessingException {
JsonNode n = (JsonNode)p.readValueAsTree();
return new ObjectId(n.get("timestamp").asInt(), n.get("machineIdentifier").asInt(), (short) n.get("processIdentifier").asInt(), n.get("counter").asInt());
}
}
There's an even easier way documented here which was a lifesaver for me. Now you can use the ObjectId in Java but when you go to/from JSON it'll be a String.
public class ObjectIdJsonSerializer extends JsonSerializer<ObjectId> {
#Override
public void serialize(ObjectId o, JsonGenerator j, SerializerProvider s) throws IOException, JsonProcessingException {
if(o == null) {
j.writeNull();
} else {
j.writeString(o.toString());
}
}
}
And then in your beans:
#JsonSerialize(using=ObjectIdJsonSerializer.class)
private ObjectId id;
I did it like this:
#Configuration
public class SpringWebFluxConfig {
#Bean
#Primary
ObjectMapper objectMapper() {
Jackson2ObjectMapperBuilder builder = new Jackson2ObjectMapperBuilder();
builder.serializerByType(ObjectId.class, new ToStringSerializer());
builder.deserializerByType(ObjectId.class, new JsonDeserializer() {
#Override
public Object deserialize(JsonParser p, DeserializationContext ctxt)
throws IOException {
Map oid = p.readValueAs(Map.class);
return new ObjectId(
(Integer) oid.get("timestamp"),
(Integer) oid.get("machineIdentifier"),
((Integer) oid.get("processIdentifier")).shortValue(),
(Integer) oid.get("counter"));
}
});
return builder.build();
}
}