| package org.codehaus.jackson.map.deser.std; |
| |
| import java.io.IOException; |
| import java.util.*; |
| |
| import org.codehaus.jackson.JsonProcessingException; |
| import org.codehaus.jackson.JsonParser; |
| import org.codehaus.jackson.JsonToken; |
| import org.codehaus.jackson.map.DeserializationConfig; |
| import org.codehaus.jackson.map.DeserializationContext; |
| import org.codehaus.jackson.map.TypeDeserializer; |
| import org.codehaus.jackson.map.annotate.JacksonStdImpl; |
| import org.codehaus.jackson.map.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). |
| * |
| * @since 1.9 (moved from higher-level package) |
| */ |
| @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(DeserializationConfig.Feature.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(DeserializationConfig.Feature.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(DeserializationConfig.Feature.USE_BIG_INTEGER_FOR_INTS)) { |
| return jp.getBigIntegerValue(); |
| } |
| return jp.getIntValue(); |
| |
| case VALUE_NUMBER_FLOAT: |
| // For [JACKSON-72], see above |
| if (ctxt.isEnabled(DeserializationConfig.Feature.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(DeserializationConfig.Feature.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[]). |
| * |
| * @since 1.9 |
| */ |
| 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); |
| } |
| } |