Skip to content

Commit 6357c09

Browse files
committed
yoj-json-jackson-v2: Add JsonConverter implementation using Jackson
1 parent 8dc1364 commit 6357c09

File tree

16 files changed

+267
-116
lines changed

16 files changed

+267
-116
lines changed

bom/pom.xml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,11 @@
137137
<artifactId>yoj-repository-ydb-v2</artifactId>
138138
<version>${project.version}</version>
139139
</dependency>
140+
<dependency>
141+
<groupId>tech.ydb.yoj</groupId>
142+
<artifactId>yoj-json-jackson-v2</artifactId>
143+
<version>${project.version}</version>
144+
</dependency>
140145
<dependency>
141146
<groupId>tech.ydb.yoj</groupId>
142147
<artifactId>yoj-util</artifactId>

json-jackson-v2/pom.xml

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<project xmlns="http://maven.apache.org/POM/4.0.0"
3+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
4+
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
5+
<modelVersion>4.0.0</modelVersion>
6+
7+
<artifactId>yoj-json-jackson-v2</artifactId>
8+
<packaging>jar</packaging>
9+
10+
<parent>
11+
<groupId>tech.ydb.yoj</groupId>
12+
<artifactId>yoj-parent</artifactId>
13+
<version>1.1.0-SNAPSHOT</version>
14+
<relativePath>../pom.xml</relativePath>
15+
</parent>
16+
17+
<name>YOJ - JSON with Jackson 2.x</name>
18+
<description>
19+
Adds JSON support to YOJ (JsonConverter implementation) using Jackson 2.x as the underlying JSON library.
20+
</description>
21+
22+
<dependencies>
23+
<dependency>
24+
<groupId>tech.ydb.yoj</groupId>
25+
<artifactId>yoj-repository</artifactId>
26+
</dependency>
27+
28+
<dependency>
29+
<groupId>javax.annotation</groupId>
30+
<artifactId>javax.annotation-api</artifactId>
31+
</dependency>
32+
<dependency>
33+
<groupId>com.google.guava</groupId>
34+
<artifactId>guava</artifactId>
35+
</dependency>
36+
<dependency>
37+
<groupId>com.fasterxml.jackson.core</groupId>
38+
<artifactId>jackson-databind</artifactId>
39+
</dependency>
40+
<dependency>
41+
<groupId>com.fasterxml.jackson.datatype</groupId>
42+
<artifactId>jackson-datatype-jdk8</artifactId>
43+
</dependency>
44+
<dependency>
45+
<groupId>com.fasterxml.jackson.datatype</groupId>
46+
<artifactId>jackson-datatype-jsr310</artifactId>
47+
</dependency>
48+
</dependencies>
49+
</project>
Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
package tech.ydb.yoj.repository.db.json;
2+
3+
import com.fasterxml.jackson.annotation.JsonAutoDetect.Visibility;
4+
import com.fasterxml.jackson.annotation.JsonInclude.Include;
5+
import com.fasterxml.jackson.databind.DeserializationFeature;
6+
import com.fasterxml.jackson.databind.JavaType;
7+
import com.fasterxml.jackson.databind.ObjectMapper;
8+
import com.fasterxml.jackson.databind.SerializationFeature;
9+
import com.fasterxml.jackson.databind.module.SimpleModule;
10+
import com.fasterxml.jackson.datatype.jdk8.Jdk8Module;
11+
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
12+
import lombok.NonNull;
13+
import lombok.RequiredArgsConstructor;
14+
import tech.ydb.yoj.repository.db.common.JsonConverter;
15+
import tech.ydb.yoj.repository.db.exception.ConversionException;
16+
17+
import javax.annotation.Nullable;
18+
import java.io.IOException;
19+
import java.lang.reflect.Type;
20+
import java.util.ArrayList;
21+
import java.util.LinkedHashMap;
22+
import java.util.LinkedHashSet;
23+
import java.util.List;
24+
import java.util.Map;
25+
import java.util.Set;
26+
import java.util.TimeZone;
27+
import java.util.function.UnaryOperator;
28+
29+
/**
30+
* {@link JsonConverter YOJ JSON Converter} implementation using Jackson as the underlying JSON library.
31+
* Use it to support JSON-valued fields ({@link tech.ydb.yoj.databind.schema.Column @Column(flatten=false)} composite
32+
* objects and dynamic fields with type of interface/abstract class, e.g. {@link java.util.List}):
33+
* <blockquote>
34+
* <pre>
35+
* CommonConverters.defineJsonConverter(JacksonJsonConverter.getDefault());
36+
* </pre>
37+
* </blockquote>
38+
* <p><strong>Note that the {@code CommonConverters.defineJsonConverter()} configuration API is unstable and subject to
39+
* change (and potential deprecation for removal.)
40+
* </strong>
41+
* <p>
42+
* You can obtain an instance of {@link JacksonJsonConverter} in a number of ways:
43+
* <ul>
44+
* <li>To get {@link JacksonJsonConverter} which uses reasonable defaults, call {@link #getDefault()}.</li>
45+
* <li>To customize these reasonable defaults, use the {@link #JacksonJsonConverter(UnaryOperator)} constructor:
46+
* <blockquote>
47+
* <pre>
48+
* CommonConverters.defineJsonConverter(new JacksonJsonConverter(mapper -> mapper
49+
* .set[...]()
50+
* .registerModule(...)
51+
* .configure(...)
52+
* ));
53+
* </pre>
54+
* </blockquote>
55+
* </li>
56+
* <li>To supply an externally created {@link ObjectMapper}, use the {@link #JacksonJsonConverter(ObjectMapper)}
57+
* constructor.</li>
58+
* </ul>
59+
*/
60+
@RequiredArgsConstructor
61+
public final class JacksonJsonConverter implements JsonConverter {
62+
private static final JsonConverter instance = new JacksonJsonConverter(createDefaultObjectMapper());
63+
64+
private final ObjectMapper mapper;
65+
66+
public JacksonJsonConverter(UnaryOperator<ObjectMapper> mapperBuilder) {
67+
this(mapperBuilder.apply(createDefaultObjectMapper()));
68+
}
69+
70+
/**
71+
* @return {@code JsonConverter} with reasonable defaults, using Jackson for JSON serialization and deserialization
72+
*/
73+
public static JsonConverter getDefault() {
74+
return instance;
75+
}
76+
77+
@Override
78+
public String toJson(@NonNull Type type, @Nullable Object o) throws ConversionException {
79+
try {
80+
return mapper.writerFor(mapper.getTypeFactory().constructType(type)).writeValueAsString(o);
81+
} catch (IOException e) {
82+
throw new ConversionException("Could not serialize an object of type `" + type + "` to JSON", e);
83+
}
84+
}
85+
86+
@Override
87+
public <T> T fromJson(@NonNull Type type, @NonNull String content) throws ConversionException {
88+
try {
89+
return mapper.readerFor(mapper.getTypeFactory().constructType(type)).readValue(content);
90+
} catch (IOException e) {
91+
throw new ConversionException("Could not deserialize an object of type `" + type + "` from JSON", e);
92+
}
93+
}
94+
95+
@Override
96+
@SuppressWarnings("unchecked")
97+
public <T> T fromObject(@NonNull Type type, @Nullable Object content) throws ConversionException {
98+
try {
99+
JavaType jacksonType = mapper.getTypeFactory().constructType(type);
100+
return content != null
101+
? mapper.convertValue(content, jacksonType)
102+
: (T) (jacksonType.isCollectionLikeType() ? List.of() : Map.of());
103+
} catch (Exception e) {
104+
throw new ConversionException("Could not convert an object to type `" + type + "`", e);
105+
}
106+
}
107+
108+
public String toString() {
109+
return "JacksonJsonConverter";
110+
}
111+
112+
private static ObjectMapper createDefaultObjectMapper() {
113+
ObjectMapper mapper = new ObjectMapper();
114+
mapper.setTimeZone(TimeZone.getDefault());
115+
mapper.registerModule(new Jdk8Module());
116+
mapper.registerModule(new JavaTimeModule());
117+
mapper.registerModule(new SimpleModule()
118+
.addAbstractTypeMapping(Set.class, LinkedHashSet.class)
119+
.addAbstractTypeMapping(Map.class, LinkedHashMap.class)
120+
.addAbstractTypeMapping(List.class, ArrayList.class)
121+
);
122+
mapper.setSerializationInclusion(Include.NON_NULL);
123+
mapper.configure(SerializationFeature.INDENT_OUTPUT, false);
124+
mapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false);
125+
mapper.configure(SerializationFeature.WRITE_DATE_KEYS_AS_TIMESTAMPS, false);
126+
mapper.configure(SerializationFeature.WRITE_ENUMS_USING_TO_STRING, false);
127+
mapper.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false);
128+
mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
129+
mapper.setVisibility(mapper.getSerializationConfig().getDefaultVisibilityChecker()
130+
.withFieldVisibility(Visibility.ANY)
131+
.withGetterVisibility(Visibility.NONE)
132+
.withIsGetterVisibility(Visibility.NONE)
133+
.withSetterVisibility(Visibility.NONE)
134+
);
135+
return mapper;
136+
}
137+
}

