001/*
002 * Copyright 2012-2018 the original author or authors.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 *      http://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016
017package org.springframework.boot.jackson;
018
019import java.io.IOException;
020import java.math.BigDecimal;
021import java.math.BigInteger;
022
023import com.fasterxml.jackson.core.JsonParser;
024import com.fasterxml.jackson.core.ObjectCodec;
025import com.fasterxml.jackson.core.TreeNode;
026import com.fasterxml.jackson.databind.DeserializationContext;
027import com.fasterxml.jackson.databind.JsonDeserializer;
028import com.fasterxml.jackson.databind.JsonMappingException;
029import com.fasterxml.jackson.databind.JsonNode;
030import com.fasterxml.jackson.databind.node.NullNode;
031
032import org.springframework.util.Assert;
033
034/**
035 * Helper base class for {@link JsonDeserializer} implementations that deserialize
036 * objects.
037 *
038 * @param <T> the supported object type
039 * @author Phillip Webb
040 * @since 1.4.0
041 * @see JsonObjectSerializer
042 */
043public abstract class JsonObjectDeserializer<T>
044                extends com.fasterxml.jackson.databind.JsonDeserializer<T> {
045
046        @Override
047        public final T deserialize(JsonParser jp, DeserializationContext ctxt)
048                        throws IOException {
049                try {
050                        ObjectCodec codec = jp.getCodec();
051                        JsonNode tree = codec.readTree(jp);
052                        return deserializeObject(jp, ctxt, codec, tree);
053                }
054                catch (Exception ex) {
055                        if (ex instanceof IOException) {
056                                throw (IOException) ex;
057                        }
058                        throw new JsonMappingException(jp, "Object deserialize error", ex);
059                }
060        }
061
062        /**
063         * Deserialize JSON content into the value type this serializer handles.
064         * @param jsonParser the source parser used for reading JSON content
065         * @param context context that can be used to access information about this
066         * deserialization activity
067         * @param codec the {@link ObjectCodec} associated with the parser
068         * @param tree deserialized JSON content as tree expressed using set of
069         * {@link TreeNode} instances
070         * @return the deserialized object
071         * @throws IOException on error
072         * @see #deserialize(JsonParser, DeserializationContext)
073         */
074        protected abstract T deserializeObject(JsonParser jsonParser,
075                        DeserializationContext context, ObjectCodec codec, JsonNode tree)
076                        throws IOException;
077
078        /**
079         * Helper method to extract a value from the given {@code jsonNode} or return
080         * {@code null} when the node itself is {@code null}.
081         * @param jsonNode the source node (may be {@code null})
082         * @param type the data type. May be {@link String}, {@link Boolean}, {@link Long},
083         * {@link Integer}, {@link Short}, {@link Double}, {@link Float}, {@link BigDecimal}
084         * or {@link BigInteger}.
085         * @param <D> the data type requested
086         * @return the node value or {@code null}
087         */
088        @SuppressWarnings({ "unchecked" })
089        protected final <D> D nullSafeValue(JsonNode jsonNode, Class<D> type) {
090                Assert.notNull(type, "Type must not be null");
091                if (jsonNode == null) {
092                        return null;
093                }
094                if (type == String.class) {
095                        return (D) jsonNode.textValue();
096                }
097                if (type == Boolean.class) {
098                        return (D) Boolean.valueOf(jsonNode.booleanValue());
099                }
100                if (type == Long.class) {
101                        return (D) Long.valueOf(jsonNode.longValue());
102                }
103                if (type == Integer.class) {
104                        return (D) Integer.valueOf(jsonNode.intValue());
105                }
106                if (type == Short.class) {
107                        return (D) Short.valueOf(jsonNode.shortValue());
108                }
109                if (type == Double.class) {
110                        return (D) Double.valueOf(jsonNode.doubleValue());
111                }
112                if (type == Float.class) {
113                        return (D) Float.valueOf(jsonNode.floatValue());
114                }
115                if (type == BigDecimal.class) {
116                        return (D) jsonNode.decimalValue();
117                }
118                if (type == BigInteger.class) {
119                        return (D) jsonNode.bigIntegerValue();
120                }
121                throw new IllegalArgumentException("Unsupported value type " + type.getName());
122        }
123
124        /**
125         * Helper method to return a {@link JsonNode} from the tree.
126         * @param tree the source tree
127         * @param fieldName the field name to extract
128         * @return the {@link JsonNode}
129         */
130        protected final JsonNode getRequiredNode(JsonNode tree, String fieldName) {
131                Assert.notNull(tree, "Tree must not be null");
132                JsonNode node = tree.get(fieldName);
133                Assert.state(node != null && !(node instanceof NullNode),
134                                () -> "Missing JSON field '" + fieldName + "'");
135                return node;
136        }
137
138}