Jackson Custom Deserializer breaks default ones - java

I'm currently implementing parsing of JSON to POJO.
My Model class looks like this:
public class InvoiceModel {
#JsonDeserialize(using=MeteorDateDeserializer.class)
Date date;
String userId;
String customerName;
ArrayList<PaymentModel> payments;
[...getters, setters...]
}
a JSON string could look like this:
{
"date": {
"$date": 1453812396858
},
"userId": "igxL4tNwR58xuuJbE",
"customerName": "S04",
"payments": [
{
"value": 653.5,
"paymentMethod": "Cash",
"userId": "igxL4tNwR58xuuJbE",
"date": {
"$date": 1453812399033
}
}
]
}
though the payments field may be omitted. Anywho, doesn't really matter too much for the question. You see, the format for parsing the date is somewhat "odd" as in it is encapsulated in the "$date" property of the "date" object. This is however not in my control.
To get around this problem I wrote a custom JSON Deserializer to use for this date property. It looks like this:
public class MeteorDateDeserializer extends org.codehaus.jackson.map.JsonDeserializer<Date> {
#Override
public Date deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException, JsonProcessingException {
// parse the "$date" field found by traversing the given tokens of jsonParser
while(!jsonParser.isClosed()){
JsonToken jsonToken = jsonParser.nextToken();
if(JsonToken.FIELD_NAME.equals(jsonToken)){
String fieldName = jsonParser.getCurrentName();
jsonToken = jsonParser.nextToken();
if("$date".equals(fieldName)){
long timeStamp = jsonParser.getLongValue();
return new java.util.Date(timeStamp);
}
}
}
return null;
}
}
The exact problem is the following:
The returned InvoiceModel POJO has every attribute apart from "date" set to null, whereas the date is parsed fine. I have narrowed down the problem to the custom Date Deserializer by not deserializing the date at all (just deserializing the 2 string values and the payments array, which works fine).
My thesis is that the annotation conveys to Jackson that the custom deserializer is to be used for the whole class instead of being used just for the date field.
According to the doc this should not be the case:
Annotation use for configuring deserialization aspects, by attaching to "setter" methods or fields, or to
The call to the serialization is nothing special. It's just a standard ObjectMapper call.
InvoiceModel invoice = mapper.readValue(s2, InvoiceModel.class);
where s2 is the JSON string.
My version of Jackson is 1.9.7

Related

How deserialize json object array as array of json strings?

Consider json input:
{
companies: [
{
"id": 1,
"name": "name1"
},
{
"id": 1,
"name": "name1"
}
],
nextPage: 2
}
How deserialize this into class:
public class MyClass {
List<String> companies;
Integer nextPage;
}
Where List<String> companies; consists of strings:
{"id": 1,"name": "name1"}
{"id": 1,"name": "name1"}
#JsonRawValue doesn't work for List<String> companies;
Is there a way to configure Jackson serialization to keep companies array with raw json string with annotations only? (E.g. without writing custom deserializator)
There is no annotation-only solution for your problem. Somehow you have to convert JSON Object to java.lang.String and you need to specify that conversion.
You can:
Write custom deserializer which is probably most obvious solution but forbidden in question.
Register custom com.fasterxml.jackson.databind.deser.DeserializationProblemHandler and handle com.fasterxml.jackson.databind.exc.MismatchedInputException situation in more sophisticated way.
Implement com.fasterxml.jackson.databind.util.Converter interface and convert JsonNode to String. It is semi-annotational way to solve a problem but we do not implement the worst part - deserialisation.
Let's go to point 2. right away.
2. DeserializationProblemHandler
Solution is pretty simple:
ObjectMapper mapper = new ObjectMapper();
mapper.addHandler(new DeserializationProblemHandler() {
#Override
public Object handleUnexpectedToken(DeserializationContext ctxt, JavaType targetType, JsonToken t, JsonParser p, String failureMsg) throws IOException {
if (targetType.getRawClass() == String.class) {
// read as tree and convert to String
return p.readValueAsTree().toString();
}
return super.handleUnexpectedToken(ctxt, targetType, t, p, failureMsg);
}
});
Read a whole piece of JSON as TreeNode and convert it to String using toString method. Helpfully, toString generates valid JSON. Downside, this solution has a global scope for given ObjectMapper instance.
3. Custom Converter
This solution requires to implement com.fasterxml.jackson.databind.util.Converter interface which converts com.fasterxml.jackson.databind.JsonNode to String:
class JsonNode2StringConverter implements Converter<JsonNode, String> {
#Override
public String convert(JsonNode value) {
return value.toString();
}
#Override
public JavaType getInputType(TypeFactory typeFactory) {
return typeFactory.constructType(new TypeReference<JsonNode>() {
});
}
#Override
public JavaType getOutputType(TypeFactory typeFactory) {
return typeFactory.constructType(new TypeReference<String>() {
});
}
}
and now, you can use annotation like below:
#JsonDeserialize(contentConverter = JsonNode2StringConverter.class)
private List<String> companies;
Solutions 2. and 3. solve this problem almost in the same way - read node and convert it back to JSON, but uses different approaches.
If, you want to avoid deserialising and serialising process you can take a look on solution provided in this article: Deserializing JSON property as String with Jackson and take a look at:
How to serialize JSON with array field to object with String field?
How to get a part of JSON as a plain text using Jackson
How to extract part of the original text from JSON with Jackson?