pom.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
<modules>
1919
<module>bom</module>
2020
<module>databind</module>
21+
<module>json-jackson-v2</module>
2122
<module>repository</module>
2223
<module>repository-test</module>
2324
<module>repository-inmemory</module>

repository-inmemory/pom.xml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,11 @@
4545
<artifactId>yoj-repository-test</artifactId>
4646
<scope>test</scope>
4747
</dependency>
48+
<dependency>
49+
<groupId>tech.ydb.yoj</groupId>
50+
<artifactId>yoj-json-jackson-v2</artifactId>
51+
<scope>test</scope>
52+
</dependency>
4853
</dependencies>
4954

5055
<build>

repository-inmemory/src/test/java/tech/ydb/yoj/repository/test/inmemory/TestInMemoryRepository.java

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,12 @@
55
import tech.ydb.yoj.repository.db.Table;
66
import tech.ydb.yoj.repository.db.TableQueryBuilder;
77
import tech.ydb.yoj.repository.db.TxOptions;
8+
import tech.ydb.yoj.repository.db.common.CommonConverters;
9+
import tech.ydb.yoj.repository.db.json.JacksonJsonConverter;
810
import tech.ydb.yoj.repository.test.sample.TestEntityOperations;
911
import tech.ydb.yoj.repository.test.sample.TestEntityOperations.BubbleTable;
1012
import tech.ydb.yoj.repository.test.sample.TestEntityOperations.ComplexTable;
1113
import tech.ydb.yoj.repository.test.sample.TestEntityOperations.IndexedTable;
12-
import tech.ydb.yoj.repository.test.sample.TestJsonConverter;
1314
import tech.ydb.yoj.repository.test.sample.model.Bubble;
1415
import tech.ydb.yoj.repository.test.sample.model.Complex;
1516
import tech.ydb.yoj.repository.test.sample.model.EntityWithValidation;
@@ -27,7 +28,7 @@
2728

