Skip to content

Autodetect classes in ObjectMapper #5071

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 6 commits into
base: 3.x
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
54 changes: 54 additions & 0 deletions src/main/java/com/fasterxml/jackson/databind/ObjectMapper.java
Original file line number Diff line number Diff line change
Expand Up @@ -3055,6 +3055,23 @@ public <T> T readValue(JsonParser p, Class<T> valueType)
return (T) _readValue(getDeserializationConfig(), p, _typeFactory.constructType(valueType));
}

/**
* Reads the value from the given JSON content and automatically detects the class type.
*
* @param <T> the type of the object to read
* @param p the JsonParser
* @param reified don't pass any values here. It's a trick to detect the class type.
* @return the deserialized object
* @throws JsonProcessingException if there is a problem processing the JSON
*/
public <T> T readValue(JsonParser p, T... reified) throws IOException {
if (reified.length > 0) {
throw new IllegalArgumentException("Please don't pass any values here. Java will detect class automatically.");
}

return readValue(p, _typeFactory.constructType(getClassOf(reified)));
}

/**
* Method to deserialize JSON content into a Java type, reference
* to which is passed as argument. Type is passed using so-called
Expand Down Expand Up @@ -3855,6 +3872,34 @@ public <T> T readValue(String content, Class<T> valueType)
return readValue(content, _typeFactory.constructType(valueType));
}

/**
* Utility method to get the class type from a varargs array.
*
* @param <T> the generic type
* @param array the varargs array
* @return the class of the array component
*/
private static <T> Class<T> getClassOf(T[] array) {
return (Class<T>) array.getClass().getComponentType();
}

/**
* Reads the value from the given JSON content and automatically detects the class type.
*
* @param <T> the type of the object to read
* @param content the JSON string
* @param reified don't pass any values here. It's a trick to detect the class type.
* @return the deserialized object
* @throws JsonProcessingException if there is a problem processing the JSON
*/
public <T> T readValue(String content, T... reified) throws JsonProcessingException {
if (reified.length > 0) {
throw new IllegalArgumentException("Please don't pass any values here. Java will detect class automatically.");
}

return readValue(content, _typeFactory.constructType(getClassOf(reified)));
}

