Jackson deserialize with missing fields dos not throw exception - json

Trying with that simple app
public class JsonTest {
public static void main(String[] args) throws Exception {
String json = "{\"name\":\"john\"}";
System.out.println(json);
ObjectMapper objectMapper = new ObjectMapper();
Person person = objectMapper.readValue(json, Person.class);
System.out.println(person);
}
#Data
private static class Person {
String name;
Integer age;
}
}
I don't understand why it does not throw an exception.
It gives me
{"name":"john"}
JsonTest.Person(name=john, age=null)
I also tried adding objectMapper .configure(DeserializationFeature.FAIL_ON_MISSING_CREATOR_PROPERTIES, true); or even add #NotNull or #JsonProperty(required = true) in front of Integer age but it has no effect.
The only solution I found is, after the deserialization, to test if age is null.
Is there another solution ?

Related

#JsonUnwrapped creates non-null object even when all fields are null

I ran into this problem in the context of SpringBoot, but it seems to be just a Jackson issue.
I have a DataObject which has several fields. The DataObject is contained within a Model. All the fields of DataObject are optional. But if all the fields are null, then I want the dataoObject itself to be null, but that doesn't seem to be the way it works.
Here my sample code
#Getter
#ToString
#NoArgsConstructor
public class TestContract {
private String field1;
#JsonUnwrapped
#Valid
private DataObject dataObject;
public static void main(String[] args) throws JsonProcessingException {
//language=JSON
String json = "{ \"field1\" : \"value1\"}";
ObjectMapper mapper = new ObjectMapper();
TestContract contract = mapper.readValue(json, TestContract.class);
System.out.println("contract: " + contract);
}
}
#Getter
#NoArgsConstructor
#ToString
public class DataObject {
private String nested1;
private String nested2;
}
The output I end up with is
contract: TestContract(field1=value1, dataObject=DataObject(nested1=null, nested2=null))
Is there any way to have the object end up as null?
The issue is due to the fact that the JsonUnwrapped annotation has the enabled property with a default true value, so even if the property dataObject is not present in your json message an empty dataObject object will be created in any case. To solve the issue, you could use a JsonUnwrapped(enabled = false) annotation or you could directly delete the JsonUnwrapped annotation.
Update : due to the fact that according to the project's specifics is it not possible to erase the JsonUnwrapped annotation, a workaround is the creation of a custom deserializer leaving untouched the main code like below :
#Getter
#ToString
#NoArgsConstructor
public class TestContract {
private String field1;
#JsonUnwrapped
private DataObject dataObject;
public static void main(String[] args) throws JsonProcessingException, IOException {
//language=JSON
String json = "{ \"field1\" : \"value1\"}";
ObjectMapper mapper = new ObjectMapper();
TestContract contract = mapper.readValue(json, TestContract.class);
System.out.println("contract: " + contract);
}
}
#Getter
#Setter
#NoArgsConstructor
#ToString
#JsonDeserialize(using = DataObjectDeserializer.class)
public class DataObject {
private String nested1;
private String nested2;
}
public class DataObjectDeserializer extends JsonDeserializer<DataObject> {
#Override
public DataObject deserialize(JsonParser jp, DeserializationContext dc) throws IOException, JsonProcessingException {
JsonNode node = jp.getCodec().readTree(jp);
String nested1 = node.get("nested1").asText();
String nested2 = node.get("nested2").asText();
DataObject dataObject = new DataObject();
dataObject.setNested1(nested1);
dataObject.setNested2(nested2);
return dataObject;
}
}

Return an object using ResponseEntity with JSONObject as one of the object's field

My model looks like below, where in bookJson is a json object -
{
"name" : "somebook",
"author" : "someauthor"
}
public class Book{
private int id;
private JSONObject bookJson;
public int getId(){return this.id;}
public JSONObject getBookJson(){this.bookJson;}
public void setId(int id){this.id = id;}
public void setBookJson(JSONObject json){this.bookJson = json;}
}
JSONObject belongs to org.json package
When My RestController returns Book object in a ResponseEntity object , I get the error -
"errorDesc": "Type definition error: [simple type, class org.json.JSONObject]; nested exception is com.fasterxml.jackson.databind.exc.InvalidDefinitionException: No serializer found for class org.json.JSONObject and no properties discovered to create BeanSerializer (to avoid exception, disable SerializationFeature.FAIL_ON_EMPTY_BEANS)
What is the best way to achieve this?
Can we not achieve this without having to have a model class with all fields of bookJson?
Figured this out.
Added JSONObject as a Map instead and used ObjectMapper to do all the conversions.
public class Book{
private int id;
private Map<String, Object>bookJson;
public int getId(){return this.id;}
public Map<String, Object>getBookJson(){this.bookJson;}
public void setId(int id){this.id = id;}
public void setBookJson(Map<String, Object> json){this.bookJson = json;}
}
ObjectMapper mapper = new ObjectMapper();
try {
map = mapper.readValue(json, new TypeReference<Map<String, Object>>(){});
} catch (IOException e) {
throw new JsonParsingException(e.getMessage(), e);
}
You need to return the ResponseEntity of List<Book> type.
public ResponseEntity<List<Book>> doSomething() {
return new ResponseEntity(bookList);
}

