Skip to content

Commit 1e5fd12

Browse files
committed
Start work on #1207, add handleWeirdKey() in both DeserializationContext and DeserializationProblemHandler
1 parent 48437a7 commit 1e5fd12

File tree

5 files changed

+143
-77
lines changed

5 files changed

+143
-77
lines changed

src/main/java/com/fasterxml/jackson/databind/DeserializationContext.java

Lines changed: 65 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -802,20 +802,22 @@ public <T> T readPropertyValue(JsonParser p, BeanProperty prop, JavaType type) t
802802

803803
/*
804804
/**********************************************************
805-
/* Methods for problem handling, reporting
805+
/* Methods for problem handling
806806
/**********************************************************
807807
*/
808808

809809
/**
810-
* Method deserializers can call to inform configured {@link DeserializationProblemHandler}s
811-
* of an unrecognized property.
810+
* Method that deserializers should call if they encounter an unrecognized
811+
* property (and once that is not explicitly designed as ignorable), to
812+
* inform possibly configured {@link DeserializationProblemHandler}s and
813+
* let it handle the problem.
812814
*
813815
* @return True if there was a configured problem handler that was able to handle the
814816
* problem
815817
*/
816818
public boolean handleUnknownProperty(JsonParser p, JsonDeserializer<?> deser,
817819
Object instanceOrClass, String propName)
818-
throws IOException, JsonProcessingException
820+
throws IOException
819821
{
820822
LinkedNode<DeserializationProblemHandler> h = _config.getProblemHandlers();
821823
if (h != null) {
@@ -831,7 +833,52 @@ public boolean handleUnknownProperty(JsonParser p, JsonDeserializer<?> deser,
831833
}
832834

833835
/**
834-
* Helper method for reporting a problem with unhandled unknown exception
836+
* Method that deserializers should call if they encounter an unrecognized
837+
* property (and once that is not explicitly designed as ignorable), to
838+
* inform possibly configured {@link DeserializationProblemHandler}s and
839+
* let it handle the problem.
840+
*
841+
* @param keyClass Expected type for key
842+
* @param keyValue String value from which to deserialize key
843+
* @param msg Error message template caller wants to use if exception is to be thrown
844+
* @param msgArgs Optional arguments to use for message, if any
845+
*
846+
* @return Key value to use
847+
*
848+
* @throws JsonMappingException
849+
*
850+
* @since 2.8
851+
*/
852+
public Object handleWeirdKey(Class<?> keyClass, String keyValue,
853+
String msg, Object... msgArgs)
854+
throws IOException
855+
{
856+
// but if not handled, just throw exception
857+
if (msgArgs.length > 0) {
858+
msg = String.format(msg, msgArgs);
859+
}
860+
LinkedNode<DeserializationProblemHandler> h = _config.getProblemHandlers();
861+
if (h != null) {
862+
while (h != null) {
863+
// Can bail out if it's handled
864+
Object key = h.value().handleWeirdKey(this, keyClass, keyValue, msg);
865+
if (key != DeserializationProblemHandler.NOT_HANDLED) {
866+
return key;
867+
}
868+
h = h.next();
869+
}
870+
}
871+
throw this.weirdKeyException(keyClass, keyValue, msg);
872+
}
873+
874+
/*
875+
/**********************************************************
876+
/* Methods for problem reporting
877+
/**********************************************************
878+
*/
879+
880+
/**
881+
* Helper method for reporting a problem with unhandled unknown property.
835882
*
836883
* @param instanceOrClass Either value being populated (if one has been
837884
* instantiated), or Class that indicates type that would be (or
@@ -900,19 +947,6 @@ public void reportWeirdNumberException(Number value, Class<?> instClass,
900947
throw weirdNumberException(value, instClass, msg);
901948
}
902949

903-
/**
904-
* @since 2.8
905-
*/
906-
public void reportWeirdKeyException(Class<?> keyClass, String keyValue,
907-
String msg, Object... msgArgs)
908-
throws JsonMappingException
909-
{
910-
if (msgArgs.length > 0) {
911-
msg = String.format(msg, msgArgs);
912-
}
913-
throw weirdKeyException(keyClass, keyValue, msg);
914-
}
915-
916950
/**
917951
* @since 2.8
918952
*/
@@ -1015,6 +1049,19 @@ public JsonMappingException mappingException(String msgTemplate, Object... args)
10151049
return JsonMappingException.from(getParser(), msgTemplate);
10161050
}
10171051

1052+
/**
1053+
* Helper method for constructing exception to indicate that given JSON
1054+
* Object field name was not in format to be able to deserialize specified
1055+
* key type.
1056+
*/
1057+
public JsonMappingException weirdKeyException(Class<?> keyClass, String keyValue,
1058+
String msg) {
1059+
return InvalidFormatException.from(_parser,
1060+
String.format("Can not deserialize Map key of type %s from String %s: %s",
1061+
keyClass.getName(), _quotedString(keyValue), msg),
1062+
keyValue, keyClass);
1063+
}
1064+
10181065
/*
10191066
/**********************************************************
10201067
/* Methods for constructing semantic exceptions; mostly
@@ -1082,21 +1129,6 @@ public JsonMappingException weirdNumberException(Number value, Class<?> instClas
10821129
value, instClass);
10831130
}
10841131

1085-
/**
1086-
* Helper method for constructing exception to indicate that given JSON
1087-
* Object field name was not in format to be able to deserialize specified
1088-
* key type.
1089-
*
1090-
* @deprecated Since 2.8 use {@link #reportWeirdKeyException} instead
1091-
*/
1092-
@Deprecated
1093-
public JsonMappingException weirdKeyException(Class<?> keyClass, String keyValue, String msg) {
1094-
return InvalidFormatException.from(_parser,
1095-
String.format("Can not deserialize Map key of type %s from String %s: %s",
1096-
keyClass.getName(), _quotedString(keyValue), msg),
1097-
keyValue, keyClass);
1098-
}
1099-
11001132
/**
11011133
* Helper method for indicating that the current token was expected to be another
11021134
* token.

src/main/java/com/fasterxml/jackson/databind/deser/DeserializationProblemHandler.java

Lines changed: 44 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
import java.io.IOException;
44

55
import com.fasterxml.jackson.core.JsonParser;
6-
import com.fasterxml.jackson.core.JsonProcessingException;
76
import com.fasterxml.jackson.databind.DeserializationConfig;
87
import com.fasterxml.jackson.databind.DeserializationContext;
98
import com.fasterxml.jackson.databind.JsonDeserializer;
@@ -31,7 +30,15 @@
3130
public abstract class DeserializationProblemHandler
3231
{
3332
/**
34-
* Method called when a JSON Map ("Object") entry with an unrecognized
33+
* Marker value returned by some handler methods to indicate that
34+
* they could not handle problem and produce replacement value.
35+
*
36+
* @since 2.7
37+
*/
38+
public final static Object NOT_HANDLED = new Object();
39+
40+
/**
41+
* Method called when a JSON Object property with an unrecognized
3542
* name is encountered.
3643
* Content (supposedly) matching the property are accessible via
3744
* parser that can be obtained from passed deserialization context.
@@ -42,9 +49,8 @@ public abstract class DeserializationProblemHandler
4249
* parser.skipChildren();
4350
*</pre>
4451
*<p>
45-
* Note: version 1.2 added new deserialization feature
46-
* {@link com.fasterxml.jackson.databind.DeserializationFeature#FAIL_ON_UNKNOWN_PROPERTIES}).
47-
* It will only have effect <b>after</b> handler is called, and only
52+
* Note: {@link com.fasterxml.jackson.databind.DeserializationFeature#FAIL_ON_UNKNOWN_PROPERTIES})
53+
* takes effect only <b>after</b> handler is called, and only
4854
* if handler did <b>not</b> handle the problem.
4955
*
5056
* @param beanOrClass Either bean instance being deserialized (if one
@@ -60,8 +66,40 @@ public abstract class DeserializationProblemHandler
6066
*/
6167
public boolean handleUnknownProperty(DeserializationContext ctxt, JsonParser p,
6268
JsonDeserializer<?> deserializer, Object beanOrClass, String propertyName)
63-
throws IOException, JsonProcessingException
69+
throws IOException
6470
{
6571
return false;
6672
}
73+
74+
/**
75+
* Method called when a property name from input can not be converted to a
76+
* non-Java-String key type (passed as <code>rawKeyType</code>) due to format
77+
* problem. Handler may choose to do one of 3 things:
78+
*<ul>
79+
* <li>Indicate it does not know what to do by returning {@link #NOT_HANDLED}
80+
* </li>
81+
* <li>Throw a {@link IOException} to indicate specific fail message (instead of
82+
* standard exception caller would throw
83+
* </li>
84+
* <li>Return actual key value to use as replacement, and continue processing.
85+
* </li>
86+
* </ul>
87+
*
88+
* @since 2.8
89+
*
90+
* @param failureMsg Message that will be used by caller (by calling
91+
* {@link DeserializationContext#weirdKeyException(Class, String, String)})
92+
* to indicate type of failure unless handler produces key to use
93+
*
94+
* @return Either {@link #NOT_HANDLED} to indicate that handler does not know
95+
* what to do (and exception may be thrown), or value to use as key (possibly
96+
* <code>null</code>
97+
*/
98+
public Object handleWeirdKey(DeserializationContext ctxt,
99+
Class<?> rawKeyType, String keyValue,
100+
String failureMsg)
101+
throws IOException
102+
{
103+
return NOT_HANDLED;
104+
}
67105
}

