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());
}
}
Related
I have a JSON string which I would like to translate into POJO using ObjectMapper.readValue method.
The thing is that the input Json string contains keys which I would like to filter out before the deserialization.
I came across DelegatingDeserialization class which according to my understanding allows you to extend it and override one of the deserialize method to reconstruct the json input and then pass it on the chain.
The thing is that I try to enable this custom delegating deserializer by adding the
#JsonDeserialize(using = CustomDelegatingDeserialization.class) on top of my Pojo - is that the right way to instantiate it??
Here is a snippet of my custom delegator:
public static class CustomDeserializer extends DelegatingDeserializer {
public CustomDeserializer() {
super(null);
}
public CustomDeserializer(JsonDeserializer<?> defaultDeserializer) {
super(defaultDeserializer);
}
#Override
protected JsonDeserializer<?> newDelegatingInstance(JsonDeserializer<?> newDelegatee) {
return new CustomDeserializer(newDelegatee);
}
#Override
public Object deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
return super.deserialize(restructure(p), ctxt);
}
private JsonParser restructure(JsonParser jp) throws IOException {
...
return newJsonParser;
}
}
Am I taking the right path or there is a more fitting solution??
THank you!
EDIT 1
Another approach is to have a CustomJsonDeserializer extends JsonDeserializer<T> and override its deserialize method then reconstruct the Node and propagate it by returning codec.treeToValue(jsonNode, Pojo.class); this makes sense BUT it gets me into infinite loop! any idea why?
Assuming your POJO doesn't have a property that you would like to ignore you can use annotation #JsonIgnoreProperties(ignoreUnknown = true)for your class. That tells Jeckson to ignore properties that are not present in your POJO. Read more on the issue how to ignore some properties here: Jackson Unmarshalling JSON with Unknown Properties
Jackson reads input numbers as Strings. As an example below Student class read name 4567 as a String.
ex: input
{
name: 4567
...
}
Java class
Class Student {
String name;
...
}
Jackson is parsing the JSON text and mapping the number value to the string field, and I don't want the type conversion, i.e. for the number to be converted to a string. In this scenario Jackson converts the value from int (4567) to String("4567").
How this behavior can changes to throw an exception if other type provided to fail ?
Custom deserializer registered for java.lang.String should definitely work and let you prevent conversion. Deserializers will directly see content via JsonParser so they can detect underlying token type.
This will help you:
public class ForceStringDeserializer extends JsonDeserializer<String> {
#Override
public String deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException {
if (jsonParser.getCurrentToken() == JsonToken.VALUE_NUMBER_INT) {
throw deserializationContext.wrongTokenException(jsonParser, JsonToken.VALUE_STRING, "Attempted to parse int to string but this is forbidden");
}
return jsonParser.getValueAsString();
}
}
You can find more info here.
add to application.properties
spring.jackson.mapper.allow-coercion-of-scalars=false
or if you configure object maper like bean
objectMapper.configure(MapperFeature.ALLOW_COERCION_OF_SCALARS, false);
Consider the following example:
I have a json string = {"timestamp":1504111920} which needs to be converted to CodeTimestamp class. The timestamp present in above json string is in epoch second.
CodeTimestamp class:
#Getter
#Setter
#NoArgsConstructor
class CodeTimestamp {
private Date timestamp;
}
By directly using fasterxml jackson mapper, I'll not be able to get the correct date since it assumes timestamp to be in epoch millisecond. So, I would need to write a custom deserializer.
However, I cannot edit/modify CodeTimestamp class. Is there any way to write JsonDeserializer in mixin?
I'm facing issues while deserializing. Following is the code:
public abstract class StreamRecordMixIn {
#JsonDeserialize(using = UnixTimestampDeserializer.class)
private Date approximateCreationDateTime;
}
public class UnixTimestampDeserializer extends JsonDeserializer<Date> {
#Override
public Date deserialize(JsonParser parser, DeserializationContext context)
throws IOException, JsonProcessingException {
String unixTimestamp = parser.getText().trim();
return new Date(TimeUnit.SECONDS.toMillis(Long.valueOf(unixTimestamp)));
}
}
Code to initialize and use object mapper:
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.configure(MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES, true);
objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
objectMapper.addMixIn(CodeTimestamp.class, StreamRecordMixIn.class);
CodeTimestamp codeTimeStamp = objectMapper.readValue(payload, CodeTimestamp.class);
Error:
Caused by: java.lang.IllegalArgumentException: Class com.test.TestConverter$UnixTimestampDeserializer has no default (no arg) constructor
at com.fasterxml.jackson.databind.util.ClassUtil.createInstance(ClassUtil.java:378)
at com.fasterxml.jackson.databind.deser.DefaultDeserializationContext.deserializerInstance(DefaultDeserializationContext.java:218)
at com.fasterxml.jackson.databind.deser.BasicDeserializerFactory.findDeserializerFromAnnotation(BasicDeserializerFactory.java:1735)
at com.fasterxml.jackson.databind.deser.BeanDeserializerFactory.constructSettableProperty(BeanDeserializerFactory.java:730)
at com.fasterxml.jackson.databind.deser.BeanDeserializerFactory.addBeanProps(BeanDeserializerFactory.java:507)
at com.fasterxml.jackson.databind.deser.BeanDeserializerFactory.buildBeanDeserializer(BeanDeserializerFactory.java:229)
at com.fasterxml.jackson.databind.deser.BeanDeserializerFactory.createBeanDeserializer(BeanDeserializerFactory.java:142)
at com.fasterxml.jackson.databind.deser.DeserializerCache._createDeserializer2(DeserializerCache.java:403)
at com.fasterxml.jackson.databind.deser.DeserializerCache._createDeserializer(DeserializerCache.java:352)
at com.fasterxml.jackson.databind.deser.DeserializerCache._createAndCache2(DeserializerCache.java:264)
... 23 more
The mistake here is custom deserializer not declared as static. So if I used it as mentioned below, it works.
public static class UnixTimestampDeserializer extends JsonDeserializer<Date> {
#Override
public Date deserialize(JsonParser parser, DeserializationContext context)
throws IOException, JsonProcessingException {
String unixTimestamp = parser.getText().trim();
return new Date(TimeUnit.SECONDS.toMillis(Long.valueOf(unixTimestamp)));
}
}
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
I created custom JsonDeserializer for that can be applied to any field with type String.
public class EmptyToNullStringDeserializer extends JsonDeserializer<String> {
#Override
public String deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException, JsonProcessingException {
String text = jp.getText();
return "" == text ? null : text;
}
}
It can be used in models.
class SomeClass {
#JsonDeserialize(using = EmptyToNullStringDeserializer.class)
private String someField;
}
It converts JSON
{"someField": ""}
into Java object where someField equals to null (not "")
Question: How to create generic JsonDeserializer that sets null to all Java object fields that equals to "" in JSON?
It should be used as:
#JsonDeserialize(using = EmptyToNullStringDeserializer.class)
class SomeClass {
private String someField;
}
This more of a Jackson question than a Spring question. You would just need to register your custom deserializer with the ObjectMapper...
SimpleModule module = new SimpleModule("MyModule", new Version(1, 0, 0, null));
module.addDeserializer(String.class, new EmptyToNullStringDeserializer());
mapper.registerModule(module);
How you get access to that ObjectMapper depends on if you are using Spring Boot or just plain Spring.
You need to register your custom deserializer with ObjectMapper