Skip to content

Commit 400f5be

Browse files
philleonardcowtowncoder
authored andcommitted
Support property naming strategy for Guava Ranges (#57)
Implement #56, support for naming strategy for `Range` values.
1 parent 43af85c commit 400f5be

3 files changed

Lines changed: 150 additions & 29 deletions

File tree

guava/src/main/java/com/fasterxml/jackson/datatype/guava/deser/RangeDeserializer.java

Lines changed: 64 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
package com.fasterxml.jackson.datatype.guava.deser;
22

3+
import static java.util.Arrays.asList;
4+
35
import java.io.IOException;
4-
import java.util.Arrays;
56

67
import com.google.common.base.Preconditions;
78
import com.google.common.collect.BoundType;
@@ -15,6 +16,8 @@
1516
import com.fasterxml.jackson.databind.type.TypeFactory;
1617

1718
import com.fasterxml.jackson.datatype.guava.deser.util.RangeFactory;
19+
import java.util.HashMap;
20+
import java.util.Map;
1821

1922
/**
2023
* Jackson deserializer for a Guava {@link Range}.
@@ -33,6 +36,8 @@ public class RangeDeserializer
3336

3437
protected final JsonDeserializer<Object> _endpointDeserializer;
3538

39+
private final Map<String, String> _fieldNames;
40+
3641
private BoundType _defaultBoundType;
3742

3843
/*
@@ -48,27 +53,35 @@ public class RangeDeserializer
4853
public RangeDeserializer(JavaType rangeType) {
4954
this(null, rangeType);
5055
}
51-
56+
5257
public RangeDeserializer(BoundType defaultBoundType, JavaType rangeType) {
53-
this(rangeType, null);
58+
this(rangeType, null, new HashMap<>());
5459
_defaultBoundType = defaultBoundType;
5560
}
5661

5762
@SuppressWarnings("unchecked")
58-
public RangeDeserializer(JavaType rangeType, JsonDeserializer<?> endpointDeser)
63+
public RangeDeserializer(JavaType rangeType, JsonDeserializer<?> endpointDeser, Map<String, String> fieldNames)
5964
{
6065
super(rangeType);
6166
_rangeType = rangeType;
6267
_endpointDeserializer = (JsonDeserializer<Object>) endpointDeser;
68+
_fieldNames = fieldNames;
6369
}
6470

6571
@SuppressWarnings("unchecked")
6672
public RangeDeserializer(JavaType rangeType, JsonDeserializer<?> endpointDeser, BoundType defaultBoundType)
73+
{
74+
this(rangeType, endpointDeser, defaultBoundType, new HashMap<>());
75+
}
76+
77+
@SuppressWarnings("unchecked")
78+
public RangeDeserializer(JavaType rangeType, JsonDeserializer<?> endpointDeser, BoundType defaultBoundType, Map<String, String> fieldNames)
6779
{
6880
super(rangeType);
6981
_rangeType = rangeType;
7082
_endpointDeserializer = (JsonDeserializer<Object>) endpointDeser;
7183
_defaultBoundType = defaultBoundType;
84+
_fieldNames = fieldNames;
7285
}
7386

7487
@Override
@@ -78,23 +91,40 @@ public RangeDeserializer(JavaType rangeType, JsonDeserializer<?> endpointDeser,
7891
public JsonDeserializer<?> createContextual(DeserializationContext ctxt,
7992
BeanProperty property) throws JsonMappingException
8093
{
94+
PropertyNamingStrategy propertyNamingStrategy = ctxt.getConfig().getPropertyNamingStrategy();
8195
if (_endpointDeserializer == null) {
8296
JavaType endpointType = _rangeType.containedType(0);
8397
if (endpointType == null) { // should this ever occur?
8498
endpointType = TypeFactory.unknownType();
8599
}
86100
JsonDeserializer<Object> deser = ctxt.findContextualValueDeserializer(endpointType, property);
87-
return new RangeDeserializer(_rangeType, deser, _defaultBoundType);
101+
if (propertyNamingStrategy != null) {
102+
return new RangeDeserializer(_rangeType, deser, _defaultBoundType, getPropertyNames(ctxt, propertyNamingStrategy));
103+
} else {
104+
return new RangeDeserializer(_rangeType, deser, _defaultBoundType);
105+
}
106+
}
107+
else if (propertyNamingStrategy != null) {
108+
return new RangeDeserializer(_rangeType, _endpointDeserializer, _defaultBoundType, getPropertyNames(ctxt, propertyNamingStrategy));
88109
}
89110
return this;
90111
}
91112

113+
private Map<String, String> getPropertyNames(DeserializationContext ctxt, PropertyNamingStrategy propertyNamingStrategy) {
114+
DeserializationConfig config = ctxt.getConfig();
115+
HashMap<String, String> fieldNames = new HashMap<>();
116+
for (String field : asList("lowerEndpoint", "upperEndpoint", "lowerBoundType", "upperBoundType")) {
117+
fieldNames.put(field, propertyNamingStrategy.nameForField(config, null, field));
118+
}
119+
return fieldNames;
120+
}
121+
92122
/*
93123
/**********************************************************
94124
/* Actual deserialization
95125
/**********************************************************
96126
*/
97-
127+
98128
@Override
99129
public Object deserializeWithType(JsonParser jp, DeserializationContext ctxt,
100130
TypeDeserializer typeDeserializer)
@@ -122,16 +152,16 @@ public Range<?> deserialize(JsonParser p, DeserializationContext context)
122152
expect(context, JsonToken.FIELD_NAME, t);
123153
String fieldName = p.currentName();
124154
try {
125-
if (fieldName.equals("lowerEndpoint")) {
155+
if (fieldName.equals(getFieldName("lowerEndpoint"))) {
126156
p.nextToken();
127157
lowerEndpoint = deserializeEndpoint(context, p);
128-
} else if (fieldName.equals("upperEndpoint")) {
158+
} else if (fieldName.equals(getFieldName("upperEndpoint"))) {
129159
p.nextToken();
130160
upperEndpoint = deserializeEndpoint(context, p);
131-
} else if (fieldName.equals("lowerBoundType")) {
161+
} else if (fieldName.equals(getFieldName("lowerBoundType"))) {
132162
p.nextToken();
133163
lowerBoundType = deserializeBoundType(context, p);
134-
} else if (fieldName.equals("upperBoundType")) {
164+
} else if (fieldName.equals(getFieldName("upperBoundType"))) {
135165
p.nextToken();
136166
upperBoundType = deserializeBoundType(context, p);
137167
} else {
@@ -148,19 +178,32 @@ public Range<?> deserialize(JsonParser p, DeserializationContext context)
148178
try {
149179
if ((lowerEndpoint != null) && (upperEndpoint != null)) {
150180
Preconditions.checkState(lowerEndpoint.getClass() == upperEndpoint.getClass(),
151-
"Endpoint types are not the same - 'lowerEndpoint' deserialized to [%s], and 'upperEndpoint' deserialized to [%s].",
181+
"Endpoint types are not the same - '%s' deserialized to [%s], and '%s' deserialized to [%s].",
182+
getFieldName("lowerEndpoint"),
152183
lowerEndpoint.getClass().getName(),
184+
getFieldName("upperEndpoint"),
153185
upperEndpoint.getClass().getName());
154-
Preconditions.checkState(lowerBoundType != null, "'lowerEndpoint' field found, but not 'lowerBoundType'");
155-
Preconditions.checkState(upperBoundType != null, "'upperEndpoint' field found, but not 'upperBoundType'");
186+
Preconditions.checkState(lowerBoundType != null,
187+
"'%s' field found, but not '%s'",
188+
getFieldName("lowerEndpoint"),
189+
getFieldName("lowerBoundType"));
190+
Preconditions.checkState(upperBoundType != null,
191+
"'%s' field found, but not '%s'",
192+
getFieldName("upperEndpoint"),
193+
getFieldName("upperBoundType"));
156194
return RangeFactory.range(lowerEndpoint, lowerBoundType, upperEndpoint, upperBoundType);
157195
}
158196
if (lowerEndpoint != null) {
159-
Preconditions.checkState(lowerBoundType != null, "'lowerEndpoint' field found, but not 'lowerBoundType'");
197+
Preconditions.checkState(lowerBoundType != null,
198+
"'%s' field found, but not '%s'",
199+
getFieldName("lowerEndpoint"),
200+
getFieldName("lowerBoundType"));
160201
return RangeFactory.downTo(lowerEndpoint, lowerBoundType);
161202
}
162203
if (upperEndpoint != null) {
163-
Preconditions.checkState(upperBoundType != null, "'upperEndpoint' field found, but not 'upperBoundType'");
204+
Preconditions.checkState(upperBoundType != null,
205+
"'%s' field found, but not '%s'",
206+
getFieldName("lowerEndpoint"));
164207
return RangeFactory.upTo(upperEndpoint, upperBoundType);
165208
}
166209
return RangeFactory.all();
@@ -169,6 +212,11 @@ public Range<?> deserialize(JsonParser p, DeserializationContext context)
169212
}
170213
}
171214

215+
private String getFieldName(String fieldName) {
216+
String name = _fieldNames.get(fieldName);
217+
return name == null ? fieldName : name;
218+
}
219+
172220
private BoundType deserializeBoundType(DeserializationContext context, JsonParser p) throws IOException
173221
{
174222
expect(context, JsonToken.VALUE_STRING, p.currentToken());
@@ -178,7 +226,7 @@ private BoundType deserializeBoundType(DeserializationContext context, JsonParse
178226
} catch (IllegalArgumentException e) {
179227
return (BoundType) context.handleWeirdStringValue(BoundType.class, name,
180228
"not a valid BoundType name (should be one oF: %s)",
181-
Arrays.asList(BoundType.values()));
229+
asList(BoundType.values()));
182230
}
183231
}
184232

guava/src/main/java/com/fasterxml/jackson/datatype/guava/ser/RangeSerializer.java

Lines changed: 50 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
package com.fasterxml.jackson.datatype.guava.ser;
22

3+
import static java.util.Arrays.asList;
4+
35
import java.io.IOException;
46

57
import com.fasterxml.jackson.core.*;
@@ -12,6 +14,8 @@
1214

1315
import com.google.common.collect.BoundType;
1416
import com.google.common.collect.Range;
17+
import java.util.HashMap;
18+
import java.util.Map;
1519

1620
/**
1721
* Jackson serializer for a Guava {@link Range}.
@@ -23,6 +27,8 @@ public class RangeSerializer extends StdSerializer<Range<?>>
2327

2428
protected final JsonSerializer<Object> _endpointSerializer;
2529

30+
private final Map<String, String> _fieldNames;
31+
2632
/*
2733
/**********************************************************
2834
/* Life-cycle
@@ -31,12 +37,17 @@ public class RangeSerializer extends StdSerializer<Range<?>>
3137

3238
public RangeSerializer(JavaType type) { this(type, null); }
3339

40+
public RangeSerializer(JavaType type, JsonSerializer<?> endpointSer) {
41+
this(type, endpointSer, new HashMap<>());
42+
}
43+
3444
@SuppressWarnings("unchecked")
35-
public RangeSerializer(JavaType type, JsonSerializer<?> endpointSer)
45+
public RangeSerializer(JavaType type, JsonSerializer<?> endpointSer, Map<String, String> fieldNames)
3646
{
3747
super(type);
3848
_rangeType = type;
3949
_endpointSerializer = (JsonSerializer<Object>) endpointSer;
50+
_fieldNames = fieldNames;
4051
}
4152

4253
@Override
@@ -48,12 +59,17 @@ public boolean isEmpty(SerializerProvider prov, Range<?> value) {
4859
public JsonSerializer<?> createContextual(SerializerProvider prov,
4960
BeanProperty property) throws JsonMappingException
5061
{
62+
PropertyNamingStrategy propertyNamingStrategy = prov.getConfig().getPropertyNamingStrategy();
5163
if (_endpointSerializer == null) {
5264
JavaType endpointType = _rangeType.containedTypeOrUnknown(0);
5365
// let's not consider "untyped" (java.lang.Object) to be meaningful here...
5466
if (endpointType != null && !endpointType.hasRawClass(Object.class)) {
5567
JsonSerializer<?> ser = prov.findSecondaryPropertySerializer(endpointType, property);
56-
return new RangeSerializer(_rangeType, ser);
68+
if (propertyNamingStrategy != null) {
69+
return new RangeSerializer(_rangeType, ser, getPropertyNames(prov.getConfig(), propertyNamingStrategy));
70+
} else {
71+
return new RangeSerializer(_rangeType, ser);
72+
}
5773
}
5874
/* 21-Sep-2014, tatu: Need to make sure all serializers get proper contextual
5975
* access, in case they rely on annotations on properties... (or, more generally,
@@ -62,12 +78,28 @@ public JsonSerializer<?> createContextual(SerializerProvider prov,
6278
} else {
6379
JsonSerializer<?> cs = _endpointSerializer.createContextual(prov, property);
6480
if (cs != _endpointSerializer) {
65-
return new RangeSerializer(_rangeType, cs);
81+
if (propertyNamingStrategy != null) {
82+
return new RangeSerializer(_rangeType, cs, getPropertyNames(prov.getConfig(), propertyNamingStrategy));
83+
} else {
84+
return new RangeSerializer(_rangeType, cs);
85+
}
6686
}
6787
}
88+
if (propertyNamingStrategy != null) {
89+
return new RangeSerializer(_rangeType, _endpointSerializer, getPropertyNames(prov.getConfig(), propertyNamingStrategy));
90+
}
6891
return this;
6992
}
7093

94+
private Map<String, String> getPropertyNames(SerializationConfig config, PropertyNamingStrategy propertyNamingStrategy) {
95+
final HashMap<String, String> fieldNames = new HashMap<>();
96+
for (String field : asList("lowerEndpoint", "upperEndpoint", "lowerBoundType", "upperBoundType")) {
97+
fieldNames.put(field, propertyNamingStrategy.nameForField(config, null, field));
98+
}
99+
return fieldNames;
100+
}
101+
102+
71103
/*
72104
/**********************************************************
73105
/* Serialization methods
@@ -101,27 +133,32 @@ private void _writeContents(Range<?> value, JsonGenerator g, SerializerProvider
101133
{
102134
if (value.hasLowerBound()) {
103135
if (_endpointSerializer != null) {
104-
g.writeFieldName("lowerEndpoint");
136+
g.writeFieldName(getFieldName("lowerEndpoint"));
105137
_endpointSerializer.serialize(value.lowerEndpoint(), g, provider);
106138
} else {
107-
provider.defaultSerializeField("lowerEndpoint", value.lowerEndpoint(), g);
139+
provider.defaultSerializeField(getFieldName("lowerEndpoint"), value.lowerEndpoint(), g);
108140
}
109141
// 20-Mar-2016, tatu: Should not use default handling since it leads to
110142
// [datatypes-collections#12] with default typing
111-
g.writeStringField("lowerBoundType", value.lowerBoundType().name());
143+
g.writeStringField(getFieldName("lowerBoundType"), value.lowerBoundType().name());
112144
}
113145
if (value.hasUpperBound()) {
114146
if (_endpointSerializer != null) {
115-
g.writeFieldName("upperEndpoint");
147+
g.writeFieldName(getFieldName("upperEndpoint"));
116148
_endpointSerializer.serialize(value.upperEndpoint(), g, provider);
117149
} else {
118-
provider.defaultSerializeField("upperEndpoint", value.upperEndpoint(), g);
150+
provider.defaultSerializeField(getFieldName("upperEndpoint"), value.upperEndpoint(), g);
119151
}
120152
// same as above; should always be just String so
121-
g.writeStringField("upperBoundType", value.upperBoundType().name());
153+
g.writeStringField(getFieldName("upperBoundType"), value.upperBoundType().name());
122154
}
123155
}
124156

157+
private String getFieldName(String fieldName) {
158+
String name = _fieldNames.get(fieldName);
159+
return name == null ? fieldName : name;
160+
}
161+
125162
@Override
126163
public void acceptJsonFormatVisitor(JsonFormatVisitorWrapper visitor, JavaType typeHint)
127164
throws JsonMappingException
@@ -135,10 +172,10 @@ public void acceptJsonFormatVisitor(JsonFormatVisitorWrapper visitor, JavaType t
135172
// should probably keep track of `property`...
136173
JsonSerializer<?> btSer = visitor.getProvider()
137174
.findSecondaryPropertySerializer(btType, null);
138-
objectVisitor.property("lowerEndpoint", _endpointSerializer, endpointType);
139-
objectVisitor.property("lowerBoundType", btSer, btType);
140-
objectVisitor.property("upperEndpoint", _endpointSerializer, endpointType);
141-
objectVisitor.property("upperBoundType", btSer, btType);
175+
objectVisitor.property(getFieldName("lowerEndpoint"), _endpointSerializer, endpointType);
176+
objectVisitor.property(getFieldName("lowerBoundType"), btSer, btType);
177+
objectVisitor.property(getFieldName("upperEndpoint"), _endpointSerializer, endpointType);
178+
objectVisitor.property(getFieldName("upperBoundType"), btSer, btType);
142179
}
143180
}
144181
}

guava/src/test/java/com/fasterxml/jackson/datatype/guava/TestRange.java

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
import com.fasterxml.jackson.databind.DefaultTyping;
77
import com.fasterxml.jackson.databind.JsonMappingException;
88
import com.fasterxml.jackson.databind.ObjectMapper;
9+
import com.fasterxml.jackson.databind.PropertyNamingStrategy;
910
import com.fasterxml.jackson.databind.json.JsonMapper;
1011
import com.fasterxml.jackson.datatype.guava.deser.util.RangeFactory;
1112
import com.google.common.collect.BoundType;
@@ -63,6 +64,21 @@ public void testSerialization() throws Exception
6364
testSerialization(MAPPER, RangeFactory.singleton(1));
6465
}
6566

67+
public void testSerializationWithPropertyNamingStrategy() throws Exception
68+
{
69+
ObjectMapper mappper = builderWithModule().propertyNamingStrategy(PropertyNamingStrategy.SNAKE_CASE).build();
70+
testSerialization(mappper, RangeFactory.open(1, 10));
71+
testSerialization(mappper, RangeFactory.openClosed(1, 10));
72+
testSerialization(mappper, RangeFactory.closedOpen(1, 10));
73+
testSerialization(mappper, RangeFactory.closed(1, 10));
74+
testSerialization(mappper, RangeFactory.atLeast(1));
75+
testSerialization(mappper, RangeFactory.greaterThan(1));
76+
testSerialization(mappper, RangeFactory.atMost(10));
77+
testSerialization(mappper, RangeFactory.lessThan(10));
78+
testSerialization(mappper, RangeFactory.all());
79+
testSerialization(mappper, RangeFactory.singleton(1));
80+
}
81+
6682
public void testWrappedSerialization() throws Exception
6783
{
6884
testSerializationWrapped(MAPPER, RangeFactory.open(1, 10));
@@ -258,6 +274,26 @@ public void testDefaultBoundTypeBothBoundTypesClosedWithOpenConfigured() throws
258274
assertEquals(BoundType.CLOSED, r.upperBoundType());
259275
}
260276

277+
public void testSnakeCaseNamingStrategy() throws Exception
278+
{
279+
String json = "{\"lower_endpoint\": 12, \"lower_bound_type\": \"CLOSED\", \"upper_endpoint\": 33, \"upper_bound_type\": \"CLOSED\"}";
280+
281+
GuavaModule mod = new GuavaModule().defaultBoundType(BoundType.CLOSED);
282+
ObjectMapper mapper = JsonMapper.builder()
283+
.addModule(mod)
284+
.propertyNamingStrategy(PropertyNamingStrategy.SNAKE_CASE)
285+
.build();
286+
287+
@SuppressWarnings("unchecked")
288+
Range<Integer> r = (Range<Integer>) mapper.readValue(json, Range.class);
289+
290+
assertEquals(Integer.valueOf(12), r.lowerEndpoint());
291+
assertEquals(Integer.valueOf(33), r.upperEndpoint());
292+
293+
assertEquals(BoundType.CLOSED, r.lowerBoundType());
294+
assertEquals(BoundType.CLOSED, r.upperBoundType());
295+
}
296+
261297
// [datatypes-collections#12]
262298
public void testRangeWithDefaultTyping() throws Exception
263299
{

0 commit comments

Comments
 (0)