Skip to content

Commit e6d8684

Browse files
authored
Merge pull request #309 from jdereg/codex/modify-jsonparser-to-delay-type-resolution
Defer class resolution in parser
2 parents f05d3c6 + cbcb92d commit e6d8684

File tree

6 files changed

+86
-8
lines changed

6 files changed

+86
-8
lines changed

src/main/java/com/cedarsoftware/io/JsonParser.java

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -343,8 +343,8 @@ private JsonObject readJsonObject(Type suggestedType) throws IOException {
343343
// Process key-value pairing.
344344
switch (field) {
345345
case TYPE:
346-
Class<?> type = loadType(value);
347-
jObj.setType(type);
346+
String typeName = loadType(value);
347+
jObj.setTypeName(typeName);
348348
break;
349349

350350
case ENUM: // Legacy support (@enum was used to indicate EnumSet in prior versions)
@@ -854,8 +854,12 @@ private void loadEnum(Object value, JsonObject jObj) {
854854
if (!(value instanceof String)) {
855855
error("Expected a String for " + ENUM + ", instead got: " + value);
856856
}
857-
Class<?> enumClass = stringToClass((String) value);
858-
jObj.setType(enumClass);
857+
String enumClassName = (String) value;
858+
String substitute = readOptions.getTypeNameAlias(enumClassName);
859+
if (substitute != null) {
860+
enumClassName = substitute;
861+
}
862+
jObj.setTypeName(enumClassName);
859863

860864
// Only set empty items if no items were specified in JSON
861865
if (jObj.getItems() == null) {
@@ -868,7 +872,7 @@ private void loadEnum(Object value, JsonObject jObj) {
868872
*
869873
* @param value Object should be a String, if not an exception is thrown. It is the value associated to the @type field.
870874
*/
871-
private Class<?> loadType(Object value) {
875+
private String loadType(Object value) {
872876
if (!(value instanceof String)) {
873877
error("Expected a String for " + TYPE + ", instead got: " + value);
874878
}
@@ -878,8 +882,8 @@ private Class<?> loadType(Object value) {
878882
javaType = substitute;
879883
}
880884

881-
// Resolve class during parsing
882-
return stringToClass(javaType);
885+
// Defer class resolution
886+
return javaType;
883887
}
884888

885889
/**

src/main/java/com/cedarsoftware/io/JsonValue.java

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,8 @@ public abstract class JsonValue {
4747
protected Long refId = null;
4848
protected int line;
4949
protected int col;
50+
// Hold the raw @type/@enum string when the class cannot be resolved during parsing
51+
String typeName = null;
5052

5153
// Cache for storing whether a Type is fully resolved.
5254
private static final Map<Type, Boolean> typeResolvedCache = new ConcurrentHashMap<>();
@@ -140,7 +142,15 @@ public String getRawTypeName() {
140142
if (type != null) {
141143
return TypeUtilities.getRawClass(type).getName();
142144
}
143-
return null;
145+
return typeName;
146+
}
147+
148+
public String getTypeName() {
149+
return typeName;
150+
}
151+
152+
public void setTypeName(String typeName) {
153+
this.typeName = typeName;
144154
}
145155

146156
public long getId() {
@@ -162,6 +172,7 @@ public boolean hasId() {
162172
void clear() {
163173
id = -1;
164174
type = null;
175+
typeName = null;
165176
refId = null;
166177
}
167178

src/main/java/com/cedarsoftware/io/MapResolver.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -203,6 +203,7 @@ else if (element instanceof JsonObject) {
203203

204204
if (refId == null) {
205205
// Convert JsonObject to its destination type if possible
206+
resolvePendingType(jsonObject);
206207
Class<?> type = jsonObject.getRawType();
207208
if (type != null && converter.isConversionSupportedFor(Map.class, type)) {
208209
Object converted = converter.convert(jsonObject, type);
@@ -213,6 +214,7 @@ else if (element instanceof JsonObject) {
213214
}
214215
} else { // Connect reference
215216
JsonObject refObject = refTracker.getOrThrow(refId);
217+
resolvePendingType(refObject);
216218
Class<?> type = refObject.getRawType();
217219

218220
if (type != null && converter.isConversionSupportedFor(Map.class, type)) {
@@ -281,6 +283,7 @@ protected void traverseCollection(final JsonObject jsonObj) {
281283
} else {
282284
jObj.setType(Object.class);
283285
createInstance(jObj);
286+
resolvePendingType(jObj);
284287
boolean isNonRefClass = getReadOptions().isNonReferenceableClass(jObj.getRawType());
285288
if (!isNonRefClass) {
286289
traverseSpecificType(jObj);

src/main/java/com/cedarsoftware/io/ObjectResolver.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,7 @@ public void assignField(final JsonObject jsonObj, final Injector injector, final
134134
}
135135

136136
final JsonObject jObj = (JsonObject) rhs;
137+
resolvePendingType(jObj);
137138
Type explicitType = jObj.getType();
138139
if (explicitType != null && !TypeUtilities.hasUnresolvedType(explicitType)) {
139140
// If the field has an explicit type, use it.

src/main/java/com/cedarsoftware/io/Resolver.java

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -211,6 +211,7 @@ public <T> T toJavaObjects(JsonObject rootObj, Type rootType) {
211211
if (rootObj.isFinished) {
212212
return (T) rootObj.getTarget();
213213
} else {
214+
resolvePendingType(rootObj);
214215
if (rootObj.getType() == null) {
215216
// If there is no explicit type hint in the JSON, use the provided root type.
216217
rootObj.setType(rootType);
@@ -455,6 +456,8 @@ Object createInstance(JsonObject jsonObj) {
455456
return target;
456457
}
457458

459+
resolvePendingType(jsonObj);
460+
458461
// Use the refined Type (if available) to determine the target type.
459462
Class<?> targetType = resolveTargetType(jsonObj);
460463

@@ -549,6 +552,35 @@ private boolean shouldCreateArray(JsonObject jsonObj, Class<?> targetType) {
549552
return targetType.isArray() || (items != null && targetType == Object.class && jsonObj.getKeys() == null);
550553
}
551554

555+
// Resolve any pending type name on the JsonObject to a concrete Class
556+
protected void resolvePendingType(JsonObject jsonObj) {
557+
if (jsonObj.getType() == null) {
558+
String name = jsonObj.getTypeName();
559+
if (name == null) {
560+
return;
561+
}
562+
563+
String alias = readOptions.getTypeNameAlias(name);
564+
if (alias != null) {
565+
name = alias;
566+
}
567+
568+
Class<?> clazz = ClassUtilities.forName(name, readOptions.getClassLoader());
569+
if (clazz == null) {
570+
if (readOptions.isFailOnUnknownType()) {
571+
throw new JsonIoException("Unknown type (class) '" + name + "' not defined.");
572+
}
573+
clazz = readOptions.getUnknownTypeClass();
574+
if (clazz == null) {
575+
clazz = LinkedHashMap.class;
576+
}
577+
}
578+
579+
jsonObj.setType(clazz);
580+
jsonObj.setTypeName(null);
581+
}
582+
}
583+
552584
private Object createArrayInstance(JsonObject jsonObj, Class<?> targetType) {
553585
Object[] items = jsonObj.getItems();
554586
int size = (items == null) ? 0 : items.length;
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
package com.cedarsoftware.io;
2+
3+
import java.io.StringReader;
4+
5+
import com.cedarsoftware.io.JsonReader.DefaultReferenceTracker;
6+
import com.cedarsoftware.util.FastReader;
7+
import com.cedarsoftware.util.convert.Converter;
8+
import org.junit.jupiter.api.Test;
9+
10+
import static org.junit.jupiter.api.Assertions.*;
11+
12+
/** Test that JsonParser keeps @type values as Strings during parsing. */
13+
public class JsonParserTypeStringTest {
14+
@Test
15+
public void testParserKeepsTypeName() throws Exception {
16+
String json = "{\"@type\":\"some.missing.Type\",\"value\":1}";
17+
ReadOptions options = new ReadOptionsBuilder().returnAsJsonObjects().failOnUnknownType(false).build();
18+
Converter converter = new Converter(options.getConverterOptions());
19+
MapResolver resolver = new MapResolver(options, new DefaultReferenceTracker(), converter);
20+
JsonParser parser = new JsonParser(new FastReader(new StringReader(json)), resolver);
21+
Object obj = parser.readValue(Object.class);
22+
assertTrue(obj instanceof JsonObject);
23+
JsonObject jObj = (JsonObject) obj;
24+
assertNull(jObj.getType());
25+
assertEquals("some.missing.Type", jObj.getRawTypeName());
26+
}
27+
}

0 commit comments

Comments
 (0)