Skip to content

Commit 98ee687

Browse files
committed
Include JacksonJsonToObjectConverter, Spring Converter implementation to convert/map JSON to a POJO using Jackson's ObjectMapper.
1 parent dd439fe commit 98ee687

File tree

2 files changed

+458
-0
lines changed

2 files changed

+458
-0
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,144 @@
1+
/*
2+
* Copyright 2020 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
13+
* or implied. See the License for the specific language governing
14+
* permissions and limitations under the License.
15+
*/
16+
package org.springframework.geode.data.json.converter.support;
17+
18+
import com.fasterxml.jackson.core.JsonProcessingException;
19+
import com.fasterxml.jackson.databind.DeserializationFeature;
20+
import com.fasterxml.jackson.databind.JsonNode;
21+
import com.fasterxml.jackson.databind.MapperFeature;
22+
import com.fasterxml.jackson.databind.ObjectMapper;
23+
import com.fasterxml.jackson.databind.node.JsonNodeType;
24+
import com.fasterxml.jackson.databind.node.POJONode;
25+
26+
import org.springframework.core.convert.converter.Converter;
27+
import org.springframework.dao.DataRetrievalFailureException;
28+
import org.springframework.data.mapping.MappingException;
29+
import org.springframework.geode.data.json.converter.JsonToObjectConverter;
30+
import org.springframework.geode.pdx.PdxInstanceWrapper;
31+
import org.springframework.lang.NonNull;
32+
import org.springframework.lang.Nullable;
33+
import org.springframework.util.Assert;
34+
import org.springframework.util.ClassUtils;
35+
import org.springframework.util.StringUtils;
36+
37+
/**
38+
* {@link JsonToObjectConverter} implementation using Jackson to convert {@link String JSON}
39+
* to an {@link Object} (POJO).
40+
*
41+
* @author John Blum
42+
* @see com.fasterxml.jackson.databind.JsonNode
43+
* @see com.fasterxml.jackson.databind.ObjectMapper
44+
* @see com.fasterxml.jackson.databind.node.POJONode
45+
* @see org.springframework.core.convert.converter.Converter
46+
* @see org.springframework.geode.data.json.converter.JsonToObjectConverter
47+
* @since 1.3.0
48+
*/
49+
public class JacksonJsonToObjectConverter implements JsonToObjectConverter {
50+
51+
protected static final String AT_TYPE_FIELD_NAME = PdxInstanceWrapper.AT_TYPE_FIELD_NAME;
52+
53+
private ObjectMapper objectMapper = newObjectMapper();
54+
55+
/**
56+
* Constructs a new Jackson {@link ObjectMapper} to convert {@link String JSON} into an {@link Object} (POJO).
57+
*
58+
* @return a new Jackson {@link ObjectMapper}; never {@literal null}.
59+
* @see com.fasterxml.jackson.databind.ObjectMapper
60+
*/
61+
// TODO configure via an SPI
62+
private @NonNull ObjectMapper newObjectMapper() {
63+
64+
return new ObjectMapper()
65+
.configure(DeserializationFeature.FAIL_ON_IGNORED_PROPERTIES, false)
66+
.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
67+
.configure(MapperFeature.ACCEPT_CASE_INSENSITIVE_ENUMS, true)
68+
.findAndRegisterModules();
69+
}
70+
71+
/**
72+
* Returns a reference to the configured Jackson {@link ObjectMapper} used by this {@link Converter}
73+
* to convert {@link String JSON} into an {@link Object} (POJO).
74+
*
75+
* @return a reference to the configured Jackson {@link ObjectMapper}.
76+
*/
77+
protected @NonNull ObjectMapper getObjectMapper() {
78+
return this.objectMapper;
79+
}
80+
81+
/**
82+
* Converts from {@link String JSON} to an {@link Object} (POJO) using Jackson's {@link ObjectMapper}.
83+
*
84+
* @param json {@link String} containing {@literal JSON} to convert.
85+
* @return an {@link Object} (POJO) converted from the given {@link String JSON}.
86+
* @see #getObjectMapper()
87+
*/
88+
@Override
89+
public @Nullable Object convert(@Nullable String json) {
90+
91+
if (StringUtils.hasText(json)) {
92+
93+
String objectTypeName = null;
94+
95+
try {
96+
97+
ObjectMapper objectMapper = getObjectMapper();
98+
99+
JsonNode jsonNode = objectMapper.readTree(json);
100+
101+
if (isPojo(jsonNode)) {
102+
return ((POJONode) jsonNode).getPojo();
103+
}
104+
else {
105+
106+
Assert.state(jsonNode.isObject(), () -> String.format("The JSON [%s] must be an object", json));
107+
108+
Assert.state(jsonNode.has(AT_TYPE_FIELD_NAME),
109+
() -> String.format("The JSON object [%1$s] must have an '%2$s' metadata field",
110+
json, AT_TYPE_FIELD_NAME));
111+
112+
objectTypeName = jsonNode.get(AT_TYPE_FIELD_NAME).asText();
113+
114+
Class<?> objectType =
115+
ClassUtils.forName(objectTypeName, Thread.currentThread().getContextClassLoader());
116+
117+
return objectMapper.readValue(json, objectType);
118+
}
119+
}
120+
catch (ClassNotFoundException cause) {
121+
throw new MappingException(String.format("Failed to map JSON [%1$s] to an Object of type [%2$s]",
122+
json, objectTypeName), cause);
123+
}
124+
catch (JsonProcessingException cause) {
125+
throw new DataRetrievalFailureException(String.format("Failed to read JSON [%s]", json), cause);
126+
}
127+
}
128+
129+
return null;
130+
}
131+
132+
/**
133+
* Null-safe method to determine whether the given {@link JsonNode} represents a {@link Object POJO}.
134+
*
135+
* @param jsonNode {@link JsonNode} to evaluate.
136+
* @return a boolean value indicating whether the given {@link JsonNode} represents a {@link Object POJO}.
137+
* @see com.fasterxml.jackson.databind.JsonNode
138+
*/
139+
boolean isPojo(@Nullable JsonNode jsonNode) {
140+
141+
return jsonNode != null
142+
&& (jsonNode instanceof POJONode || jsonNode.isPojo() || JsonNodeType.POJO.equals(jsonNode.getNodeType()));
143+
}
144+
}

0 commit comments

Comments
 (0)