package com.fasterxml.jackson.databind.deser.impl; import java.io.IOException; import java.util.HashSet; import com.fasterxml.jackson.core.*; import com.fasterxml.jackson.databind.*; import com.fasterxml.jackson.databind.deser.*; import com.fasterxml.jackson.databind.introspect.AnnotatedMethod; import com.fasterxml.jackson.databind.util.NameTransformer; public class BeanAsArrayBuilderDeserializer extends BeanDeserializerBase { private static final long serialVersionUID = 1L; /** * Deserializer we delegate operations that we can not handle. */ protected final BeanDeserializerBase _delegate; /** * Properties in order expected to be found in JSON array. */ protected final SettableBeanProperty[] _orderedProperties; protected final AnnotatedMethod _buildMethod; /* /********************************************************** /* Life-cycle, construction, initialization /********************************************************** */ /** * Main constructor used both for creating new instances (by * {@link BeanDeserializer#asArrayDeserializer}) and for * creating copies with different delegate. */ public BeanAsArrayBuilderDeserializer(BeanDeserializerBase delegate, SettableBeanProperty[] ordered, AnnotatedMethod buildMethod) { super(delegate); _delegate = delegate; _orderedProperties = ordered; _buildMethod = buildMethod; } @Override public JsonDeserializer<Object> unwrappingDeserializer(NameTransformer unwrapper) { /* We can't do much about this; could either replace _delegate * with unwrapping instance, or just replace this one. Latter seems * more sensible. */ return _delegate.unwrappingDeserializer(unwrapper); } @Override public BeanAsArrayBuilderDeserializer withObjectIdReader(ObjectIdReader oir) { return new BeanAsArrayBuilderDeserializer(_delegate.withObjectIdReader(oir), _orderedProperties, _buildMethod); } @Override public BeanAsArrayBuilderDeserializer withIgnorableProperties(HashSet<String> ignorableProps) { return new BeanAsArrayBuilderDeserializer(_delegate.withIgnorableProperties(ignorableProps), _orderedProperties, _buildMethod); } @Override protected BeanAsArrayBuilderDeserializer asArrayDeserializer() { return this; } /* /********************************************************** /* JsonDeserializer implementation /********************************************************** */ protected final Object finishBuild(DeserializationContext ctxt, Object builder) throws IOException { try { return _buildMethod.getMember().invoke(builder); } catch (Exception e) { wrapInstantiationProblem(e, ctxt); return null; } } @Override public Object deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException, JsonProcessingException { // Let's delegate just in case we got a JSON Object (could error out, alternatively?) if (jp.getCurrentToken() != JsonToken.START_ARRAY) { return finishBuild(ctxt, _deserializeFromNonArray(jp, ctxt)); } if (!_vanillaProcessing) { return finishBuild(ctxt, _deserializeNonVanilla(jp, ctxt)); } Object builder = _valueInstantiator.createUsingDefault(ctxt); final SettableBeanProperty[] props = _orderedProperties; int i = 0; final int propCount = props.length; while (true) { if (jp.nextToken() == JsonToken.END_ARRAY) { return finishBuild(ctxt, builder); } if (i == propCount) { break; } SettableBeanProperty prop = props[i]; if (prop != null) { // normal case try { builder = prop.deserializeSetAndReturn(jp, ctxt, builder); } catch (Exception e) { wrapAndThrow(e, builder, prop.getName(), ctxt); } } else { // just skip? jp.skipChildren(); } ++i; } // Ok; extra fields? Let's fail, unless ignoring extra props is fine if (!_ignoreAllUnknown) { throw ctxt.mappingException("Unexpected JSON values; expected at most "+propCount+" properties (in JSON Array)"); } // otherwise, skip until end while (jp.nextToken() != JsonToken.END_ARRAY) { jp.skipChildren(); } return finishBuild(ctxt, builder); } @Override public Object deserialize(JsonParser jp, DeserializationContext ctxt, Object builder) throws IOException, JsonProcessingException { /* No good way to verify that we have an array... although could I guess * check via JsonParser. So let's assume everything is working fine, for now. */ if (_injectables != null) { injectValues(ctxt, builder); } final SettableBeanProperty[] props = _orderedProperties; int i = 0; final int propCount = props.length; while (true) { if (jp.nextToken() == JsonToken.END_ARRAY) { return finishBuild(ctxt, builder); } if (i == propCount) { break; } SettableBeanProperty prop = props[i]; if (prop != null) { // normal case try { builder = prop.deserializeSetAndReturn(jp, ctxt, builder); } catch (Exception e) { wrapAndThrow(e, builder, prop.getName(), ctxt); } } else { // just skip? jp.skipChildren(); } ++i; } // Ok; extra fields? Let's fail, unless ignoring extra props is fine if (!_ignoreAllUnknown) { throw ctxt.mappingException("Unexpected JSON values; expected at most "+propCount+" properties (in JSON Array)"); } // otherwise, skip until end while (jp.nextToken() != JsonToken.END_ARRAY) { jp.skipChildren(); } return finishBuild(ctxt, builder); } /* /********************************************************** /* Helper methods, non-standard creation /********************************************************** */ /** * Alternate deserialization method that has to check many more configuration * aspects than the "vanilla" processing. * Note: should NOT resolve builder; caller will do that * * @return Builder object in use. */ protected Object _deserializeNonVanilla(JsonParser jp, DeserializationContext ctxt) throws IOException, JsonProcessingException { if (_nonStandardCreation) { return _deserializeWithCreator(jp, ctxt); } Object builder = _valueInstantiator.createUsingDefault(ctxt); if (_injectables != null) { injectValues(ctxt, builder); } Class<?> activeView = _needViewProcesing ? ctxt.getActiveView() : null; final SettableBeanProperty[] props = _orderedProperties; int i = 0; final int propCount = props.length; while (true) { if (jp.nextToken() == JsonToken.END_ARRAY) { return builder; } if (i == propCount) { break; } SettableBeanProperty prop = props[i]; ++i; if (prop != null) { // normal case if (activeView == null || prop.visibleInView(activeView)) { try { prop.deserializeSetAndReturn(jp, ctxt, builder); } catch (Exception e) { wrapAndThrow(e, builder, prop.getName(), ctxt); } continue; } } // otherwise, skip it (view-filtered, no prop etc) jp.skipChildren(); } // Ok; extra fields? Let's fail, unless ignoring extra props is fine if (!_ignoreAllUnknown) { throw ctxt.mappingException("Unexpected JSON values; expected at most "+propCount+" properties (in JSON Array)"); } // otherwise, skip until end while (jp.nextToken() != JsonToken.END_ARRAY) { jp.skipChildren(); } return builder; } protected Object _deserializeWithCreator(JsonParser jp, DeserializationContext ctxt) throws IOException, JsonProcessingException { if (_delegateDeserializer != null) { return _valueInstantiator.createUsingDelegate(ctxt, _delegateDeserializer.deserialize(jp, ctxt)); } if (_propertyBasedCreator != null) { return _deserializeUsingPropertyBased(jp, ctxt); } // should only occur for abstract types... if (_beanType.isAbstract()) { throw JsonMappingException.from(jp, "Can not instantiate abstract type "+_beanType +" (need to add/enable type information?)"); } throw JsonMappingException.from(jp, "No suitable constructor found for type " +_beanType+": can not instantiate from JSON object (need to add/enable type information?)"); } /** * Method called to deserialize bean using "property-based creator": * this means that a non-default constructor or factory method is * called, and then possibly other setters. The trick is that * values for creator method need to be buffered, first; and * due to non-guaranteed ordering possibly some other properties * as well. */ protected final Object _deserializeUsingPropertyBased(final JsonParser jp, final DeserializationContext ctxt) throws IOException, JsonProcessingException { final PropertyBasedCreator creator = _propertyBasedCreator; PropertyValueBuffer buffer = creator.startBuilding(jp, ctxt, _objectIdReader); final SettableBeanProperty[] props = _orderedProperties; final int propCount = props.length; int i = 0; Object builder = null; for (; jp.nextToken() != JsonToken.END_ARRAY; ++i) { SettableBeanProperty prop = (i < propCount) ? props[i] : null; if (prop == null) { // we get null if there are extra elements; maybe otherwise too? jp.skipChildren(); continue; } // if we have already constructed POJO, things are simple: if (builder != null) { try { builder = prop.deserializeSetAndReturn(jp, ctxt, builder); } catch (Exception e) { wrapAndThrow(e, builder, prop.getName(), ctxt); } continue; } final String propName = prop.getName(); // if not yet, maybe we got a creator property? SettableBeanProperty creatorProp = creator.findCreatorProperty(propName); if (creatorProp != null) { // Last creator property to set? Object value = creatorProp.deserialize(jp, ctxt); if (buffer.assignParameter(creatorProp.getCreatorIndex(), value)) { try { builder = creator.build(ctxt, buffer); } catch (Exception e) { wrapAndThrow(e, _beanType.getRawClass(), propName, ctxt); continue; // never gets here } // polymorphic? if (builder.getClass() != _beanType.getRawClass()) { /* 23-Jul-2012, tatu: Not sure if these could ever be properly * supported (since ordering of elements may not be guaranteed); * but make explicitly non-supported for now. */ throw ctxt.mappingException("Can not support implicit polymorphic deserialization for POJOs-as-Arrays style: " +"nominal type "+_beanType.getRawClass().getName()+", actual type "+builder.getClass().getName()); } } continue; } // Object Id property? if (buffer.readIdProperty(propName)) { continue; } // regular property? needs buffering buffer.bufferProperty(prop, prop.deserialize(jp, ctxt)); } // In case we didn't quite get all the creator properties, we may have to do this: if (builder == null) { try { builder = creator.build(ctxt, buffer); } catch (Exception e) { wrapInstantiationProblem(e, ctxt); return null; // never gets here } } return builder; } /* /********************************************************** /* Helper methods, error reporting /********************************************************** */ protected Object _deserializeFromNonArray(JsonParser jp, DeserializationContext ctxt) throws IOException, JsonProcessingException { // Let's start with failure throw ctxt.mappingException("Can not deserialize a POJO (of type "+_beanType.getRawClass().getName() +") from non-Array representation (token: "+jp.getCurrentToken() +"): type/property designed to be serialized as JSON Array"); // in future, may allow use of "standard" POJO serialization as well; if so, do: //return _delegate.deserialize(jp, ctxt); } }