Skip to content

Commit b85e057

Browse files
committed
HHH-19890 Add Jackson 3 FormatMapper support
1 parent 2269a00 commit b85e057

File tree

14 files changed

+768
-16
lines changed

14 files changed

+768
-16
lines changed

documentation/src/main/asciidoc/introduction/Configuration.adoc

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -116,8 +116,8 @@ and `org.ehcache:ehcache`
116116
and `com.github.ben-manes.caffeine:jcache`
117117
| Distributed second-level cache support via {infinispan}[Infinispan] | `org.infinispan:infinispan-hibernate-cache-v60`
118118
// | SCRAM authentication support for PostgreSQL | `com.ongres.scram:client:2.1`
119-
| A JSON serialization library for working with JSON datatypes, for example, {jackson}[Jackson] or {yasson}[Yasson] |
120-
`com.fasterxml.jackson.core:jackson-databind` +
119+
| A JSON serialization library for working with JSON datatypes, for example, {jackson}[Jackson 2], {jackson3}[Jackson 3] or {yasson}[Yasson] |
120+
`com.fasterxml.jackson.core:jackson-databind`, `tools.jackson.core:jackson-databind` +
121121
or `org.eclipse:yasson`
122122
| <<spatial,Hibernate Spatial>> | `org.hibernate.orm:hibernate-spatial`
123123
| <<envers,Envers>>, for auditing historical data | `org.hibernate.orm:hibernate-envers`

