Spring Boot Cannot Deserialize Object That contanis OffsetDateTime - java

I'm trying to call a rest endpoint which returns a pojo object which looks like this:
public class Process {
#JsonProperty("id")
private String id = null;
#JsonProperty("processDefinitionId")
private String processDefinitionId = null;
#JsonProperty("businessKey")
private String businessKey = null;
#JsonProperty("startedAt")
private OffsetDateTime startedAt = null;
#JsonProperty("endedAt")
private OffsetDateTime endedAt = null;
#JsonProperty("durationInMs")
private Integer durationInMs = null;
#JsonProperty("startActivityDefinitionId")
private String startActivityDefinitionId = null;
#JsonProperty("endActivityDefinitionId")
private String endActivityDefinitionId = null;
#JsonProperty("startUserId")
private String startUserId = null;
#JsonProperty("deleteReason")
private String deleteReason = null;
//constructors and setters+getters
}
Here is the call:
ResponseEntity<Process> responseModel = restTemplate.exchange("http://localhost:8062/processes", HttpMethod.POST, httpEntity, Process.class);
The problem is that i've tried a few methods like ignoring the OffsetDateTime properties or trying to change the format of that date but it will throw this error:
com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Cannot construct instance of `org.threeten.bp.OffsetDateTime` (no Creators, like default construct, exist): no String-argument constructor/factory method to deserialize from String value ('2019-10-04T13:20:29.315Z')
Or it will return null :(
What would be a good solution to solve this?

The error states it cannot construct instance of org.threeten.bp.OffsetDateTime. You need to use
java.time.offsetdatetime
Then in your model you can format it whatever way you like e.g.
#JsonProperty("endedAt") //this line is not needed when it is the same as the instance variable name
#JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd#HH:mm:ss.SSSZ")
private OffsetDateTime endedAt;

I had the same problem with a bean generated by swagger. To solve it I created some serializer and deserializer for date types: org.threeten.bp.LocalDate and org.threeten.bp.OffsetDateTime. And it works well :).
#Bean
#Primary
public ObjectMapper serializingObjectMapper() {
ObjectMapper objectMapper = new ObjectMapper();
JavaTimeModule javaTimeModule = new JavaTimeModule();
javaTimeModule.addSerializer(OffsetDateTime.class, new OffsetDateTimeSerializer());
javaTimeModule.addDeserializer(OffsetDateTime.class, new OffsetDateTimeDeserializer());
javaTimeModule.addSerializer(LocalDate.class, new LocalDateSerializer());
javaTimeModule.addDeserializer(LocalDate.class, new LocalDateDeserializer());
objectMapper.registerModule(javaTimeModule);
return objectMapper;
}
public static class OffsetDateTimeSerializer extends JsonSerializer<OffsetDateTime> {
#Override
public void serialize(OffsetDateTime arg0, JsonGenerator arg1, SerializerProvider arg2) throws IOException {
arg1.writeString(arg0.toString());
}
}
public static class OffsetDateTimeDeserializer extends JsonDeserializer<OffsetDateTime> {
#Override
public OffsetDateTime deserialize(JsonParser arg0, DeserializationContext arg1) throws IOException {
return OffsetDateTime.parse(arg0.getText());
}
}
public static class LocalDateSerializer extends JsonSerializer<LocalDate> {
#Override
public void serialize(LocalDate arg0, JsonGenerator arg1, SerializerProvider arg2) throws IOException {
arg1.writeString(arg0.toString());
}
}
public static class LocalDateDeserializer extends JsonDeserializer<LocalDate> {
#Override
public LocalDate deserialize(JsonParser arg0, DeserializationContext arg1) throws IOException {
return LocalDate.parse(arg0.getText());
}
}

Related

Junit test : Properties value are not resolved in Deserializer class

