Search code examples
javaspring-bootjava-11json-deserializationzoneddatetime

Deserialize and serialize ZonedDateTime fields in shared objects between controller based on which controller it is used as a parameter for?


I have this Object, which has a field that is a ZonedDateTime:

import java.time.ZonedDateTime;

public class TestObject {

    private ZonedDateTime testTime;

    public ZonedDateTime getTestTime() {
        return testTime;
    }

    public void setTestTime(ZonedDateTime testTime) {
        this.testTime = testTime;
    }

}

And two different controller where endpoints use the same object:

@Controller
@RequestMapping("/web")
@Import(ControllerWebConfiguration.class)
public class ControllerWeb {

    @PostMapping("/example")
    public ResponseEntity<String> example(@RequestBody TestObject testObject) {
        return ResponseEntity.ok(testObject.toString());
    }
}
@Controller
@RequestMapping("/mobile")
@Import(ControllerMobileConfiguration.class)
public class ControllerMobile {

    @PostMapping("/example")
    public ResponseEntity<String> example(@RequestBody TestObject testObject) {
        return ResponseEntity.ok(testObject.toString());
    }
}

They both use slightly different "deserialize" for their ZonedDateTime:

public class ZonedDateTimeDeserializerWeb extends JsonDeserializer<ZonedDateTime> {

    @Override
    public ZonedDateTime deserialize(JsonParser jsonParser, DeserializationContext ctxt) throws IOException {

        ObjectCodec oc = jsonParser.getCodec();
        JsonNode node = oc.readTree(jsonParser);

        String dateTimeString = node.asText();

        return ZonedDateTime.parse(dateTimeString, DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ssX"));

    }
}
public class ZonedDateTimeDeserializerMobile extends JsonDeserializer<ZonedDateTime> {

    @Override
    public ZonedDateTime deserialize(JsonParser jsonParser, DeserializationContext ctxt) throws IOException {

        ObjectCodec oc = jsonParser.getCodec();
        JsonNode node = oc.readTree(jsonParser);

        String dateTimeString = node.asText();

        return ZonedDateTime.parse(dateTimeString, DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ssX"));

    }
}

@Configuration
public class ControllerWebConfiguration {

    @Bean
    public ObjectMapper objectMapperWeb() {
        ObjectMapper objectMapper = new ObjectMapper();

        SimpleModule moduleWeb = new SimpleModule();
        moduleWeb.addDeserializer(ZonedDateTime.class, new ZonedDateTimeDeserializerWeb());
        objectMapper.registerModule(moduleWeb);

        return objectMapper;
    }

}
@Configuration
public class ControllerMobileConfiguration {

    @Bean
    public ObjectMapper objectMapperMobile() {
        ObjectMapper objectMapper = new ObjectMapper();

        SimpleModule moduleMobile = new SimpleModule();
        moduleMobile.addDeserializer(ZonedDateTime.class, new ZonedDateTimeDeserializerMobile());
        objectMapper.registerModule(moduleMobile);

        return objectMapper;
    }

}

I have tried the above solution but Springboot does not allow two objectMapper beans; Is there any way to achieve what I want?

Thanks in advance!


Solution

  • As explained in this answer, under the hood Spring uses a single MessageConverter which in turn uses the primary ObjectMapper. So you can't just have multiple deserializer beans of the same class using the @Primary and @Qualifier annotations, as is shown in this Baeldung tutorial, as it will not work.

    You can use the solution provided in the linked answer, but I think it is not worth the hassle in your case. I would just replace the TestObject with an interface that defines the getter/setter, and implement the interface in a WebTestObject and a MobileTestObject, each one with a field annotated with an hint for Jackson, for example using @JsonSerialize or Spring @DateTimeFormat.

    public interface TestObject {
    
        ZonedDateTime getTestTime();
        void setTestTime(ZonedDateTime testTime);
    
    }
    
    @Getter @Setter
    public class WebTestObject implements TestObject {
    
        @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ssX")
        private ZonedDateTime testTime;
    
    }
    
    @Getter @Setter
    public class MobileTestObject implements TestObject {
    
        @DateTimeFormat(pattern = "yyyy-MM-dd'T'HH:mm:ssX")
        private ZonedDateTime testTime;
    
    }
    
    @Controller
    public class Controller {
    
        @PostMapping("/mobile/example")
        public ResponseEntity<String> mobileExample(@RequestBody MobileTestObject testObject) {
            //in your business logic you can then just use the TestObject interface
            return ResponseEntity.ok(testObject.toString());
        }
    
        @PostMapping("/web/example")
        public ResponseEntity<String> webExample(@RequestBody WebTestObject testObject) {
            //in your business logic you can then just use the TestObject interface
            return ResponseEntity.ok(testObject.toString());
        }
    }