diff --git a/build.gradle b/build.gradle index b533db0..0c6286a 100644 --- a/build.gradle +++ b/build.gradle @@ -8,7 +8,7 @@ plugins { } group = 'com.flexcodelabs' -version = '0.0.29' +version = '0.0.30' description = 'Flextuma App' java { diff --git a/src/main/java/com/flexcodelabs/flextuma/core/config/JacksonConfig.java b/src/main/java/com/flexcodelabs/flextuma/core/config/JacksonConfig.java index 1be9ec8..94e9177 100644 --- a/src/main/java/com/flexcodelabs/flextuma/core/config/JacksonConfig.java +++ b/src/main/java/com/flexcodelabs/flextuma/core/config/JacksonConfig.java @@ -5,6 +5,7 @@ import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.SerializationFeature; import com.fasterxml.jackson.databind.json.JsonMapper; +import com.fasterxml.jackson.databind.module.SimpleModule; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Primary; @@ -15,12 +16,16 @@ public class JacksonConfig { @Bean @Primary public ObjectMapper objectMapper() { + SimpleModule lazyLoadingSafeModule = new SimpleModule() + .setSerializerModifier(new LazyLoadingSafeBeanSerializerModifier()); + return JsonMapper.builder() .serializationInclusion(JsonInclude.Include.NON_NULL) .disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS) .disable(SerializationFeature.FAIL_ON_EMPTY_BEANS) .disable(DeserializationFeature.FAIL_ON_NULL_FOR_PRIMITIVES) + .addModule(lazyLoadingSafeModule) .findAndAddModules() .build(); } -} \ No newline at end of file +} diff --git a/src/main/java/com/flexcodelabs/flextuma/core/config/LazyLoadingSafeBeanSerializerModifier.java b/src/main/java/com/flexcodelabs/flextuma/core/config/LazyLoadingSafeBeanSerializerModifier.java new file mode 100644 index 0000000..a25d055 --- /dev/null +++ b/src/main/java/com/flexcodelabs/flextuma/core/config/LazyLoadingSafeBeanSerializerModifier.java @@ -0,0 +1,62 @@ +package com.flexcodelabs.flextuma.core.config; + +import java.util.List; + +import org.hibernate.LazyInitializationException; + +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.databind.BeanDescription; +import com.fasterxml.jackson.databind.JsonMappingException; +import com.fasterxml.jackson.databind.SerializationConfig; +import com.fasterxml.jackson.databind.SerializerProvider; +import com.fasterxml.jackson.databind.ser.BeanPropertyWriter; +import com.fasterxml.jackson.databind.ser.BeanSerializerModifier; + +public class LazyLoadingSafeBeanSerializerModifier extends BeanSerializerModifier { + + @Override + public List changeProperties(SerializationConfig config, BeanDescription beanDesc, + List beanProperties) { + return beanProperties.stream() + .map(LazyLoadingSafePropertyWriter::new) + .map(BeanPropertyWriter.class::cast) + .toList(); + } + + private static final class LazyLoadingSafePropertyWriter extends BeanPropertyWriter { + + private LazyLoadingSafePropertyWriter(BeanPropertyWriter base) { + super(base); + } + + @Override + public void serializeAsField(Object bean, JsonGenerator gen, SerializerProvider prov) throws Exception { + try { + super.serializeAsField(bean, gen, prov); + } catch (Exception ex) { + if (!isLazyLoadingFailure(ex)) { + throw ex; + } + + if (!gen.canOmitFields()) { + super.serializeAsOmittedField(bean, gen, prov); + } + } + } + + private boolean isLazyLoadingFailure(Throwable throwable) { + Throwable current = throwable; + while (current != null) { + if (current instanceof LazyInitializationException) { + return true; + } + if (current instanceof JsonMappingException jsonMappingException) { + current = jsonMappingException.getCause(); + continue; + } + current = current.getCause(); + } + return false; + } + } +} diff --git a/src/test/java/com/flexcodelabs/flextuma/core/config/JacksonConfigTest.java b/src/test/java/com/flexcodelabs/flextuma/core/config/JacksonConfigTest.java index eb5d17c..fe5fca3 100644 --- a/src/test/java/com/flexcodelabs/flextuma/core/config/JacksonConfigTest.java +++ b/src/test/java/com/flexcodelabs/flextuma/core/config/JacksonConfigTest.java @@ -4,6 +4,7 @@ import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.SerializationFeature; +import org.hibernate.LazyInitializationException; import org.junit.jupiter.api.Test; import java.time.LocalDateTime; @@ -68,4 +69,22 @@ void objectMapper_shouldSerializeEmptyBeanWithoutError() throws JsonProcessingEx String json = objectMapper.writeValueAsString(bean); assertEquals("{}", json); } + + static class LazyBean { + public String getName() { + return "test"; + } + + public Object getLazyRelation() { + throw new LazyInitializationException("no session"); + } + } + + @Test + void objectMapper_shouldSkipLazyFieldsThatCannotBeInitialized() throws JsonProcessingException { + String json = objectMapper.writeValueAsString(new LazyBean()); + + assertTrue(json.contains("\"name\":\"test\"")); + assertFalse(json.contains("lazyRelation")); + } }