Java tutorial
/******************************************************************************* * Copyright 2015, The IKANOW Open Source Project. * * 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. *******************************************************************************/ package com.ikanow.aleph2.data_model.utils; import java.io.IOException; import java.math.BigDecimal; import java.util.Iterator; import java.util.LinkedHashMap; import java.util.Map; import java.util.Optional; import java.util.stream.Collectors; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.node.JsonNodeType; import com.fasterxml.jackson.databind.node.ObjectNode; import com.fasterxml.jackson.databind.node.POJONode; /** Utility classes for managing JSON transforms * @author Alex */ public class JsonUtils { protected static final ObjectMapper _mapper = BeanTemplateUtils.configureMapper(Optional.empty()); public static final String _ID = "_id"; // (useful const) /** Takes a tuple expressed as LinkedHashMap<String, Object> (by convention the Objects are primitives, JsonNode, or POJO), and where one of the objects * is a JSON representation of the original object and creates an object by folding them all together * Note the other fields of the tuple take precedence over the JSON * @param in - the tuple * @param mapper - the Jackson object mapper * @param json_field - optional fieldname of the string representation of the JSON - if not present then the last field is used (set to eg "" if there is no base object) * @return */ public static JsonNode foldTuple(final LinkedHashMap<String, Object> in, final ObjectMapper mapper, final Optional<String> json_field) { try { // (do this imperatively to handle the "last element can be the base object case" final Iterator<Map.Entry<String, Object>> it = in.entrySet().iterator(); ObjectNode acc = mapper.createObjectNode(); while (it.hasNext()) { final Map.Entry<String, Object> kv = it.next(); if ((json_field.isPresent() && kv.getKey().equals(json_field.get())) || !json_field.isPresent() && !it.hasNext()) { acc = (ObjectNode) ((ObjectNode) mapper.readTree(kv.getValue().toString())).setAll(acc); } else { final ObjectNode acc_tmp = acc; Patterns.match(kv.getValue()).andAct().when(String.class, s -> acc_tmp.put(kv.getKey(), s)) .when(Long.class, l -> acc_tmp.put(kv.getKey(), l)) .when(Integer.class, l -> acc_tmp.put(kv.getKey(), l)) .when(Boolean.class, b -> acc_tmp.put(kv.getKey(), b)) .when(Double.class, d -> acc_tmp.put(kv.getKey(), d)) .when(JsonNode.class, j -> acc_tmp.set(kv.getKey(), j)) .when(Float.class, f -> acc_tmp.put(kv.getKey(), f)) .when(BigDecimal.class, f -> acc_tmp.put(kv.getKey(), f)) .otherwise(x -> acc_tmp.set(kv.getKey(), BeanTemplateUtils.toJson(x))); } } return acc; } catch (Exception e) { throw new RuntimeException(e); } // (convert to unchecked exception) } /** Returns a nested sub-element from a path in dot notation, else empty * @param path in dot notation * @return */ public static Optional<JsonNode> getProperty(final String path, final JsonNode obj) { final String[] paths = path.split("[.]"); final String last = paths[paths.length - 1]; JsonNode mutable_curr = obj; for (String p : paths) { final JsonNode j = mutable_curr.get(p); if (last == p) { return Optional.ofNullable(j).filter(__ -> !j.isNull()); } else if (null == j) { return Optional.empty(); } else if (j.isArray()) { // if it's an array get the first value - for anything more complicated need jpath mutable_curr = j.get(0); } else if (j.isObject()) { mutable_curr = j; } else return Optional.empty(); // not at the end of the chain and it's not something you can map through } return Optional.empty(); } /** Converts (possibly recursively) a JsonNode to its Java equivalent * @param to_convert - the JsonNode to convert to... * @return - ...a Java primitive, or Map<String, Object>, or List<Object> (where Object is a java type) */ public static Object jacksonToJava(final JsonNode to_convert) { final JsonNodeType type = to_convert.getNodeType(); // (we'll go old school for this...) switch (type) { case ARRAY: return Optionals.streamOf(to_convert.elements(), false).map(j -> jacksonToJava(j)) .collect(Collectors.toList()); case BINARY: try { return to_convert.binaryValue(); } catch (IOException e) { return null; } case BOOLEAN: return to_convert.asBoolean(); case NUMBER: if (to_convert.isFloatingPointNumber()) { return to_convert.asDouble(); } else { return to_convert.asLong(); } case OBJECT: return _mapper.convertValue(to_convert, Map.class); case POJO: return ((POJONode) to_convert).getPojo(); case STRING: return to_convert.asText(); default: // (MISSING, NULL) return null; } } }