I'm writing some junit tests for my rest webservice. When I run my test, I got my response from the webservice. So I use objectMapper to map the json to my POJO class. However it fails on this line :
return objectMapper.readValue(json, clazz);
Error : com.fasterxml.jackson.databind.JsonMappingException: zoneId (through reference chain: com.customer.CustomerResponse["customerDetails"]->com.customer.CustomerDetails["licenseDate"])
This is because I'm using this annotation in my POJO class : #JsonDeserialize(using = CustomDateDeserializer.class) and in the Deserializer, I have a variable timezone , where the value is taken from properties file. It seems that the object Mapper is not able to resolve this properties in my Deserializer class :
#Value("${current.timezone}")
private String timezone;
Can you please help how to resolve this issue ? Thanks
#Transactional
#Rollback
class CustomerServiceIntegrationTest {
#Autowired
private DbUnitUtils dbUnitUtils;
protected MockMvc mvc;
#BeforeEach
void setup() throws Exception {
mvc = MockMvcBuilders.webAppContextSetup(webApplicationContext).build();
}
#BeforeEach
void setupData() throws Exception {
dbUnitUtils.initialiseDataSet(new File("src/test/resources/dbunit/test-dataset.xml"),
DatabaseOperation.CLEAN_INSERT);
}
#Test
void listCustomer_WhenIdExists_Then_ReturnCustomerInfo() throws Exception {
/*
* setup test-data
*/
Long idCustomer= 15L;
String uri = "/customer/" + 15L;
/*
* invoke endpoint being tested
*/
MvcResult mvcResult = mvc.perform(MockMvcRequestBuilders.get(uri).accept(MediaType.APPLICATION_JSON_VALUE))
.andReturn();
/*
* Verification
*/
int status = mvcResult.getResponse().getStatus();
assertEquals(200, status);
CustomerResponse customerResponse =
mapFromJson(mvcResult.getResponse().getContentAsString(), CustomerResponse.class);
assertEquals(idCustomer, customerResponse.getId());
}
protected <T> T mapFromJson(String json, Class<T> clazz)
throws JsonParseException, JsonMappingException, IOException {
ObjectMapper objectMapper = new ObjectMapper();
SimpleModule module =
new SimpleModule("CustomDateDeserializer", new Version(1, 0, 0, null, null, null));
module.addDeserializer(ZonedDateTime.class, new CustomDateDeserializer());
objectMapper.registerModule(module);
return objectMapper.readValue(json, clazz);
}
}
public class CustomerResponse{
String name;
String address;
String phoneNo;
CustomerDetails customerDetails;
//getter and setter
}
public class CustomerDetails {
#JsonDeserialize(using = CustomDateDeserializer.class)
#JsonFormat(shape = JsonFormat.Shape.STRING, pattern = Utils.FORMAT_DATE)
private ZonedDateTime licenseDate;
//getter and setters
}
public class CustomDateDeserializer extends StdDeserializer<ZonedDateTime> {
#Serial
private static final long serialVersionUID = 1L;
#Value("${current.timezone}")
private String timezone;
public CustomDateDeserializer () {
super((Class<?>) null);
}
protected CustomDateDeserializer (Class<?> vc) {
super(vc);
}
#Override
public ZonedDateTime deserialize(JsonParser p, DeserializationContext ctxt) throws
IOException {
String stringValue = p.getCodec().readValue(p, String.class);
if (StringUtils.isBlank(stringValue)) {
return null;
}
LocalDate localDate = LocalDate.parse(stringValue);
return localDate.atStartOfDay().atZone(ZoneId.of(timezone));
}
}
}

Deserialize multiple json fields into single java property

