package com.fasterxml.jackson.databind.deser.std; import java.io.IOException; import java.util.*; import com.fasterxml.jackson.core.*; import com.fasterxml.jackson.databind.DeserializationContext; import com.fasterxml.jackson.databind.DeserializationFeature; import com.fasterxml.jackson.databind.annotation.JacksonStdImpl; import com.fasterxml.jackson.databind.jsontype.TypeDeserializer; import com.fasterxml.jackson.databind.util.ObjectBuffer; /** * Deserializer implementation that is used if it is necessary to bind content of * "unknown" type; something declared as basic {@link java.lang.Object} * (either explicitly, or due to type erasure). * If so, "natural" mapping is used to convert JSON values to their natural * Java object matches: JSON arrays to Java {@link java.util.List}s (or, if configured, * Object[]), JSON objects to {@link java.util.Map}s, numbers to * {@link java.lang.Number}s, booleans to {@link java.lang.Boolean}s and * strings to {@link java.lang.String} (and nulls to nulls). */ @JacksonStdImpl public class UntypedObjectDeserializer extends StdDeserializer<Object> { private final static Object[] NO_OBJECTS = new Object[0]; public UntypedObjectDeserializer() { super(Object.class); } /* /********************************************************** /* Deserializer API /********************************************************** */ @Override public Object deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException, JsonProcessingException { switch (jp.getCurrentToken()) { case START_OBJECT: return mapObject(jp, ctxt); case END_OBJECT: // invalid break; case START_ARRAY: return mapArray(jp, ctxt); case END_ARRAY: // invalid break; case FIELD_NAME: return mapObject(jp, ctxt); case VALUE_EMBEDDED_OBJECT: return jp.getEmbeddedObject(); case VALUE_STRING: return jp.getText(); case VALUE_NUMBER_INT: /* [JACKSON-100]: caller may want to get all integral values * returned as BigInteger, for consistency */ if (ctxt.isEnabled(DeserializationFeature.USE_BIG_INTEGER_FOR_INTS)) { return jp.getBigIntegerValue(); // should be optimal, whatever it is } return jp.getNumberValue(); // should be optimal, whatever it is case VALUE_NUMBER_FLOAT: /* [JACKSON-72]: need to allow overriding the behavior regarding * which type to use */ if (ctxt.isEnabled(DeserializationFeature.USE_BIG_DECIMAL_FOR_FLOATS)) { return jp.getDecimalValue(); } return Double.valueOf(jp.getDoubleValue()); case VALUE_TRUE: return Boolean.TRUE; case VALUE_FALSE: return Boolean.FALSE; case VALUE_NULL: // should not get this but... return null; } throw ctxt.mappingException(Object.class); } @Override public Object deserializeWithType(JsonParser jp, DeserializationContext ctxt, TypeDeserializer typeDeserializer) throws IOException, JsonProcessingException { JsonToken t = jp.getCurrentToken(); switch (t) { // First: does it look like we had type id wrapping of some kind? case START_ARRAY: case START_OBJECT: case FIELD_NAME: /* Output can be as JSON Object, Array or scalar: no way to know * a this point: */ return typeDeserializer.deserializeTypedFromAny(jp, ctxt); /* Otherwise we probably got a "native" type (ones that map * naturally and thus do not need or use type ids) */ case VALUE_STRING: return jp.getText(); case VALUE_NUMBER_INT: // For [JACKSON-100], see above: if (ctxt.isEnabled(DeserializationFeature.USE_BIG_INTEGER_FOR_INTS)) { return jp.getBigIntegerValue(); } /* and as per [JACKSON-839], allow "upgrade" to bigger types: out-of-range * entries can not be produced without type, so this should "just work", * even if it is bit unclean */ return jp.getNumberValue(); case VALUE_NUMBER_FLOAT: // For [JACKSON-72], see above if (ctxt.isEnabled(DeserializationFeature.USE_BIG_DECIMAL_FOR_FLOATS)) { return jp.getDecimalValue(); } return Double.valueOf(jp.getDoubleValue()); case VALUE_TRUE: return Boolean.TRUE; case VALUE_FALSE: return Boolean.FALSE; case VALUE_EMBEDDED_OBJECT: return jp.getEmbeddedObject(); case VALUE_NULL: // should not get this far really but... return null; } throw ctxt.mappingException(Object.class); } /* /********************************************************** /* Internal methods /********************************************************** */ /** * Method called to map a JSON Array into a Java value. */ protected Object mapArray(JsonParser jp, DeserializationContext ctxt) throws IOException, JsonProcessingException { if (ctxt.isEnabled(DeserializationFeature.USE_JAVA_ARRAY_FOR_JSON_ARRAY)) { return mapArrayToArray(jp, ctxt); } // Minor optimization to handle small lists (default size for ArrayList is 10) if (jp.nextToken() == JsonToken.END_ARRAY) { return new ArrayList<Object>(4); } ObjectBuffer buffer = ctxt.leaseObjectBuffer(); Object[] values = buffer.resetAndStart(); int ptr = 0; int totalSize = 0; do { Object value = deserialize(jp, ctxt); ++totalSize; if (ptr >= values.length) { values = buffer.appendCompletedChunk(values); ptr = 0; } values[ptr++] = value; } while (jp.nextToken() != JsonToken.END_ARRAY); // let's create almost full array, with 1/8 slack ArrayList<Object> result = new ArrayList<Object>(totalSize + (totalSize >> 3) + 1); buffer.completeAndClearBuffer(values, ptr, result); return result; } /** * Method called to map a JSON Object into a Java value. */ protected Object mapObject(JsonParser jp, DeserializationContext ctxt) throws IOException, JsonProcessingException { JsonToken t = jp.getCurrentToken(); if (t == JsonToken.START_OBJECT) { t = jp.nextToken(); } // 1.6: minor optimization; let's handle 1 and 2 entry cases separately if (t != JsonToken.FIELD_NAME) { // and empty one too // empty map might work; but caller may want to modify... so better just give small modifiable return new LinkedHashMap<String,Object>(4); } String field1 = jp.getText(); jp.nextToken(); Object value1 = deserialize(jp, ctxt); if (jp.nextToken() != JsonToken.FIELD_NAME) { // single entry; but we want modifiable LinkedHashMap<String, Object> result = new LinkedHashMap<String, Object>(4); result.put(field1, value1); return result; } String field2 = jp.getText(); jp.nextToken(); Object value2 = deserialize(jp, ctxt); if (jp.nextToken() != JsonToken.FIELD_NAME) { LinkedHashMap<String, Object> result = new LinkedHashMap<String, Object>(4); result.put(field1, value1); result.put(field2, value2); return result; } // And then the general case; default map size is 16 LinkedHashMap<String, Object> result = new LinkedHashMap<String, Object>(); result.put(field1, value1); result.put(field2, value2); do { String fieldName = jp.getText(); jp.nextToken(); result.put(fieldName, deserialize(jp, ctxt)); } while (jp.nextToken() != JsonToken.END_OBJECT); return result; } /** * Method called to map a JSON Array into a Java Object array (Object[]). */ protected Object[] mapArrayToArray(JsonParser jp, DeserializationContext ctxt) throws IOException, JsonProcessingException { // Minor optimization to handle small lists (default size for ArrayList is 10) if (jp.nextToken() == JsonToken.END_ARRAY) { return NO_OBJECTS; } ObjectBuffer buffer = ctxt.leaseObjectBuffer(); Object[] values = buffer.resetAndStart(); int ptr = 0; do { Object value = deserialize(jp, ctxt); if (ptr >= values.length) { values = buffer.appendCompletedChunk(values); ptr = 0; } values[ptr++] = value; } while (jp.nextToken() != JsonToken.END_ARRAY); return buffer.completeAndClearBuffer(values, ptr); } }