001 002package io.vrap.rmf.base.client.utils.json; 003 004import java.io.IOException; 005import java.io.InputStream; 006import java.util.*; 007 008import com.fasterxml.jackson.annotation.JsonInclude; 009import com.fasterxml.jackson.core.JsonProcessingException; 010import com.fasterxml.jackson.core.type.TypeReference; 011import com.fasterxml.jackson.databind.*; 012import com.fasterxml.jackson.databind.module.SimpleModule; 013import com.fasterxml.jackson.databind.node.ArrayNode; 014import com.fasterxml.jackson.databind.node.ObjectNode; 015import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; 016 017import io.vrap.rmf.base.client.utils.json.modules.ModuleOptions; 018import io.vrap.rmf.base.client.utils.json.modules.ZonedDateTimeDeserializationModule; 019import io.vrap.rmf.base.client.utils.json.modules.ZonedDateTimeSerializationModule; 020 021/** 022 * Class with methods to customize the JSON serialization/deserialization 023 */ 024public class JsonUtils { 025 026 private static final ObjectMapper OBJECT_MAPPER; 027 028 static { 029 OBJECT_MAPPER = createObjectMapper(); 030 } 031 032 /** 033 * creates a new {@link ObjectMapper } instance 034 * @return ObjectMapper 035 */ 036 public static ObjectMapper createObjectMapper() { 037 return createObjectMapper(name -> null); 038 } 039 040 /** 041 * 042 * @param options configuration for jackson modules supplied by a {@link ModuleSupplier} 043 * @return ObjectMapper 044 */ 045 public static ObjectMapper createObjectMapper(final ModuleOptions options) { 046 ServiceLoader<SimpleModule> loader = ServiceLoader.load(SimpleModule.class, 047 SimpleModule.class.getClassLoader()); 048 049 ServiceLoader<ModuleSupplier> suppliers = ServiceLoader.load(ModuleSupplier.class, 050 ModuleSupplier.class.getClassLoader()); 051 final List<SimpleModule> moduleList = new ArrayList<>(); 052 suppliers.iterator().forEachRemaining(moduleSupplier -> moduleList.add(moduleSupplier.getModule(options))); 053 054 final ObjectMapper objectMapper = new ObjectMapper(); 055 objectMapper.registerModule(new JavaTimeModule()) //provides serialization and deserialization for LocalDate and LocalTime (JSR310 Jackson module) 056 .registerModule(new ZonedDateTimeSerializationModule()) //custom serializer for LocalDate, LocalTime and ZonedDateTime 057 .registerModule(new ZonedDateTimeDeserializationModule()) //custom deserializer for ZonedDateTime 058 .registerModules(loader) 059 .registerModules(moduleList) 060 .setSerializationInclusion(JsonInclude.Include.NON_NULL) //ignore null fields 061 .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false) 062 .configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false); 063 return objectMapper; 064 } 065 066 /** 067 * serializes the given object to JSON as a byte array 068 * @param value object to be serialized 069 * @return json byte array 070 * @throws JsonProcessingException serialization errors 071 */ 072 public static byte[] toJsonByteArray(final Object value) throws JsonProcessingException { 073 return OBJECT_MAPPER.writeValueAsBytes(value); 074 } 075 076 /** 077 * serializes the given object to JSON as a byte array 078 * @param value object to be serialized 079 * @return json string 080 * @throws JsonProcessingException serialization errors 081 */ 082 public static String toJsonString(final Object value) throws JsonProcessingException { 083 return OBJECT_MAPPER.writeValueAsString(value); 084 } 085 086 /** 087 * deserializes the given json string to the given class 088 * @param clazz class to serialize to 089 * @param content json as string 090 * @param <T> type of the result 091 * @return deserialized object 092 */ 093 public static <T> T fromJsonString(final String content, final Class<T> clazz) { 094 return executing(() -> OBJECT_MAPPER.readValue(content, clazz)); 095 } 096 097 /** 098 * Reads a Java object from JSON data (String). 099 * 100 * @param jsonAsString the JSON data which represents sth. of type {@code <T>} 101 * @param typeReference the full generic type information about the object to create 102 * @param <T> the type of the result 103 * @return the created objected 104 */ 105 public static <T> T fromJsonString(final String jsonAsString, final TypeReference<T> typeReference) { 106 return executing(() -> OBJECT_MAPPER.readValue(jsonAsString, typeReference)); 107 } 108 109 /** 110 * Reads a Java object from JsonNode data. 111 * <p> 112 * 113 * @param jsonNode the JSON data which represents sth. of type {@code <T>} 114 * @param typeReference the full generic type information about the object to create 115 * @param <T> the type of the result 116 * @return the created objected 117 */ 118 public static <T> T fromJsonNode(final JsonNode jsonNode, final TypeReference<T> typeReference) { 119 return executing(() -> OBJECT_MAPPER.readerFor(typeReference).readValue(jsonNode)); 120 } 121 122 /** 123 * Converts a commercetools Composable Commerce Java object to JSON as {@link JsonNode}. 124 * <p>If {@code value} is of type String and contains JSON data, that will be ignored, {@code value} will be treated as just any String. 125 * If you want to parse a JSON String to a JsonNode use {@link JsonUtils#parse(java.lang.String)} instead.</p> 126 * <p> 127 * 128 * @param value the object to convert 129 * @return new json 130 */ 131 public static JsonNode toJsonNode(final Object value) { 132 return OBJECT_MAPPER.valueToTree(value); 133 } 134 135 /** 136 * Parses a String containing JSON data and produces a {@link JsonNode}. 137 * 138 * @param jsonAsString json data 139 * @return new JsonNode 140 */ 141 public static JsonNode parse(final String jsonAsString) { 142 return executing(() -> OBJECT_MAPPER.readTree(jsonAsString)); 143 } 144 145 /** 146 * deserializes the given json string to the given class 147 * @param clazz class to serialize to 148 * @param content json as byte array 149 * @return deserialized object 150 * @throws JsonException deserialization errors 151 * @param <T> type of the result 152 */ 153 public static <T> T fromJsonByteArray(final byte[] content, final Class<T> clazz) { 154 return executing(() -> OBJECT_MAPPER.readValue(content, clazz)); 155 } 156 157 /** 158 * deserializes the given json string to the given class 159 * @param clazz class to serialize to 160 * @param content json as inputstream 161 * @return deserialized object 162 * @throws JsonException deserialization errors 163 * @param <T> type of the result 164 */ 165 public static <T> T fromInputStream(final InputStream content, final Class<T> clazz) { 166 return executing(() -> OBJECT_MAPPER.readValue(content, clazz)); 167 } 168 169 /** 170 * deserializes the given json string to the given class 171 * @param typeReference the full generic type information about the object to create 172 * @param content json as inputstream 173 * @return deserialized object 174 * @throws JsonException deserialization errors 175 * @param <T> type of the result 176 */ 177 public static <T> T fromInputStream(final InputStream content, final TypeReference<T> typeReference) { 178 return executing(() -> OBJECT_MAPPER.readValue(content, typeReference)); 179 } 180 181 /** 182 * default {@link ObjectMapper} 183 * @return ObjectMapper 184 */ 185 public static ObjectMapper getConfiguredObjectMapper() { 186 return OBJECT_MAPPER; 187 } 188 189 /** 190 * Very simple way to "erase" passwords - 191 * replaces all field values whose names contains {@code 'pass'} by {@code '**removed from output**'}. 192 * @param node Json object to be redacted 193 * @return Json object 194 */ 195 private static JsonNode secure(final JsonNode node) { 196 if (node.isObject()) { 197 ObjectNode objectNode = (ObjectNode) node; 198 Iterator<Map.Entry<String, JsonNode>> fields = node.fields(); 199 while (fields.hasNext()) { 200 Map.Entry<String, JsonNode> field = fields.next(); 201 if (field.getValue().isTextual() && (field.getKey().toLowerCase().contains("pass") 202 || field.getKey().toLowerCase().contains("access_token") 203 || field.getKey().toLowerCase().contains("refresh_token"))) { 204 objectNode.put(field.getKey(), "**removed from output**"); 205 } 206 else { 207 secure(field.getValue()); 208 } 209 } 210 return objectNode; 211 } 212 else if (node.isArray()) { 213 ArrayNode arrayNode = (ArrayNode) node; 214 Iterator<JsonNode> elements = arrayNode.elements(); 215 while (elements.hasNext()) { 216 secure(elements.next()); 217 } 218 return arrayNode; 219 } 220 else { 221 return node; 222 } 223 } 224 225 /** 226 * Pretty prints a given JSON string. 227 * 228 * @param json JSON code as String which should be formatted 229 * @return <code>json</code> formatted 230 */ 231 public static String prettyPrint(final String json) { 232 return executing(() -> { 233 final ObjectMapper jsonParser = new ObjectMapper(); 234 final JsonNode jsonTree = jsonParser.readValue(json, JsonNode.class); 235 secure(jsonTree); 236 final ObjectWriter writer = jsonParser.writerWithDefaultPrettyPrinter(); 237 return writer.writeValueAsString(jsonTree); 238 }); 239 } 240 241 public static <T> T executing(final SupplierThrowingIOException<T> supplier) { 242 try { 243 return supplier.get(); 244 } 245 catch (final IOException e) { 246 throw new JsonException(e); 247 } 248 } 249 250 @FunctionalInterface 251 public interface SupplierThrowingIOException<T> { 252 T get() throws IOException; 253 } 254}