I want to convert a json into Java class by having custom deserializer.
I'm able to serialize ACC_NUM, NAME and any other fields from json but not sure what can be done to convert MOBILE_NUMBER_1,MOBILE_NUMBER_2 like fields into single JSONArray(See AccountInfo class). There can be many more fields like this and count also is not fixed. Example there can be ADDRESS_1, ADDRESS_2 till ADDRESS_20 and so on and all this fields should go in JSONArray of ADDRESS after deserilization.
I have a Map of Map which holds info like this:
{
"accountInfo": {
"ACC_NUM": "1234567890",
"NAME": "John Cena",
"MOBILE_NUMBER_1": "12376534",
"MOBILE_NUMBER_2": "12376534",
"MOBILE_NUMBER_3": "12376534",
"MOBILE_NUMBER_4": "12376534"
},
"someOther": {
//similer to above
}
}
This info I want to convert to the following class CommonInfo:
public class CommonInfo {
private AccountInfo accountInfo;
//other properties...
}
#JsonIgnoreProperties(ignoreUnknown = true)
public class AccountInfo {
#JsonProperty("ACC_NUM")
private FieldValue<BigInteger> accountNum;
#JsonProperty("NAME")
private FieldValue<String> name;
#JsonProperty("MOBILE_NUMBER")
private FieldValue<JSONArray> mobileNumber;
}
//FieldValue class
public interface FieldValue<T> {
T getInitialValue();
void setInitialValue(T initialValue);
T getValue();
void setValue(T value);
}
#JsonInclude(JsonInclude.Include.ALWAYS)
public class FieldValueImpl<T> implements FieldValue<T> {
protected T initialValue;
protected T value;
//getters, setters, cons..
}
My service code takes json/Map and tries to convert it to CommonInfo class
#Service
public class MyService {
private final ObjectMapper jsonMapper = new ObjectMapper();
#PostConstruct
protected void init() {
SimpleModule simpleModule = new SimpleModule();
simpleModule.addSerializer(FieldValue.class, new FieldValueSerializer());
simpleModule.addDeserializer(FieldValue.class, new FieldValueDeserializer());
jsonMapper.configure(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY, true);
jsonMapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false);
jsonMapper.registerModule(simpleModule);
}
public CommonInfo setPojoResult(Map<String, LinkedHashMap<String, String>> contentAsMap) {
return jsonMapper.convertValue(contentAsMap, CommonInfo.class);
}
}
Serializer and Deserializer looks like this:
public class FieldValueDeserializer extends JsonDeserializer<FieldValue<?>> implements ContextualDeserializer {
private JavaType valueType;
public JsonDeserializer<?> createContextual(DeserializationContext ctxt, BeanProperty property)
throws JsonMappingException {
var deserializer = new FieldValueDeserializer();
if (property == null) {
deserializer.valueType = ctxt.getContextualType().containedType(0);
} else {
var wrapperType = property.getType();
var valueType = wrapperType.containedType(0);
deserializer.valueType = valueType;
}
return deserializer;
}
#Override
public FieldValue<?> deserialize(JsonParser parser, DeserializationContext context) throws IOException {
FieldValueDeserializer deserializer = new FieldValueDeserializer();
deserializer.getKnownPropertyNames();
FieldValue<?> fieldValueImpl = new FieldValueImpl<>();
if (valueType.toString().contains("java.time.LocalDate")) {
JsonNode node = parser.getCodec().readTree(parser);
FieldValue<LocalDate> f1 = new FieldValueImpl<>();
f1.setValue(DateUtils.convertJulianToLocalDate(node.textValue()));
return f1;
} else {
fieldValueImpl.setValue(context.readValue(parser, valueType));
}
return fieldValueImpl;
}
}
//--
public class FieldValueSerializer extends StdSerializer<FieldValue> {
public FieldValueSerializer() {
this(null);
}
public FieldValueSerializer(Class<FieldValue> vc) {
super(vc);
}
#Override
public void serialize(FieldValue value, JsonGenerator jgen, SerializerProvider provider) throws IOException {
jgen.writeString(String.valueOf(value.getCurValue()));
}
}

How to remove a field from POJO