2829
public class TestInMemoryRepository extends InMemoryRepository {
2930
static {
30-
TestJsonConverter.register();
31+
CommonConverters.defineJsonConverter(JacksonJsonConverter.getDefault());
3132
}
3233

3334
@Override

repository-test/src/main/java/tech/ydb/yoj/repository/test/sample/TestJsonConverter.java

Lines changed: 0 additions & 96 deletions
This file was deleted.

repository-ydb-v1/pom.xml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,11 @@
7676
<artifactId>yoj-repository-test</artifactId>
7777
<scope>test</scope>
7878
</dependency>
79+
<dependency>
80+
<groupId>tech.ydb.yoj</groupId>
81+
<artifactId>yoj-json-jackson-v2</artifactId>
82+
<scope>test</scope>
83+
</dependency>
7984
<dependency>
8085
<groupId>org.mockito</groupId>
8186
<artifactId>mockito-core</artifactId>

repository-ydb-v1/src/test/java/tech/ydb/yoj/repository/ydb/TestYdbRepository.java

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,14 @@
77
import tech.ydb.yoj.repository.db.Table;
88
import tech.ydb.yoj.repository.db.TableQueryBuilder;
99
import tech.ydb.yoj.repository.db.TxOptions;
10+
import tech.ydb.yoj.repository.db.common.CommonConverters;
11+
import tech.ydb.yoj.repository.db.json.JacksonJsonConverter;
1012
import tech.ydb.yoj.repository.db.statement.Changeset;
1113
import tech.ydb.yoj.repository.test.sample.TestEntityOperations;
1214
import tech.ydb.yoj.repository.test.sample.TestEntityOperations.BubbleTable;
1315
import tech.ydb.yoj.repository.test.sample.TestEntityOperations.ComplexTable;
1416
import tech.ydb.yoj.repository.test.sample.TestEntityOperations.IndexedTable;
1517
import tech.ydb.yoj.repository.test.sample.TestEntityOperations.Supabubble2Table;
16-
import tech.ydb.yoj.repository.test.sample.TestJsonConverter;
1718
import tech.ydb.yoj.repository.test.sample.model.Bubble;
1819
import tech.ydb.yoj.repository.test.sample.model.Complex;
1920
import tech.ydb.yoj.repository.test.sample.model.EntityWithValidation;
@@ -34,7 +35,7 @@
3435

3536
public class TestYdbRepository extends YdbRepository {
3637
static {
37-
TestJsonConverter.register();
38+
CommonConverters.defineJsonConverter(JacksonJsonConverter.getDefault());
3839
}
3940

4041
@Override

repository-ydb-v1/src/test/java/tech/ydb/yoj/repository/ydb/YqlTypeTest.java

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,9 @@
1313
import tech.ydb.yoj.databind.schema.ObjectSchema;
1414
import tech.ydb.yoj.databind.schema.Schema;
1515
import tech.ydb.yoj.repository.db.Entity;
16+
import tech.ydb.yoj.repository.db.common.CommonConverters;
1617
import tech.ydb.yoj.repository.db.exception.ConversionException;
17-
import tech.ydb.yoj.repository.test.sample.TestJsonConverter;
18+
import tech.ydb.yoj.repository.db.json.JacksonJsonConverter;
1819
import tech.ydb.yoj.repository.test.sample.model.NonDeserializableObject;
1920
import tech.ydb.yoj.repository.ydb.yql.YqlPrimitiveType;
2021
import tech.ydb.yoj.repository.ydb.yql.YqlType;
@@ -30,7 +31,7 @@
3031
public class YqlTypeTest {
3132
static {
3233
FieldValueType.registerStringValueType(UUID.class);
33-
TestJsonConverter.register();
34+
CommonConverters.defineJsonConverter(JacksonJsonConverter.getDefault());
3435
}
3536

3637
@Test

0 commit comments

Comments
 (0)