documentation/src/main/asciidoc/introduction/Mapping.adoc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -809,7 +809,7 @@ class Person {
809809
----
810810

811811
We also need to add Jackson or an implementation of JSONB—for example, Yasson—to our runtime classpath.
812-
To use Jackson we could add this line to our Gradle build:
812+
To use Jackson 2 we could add this line to our Gradle build:
813813

814814
[source,groovy]
815815
----

hibernate-core/hibernate-core.gradle

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,8 @@ dependencies {
4242
compileOnly jakartaLibs.jsonbApi
4343
compileOnly libs.jackson
4444
compileOnly libs.jacksonXml
45+
compileOnly libs.jackson3
46+
compileOnly libs.jackson3Xml
4547
compileOnly jdbcLibs.postgresql
4648
compileOnly jdbcLibs.edb
4749

@@ -79,6 +81,8 @@ dependencies {
7981
testImplementation libs.jackson
8082
testRuntimeOnly libs.jacksonXml
8183
testRuntimeOnly libs.jacksonJsr310
84+
testImplementation libs.jackson3
85+
testImplementation libs.jackson3Xml
8286

8387
testAnnotationProcessor project( ':hibernate-processor' )
8488

hibernate-core/src/main/java/org/hibernate/boot/internal/SessionFactoryOptionsBuilder.java

Lines changed: 31 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -101,10 +101,11 @@
101101
import static org.hibernate.jpa.internal.util.CacheModeHelper.interpretCacheMode;
102102
import static org.hibernate.jpa.internal.util.ConfigurationHelper.getFlushMode;
103103
import static org.hibernate.stat.Statistics.DEFAULT_QUERY_STATISTICS_MAX_SIZE;
104+
import static org.hibernate.type.format.jackson.JacksonIntegration.getJsonJackson3FormatMapperOrNull;
104105
import static org.hibernate.type.format.jackson.JacksonIntegration.getJsonJacksonFormatMapperOrNull;
105106
import static org.hibernate.type.format.jackson.JacksonIntegration.getOsonJacksonFormatMapperOrNull;
107+
import static org.hibernate.type.format.jackson.JacksonIntegration.getXMLJackson3FormatMapperOrNull;
106108
import static org.hibernate.type.format.jackson.JacksonIntegration.getXMLJacksonFormatMapperOrNull;
107-
import static org.hibernate.type.format.jackson.JacksonIntegration.isJacksonOsonExtensionAvailable;
108109
import static org.hibernate.type.format.jakartajson.JakartaJsonIntegration.getJakartaJsonBFormatMapperOrNull;
109110

110111
/**
@@ -851,14 +852,25 @@ private static FormatMapper jsonFormatMapper(Object setting, boolean osonExtensi
851852
setting,
852853
selector,
853854
() -> {
855+
final FormatMapper jackson3FormatMapper = getJsonJackson3FormatMapperOrNull( creationContext );
856+
if ( jackson3FormatMapper != null ) {
857+
return jackson3FormatMapper;
858+
}
859+
854860
// Prefer the OSON Jackson FormatMapper by default if available
855-
final FormatMapper jsonJacksonFormatMapper =
856-
osonExtensionEnabled && isJacksonOsonExtensionAvailable()
857-
? getOsonJacksonFormatMapperOrNull( creationContext )
858-
: getJsonJacksonFormatMapperOrNull( creationContext );
859-
return jsonJacksonFormatMapper != null
860-
? jsonJacksonFormatMapper
861-
: getJakartaJsonBFormatMapperOrNull();
861+
final FormatMapper jacksonOsonFormatMapper = osonExtensionEnabled
862+
? getOsonJacksonFormatMapperOrNull( creationContext )
863+
: null;
864+
if ( jacksonOsonFormatMapper != null ) {
865+
return jacksonOsonFormatMapper;
866+
}
867+
868+
final FormatMapper jacksonFormatMapper = getJsonJacksonFormatMapperOrNull( creationContext );
869+
if ( jacksonFormatMapper != null ) {
870+
return jacksonFormatMapper;
871+
}
872+
873+
return getJakartaJsonBFormatMapperOrNull();
862874
},
863875
creationContext
864876
);
@@ -869,10 +881,17 @@ private static FormatMapper xmlFormatMapper(Object setting, StrategySelector sel
869881
setting,
870882
selector,
871883
() -> {
872-
final FormatMapper jacksonFormatMapper = getXMLJacksonFormatMapperOrNull( creationContext );
873-
return jacksonFormatMapper != null
874-
? jacksonFormatMapper
875-
: new JaxbXmlFormatMapper( legacyFormat );
884+
final FormatMapper jackson3FormatMapper = getXMLJackson3FormatMapperOrNull( creationContext );
885+
if (jackson3FormatMapper != null) {
886+
return jackson3FormatMapper;
887+
}
888+
889+
final FormatMapper jacksonFormatMapper = getXMLJacksonFormatMapperOrNull( creationContext );
890+
if (jacksonFormatMapper != null) {
891+
return jacksonFormatMapper;
892+
}
893+
894+
return new JaxbXmlFormatMapper( legacyFormat );
876895
},
877896
creationContext
878897
);

hibernate-core/src/main/java/org/hibernate/boot/registry/selector/internal/StrategySelectorBuilder.java

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,8 @@
4545
import org.hibernate.resource.transaction.backend.jta.internal.JtaTransactionCoordinatorBuilderImpl;
4646
import org.hibernate.resource.transaction.spi.TransactionCoordinatorBuilder;
4747
import org.hibernate.type.format.FormatMapper;
48+
import org.hibernate.type.format.jackson.Jackson3JsonFormatMapper;
49+
import org.hibernate.type.format.jackson.Jackson3XmlFormatMapper;
4850
import org.hibernate.type.format.jackson.JacksonIntegration;
4951
import org.hibernate.type.format.jackson.JacksonJsonFormatMapper;
5052
import org.hibernate.type.format.jackson.JacksonOsonFormatMapper;
@@ -303,6 +305,11 @@ private static void addJsonFormatMappers(StrategySelectorImpl strategySelector)
303305
JsonBJsonFormatMapper.SHORT_NAME,
304306
JsonBJsonFormatMapper.class
305307
);
308+
strategySelector.registerStrategyImplementor(
309+
FormatMapper.class,
310+
Jackson3JsonFormatMapper.SHORT_NAME,
311+
Jackson3JsonFormatMapper.class
312+
);
306313
strategySelector.registerStrategyImplementor(
307314
FormatMapper.class,
308315
JacksonJsonFormatMapper.SHORT_NAME,
@@ -318,6 +325,11 @@ private static void addJsonFormatMappers(StrategySelectorImpl strategySelector)
318325
}
319326

320327
private static void addXmlFormatMappers(StrategySelectorImpl strategySelector) {
328+
strategySelector.registerStrategyImplementor(
329+
FormatMapper.class,
330+
Jackson3XmlFormatMapper.SHORT_NAME,
331+
Jackson3XmlFormatMapper.class
332+
);
321333
strategySelector.registerStrategyImplementor(
322334
FormatMapper.class,
323335
JacksonXmlFormatMapper.SHORT_NAME,
Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
/*
2+
* SPDX-License-Identifier: Apache-2.0
3+
* Copyright Red Hat Inc. and Hibernate Authors
4+
*/
5+
package org.hibernate.type.format.jackson;
6+
7+
import org.hibernate.type.descriptor.WrapperOptions;
8+
import org.hibernate.type.descriptor.java.JavaType;
9+
import org.hibernate.type.format.AbstractJsonFormatMapper;
10+
import org.hibernate.type.format.FormatMapperCreationContext;
11+
import tools.jackson.core.JacksonException;
12+
import tools.jackson.core.JsonGenerator;
13+
import tools.jackson.core.JsonParser;
14+
import tools.jackson.databind.JacksonModule;
15+
import tools.jackson.databind.cfg.DateTimeFeature;
16+
import tools.jackson.databind.cfg.MapperBuilder;
17+
import tools.jackson.databind.json.JsonMapper;
18+
19+
import java.lang.reflect.Type;
20+
import java.util.List;
21+
22+
/**
23+
* @author Christian Beikov
24+
* @author Yanming Zhou
25+
* @author Nick Rayburn
26+
*/
27+
public final class Jackson3JsonFormatMapper extends AbstractJsonFormatMapper {
28+
29+
public static final String SHORT_NAME = "jackson3";
30+
31+
private final JsonMapper jsonMapper;
32+
33+
public Jackson3JsonFormatMapper() {
34+
this( MapperBuilder.findModules( Jackson3JsonFormatMapper.class.getClassLoader() ) );
35+
}
36+
37+
public Jackson3JsonFormatMapper(FormatMapperCreationContext creationContext) {
38+
this( JacksonIntegration.loadJackson3Modules( creationContext ) );
39+
}
40+
41+
private Jackson3JsonFormatMapper(List<JacksonModule> modules) {
42+
this( JsonMapper.builder()
43+
.addModules( modules )
44+
.disable( DateTimeFeature.ONE_BASED_MONTHS )
45+
.enable( DateTimeFeature.WRITE_DATES_AS_TIMESTAMPS )
46+
.enable( DateTimeFeature.WRITE_DURATIONS_AS_TIMESTAMPS )
47+
.build()
48+
);
49+
}
50+
51+
public Jackson3JsonFormatMapper(JsonMapper jsonMapper) {
52+
this.jsonMapper = jsonMapper;
53+
}
54+
55+
@Override
56+
public <T> void writeToTarget(T value, JavaType<T> javaType, Object target, WrapperOptions options)
57+
throws JacksonException {
58+
jsonMapper.writerFor( jsonMapper.constructType( javaType.getJavaType() ) )
59+
.writeValue( (JsonGenerator) target, value );
60+
}
61+
62+
@Override
63+
public <T> T readFromSource(JavaType<T> javaType, Object source, WrapperOptions options) throws JacksonException {
64+
return jsonMapper.readValue( (JsonParser) source, jsonMapper.constructType( javaType.getJavaType() ) );
65+
}
66+
67+
@Override
68+
public boolean supportsSourceType(Class<?> sourceType) {
69+
return JsonParser.class.isAssignableFrom( sourceType );
70+
}
71+
72+
@Override
73+
public boolean supportsTargetType(Class<?> targetType) {
74+
return JsonGenerator.class.isAssignableFrom( targetType );
75+
}
76+
77+
@Override
78+
public <T> T fromString(CharSequence charSequence, Type type) {
79+
try {
80+
return jsonMapper.readValue( charSequence.toString(), jsonMapper.constructType( type ) );
81+
}
82+
catch (JacksonException e) {
83+
throw new IllegalArgumentException( "Could not deserialize string to java type: " + type, e );
84+
}
85+
}
86+
87+
@Override
88+
public <T> String toString(T value, Type type) {
89+
try {
90+
return jsonMapper.writerFor( jsonMapper.constructType( type ) ).writeValueAsString( value );
91+
}
92+
catch (JacksonException e) {
93+
throw new IllegalArgumentException( "Could not serialize object of java type: " + type, e );
94+
}
95+
}
96+
}

0 commit comments

Comments
 (0)