public class user {
private String planId;
private String eid;
#JsonProperty(access = JsonProperty.Access.WRITE_ONLY)
private Collection<String> userIds;
}
I have a pojo like above
and the code which is use for creating the json request object is this :
public UserCollection getUserCollection(final user args) {
Map<String, String> headersMap = new HashMap<>();
ObjectMapper jacksonMapper = new ObjectMapper();
jacksonMapper.disable(MapperFeature.USE_ANNOTATIONS); // this line is creating the userIds field to reflect
//By any code we can remove the userId field from the args object
String responseBody = null;
String responseStatus = null;
String jsonRequestBody = jacksonMapper.writeValueAsString(args);
}
I just want to remove userIds from the args by not removing any of the above code.
Thanks in advance.
I don't know how you should solve this without removing the annotation processing code, you should maybe add a custom serializer. You can read further about the topic at here.
public class HelloWorld {
public static void main(String[] args) throws JsonProcessingException {
User myItem = new User("planId", "eId", List.of("1", "2"));
ObjectMapper mapper = new ObjectMapper();
SimpleModule module = new SimpleModule();
module.addSerializer(User.class, new UserSerializer());
mapper.registerModule(module);
String serialized = mapper.writeValueAsString(myItem);
}
#Data
#AllArgsConstructor
public static class User {
private String planId;
private String eId;
private Collection<String> userIds;
}
public static class UserSerializer extends StdSerializer<User> {
public UserSerializer() {
this(null);
}
public UserSerializer(Class<User> t) {
super(t);
}
#Override
public void serialize(User value, JsonGenerator gen, SerializerProvider provider) throws IOException{
gen.writeStartObject();
gen.writeStringField("planId", value.planId);
gen.writeStringField("eId", value.eId);
gen.writeEndObject();
}
}
}
If you had to use it without annotations, and cannot add a custom serializer you should map the User class into a more specific class without the field in question and then serialize that one.

Java Jackson: why do I get JsonMappingException although I used #JsonDeserialize

I need some help with Jackson library in Java. I have the following class:
#JsonIgnoreProperties(ignoreUnknown = true)
public class MarketHistoryData {
private String countryCode;
#JsonDeserialize(keyUsing = BpTimeDeserializer.class)
private Map<BpTime, Double> hourToExpectedEngagePaidListings;
#JsonDeserialize(keyUsing = BpTimeDeserializer.class)
private Map<BpTime, Double> hourMinuteToExpectedEngagePaidListings;
public MarketHistoryData() {}
...
// getters and setters
}
I understood that Jackson has difficulties with deserializing a map which its keys are an object. Therefore, I added the annotations:
#JsonDeserialize(keyUsing = BpTimeDeserializer.class)
The class BpTimeDeserializer is:
public class BpTimeDeserializer extends KeyDeserializer {
private ObjectMapper mapper = new ObjectMapper();
#Override
public BpTime deserializeKey(String key, DeserializationContext ctxt) throws IOException {
return mapper.readValue(key, BpTime.class);
}
}
Still, I get an error during the deserialization process:
*****.UncheckedExecutionException:
com.fasterxml.jackson.databind.JsonMappingException: Unrecognized
token 'com': was expecting (JSON String, Number, Array, Object or
token 'null', 'true' or 'false') at [Source:
(String)"****BpTime#5922062e[hour=1,minute=0]";
line: 1, column: 1] (through reference chain:
***PacerCycleOutput["campaignIdToFactorComputationData"]->java.util.LinkedHashMap["1011620661"]->****FactorComputationData["selectedMarketHistoryDataForCampaign"]->***MarketHistoryData["hourToExpectedEngagePaidListings"])
Do you know what I can do to overcome this error?
I finally found a way how to resolve this error.
#JsonIgnoreProperties(ignoreUnknown = true)
public class MarketHistoryData {
private String countryCode;
#JsonDeserialize(keyUsing = BpTimeDeserializer.class)
#JsonSerialize(keyUsing = BpTimeSerializer.class)
private Map<BpTime, Double> hourToExpectedEngagePaidListings;
#JsonDeserialize(keyUsing = BpTimeDeserializer.class)
#JsonSerialize(keyUsing = BpTimeSerializer.class)
private Map<BpTime, Double> hourMinuteToExpectedEngagePaidListings;
...
}
public class BpTimeDeserializer extends KeyDeserializer {
private ObjectMapper mapper = new ObjectMapper();
#Override
public BpTime deserializeKey(String key, DeserializationContext ctxt) throws IOException {
String[] keySplit = key.split(" ");
BpTime bpTime = new BpTime();
if (!keySplit[0].equals("-1")) {
bpTime.setHour(Integer.parseInt(keySplit[0]));
}
if (!keySplit[1].equals("-1")) {
bpTime.setMinute(Integer.parseInt(keySplit[1]));
}
return bpTime;
}
}
public class BpTimeSerializer extends StdSerializer<BpTime> {
public BpTimeSerializer() {
this(null);
}
public BpTimeSerializer(Class<BpTime> t) {
super(t);
}
#Override
public void serialize(BpTime value, JsonGenerator generator, SerializerProvider arg2) throws IOException {
generator.writeFieldName(String.format("%s %s", ofNullable(value.getHour()).orElse(-1),
ofNullable(value.getMinute()).orElse(-1)));
}
}

