Skip to content

Commit

Permalink
yoj-json-jackson-v2: Add JsonConverter implementation using Jackson
Browse files Browse the repository at this point in the history
  • Loading branch information
nvamelichev committed Jan 30, 2024
1 parent 8dc1364 commit 6357c09
Show file tree
Hide file tree
Showing 16 changed files with 267 additions and 116 deletions.
5 changes: 5 additions & 0 deletions bom/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,11 @@
<artifactId>yoj-repository-ydb-v2</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>tech.ydb.yoj</groupId>
<artifactId>yoj-json-jackson-v2</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>tech.ydb.yoj</groupId>
<artifactId>yoj-util</artifactId>
Expand Down
49 changes: 49 additions & 0 deletions json-jackson-v2/pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>

<artifactId>yoj-json-jackson-v2</artifactId>
<packaging>jar</packaging>

<parent>
<groupId>tech.ydb.yoj</groupId>
<artifactId>yoj-parent</artifactId>
<version>1.1.0-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>

<name>YOJ - JSON with Jackson 2.x</name>
<description>
Adds JSON support to YOJ (JsonConverter implementation) using Jackson 2.x as the underlying JSON library.
</description>

<dependencies>
<dependency>
<groupId>tech.ydb.yoj</groupId>
<artifactId>yoj-repository</artifactId>
</dependency>

<dependency>
<groupId>javax.annotation</groupId>
<artifactId>javax.annotation-api</artifactId>
</dependency>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatype-jdk8</artifactId>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatype-jsr310</artifactId>
</dependency>
</dependencies>
</project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
package tech.ydb.yoj.repository.db.json;

import com.fasterxml.jackson.annotation.JsonAutoDetect.Visibility;
import com.fasterxml.jackson.annotation.JsonInclude.Include;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.databind.module.SimpleModule;
import com.fasterxml.jackson.datatype.jdk8.Jdk8Module;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import lombok.NonNull;
import lombok.RequiredArgsConstructor;
import tech.ydb.yoj.repository.db.common.JsonConverter;
import tech.ydb.yoj.repository.db.exception.ConversionException;

import javax.annotation.Nullable;
import java.io.IOException;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TimeZone;
import java.util.function.UnaryOperator;

/**
* {@link JsonConverter YOJ JSON Converter} implementation using Jackson as the underlying JSON library.
* Use it to support JSON-valued fields ({@link tech.ydb.yoj.databind.schema.Column @Column(flatten=false)} composite
* objects and dynamic fields with type of interface/abstract class, e.g. {@link java.util.List}):
* <blockquote>
* <pre>
* CommonConverters.defineJsonConverter(JacksonJsonConverter.getDefault());
* </pre>
* </blockquote>
* <p><strong>Note that the {@code CommonConverters.defineJsonConverter()} configuration API is unstable and subject to
* change (and potential deprecation for removal.)
* </strong>
* <p>
* You can obtain an instance of {@link JacksonJsonConverter} in a number of ways:
* <ul>
* <li>To get {@link JacksonJsonConverter} which uses reasonable defaults, call {@link #getDefault()}.</li>
* <li>To customize these reasonable defaults, use the {@link #JacksonJsonConverter(UnaryOperator)} constructor:
* <blockquote>
* <pre>
* CommonConverters.defineJsonConverter(new JacksonJsonConverter(mapper -> mapper
* .set[...]()
* .registerModule(...)
* .configure(...)
* ));
* </pre>
* </blockquote>
* </li>
* <li>To supply an externally created {@link ObjectMapper}, use the {@link #JacksonJsonConverter(ObjectMapper)}
* constructor.</li>
* </ul>
*/
@RequiredArgsConstructor
public final class JacksonJsonConverter implements JsonConverter {
private static final JsonConverter instance = new JacksonJsonConverter(createDefaultObjectMapper());

private final ObjectMapper mapper;

public JacksonJsonConverter(UnaryOperator<ObjectMapper> mapperBuilder) {
this(mapperBuilder.apply(createDefaultObjectMapper()));
}

/**
* @return {@code JsonConverter} with reasonable defaults, using Jackson for JSON serialization and deserialization
*/
public static JsonConverter getDefault() {
return instance;
}

@Override
public String toJson(@NonNull Type type, @Nullable Object o) throws ConversionException {
try {
return mapper.writerFor(mapper.getTypeFactory().constructType(type)).writeValueAsString(o);
} catch (IOException e) {
throw new ConversionException("Could not serialize an object of type `" + type + "` to JSON", e);
}
}

@Override
public <T> T fromJson(@NonNull Type type, @NonNull String content) throws ConversionException {
try {
return mapper.readerFor(mapper.getTypeFactory().constructType(type)).readValue(content);
} catch (IOException e) {
throw new ConversionException("Could not deserialize an object of type `" + type + "` from JSON", e);
}
}

@Override
@SuppressWarnings("unchecked")
public <T> T fromObject(@NonNull Type type, @Nullable Object content) throws ConversionException {
try {
JavaType jacksonType = mapper.getTypeFactory().constructType(type);
return content != null
? mapper.convertValue(content, jacksonType)
: (T) (jacksonType.isCollectionLikeType() ? List.of() : Map.of());
} catch (Exception e) {
throw new ConversionException("Could not convert an object to type `" + type + "`", e);
}
}

public String toString() {
return "JacksonJsonConverter";
}

private static ObjectMapper createDefaultObjectMapper() {
ObjectMapper mapper = new ObjectMapper();
mapper.setTimeZone(TimeZone.getDefault());
mapper.registerModule(new Jdk8Module());
mapper.registerModule(new JavaTimeModule());
mapper.registerModule(new SimpleModule()
.addAbstractTypeMapping(Set.class, LinkedHashSet.class)
.addAbstractTypeMapping(Map.class, LinkedHashMap.class)
.addAbstractTypeMapping(List.class, ArrayList.class)
);
mapper.setSerializationInclusion(Include.NON_NULL);
mapper.configure(SerializationFeature.INDENT_OUTPUT, false);
mapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false);
mapper.configure(SerializationFeature.WRITE_DATE_KEYS_AS_TIMESTAMPS, false);
mapper.configure(SerializationFeature.WRITE_ENUMS_USING_TO_STRING, false);
mapper.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false);
mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
mapper.setVisibility(mapper.getSerializationConfig().getDefaultVisibilityChecker()
.withFieldVisibility(Visibility.ANY)
.withGetterVisibility(Visibility.NONE)
.withIsGetterVisibility(Visibility.NONE)
.withSetterVisibility(Visibility.NONE)
);
return mapper;
}
}
1 change: 1 addition & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
<modules>
<module>bom</module>
<module>databind</module>
<module>json-jackson-v2</module>
<module>repository</module>
<module>repository-test</module>
<module>repository-inmemory</module>
Expand Down
5 changes: 5 additions & 0 deletions repository-inmemory/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,11 @@
<artifactId>yoj-repository-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>tech.ydb.yoj</groupId>
<artifactId>yoj-json-jackson-v2</artifactId>
<scope>test</scope>
</dependency>
</dependencies>