Custom Deserialiser to parse complex Json

I have to deserialise below Json
{
"Student": [
{
"Number": "12345678",
"Name": "abc"
"Country": "IN",
"AreaOfInterest": [
{
“FootBall”: “Yes”,
“Cricket”: “No”
}
]
}
],
"hasMore": false,
"links": [
{
"rel": "self",
"kind": "collection"
}
]
}
into below POJO
class {
private String number;
private String name;
private String footBall;
}
I have written Gson custom deserialiser to lift up AreaOfInterest as below
public List<? extends Student> deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
var jsonObject = json.getAsJsonObject();
Stream<JsonElement> student = StreamSupport.stream(jsonObject.getAsJsonArray("Student").spliterator(), true);
Stream<JsonElement> areaOfInterest = StreamSupport.stream(jsonObject.getAsJsonArray("Student").get(0).getAsJsonObject().get("AreaOfInterest").getAsJsonArray().spliterator(), true);
Stream.concat(student,areaOfInterest)
.map(it -> context.deserialize(it, Student.class))
.map(Student.class::cast)
.collect(List.collector())
}
But deserialiser returning two objects of Student instead of one, one is all fields are null except footBall other is actual student except footBall as null, any help how to get single object with all the fields will be of great help, thanks in advance.
This won't be your exact answer, but it might be simpler to use gson to obtain a map and construct your pojo from that map. Alternatively, if you don't like the map, create a pojo that looks like your JSON and map that pojo to the pojo you want.
Background/Reasoning: GSON is the mapper of your choice right now, but might be changed to something else, eg. Jackson, and all of your custom, framework specific mappers will need to be converted/changed if that happens. Using gson to create an object, that looks like the source, and map that to your custom POJO in your controller will make your codes intention clear and your code more resilient to framework changes.

Json array not serializing properly

I am trying to Json serialize and deserialize LocalDate array in my Java class but when i generate json schema for the web service, the parameter still shows up as LocalDate rather than String.
Following is the code :
#JsonSerialize(
contentUsing = ToStringSerializer.class
)
#JsonDeserialize(
contentUsing = LocalDateFromJSON.class
)
private LocalDate[] amortizationDates;
and in Json schema this appears as :
amortizationDates":{"type":"array","items":{"$ref":"#/definitions/LocalDate"}}
which is wrong because it should appear as String when serialized.
Any ideas on how to serialize it as String.
Edit:
I am suing Jackson for serialization and following are serializer details :
com.fasterxml.jackson.databind.ser.std.ToStringSerializer- Jackson inbuilt
LocalDateFromJSON ->
public static class LocalDateFromJSON extends JsonDeserializer<LocalDate> {
public LocalDateFromJSON() {
}
public LocalDate deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException {
return LocalDate.parse(((TextNode)jsonParser.readValueAsTree()).asText());
}
}

JsonDeserializer<Date> is not invoked

I am making a web application which makes a REST call to return me the list of order. The order has list of products and each product has expiry date. So, something like this
{
"order": 123,
"products" :[
{
...
"expiryDate": "2015 January 01",
...
}
]
}
I obviously needed to generate the POJO and because there are so many fields I choose this tool http://www.jsonschema2pojo.org/
I want to convert this String date to java.util.Date and to do that, I have created a custom date serializer and put this annotation on product expiry setter method
#JsonDeserialize(using = CustomJsonDateDeserializer.class)
public void setExpiryDate(Date date) {
...
}
// Different Class
public class CustomJsonDateDeserializer extends JsonDeserializer<Date>
{
#Override
public Date deserialize(JsonParser jsonparser,
DeserializationContext deserializationcontext) throws IOException, JsonProcessingException {
....
}
}
However, deserialize() method doesn't get invoked at all. I also put in the #JsonDeserialize to the field where it is declared but it is still not work.
Could anyone tell me where I am going wrong? Thanks for the help

How to read a single JSON field for a specific Java type with Jackson?

I'm trying to build a JPA CriteriaQuery from a client-provided JSON filter; that is, to query this Person POJO:
#Entity
public class Person {
#Id
public Long id;
public String name;
public Date dateOfBirth;
}
a client would provide a JSON like this
{
"$or": [
{ "name": { "$beginsWith": "Jo" } },
{ "dateOfBirth": { "$lessThan": "1983-01-01T00:00:00Z" } }
]
}
When parsing this filter, I need to obtain the right Java type from the provided JSON values, e.g. a Date for dateOfBirth. Given that I can easily reach the JsonNode for "1983-01-01T00:00:00Z" and that Jackson's ObjectMapper knows somewhere what type to deserialize for the dateOfBirth field of Person.class, how can I get it? Ideally it would be something in the lines of:
// jsonNode is the JsonNode for "1983-01-01T00:00:00Z"
ObjectMapper mapper = new ObjectMapper();
ObjectReader reader = mapper.readerFor(Person.class);
Date date = (Date) reader.asField("dateOfBirth").readValue(jsonNode);
That asField() method would do the magic to return a reader which will read a JSON string as Java Date (or more generally the type that the ObjectMapper would resolve applying [de]serializers annotated for that field).
Note: I'm not asking how to parse dates with Jackson, but how to read a value and get the object Jackson would deserialize normally, but for a nested field.

Categories

Resources