src/main/java/com/fasterxml/jackson/databind/deser/std/StdKeyDeserializer.java

Lines changed: 31 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import java.io.IOException;
44
import java.lang.reflect.Constructor;
55
import java.lang.reflect.Method;
6+
import java.net.MalformedURLException;
67
import java.net.URI;
78
import java.net.URL;
89
import java.util.*;
@@ -110,7 +111,7 @@ public static StdKeyDeserializer forType(Class<?> raw)
110111
}
111112
return new StdKeyDeserializer(kind, raw);
112113
}
113-
114+
114115
@Override
115116
public Object deserializeKey(String key, DeserializationContext ctxt)
116117
throws IOException
@@ -124,14 +125,12 @@ public Object deserializeKey(String key, DeserializationContext ctxt)
124125
return result;
125126
}
126127
} catch (Exception re) {
127-
ctxt.reportWeirdKeyException(_keyClass, key, "not a valid representation: %s", re.getMessage());
128-
return null;
128+
return ctxt.handleWeirdKey(_keyClass, key, "not a valid representation: %s", re.getMessage());
129129
}
130130
if (_keyClass.isEnum() && ctxt.getConfig().isEnabled(DeserializationFeature.READ_UNKNOWN_ENUM_VALUES_AS_NULL)) {
131131
return null;
132132
}
133-
ctxt.reportWeirdKeyException(_keyClass, key, "not a valid representation");
134-
return null;
133+
return ctxt.handleWeirdKey(_keyClass, key, "not a valid representation");
135134
}
136135