/**
* Method to deserialize JSON content from given JSON content String.
*
Expand Down Expand Up @@ -3957,6 +4002,15 @@ public <T> T readValue(byte[] src, int offset, int len,
return (T) _readMapAndClose(_jsonFactory.createParser(src, offset, len), _typeFactory.constructType(valueType));
}

@SuppressWarnings("unchecked")
public <T> T readValue(byte[] src, int offset, int len, T... reified) throws IOException {
if (reified.length > 0) {
throw new IllegalArgumentException("Please don't pass any values here. Java will detect class automatically.");
}

return readValue(src, offset, len, _typeFactory.constructType(getClassOf(reified)));
}

@SuppressWarnings({ "unchecked" })
public <T> T readValue(byte[] src, TypeReference<T> valueTypeRef)
throws IOException, StreamReadException, DatabindException
Expand Down
28 changes: 27 additions & 1 deletion src/main/java/com/fasterxml/jackson/databind/ObjectReader.java
Original file line number Diff line number Diff line change
Expand Up @@ -1492,7 +1492,7 @@ public void writeTree(JsonGenerator g, TreeNode rootNode) {
* @param src Source to read content from
*/
@SuppressWarnings("unchecked")
public <T> T readValue(InputStream src) throws IOException
private <T> T readValue(InputStream src) throws IOException
{
if (_dataFormatReaders != null) {
return (T) _detectBindAndClose(_dataFormatReaders.findFormat(src), false);
Expand All @@ -1515,6 +1515,12 @@ public <T> T readValue(InputStream src, Class<T> valueType) throws IOException
return (T) forType(valueType).readValue(src);
}

@SuppressWarnings("unchecked")
public <T> T readValue(InputStream src, T... reified) throws IOException
{
return forType(getClassOf(reified)).readValue(src);
}

/**
* Method that binds content read from given input source,
* using configuration of this reader.
Expand Down Expand Up @@ -1585,6 +1591,26 @@ public <T> T readValue(String src, Class<T> valueType) throws IOException
return (T) forType(valueType).readValue(src);
}

@SuppressWarnings("unchecked")
public <T> T readValue(String src, T... reified) throws IOException {
if (reified.length > 0) {
throw new IllegalArgumentException("Please don't pass any values here. Java will detect class automatically.");
}

return readValue(src, getClassOf(reified));
}

/**
* Utility method to get the class type from a varargs array.
*
* @param <T> the generic type
* @param array the varargs array
* @return the class of the array component
*/
private static <T> Class<T> getClassOf(T[] array) {
return (Class<T>) array.getClass().getComponentType();
}

/**
* Method that binds content read from given byte array,
* using configuration of this reader.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,9 @@ public void testBoundsWithByteArrayInput() throws Exception {
final JavaType TYPE = MAPPER.constructType(String.class);
_testBoundsWithByteArrayInput(
(data,offset,len)->MAPPER.readValue(data, offset, len, TYPE));

_testBoundsWithByteArrayInput(
(data,offset,len)->MAPPER.readValue(data, offset, len));
}

private void _testBoundsWithByteArrayInput(ByteBackedCreation creator) throws Exception
Expand Down
10 changes: 10 additions & 0 deletions src/test/java/com/fasterxml/jackson/databind/ObjectMapperTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -385,6 +385,16 @@ public void testJsonFactoryLinkage()
assertSame(m, f.getCodec());
}

@Test
public void testAutoDetectClasses() throws Exception
{
ObjectMapper m = new ObjectMapper();
final String JSON = "{ \"x\" : 3 }";

Bean bean = m.readValue(JSON);
assertNotNull(bean);
}

@Test
public void testProviderConfig() throws Exception
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -454,6 +454,9 @@ public void testCanPassResultToOverloadedMethod() throws Exception {
ObjectReader reader = MAPPER.readerFor(POJO.class).at("/foo/bar/caller");

process(reader.readValue(source, POJO.class));

POJO pojo = reader.readValue(source);
process(pojo);
}

void process(POJO pojo) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,50 @@ public void testTokenBufferWithSample() throws Exception
}
}

@Test
public void testTokenBufferWithSequenceWithAutoDetectClass() throws Exception
{
final ObjectMapper mapper = jsonMapperBuilder()
.disable(DeserializationFeature.FAIL_ON_TRAILING_TOKENS)
.build();

// and then sequence of other things
JsonParser p = mapper.createParser("[ 32, [ 1 ], \"abc\", { \"a\" : true } ]");
assertToken(JsonToken.START_ARRAY, p.nextToken());

assertToken(JsonToken.VALUE_NUMBER_INT, p.nextToken());
TokenBuffer buf = mapper.readValue(p);

// check manually...
JsonParser bufParser = buf.asParser();
assertToken(JsonToken.VALUE_NUMBER_INT, bufParser.nextToken());
assertEquals(32, bufParser.getIntValue());
assertNull(bufParser.nextToken());

// then bind to another
buf = mapper.readValue(p);
bufParser = buf.asParser();
assertToken(JsonToken.START_ARRAY, bufParser.nextToken());
assertToken(JsonToken.VALUE_NUMBER_INT, bufParser.nextToken());
assertEquals(1, bufParser.getIntValue());
assertToken(JsonToken.END_ARRAY, bufParser.nextToken());
assertNull(bufParser.nextToken());

// third one, with automatic binding
buf = mapper.readValue(p);
String str = mapper.readValue(buf.asParser());
assertEquals("abc", str);

// and ditto for last one
buf = mapper.readValue(p, TokenBuffer.class);
Map<?,?> map = mapper.readValue(buf.asParser());
assertEquals(1, map.size());
assertEquals(Boolean.TRUE, map.get("a"));

assertEquals(JsonToken.END_ARRAY, p.nextToken());
assertNull(p.nextToken());
}

@SuppressWarnings("resource")
@Test
public void testTokenBufferWithSequence() throws Exception
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -268,6 +268,15 @@ public void testLocaleFuzz47034() throws Exception
assertNotNull(loc);
}

@Test
public void testLocaleFuzz47034WithAutoDetectClss() throws Exception
{
Locale loc = MAPPER.reader()
.without(DeserializationFeature.FAIL_ON_TRAILING_TOKENS)
.readValue(getClass().getResourceAsStream("/fuzz/oss-fuzz-47034.json"));
assertNotNull(loc);
}

// https://bugs.chromium.org/p/oss-fuzz/issues/detail?id=47036
// @since 2.14
@Test
Expand Down