<build>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,12 @@
import tech.ydb.yoj.repository.db.Table;
import tech.ydb.yoj.repository.db.TableQueryBuilder;
import tech.ydb.yoj.repository.db.TxOptions;
import tech.ydb.yoj.repository.db.common.CommonConverters;
import tech.ydb.yoj.repository.db.json.JacksonJsonConverter;
import tech.ydb.yoj.repository.test.sample.TestEntityOperations;
import tech.ydb.yoj.repository.test.sample.TestEntityOperations.BubbleTable;
import tech.ydb.yoj.repository.test.sample.TestEntityOperations.ComplexTable;
import tech.ydb.yoj.repository.test.sample.TestEntityOperations.IndexedTable;
import tech.ydb.yoj.repository.test.sample.TestJsonConverter;
import tech.ydb.yoj.repository.test.sample.model.Bubble;
import tech.ydb.yoj.repository.test.sample.model.Complex;
import tech.ydb.yoj.repository.test.sample.model.EntityWithValidation;
Expand All @@ -27,7 +28,7 @@

public class TestInMemoryRepository extends InMemoryRepository {
static {
TestJsonConverter.register();
CommonConverters.defineJsonConverter(JacksonJsonConverter.getDefault());
}

@Override
Expand Down

This file was deleted.

5 changes: 5 additions & 0 deletions repository-ydb-v1/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,11 @@
<artifactId>yoj-repository-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>tech.ydb.yoj</groupId>
<artifactId>yoj-json-jackson-v2</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,14 @@
import tech.ydb.yoj.repository.db.Table;
import tech.ydb.yoj.repository.db.TableQueryBuilder;
import tech.ydb.yoj.repository.db.TxOptions;
import tech.ydb.yoj.repository.db.common.CommonConverters;
import tech.ydb.yoj.repository.db.json.JacksonJsonConverter;
import tech.ydb.yoj.repository.db.statement.Changeset;
import tech.ydb.yoj.repository.test.sample.TestEntityOperations;
import tech.ydb.yoj.repository.test.sample.TestEntityOperations.BubbleTable;
import tech.ydb.yoj.repository.test.sample.TestEntityOperations.ComplexTable;
import tech.ydb.yoj.repository.test.sample.TestEntityOperations.IndexedTable;
import tech.ydb.yoj.repository.test.sample.TestEntityOperations.Supabubble2Table;
import tech.ydb.yoj.repository.test.sample.TestJsonConverter;
import tech.ydb.yoj.repository.test.sample.model.Bubble;
import tech.ydb.yoj.repository.test.sample.model.Complex;
import tech.ydb.yoj.repository.test.sample.model.EntityWithValidation;
Expand All @@ -34,7 +35,7 @@

public class TestYdbRepository extends YdbRepository {
static {
TestJsonConverter.register();
CommonConverters.defineJsonConverter(JacksonJsonConverter.getDefault());
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,9 @@
import tech.ydb.yoj.databind.schema.ObjectSchema;
import tech.ydb.yoj.databind.schema.Schema;
import tech.ydb.yoj.repository.db.Entity;
import tech.ydb.yoj.repository.db.common.CommonConverters;
import tech.ydb.yoj.repository.db.exception.ConversionException;
import tech.ydb.yoj.repository.test.sample.TestJsonConverter;
import tech.ydb.yoj.repository.db.json.JacksonJsonConverter;
import tech.ydb.yoj.repository.test.sample.model.NonDeserializableObject;
import tech.ydb.yoj.repository.ydb.yql.YqlPrimitiveType;
import tech.ydb.yoj.repository.ydb.yql.YqlType;
Expand All @@ -30,7 +31,7 @@
public class YqlTypeTest {
static {
FieldValueType.registerStringValueType(UUID.class);
TestJsonConverter.register();
CommonConverters.defineJsonConverter(JacksonJsonConverter.getDefault());
}

@Test
Expand Down
Loading

0 comments on commit 6357c09

Please sign in to comment.