137136
public Class<?> getKeyClass() { return _keyClass; }
@@ -146,23 +145,21 @@ protected Object _parse(String key, DeserializationContext ctxt) throws Exceptio
146145
if ("false".equals(key)) {
147146
return Boolean.FALSE;
148147
}
149-
ctxt.reportWeirdKeyException(_keyClass, key, "value not 'true' or 'false'");
150-
break;
148+
return ctxt.handleWeirdKey(_keyClass, key, "value not 'true' or 'false'");
151149
case TYPE_BYTE:
152150
{
153151
int value = _parseInt(key);
154-
// as per [JACKSON-804], allow range up to 255, inclusive
152+
// allow range up to 255, inclusive (to support "unsigned" byte)
155153
if (value < Byte.MIN_VALUE || value > 255) {
156-
ctxt.reportWeirdKeyException(_keyClass, key, "overflow, value can not be represented as 8-bit value");
157-
// fall-through and truncate if need be
154+
return ctxt.handleWeirdKey(_keyClass, key, "overflow, value can not be represented as 8-bit value");
158155
}
159156
return Byte.valueOf((byte) value);
160157
}
161158
case TYPE_SHORT:
162159
{
163160
int value = _parseInt(key);
164161
if (value < Short.MIN_VALUE || value > Short.MAX_VALUE) {
165-
ctxt.reportWeirdKeyException(_keyClass, key, "overflow, value can not be represented as 16-bit value");
162+
return ctxt.handleWeirdKey(_keyClass, key, "overflow, value can not be represented as 16-bit value");
166163
// fall-through and truncate if need be
167164
}
168165
return Short.valueOf((short) value);
@@ -171,8 +168,7 @@ protected Object _parse(String key, DeserializationContext ctxt) throws Exceptio
171168
if (key.length() == 1) {
172169
return Character.valueOf(key.charAt(0));
173170
}
174-
ctxt.reportWeirdKeyException(_keyClass, key, "can only convert 1-character Strings");
175-
break;
171+
return ctxt.handleWeirdKey(_keyClass, key, "can only convert 1-character Strings");
176172
case TYPE_INT:
177173
return _parseInt(key);
178174

@@ -188,41 +184,46 @@ protected Object _parse(String key, DeserializationContext ctxt) throws Exceptio
188184
try {
189185
return _deser._deserialize(key, ctxt);
190186
} catch (IOException e) {
191-
ctxt.reportWeirdKeyException(_keyClass, key, "unable to parse key as locale");
187+
return ctxt.handleWeirdKey(_keyClass, key, "unable to parse key as locale");
192188
}
193-
break;
194189
case TYPE_CURRENCY:
195190
try {
196191
return _deser._deserialize(key, ctxt);
197192
} catch (IOException e) {
198-
ctxt.reportWeirdKeyException(_keyClass, key, "unable to parse key as currency");
193+
return ctxt.handleWeirdKey(_keyClass, key, "unable to parse key as currency");
199194
}
200-
break;
201195
case TYPE_DATE:
202196
return ctxt.parseDate(key);
203197
case TYPE_CALENDAR:
204198
java.util.Date date = ctxt.parseDate(key);
205199
return (date == null) ? null : ctxt.constructCalendar(date);
206200
case TYPE_UUID:
207-
return UUID.fromString(key);
201+
try {
202+
return UUID.fromString(key);
203+
} catch (Exception e) {
204+
return ctxt.handleWeirdKey(_keyClass, key, "problem: %s", e.getMessage());
205+
}
208206
case TYPE_URI:
209-
return URI.create(key);
207+
try {
208+
return URI.create(key);
209+
} catch (Exception e) {
210+
return ctxt.handleWeirdKey(_keyClass, key, "problem: %s", e.getMessage());
211+
}
210212
case TYPE_URL:
211-
return new URL(key);
213+
try {
214+
return new URL(key);
215+
} catch (MalformedURLException e) {
216+
return ctxt.handleWeirdKey(_keyClass, key, "problem: %s", e.getMessage());
217+
}
212218
case TYPE_CLASS:
213219
try {
214220
return ctxt.findClass(key);
215221
} catch (Exception e) {
216-
ctxt.reportWeirdKeyException(_keyClass, key, "unable to parse key as Class");
222+
return ctxt.handleWeirdKey(_keyClass, key, "unable to parse key as Class");
217223
}
218-
break;
219224
default:
220225
throw new IllegalStateException("Internal error: unknown key type "+_keyClass);
221226
}
222-
// 05-May-2016, tatu: In future, we may end up here if `reportWeirdKeyException()`
223-
// collects failure messages and does not immediately throw. Not 100% sure what
224-
// should be done; returning `null` seems least evil for now
225-
return null;
226227
}
227228

