blob: c1ad75a4efa95b09d293123bda7676409eb1a71f [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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 org.apache.freemarker.generator.base.util;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Map.Entry;
import java.util.function.Function;
import java.util.function.UnaryOperator;
/**
* Flattens a hierarchical {@link Map} of objects into a property {@link Map}.
* <p>
* Flattening is particularly useful when representing a JSON object as
* {@link java.util.Properties}
* <p>
* {@link MapFlattener} flattens {@link Map maps} containing nested
* {@link java.util.List}, {@link Map} and simple values into a flat representation. The
* hierarchical structure is reflected in properties using dot-notation. Nested maps are
* considered as sub-documents.
* <p>
* Input:
*
* <pre class="code">
* {"key": {"nested: 1}, "another.key": ["one", "two"] }
* </pre>
*
* <br>
* Result
*
* <pre class="code">
* key.nested=1
* another.key[0]=one
* another.key[1]=two
* </pre>
*
* @author Mark Paluch
* <p>
* Copied from https://github.com/spring-projects/spring-vault/blob/master/spring-vault-core/src/main/java/org/springframework/vault/support/JsonMapFlattener.java
*/
public abstract class MapFlattener {
private MapFlattener() {
}
/**
* Flatten a hierarchical {@link Map} into a flat {@link Map} with key names using
* property dot notation.
*
* @param inputMap must not be {@literal null}.
* @return the resulting {@link Map}.
*/
public static Map<String, Object> flatten(Map<String, ?> inputMap) {
Validate.notNull(inputMap, "Input Map must not be null");
final Map<String, Object> resultMap = new LinkedHashMap<>();
doFlatten("", inputMap.entrySet().iterator(), resultMap, UnaryOperator.identity());
return resultMap;
}
/**
* Flatten a hierarchical {@link Map} into a flat {@link Map} with key names using
* property dot notation.
*
* @param inputMap must not be {@literal null}.
* @return the resulting {@link Map}.
* @since 2.0
*/
public static Map<String, String> flattenToStringMap(Map<String, ?> inputMap) {
Validate.notNull(inputMap, "inputMap is null");
final Map<String, String> resultMap = new LinkedHashMap<>();
doFlatten("", inputMap.entrySet().iterator(), resultMap, it -> it == null ? null : it.toString());
return resultMap;
}
private static void doFlatten(String propertyPrefix,
Iterator<? extends Entry<String, ?>> inputMap,
Map<String, ?> resultMap,
Function<Object, Object> valueTransformer) {
if (StringUtils.isNotEmpty(propertyPrefix)) {
propertyPrefix = propertyPrefix + ".";
}
while (inputMap.hasNext()) {
final Entry<String, ?> entry = inputMap.next();
flattenElement(
propertyPrefix.concat(entry.getKey()),
entry.getValue(),
resultMap,
valueTransformer);
}
}
@SuppressWarnings({ "rawtypes", "unchecked" })
private static void flattenElement(String propertyPrefix, Object source,
Map<String, ?> resultMap, Function<Object, Object> valueTransformer) {
if (source instanceof Iterable) {
flattenCollection(propertyPrefix, (Iterable<Object>) source, resultMap,
valueTransformer);
return;
}
if (source instanceof Map) {
doFlatten(propertyPrefix, ((Map<String, ?>) source).entrySet().iterator(),
resultMap, valueTransformer);
return;
}
((Map) resultMap).put(propertyPrefix, valueTransformer.apply(source));
}
private static void flattenCollection(String propertyPrefix,
Iterable<Object> iterable, Map<String, ?> resultMap,
Function<Object, Object> valueTransformer) {
int counter = 0;
for (Object element : iterable) {
flattenElement(propertyPrefix + "[" + counter + "]", element, resultMap, valueTransformer);
counter++;
}
}
}