diff --git a/pom.xml b/pom.xml
index 52ee17e..839fe0e 100644
--- a/pom.xml
+++ b/pom.xml
@@ -15,6 +15,130 @@
${java.version}
+
+
+
+
+ com.fasterxml.jackson
+ jackson-bom
+ 2.15.2
+ pom
+ import
+
+
+ org.springframework.boot
+ spring-boot-dependencies
+ pom
+ import
+ 3.1.0
+
+
+
+
+ org.slf4j
+ slf4j-api
+ 2.0.7
+
+
+ ch.qos.logback
+ logback-classic
+ 1.4.8
+
+
+ ch.qos.logback.contrib
+ logback-jackson
+ 0.1.5
+
+
+ ch.qos.logback.contrib
+ logback-json-classic
+ 0.1.5
+
+
+
+
+ jakarta.validation
+ jakarta.validation-api
+ 3.0.2
+
+
+ org.openapitools
+ jackson-databind-nullable
+ 0.2.6
+
+
+
+
+ org.assertj
+ assertj-core
+ 3.24.2
+ test
+
+
+
+
+
+
+
+ org.springframework.boot
+ spring-boot-starter-actuator
+
+
+ org.springframework.boot
+ spring-boot-starter-web
+
+
+
+
+ org.springframework.boot
+ spring-boot-starter-oauth2-resource-server
+
+
+ org.springframework.boot
+ spring-boot-starter-oauth2-client
+
+
+
+
+ org.slf4j
+ slf4j-api
+
+
+ ch.qos.logback
+ logback-classic
+
+
+ ch.qos.logback.contrib
+ logback-jackson
+
+
+ ch.qos.logback.contrib
+ logback-json-classic
+
+
+
+
+ jakarta.validation
+ jakarta.validation-api
+
+
+ org.openapitools
+ jackson-databind-nullable
+
+
+
+
+ org.springframework.boot
+ spring-boot-starter-test
+ test
+
+
+ org.assertj
+ assertj-core
+ test
+
+
+
@@ -36,6 +160,26 @@
true
+
+
+ org.apache.maven.plugins
+ maven-surefire-plugin
+ 3.1.2
+
+
+ org.springframework.boot
+ spring-boot-maven-plugin
+ 2.7.12
+
+
+
+ build-info
+ repackage
+
+
+
+
+
diff --git a/src/main/java/tk/dmanstrator/sb3_serialization_test/Application.java b/src/main/java/tk/dmanstrator/sb3_serialization_test/Application.java
new file mode 100644
index 0000000..4f30228
--- /dev/null
+++ b/src/main/java/tk/dmanstrator/sb3_serialization_test/Application.java
@@ -0,0 +1,13 @@
+package tk.dmanstrator.sb3_serialization_test;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+
+@SpringBootApplication
+public class Application {
+
+ public static void main(final String[] args) {
+ SpringApplication.run(Application.class, args);
+ }
+
+}
diff --git a/src/main/java/tk/dmanstrator/sb3_serialization_test/entity/CustomObject.java b/src/main/java/tk/dmanstrator/sb3_serialization_test/entity/CustomObject.java
new file mode 100644
index 0000000..ec4d503
--- /dev/null
+++ b/src/main/java/tk/dmanstrator/sb3_serialization_test/entity/CustomObject.java
@@ -0,0 +1,6 @@
+package tk.dmanstrator.sb3_serialization_test.entity;
+
+import java.io.Serializable;
+import java.util.List;
+
+public record CustomObject(String hello, List world) implements Serializable {}
diff --git a/src/main/java/tk/dmanstrator/sb3_serialization_test/exporter/EntityExporter.java b/src/main/java/tk/dmanstrator/sb3_serialization_test/exporter/EntityExporter.java
new file mode 100644
index 0000000..cb9fceb
--- /dev/null
+++ b/src/main/java/tk/dmanstrator/sb3_serialization_test/exporter/EntityExporter.java
@@ -0,0 +1,22 @@
+package tk.dmanstrator.sb3_serialization_test.exporter;
+
+import tk.dmanstrator.sb3_serialization_test.entity.CustomObject;
+import tk.dmanstrator.sb3_serialization_test.serialization.SerializationHandlerFactory;
+
+import java.io.IOException;
+
+/**
+ * Class responsible for exporting entities.
+ */
+public class EntityExporter {
+
+ private EntityExporter() {
+ // hide default constructor
+ }
+
+ public static String exportCustomObject(final CustomObject customObject) throws IOException {
+ return SerializationHandlerFactory.getDefaultHandlerWithPrettyPrint()
+ .writeObjectAsString(customObject);
+ }
+
+}
diff --git a/src/main/java/tk/dmanstrator/sb3_serialization_test/serialization/SerializationHandler.java b/src/main/java/tk/dmanstrator/sb3_serialization_test/serialization/SerializationHandler.java
new file mode 100644
index 0000000..de70cf1
--- /dev/null
+++ b/src/main/java/tk/dmanstrator/sb3_serialization_test/serialization/SerializationHandler.java
@@ -0,0 +1,22 @@
+package tk.dmanstrator.sb3_serialization_test.serialization;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.Serializable;
+
+public interface SerializationHandler {
+
+ boolean isBinary();
+
+ String getFileNameExtension();
+
+ void writeObject(final T object, final OutputStream os) throws IOException;
+
+ String writeObjectAsString(final T object) throws IOException;
+
+ T readObject(final Class clazz, final InputStream is) throws IOException;
+
+ T readObjectFromString(final Class clazz, final String input) throws IOException;
+
+}
diff --git a/src/main/java/tk/dmanstrator/sb3_serialization_test/serialization/SerializationHandlerFactory.java b/src/main/java/tk/dmanstrator/sb3_serialization_test/serialization/SerializationHandlerFactory.java
new file mode 100644
index 0000000..29d113a
--- /dev/null
+++ b/src/main/java/tk/dmanstrator/sb3_serialization_test/serialization/SerializationHandlerFactory.java
@@ -0,0 +1,20 @@
+package tk.dmanstrator.sb3_serialization_test.serialization;
+
+import tk.dmanstrator.sb3_serialization_test.serialization.impl.JsonSerializationHandler;
+
+public final class SerializationHandlerFactory {
+
+ private static final SerializationHandler DEFAULT_HANDLER = new JsonSerializationHandler(false);
+ private static final SerializationHandler DEFAULT_HANDLER_WITH_PRETTY_PRINT = new JsonSerializationHandler(true);
+
+ private SerializationHandlerFactory() {}
+
+ public static SerializationHandler getDefaultHandler() {
+ return DEFAULT_HANDLER;
+ }
+
+ public static SerializationHandler getDefaultHandlerWithPrettyPrint() {
+ return DEFAULT_HANDLER_WITH_PRETTY_PRINT;
+ }
+
+}
diff --git a/src/main/java/tk/dmanstrator/sb3_serialization_test/serialization/impl/AbstractJsonSerializationHandler.java b/src/main/java/tk/dmanstrator/sb3_serialization_test/serialization/impl/AbstractJsonSerializationHandler.java
new file mode 100644
index 0000000..9f516b9
--- /dev/null
+++ b/src/main/java/tk/dmanstrator/sb3_serialization_test/serialization/impl/AbstractJsonSerializationHandler.java
@@ -0,0 +1,101 @@
+package tk.dmanstrator.sb3_serialization_test.serialization.impl;
+
+import com.fasterxml.jackson.annotation.JsonAutoDetect.Visibility;
+import com.fasterxml.jackson.annotation.JsonInclude.Include;
+import com.fasterxml.jackson.annotation.PropertyAccessor;
+import com.fasterxml.jackson.databind.*;
+import com.fasterxml.jackson.databind.json.JsonMapper;
+import com.fasterxml.jackson.databind.jsontype.BasicPolymorphicTypeValidator;
+import com.fasterxml.jackson.databind.jsontype.PolymorphicTypeValidator;
+import com.fasterxml.jackson.datatype.jdk8.Jdk8Module;
+import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
+import tk.dmanstrator.sb3_serialization_test.serialization.SerializationHandler;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.Serializable;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+
+public abstract class AbstractJsonSerializationHandler implements SerializationHandler {
+
+ protected static final ThreadLocal OBJECT_MAPPER_THREAD_LOCAL =
+ ThreadLocal.withInitial(AbstractJsonSerializationHandler::newObjectMapper);
+
+ private final Map, ObjectReader> readers = new ConcurrentHashMap<>();
+ private final Map, ObjectWriter> writers = new ConcurrentHashMap<>();
+
+ private final boolean doPrettyPrint;
+
+ protected AbstractJsonSerializationHandler(final boolean doPrettyPrint) {
+ this.doPrettyPrint = doPrettyPrint;
+ }
+
+ @Override
+ public void writeObject(final T object, final OutputStream os) throws IOException {
+ ObjectWriter writer = getWriter(object.getClass());
+
+ if (doPrettyPrint) {
+ writer = writer.withDefaultPrettyPrinter();
+ }
+
+ writer.writeValue(os, object);
+ }
+
+ @Override
+ public String writeObjectAsString(final T object) throws IOException {
+ ObjectWriter writer = getWriter(object.getClass());
+
+ if (doPrettyPrint) {
+ writer = writer.withDefaultPrettyPrinter();
+ }
+
+ return writer.writeValueAsString(object);
+ }
+
+ @Override
+ public T readObject(final Class clazz, final InputStream is) throws IOException {
+ return getReader(clazz).readValue(is);
+ }
+
+ @Override
+ public T readObjectFromString(final Class clazz, final String input)
+ throws IOException {
+ return getReader(clazz).readValue(input);
+ }
+
+ private static ObjectMapper newObjectMapper() {
+ final PolymorphicTypeValidator ptv = BasicPolymorphicTypeValidator
+ .builder()
+ .allowIfBaseType(List.class)
+ .allowIfBaseType(Map.class)
+ .allowIfBaseType(Set.class)
+ .allowIfSubType(List.class)
+ .allowIfSubType(Map.class)
+ .build();
+ return JsonMapper.builder()
+ .enable(MapperFeature.BLOCK_UNSAFE_POLYMORPHIC_BASE_TYPES)
+ .activateDefaultTyping(ptv)
+ .addModule(new JavaTimeModule())
+ .addModule(new Jdk8Module())
+ .visibility(PropertyAccessor.ALL, Visibility.NONE)
+ .visibility(PropertyAccessor.FIELD, Visibility.ANY)
+ .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
+ .serializationInclusion(Include.NON_NULL)
+ .build();
+ }
+
+ protected abstract ObjectMapper createObjectMapper();
+
+ private ObjectReader getReader(final Class> clazz) {
+ return readers.computeIfAbsent(clazz, c -> OBJECT_MAPPER_THREAD_LOCAL.get().readerFor(c));
+ }
+
+ private ObjectWriter getWriter(final Class> clazz) {
+ return writers.computeIfAbsent(clazz, c -> OBJECT_MAPPER_THREAD_LOCAL.get().writerFor(c));
+ }
+
+}
diff --git a/src/main/java/tk/dmanstrator/sb3_serialization_test/serialization/impl/JsonSerializationHandler.java b/src/main/java/tk/dmanstrator/sb3_serialization_test/serialization/impl/JsonSerializationHandler.java
new file mode 100644
index 0000000..cd749e2
--- /dev/null
+++ b/src/main/java/tk/dmanstrator/sb3_serialization_test/serialization/impl/JsonSerializationHandler.java
@@ -0,0 +1,42 @@
+package tk.dmanstrator.sb3_serialization_test.serialization.impl;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+
+public class JsonSerializationHandler extends AbstractJsonSerializationHandler {
+
+ /**
+ * Creates a new JSON Serializer. Won't apply pretty-printing when writing objects.
+ */
+ public JsonSerializationHandler() {
+ this(false);
+ }
+
+ /**
+ * Creates a new JSON Serializer.
+ *
+ * @param doPrettyPrint Flag to tell whether pretty-printing should be performed when writing objects or not.
+ */
+ public JsonSerializationHandler(final boolean doPrettyPrint) {
+ super(doPrettyPrint);
+ }
+
+ @Override
+ public boolean isBinary() {
+ return false;
+ }
+
+ @Override
+ public String getFileNameExtension() {
+ return ".json";
+ }
+
+ public static ObjectMapper getStandardObjectMapper() {
+ return OBJECT_MAPPER_THREAD_LOCAL.get();
+ }
+
+ @Override
+ protected ObjectMapper createObjectMapper() {
+ return new ObjectMapper();
+ }
+
+}
diff --git a/src/test/java/tk/dmanstrator/sb3_serialization_test/exporter/EntityExporterTest.java b/src/test/java/tk/dmanstrator/sb3_serialization_test/exporter/EntityExporterTest.java
new file mode 100644
index 0000000..fccee60
--- /dev/null
+++ b/src/test/java/tk/dmanstrator/sb3_serialization_test/exporter/EntityExporterTest.java
@@ -0,0 +1,23 @@
+package tk.dmanstrator.sb3_serialization_test.exporter;
+
+import org.assertj.core.api.Assertions;
+import org.junit.jupiter.api.Test;
+import tk.dmanstrator.sb3_serialization_test.entity.CustomObject;
+
+import java.io.IOException;
+import java.util.Arrays;
+
+class EntityExporterTest {
+
+ private static final String EMPTY_JSON = "{ }";
+
+ @Test
+ void testExportCustomObject() throws IOException {
+ final CustomObject customObject = new CustomObject("hello", Arrays.asList("wor", "ld"));
+ final String json = EntityExporter.exportCustomObject(customObject);
+ Assertions.assertThat(json)
+ .isNotEmpty()
+ .isNotEqualTo(EMPTY_JSON);
+ }
+
+}
\ No newline at end of file