Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit 84bfc04

Browse files
mp911deodrotbohm
authored andcommitted
Initial draft of Jackson 3 support.
See GH-3292.
1 parent 66f5ee0 commit 84bfc04

28 files changed

+1244
-107
lines changed

‎pom.xml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,11 @@
7070
<artifactId>jackson-databind</artifactId>
7171
<optional>true</optional>
7272
</dependency>
73+
<dependency>
74+
<groupId>tools.jackson.core</groupId>
75+
<artifactId>jackson-databind</artifactId>
76+
<optional>true</optional>
77+
</dependency>
7378
<dependency>
7479
<groupId>org.springframework</groupId>
7580
<artifactId>spring-web</artifactId>

‎src/main/antora/modules/ROOT/pages/repositories/core-extensions-web.adoc

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -290,9 +290,9 @@ By default, the assembler points to the controller method it was invoked in, but
290290
== Spring Data Jackson Modules
291291

292292
The core module, and some of the store specific ones, ship with a set of Jackson Modules for types, like `org.springframework.data.geo.Distance` and `org.springframework.data.geo.Point`, used by the Spring Data domain. +
293-
Those Modules are imported once xref:repositories/core-extensions.adoc#core.web[web support] is enabled and `com.fasterxml.jackson.databind.ObjectMapper` is available.
293+
Those modules are imported once xref:repositories/core-extensions.adoc#core.web[web support] is enabled and `tools.jackson.databind.ObjectMapper` is available.
294294

295-
During initialization `SpringDataJacksonModules`, like the `SpringDataJacksonConfiguration`, get picked up by the infrastructure, so that the declared ``com.fasterxml.jackson.databind.Module``s are made available to the Jackson `ObjectMapper`.
295+
During initialization `SpringDataJackson3Modules`, like the `SpringDataJackson3Configuration`, get picked up by the infrastructure, so that the declared ``tools.jackson.databind.JacksonModule``s are made available to the Jackson `ObjectMapper`.
296296

297297
Data binding mixins for the following domain types are registered by the common infrastructure.
298298

@@ -306,10 +306,15 @@ org.springframework.data.geo.Polygon
306306

307307
[NOTE]
308308
====
309-
The individual module may provide additional `SpringDataJacksonModules`. +
309+
The individual module may provide additional `SpringDataJackson3Modules`. +
310310
Please refer to the store specific section for more details.
311311
====
312312

313+
[NOTE]
314+
====
315+
Jackson 2 support is deprecated and will be removed in a future release.
316+
====
317+
313318
[[core.web.binding]]
314319
== Web Databinding Support
315320

@@ -341,7 +346,7 @@ Nested projections are supported as described in xref:repositories/projections.a
341346
If the method returns a complex, non-interface type, a Jackson `ObjectMapper` is used to map the final value.
342347

343348
For Spring MVC, the necessary converters are registered automatically as soon as `@EnableSpringDataWebSupport` is active and the required dependencies are available on the classpath.
344-
For usage with `RestTemplate`, register a `ProjectingJackson2HttpMessageConverter` (JSON) or `XmlBeamHttpMessageConverter` manually.
349+
For usage with `RestTemplate`, register a `ProjectingJacksonHttpMessageConverter` (JSON) or `XmlBeamHttpMessageConverter` manually.
345350

346351
For more information, see the https://github.com/spring-projects/spring-data-examples/tree/main/web/projection[web projection example] in the canonical https://github.com/spring-projects/spring-data-examples[Spring Data Examples repository].
347352

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
/*
2+
* Copyright 2014-2025 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package org.springframework.data.geo;
17+
18+
import tools.jackson.core.Version;
19+
import tools.jackson.databind.annotation.JsonDeserialize;
20+
import tools.jackson.databind.module.SimpleModule;
21+
22+
import java.io.Serial;
23+
import java.util.List;
24+
25+
import com.fasterxml.jackson.annotation.JsonIgnore;
26+
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
27+
import com.fasterxml.jackson.annotation.JsonProperty;
28+
29+
/**
30+
* Custom module to deserialize the geo-spatial value objects using Jackson 3.
31+
*
32+
* @author Oliver Gierke
33+
* @author Mark Paluch
34+
* @since 4.0
35+
*/
36+
@SuppressWarnings("unused")
37+
public class GeoJacksonModule extends SimpleModule {
38+
39+
private static final @Serial long serialVersionUID = 1L;
40+
41+
/**
42+
* Creates a new {@link GeoJacksonModule} registering mixins for common geo-spatial types.
43+
*/
44+
public GeoJacksonModule() {
45+
46+
super("Spring Data Geo Mixins", new Version(1, 0, 0, null, "org.springframework.data", "spring-data-commons-geo"));
47+
48+
setMixInAnnotation(Distance.class, DistanceMixin.class);
49+
setMixInAnnotation(Point.class, PointMixin.class);
50+
setMixInAnnotation(Box.class, BoxMixin.class);
51+
setMixInAnnotation(Circle.class, CircleMixin.class);
52+
setMixInAnnotation(Polygon.class, PolygonMixin.class);
53+
}
54+
55+
@JsonIgnoreProperties("unit")
56+
static abstract class DistanceMixin {
57+
58+
DistanceMixin(@JsonProperty("value") double value,
59+
@JsonProperty("metric") @JsonDeserialize(as = Metrics.class) Metric metic) {}
60+
61+
@JsonIgnore
62+
abstract double getNormalizedValue();
63+
}
64+
65+
static abstract class PointMixin {
66+
PointMixin(@JsonProperty("x") double x, @JsonProperty("y") double y) {}
67+
}
68+
69+
static abstract class CircleMixin {
70+
CircleMixin(@JsonProperty("center") Point center, @JsonProperty("radius") Distance radius) {}
71+
}
72+
73+
static abstract class BoxMixin {
74+
BoxMixin(@JsonProperty("first") Point first, @JsonProperty("second") Point point) {}
75+
}
76+
77+
static abstract class PolygonMixin {
78+
PolygonMixin(@JsonProperty("points") List<Point> points) {}
79+
}
80+
}