Serialize/Deserialize custom Map<Key, Object> in Jackson

I have a pretty simple Map I want to serialize and deserialize in Jackson, but I can't get it to work.
I have tried the following:
#JsonSerialize(keyUsing=TurnKeySerializer.class)
#JsonDeserialize(keyUsing = TurnKeyDeserializer.class)
Map<TurnKey, PlayerTurn> publicTurns = new TreeMap<>();
#JsonIgnoreProperties(ignoreUnknown = true)
#Data //Creates Getter/Setter etc
public class TurnKey implements Comparable<TurnKey> {
private final int turnNumber;
private final String username;
public TurnKey(int turnNumber, String username) {
this.turnNumber = turnNumber;
this.username = username;
}
#Override
public int compareTo(TurnKey o) {
int v = Integer.valueOf(turnNumber).compareTo(o.getTurnNumber());
if (v != 0) {
return v;
}
return username.compareTo(o.getUsername());
}
#Override
public String toString() {
return "{" +
"turnNumber:" + turnNumber +
", username:'" + username + "'" +
"}";
}
public class TurnKeySerializer extends JsonSerializer<TurnKey> {
#Override
public void serialize(TurnKey value, JsonGenerator gen, SerializerProvider serializers) throws IOException, JsonProcessingException {
if (null == value) {
throw new IOException("Could not serialize object to json, input object to serialize is null");
}
StringWriter writer = new StringWriter();
ObjectMapper mapper = new ObjectMapper();
mapper.writeValue(writer, value);
gen.writeFieldName(writer.toString());
}
}
public class TurnKeyDeserializer extends KeyDeserializer {
private static final ObjectMapper mapper = new ObjectMapper();
#Override
public TurnKey deserializeKey(String key, DeserializationContext ctxt) throws IOException, JsonProcessingException {
return mapper.readValue(key, TurnKey.class);
}
}
But I get an exception
com.fasterxml.jackson.databind.JsonMappingException: Can not deserialize instance of java.util.LinkedHashMap out of START_ARRAY token
You need to define/override the fromString() method in TurnKey. Jackson will use toString() to serialize and fromString() to deserialize. That's what "Can not find a (Map) Key deserializer" means in the error message Caused by: com.fasterxml.jackson.databind.JsonMappingException: Can not find a (Map) Key deserializer for type [simple type, class no.asgari.civilization.server.model.TurnKey] at com.fasterxml.jackson.databind.deser.DeserializerCache._handleUnknownKeyDeserializer(DeserializerCache.java:584).
A custom KeyDeserializer is not needed.

Categories