228229
/*
@@ -314,13 +315,10 @@ public final Object deserializeKey(String key, DeserializationContext ctxt)
314315
if (result != null) {
315316
return result;
316317
}
317-
ctxt.reportWeirdKeyException(_keyClass, key, "not a valid representation");
318+
return ctxt.handleWeirdKey(_keyClass, key, "not a valid representation");
318319
} catch (Exception re) {
319-
ctxt.reportWeirdKeyException(_keyClass, key, "not a valid representation: "+re.getMessage());
320+
return ctxt.handleWeirdKey(_keyClass, key, "not a valid representation: %s", re.getMessage());
320321
}
321-
// 05-May-2016, tatu: Can't happen now (2.8), but in future exceptions may
322-
// be deferred.
323-
return null;
324322
}
325323

326324
public Class<?> getKeyClass() { return _keyClass; }
@@ -350,7 +348,7 @@ protected EnumKD(EnumResolver er, AnnotatedMethod factory) {
350348
}
351349

352350
@Override
353-
public Object _parse(String key, DeserializationContext ctxt) throws JsonMappingException
351+
public Object _parse(String key, DeserializationContext ctxt) throws IOException
354352
{
355353
if (_factory != null) {
356354
try {
@@ -363,7 +361,7 @@ public Object _parse(String key, DeserializationContext ctxt) throws JsonMapping
363361
? _getToStringResolver(ctxt) : _byNameResolver;
364362
Enum<?> e = res.findEnum(key);
365363
if ((e == null) && !ctxt.getConfig().isEnabled(DeserializationFeature.READ_UNKNOWN_ENUM_VALUES_AS_NULL)) {
366-
ctxt.reportWeirdKeyException(_keyClass, key, "not one of values excepted for Enum class: %s",
364+
return ctxt.handleWeirdKey(_keyClass, key, "not one of values excepted for Enum class: %s",
367365
res.getEnumIds());
368366
// fall-through if problems are collected, not immediately thrown
369367
}

0 commit comments

Comments
 (0)