‎src/main/java/org/springframework/data/geo/GeoModule.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,10 @@
3030
*
3131
* @author Oliver Gierke
3232
* @since 1.8
33+
* @deprecated since 4.0, use {@link GeoJacksonModule} instead.
3334
*/
3435
@SuppressWarnings("unused")
36+
@Deprecated(since = "4.0", forRemoval = true)
3537
public class GeoModule extends SimpleModule {
3638

3739
private static final @Serial long serialVersionUID = 1L;

‎src/main/java/org/springframework/data/repository/init/Jackson2RepositoryPopulatorFactoryBean.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,9 @@
2727
* @author Oliver Gierke
2828
* @author Christoph Strobl
2929
* @since 1.6
30+
* @deprecated since 4.0, in favor of {@link JacksonRepositoryPopulatorFactoryBean}.
3031
*/
32+
@Deprecated(since = "4.0", forRemoval = true)
3133
public class Jackson2RepositoryPopulatorFactoryBean extends AbstractRepositoryPopulatorFactoryBean {
3234

3335
private @Nullable ObjectMapper mapper;

‎src/main/java/org/springframework/data/repository/init/Jackson2ResourceReader.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,9 @@
4040
* @author Mark Paluch
4141
* @author Johannes Englmeier
4242
* @since 1.6
43+
* @deprecated since 4.0, in favor of {@link JacksonResourceReader}.
4344
*/
45+
@Deprecated(since = "4.0", forRemoval = true)
4446
public class Jackson2ResourceReader implements ResourceReader {
4547

4648
private static final String DEFAULT_TYPE_KEY = "_class";
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
/*
2+
* Copyright 2025 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package org.springframework.data.repository.init;
17+
18+
import tools.jackson.databind.ObjectMapper;
19+
20+
import org.jspecify.annotations.Nullable;
21+
22+
import org.springframework.beans.factory.FactoryBean;
23+
24+
/**
25+
* {@link FactoryBean} to set up a {@link ResourceReaderRepositoryPopulator} with a {@link JacksonResourceReader}.
26+
*
27+
* @author Mark Paluch
28+
* @author Oliver Gierke
29+
* @author Christoph Strobl
30+
* @since 4.0
31+
*/
32+
public class JacksonRepositoryPopulatorFactoryBean extends AbstractRepositoryPopulatorFactoryBean {
33+
34+
private @Nullable ObjectMapper mapper;
35+
36+
/**
37+
* Configures the {@link ObjectMapper} to be used.
38+
*
39+
* @param mapper can be {@literal null}.
40+
*/
41+
public void setMapper(@Nullable ObjectMapper mapper) {
42+
this.mapper = mapper;
43+
}
44+
45+
@Override
46+
protected ResourceReader getResourceReader() {
47+
return mapper == null ? new JacksonResourceReader() : new JacksonResourceReader(mapper);
48+
}
49+
}
Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
/*
2+
* Copyright 2013-2025 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package org.springframework.data.repository.init;
17+
18+
import tools.jackson.databind.DeserializationFeature;
19+
import tools.jackson.databind.JsonNode;
20+
import tools.jackson.databind.ObjectMapper;
21+
import tools.jackson.databind.json.JsonMapper;
22+
23+
import java.io.IOException;
24+
import java.io.InputStream;
25+
import java.util.ArrayList;
26+
import java.util.Iterator;
27+
import java.util.List;
28+
29+
import org.jspecify.annotations.Nullable;
30+
31+
import org.springframework.core.io.Resource;
32+
import org.springframework.util.Assert;
33+
import org.springframework.util.ClassUtils;
34+
35+
/**
36+
* A {@link ResourceReader} using Jackson to read JSON into objects.
37+
*
38+
* @author Oliver Gierke
39+
* @author Christoph Strobl
40+
* @author Mark Paluch
41+
* @author Johannes Englmeier
42+
* @since 4.0
43+
*/
44+
public class JacksonResourceReader implements ResourceReader {
45+
46+
private static final String DEFAULT_TYPE_KEY = "_class";
47+
private static final ObjectMapper DEFAULT_MAPPER = JsonMapper.builder()
48+
.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false).build();
49+
50+
private final ObjectMapper mapper;
51+
private String typeKey = DEFAULT_TYPE_KEY;
52+
53+
/**
54+
* Creates a new {@link JacksonResourceReader}.
55+
*/
56+
public JacksonResourceReader() {
57+
this(DEFAULT_MAPPER);
58+
}
59+
60+
/**
61+
* Creates a new {@link JacksonResourceReader} using the given {@link ObjectMapper}.
62+
*
63+
* @param mapper
64+
*/
65+
public JacksonResourceReader(ObjectMapper mapper) {
66+
this.mapper = mapper;
67+
}
68+
69+
/**
70+
* Configures the JSON document's key to look up the type to instantiate the object. Defaults to
71+
* {@link JacksonResourceReader#DEFAULT_TYPE_KEY}.
72+
*
73+
* @param typeKey
74+
*/
75+
public void setTypeKey(@Nullable String typeKey) {
76+
this.typeKey = typeKey == null ? DEFAULT_TYPE_KEY : typeKey;
77+
}
78+
79+
@Override
80+
public Object readFrom(Resource resource, @Nullable ClassLoader classLoader) throws Exception {
81+
82+
Assert.notNull(resource, "Resource must not be null");
83+
84+
InputStream stream = resource.getInputStream();
85+
JsonNode node = mapper.readerFor(JsonNode.class).readTree(stream);
86+
87+
if (node.isArray()) {
88+
89+
Iterator<JsonNode> elements = node.iterator();
90+
List<Object> result = new ArrayList<>();
91+
92+
while (elements.hasNext()) {
93+
JsonNode element = elements.next();
94+
result.add(readSingle(element, classLoader));
95+
}
96+
97+
return result;
98+
}
99+
100+
return readSingle(node, classLoader);
101+
}
102+
103+
/**
104+
* Reads the given {@link JsonNode} into an instance of the type encoded in it using the configured type key.
105+
*
106+
* @param node must not be {@literal null}.
107+
* @param classLoader can be {@literal null}.
108+
* @return
109+
*/
110+
private Object readSingle(JsonNode node, @Nullable ClassLoader classLoader) throws IOException {
111+
112+
JsonNode typeNode = node.findValue(typeKey);
113+
114+
if (typeNode == null) {
115+
throw new IllegalArgumentException(String.format("Could not find type for type key '%s'", typeKey));
116+
}
117+
118+
String typeName = typeNode.asString();
119+
Class<?> type = ClassUtils.resolveClassName(typeName, classLoader);
120+
121+
return mapper.readerFor(type).readValue(node);
122+
}
123+
}

‎src/main/java/org/springframework/data/web/JsonProjectingMethodInterceptorFactory.java

Lines changed: 16 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -131,18 +131,27 @@ private static boolean hasJsonPathAnnotation(Class<?> type) {
131131
return false;
132132
}
133133

134-
private static class InputMessageProjecting implements MethodInterceptor {
135-
136-
private final DocumentContext context;
137-
138-
public InputMessageProjecting(DocumentContext context) {
139-
this.context = context;
140-
}
134+
private record InputMessageProjecting(DocumentContext context) implements MethodInterceptor {
141135

142136
@Override
143137
public @Nullable Object invoke(MethodInvocation invocation) throws Throwable {
144138

145139
Method method = invocation.getMethod();
140+
141+
switch (method.getName()) {
142+
case "equals" -> {
143+
// Only consider equal when proxies are identical.
144+
return (invocation.getThis() == invocation.getArguments()[0]);
145+
}
146+
case "hashCode" -> {
147+
// Use hashCode of EntityManager proxy.
148+
return context.hashCode();
149+
}
150+
case "toString" -> {
151+
return context.jsonString();
152+
}
153+
}
154+
146155
TypeInformation<?> returnType = TypeInformation.fromReturnTypeOf(method);
147156
ResolvableType type = ResolvableType.forMethodReturnType(method);
148157
boolean isCollectionResult = type.getRawClass() != null && Collection.class.isAssignableFrom(type.getRawClass());

‎src/main/java/org/springframework/data/web/ProjectingJackson2HttpMessageConverter.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,9 @@
4848
* @author Christoph Strobl
4949
* @soundtrack Richard Spaven - Ice Is Nice (Spaven's 5ive)
5050
* @since 1.13
51+
* @deprecated since 4.0, in favor of {@link ProjectingJacksonHttpMessageConverter}.
5152
*/
53+
@Deprecated(since = "4.0", forRemoval = true)
5254
public class ProjectingJackson2HttpMessageConverter extends MappingJackson2HttpMessageConverter
5355
implements BeanClassLoaderAware, BeanFactoryAware {
5456

0 commit comments

Comments
(0)

AltStyle によって変換されたページ (->オリジナル) /