Java tutorial
/* * #%L * wcm.io * %% * Copyright (C) 2014 wcm.io * %% * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * #L% */ package io.wcm.caravan.pipeline.impl; import io.wcm.caravan.pipeline.JsonPipelineInputException; import io.wcm.caravan.pipeline.JsonPipelineOutputException; import java.io.IOException; import java.io.StringWriter; import rx.functions.Func1; import com.fasterxml.jackson.core.JsonFactory; import com.fasterxml.jackson.core.JsonGenerator; import com.fasterxml.jackson.core.JsonParser; import com.fasterxml.jackson.databind.DeserializationFeature; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.node.ArrayNode; import com.fasterxml.jackson.databind.node.JsonNodeFactory; import com.fasterxml.jackson.databind.node.ObjectNode; /** * Contains common conversion functions between Jackson JSON nodes, Strings and Objects. */ public final class JacksonFunctions { private static ObjectMapper objectMapper = new ObjectMapper() .enable(DeserializationFeature.FAIL_ON_NULL_FOR_PRIMITIVES) .enable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES); private static JsonFactory jsonFactory = new JsonFactory(objectMapper) .enable(JsonParser.Feature.ALLOW_UNQUOTED_FIELD_NAMES) // this is mostly useful to write JSON like { a: 123 } in unit tests .enable(JsonParser.Feature.ALLOW_SINGLE_QUOTES); // this was also just added for convenience. private static JsonNodeFactory nodeFactory = JsonNodeFactory.withExactBigDecimals(false); // TODO: all these features should be made configurable private JacksonFunctions() { } // the basic conversions that don't need a type can simply be defined as static methods // when they are used as functions for Observable#map, you can reference them RxJackson::stringToNode /** * Parse the given JSON string using the default factory * @param jsonString a string with a valid JSON document * @return either a {@link ObjectNode} or {@link ArrayNode}, depending on the input * @throws JsonPipelineInputException if the input is not valid JSON */ public static JsonNode stringToNode(String jsonString) { JsonNode node = null; try { node = jsonFactory.createParser(jsonString).readValueAsTree(); } catch (IOException ex) { throw new JsonPipelineInputException(500, "Failed to parse JSON: " + jsonString, ex); } return node; }; /** * Parse the given JSON string using the default factory * @param jsonString a string with a valid JSON document * @return a {@link ObjectNode} * @throws JsonPipelineInputException if the input is not valid a JSON *Object* */ public static ObjectNode stringToObjectNode(String jsonString) { ObjectNode node = null; try { node = jsonFactory.createParser(jsonString).readValueAsTree(); } catch (IOException | ClassCastException ex) { throw new JsonPipelineInputException(500, "Failed to parse JSON object from: " + jsonString, ex); } return node; }; /** * Serializes the given JSON node using the default factory * @param node * @return JSON string * @throws JsonPipelineOutputException if the given node can not be serialized */ public static String nodeToString(JsonNode node) { try { StringWriter writer = new StringWriter(); JsonGenerator generator = jsonFactory.createGenerator(writer); generator.writeObject(node); return writer.toString(); } catch (IOException ex) { throw new JsonPipelineOutputException("Failed to serialize JsonNode. This was quite unexpected.", ex); } }; /** * Creates a new JSON object with a single property * @param targetProperty the name of the single property * @param propertyValue the value of the * @return the new ObjectNode */ public static ObjectNode wrapInObject(String targetProperty, JsonNode propertyValue) { ObjectNode objectNode = nodeFactory.objectNode(); objectNode.set(targetProperty, propertyValue); return objectNode; } /** * Create a JSON tree with the same structure as the given map * @param object that can be properly mapped to JSOn with Jackson (e.g. a POJO or Map) * @return an {@link ObjectNode} * @throws JsonPipelineInputException if the input is not valid JSON */ public static JsonNode pojoToNode(Object object) { JsonNode node = null; try { node = objectMapper.valueToTree(object); } catch (IllegalArgumentException ex) { throw new JsonPipelineInputException(500, "Failed to create JSONNode from object of class " + object.getClass().getName(), ex); } return node; }; /** * Serializes the given POJO into a JSON string using the default factory * @param pojo an object that can be serialized with Jackson's default {@link ObjectMapper} * @return JSON string * @throws JsonPipelineOutputException if the given object can not be serialized */ public static String pojoToString(Object pojo) { try { StringWriter writer = new StringWriter(); objectMapper.writeValue(writer, pojo); return writer.toString(); } catch (IOException ex) { throw new JsonPipelineOutputException("Failed to serialize entity to JSON string", ex); } }; // the functions that convert to a Java type depend on the target class, that's why we can't make them as plain // static functions. Instead they return a lambda that can be passed to Observable#map /** * Returns a function that will instantiate a bean of the given class, with values copied from the JSON string * @param targetType the POJO class that matches the expected JSON structure * @return a function that takes a JSON string and returns a new instance of the target type * @throws JsonPipelineInputException if the JSON could not be parsed, or the object not instantiated */ public static <T> Func1<String, T> stringToPojo(Class<T> targetType) { return jsonString -> { try { return jsonFactory.createParser(jsonString).readValueAs(targetType); } catch (IOException ex) { throw new JsonPipelineInputException(500, "Failed to create entity of " + targetType.getName() + " from JSON", ex); } }; } /** * Returns a function that will instantiate a bean of the given class, with values copied from the JSON string * @param targetType the POJO class that matches the expected JSON structure * @return a function that takes a JsonNode, and returns a new instance of the target type * @throws JsonPipelineInputException if the JSON could not be parsed, or the object not instantiated */ public static <T> Func1<JsonNode, T> nodeToPojo(Class<T> targetType) { return jsonNode -> { String jsonString = nodeToString(jsonNode); return stringToPojo(targetType).call(jsonString); }; } }