Deserialize type extending abstract class in GSON

I want to serialize/deserialize an array of Objects that extend an abstract object using gson. To do this I made a TypeHierarchyAdapter like this:
private static class BaseModelAdapter<T extends BaseModel> implements JsonSerializer<T>, JsonDeserializer<T>{
private static final String MODEL_TYPE_PROPERTY = "MODEL_TYPE_PROPERTY";
private static final String CHILDREN_PROPERTY = "children";
private static final Gson gson = new Gson();
#Override
public T deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
if(!json.getAsJsonObject().has(MODEL_TYPE_PROPERTY)) {
throw new JsonParseException("Couldn't find target class! \n" + json.toString());
}
final String className = json.getAsJsonObject().get(MODEL_TYPE_PROPERTY).getAsString();
try {
return (T) gson.fromJson(json, Class.forName(className));
} catch (ClassNotFoundException e) {
Log.e(TAG, e);
throw new JsonParseException("Couldn't find target class!", e);
}
}
#Override
public JsonElement serialize(T src, Type typeOfSrc, JsonSerializationContext context) {
final JsonObject ret = gson.toJsonTree(src).getAsJsonObject();
ret.add(CHILDREN_PROPERTY, context.serialize(src.getChildren()));
ret.addProperty(MODEL_TYPE_PROPERTY, src.getClass().getName());
return ret;
}
}
This I use the adapter like this:
TypeToken<List<BaseModel>> typeToken = new TypeToken<List<BaseModel>>(){};
GsonBuilder builder = new GsonBuilder();
builder.registerTypeHierarchyAdapter(BaseModel.class, new BaseModelAdapter<BaseModel>());
builder.setPrettyPrinting();
Gson special = builder.create();
String json = special.toJson(featured); //Works!
List<BaseModel> baseModels = special.fromJson(json, typeToken.getType()); //Exception Thrown!
It works perfectly if I make BaseModel not abstract, which I suppose I could do for the sake of making this work. But I would much rather keep BaseModel abstract as it should be. The exception thrown says it can not invoke a no-args constructor which has never been a problem with gson before. Adding a no-args constructor to my BaseModel class does not fix the problem and it still says the same message. Is there anyway I can get this to work while keeping BaseModel abstract?

How to indicate a specific Serializer for an object?

I have a model like :
SomeModel
public Long id;
public String name;
public Integer age;
public String address;
public Profile profile;
In my templates, I'd like to render a simpler version of this model, only id and name.
If I do Json.toJson(SomeModel.find.findList()); it will render a list of the SomeModels in the database, but with the complete form.
I've written a Serializer that just returns id and name, but how can I tell Json.toJson to use this serializer ?
public class SimpleSomeModelSeralizer extends JsonSerializer<SomeModel> {
#Override
public void serialize(SomeModel someModel, JsonGenerator generator, SerializerProvider serializer) throws IOException,JsonProcessingException {
if (someModel == null) return;
generator.writeStartObject();
generator.writeNumberField("id", someModel.getId());
generator.writeStringField("name", someModel.getName());
generator.writeEndObject();
}
}
I've looked at the code in Play, and of course, toJson is a simple version, that doesn't take some serializer as parameter, so I guess I have to write a longer code, but I don't know what/how to do it.
Code in Play of Json.toJson :
public static JsonNode toJson(final Object data) {
try {
return new ObjectMapper().valueToTree(data);
} catch(Exception e) {
throw new RuntimeException(e);
}
}
Is it possible to do something like this? :
new ObjectMapper().useSerializer(SimpleSomeModelSeralizer.class).valueToTree(SomeModel.find.findList());
Ok so, here's what I did. On the SomeModel class, I added a static method that returns the list as JsonNode, and I simply call it from my templates :
public static JsonNode findItemsAsJson() {
ObjectMapper mapper = new ObjectMapper();
SimpleModule someModelModule = new SimpleModule("SomeModel", new Version(1, 0, 0, null));
someModelModule.addSerializer(SomeModel.class, new SimpleSomeModelSeralizer());
mapper.registerModule(someModelModule);
return mapper.valueToTree(SomeModel.find.findList());
}
The drawback of this method is that you are bound to the query hard coded (SomeModel.find.findList()) but you can easily add a parameter to that method that is the query :
public static JsonNode findItemsAsJson(Query<SomeModel> query) {
ObjectMapper mapper = new ObjectMapper();
SimpleModule someModelModule = new SimpleModule("SomeModel", new Version(1, 0, 0, null));
someModelModule.addSerializer(SomeModel.class, new SimpleSomeModelSeralizer());
mapper.registerModule(someModelModule);
return mapper.valueToTree(query.findList());
}
And you call it with :
SomeModel.findItemsAsJson(SomeModel.find.like("name", "B%").query());
Hope it'll helps :)

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();
}
}