Я не думаю, что Gson - правильный инструмент, но мне действительно нравится этот вопрос.Это общий алгоритм, и вы можете реализовать его самостоятельно и обобщить для поддержки различных сценариев.Вот пример реализации, но, поскольку я слабо пишу алгоритмы, он может дать вам идею переопределить его гораздо лучшим образом.
final class Split {
private Split() {
}
static <E, R, K, V> R split(final Iterator<E> iterator, final Splitter<E, R, K, V> splitter) {
final R outerResult = splitter.newResult();
while ( iterator.hasNext() ) {
final E e = iterator.next();
doSplit(splitter, outerResult, e);
}
return outerResult;
}
static <E, R, K, V> R split(final Iterable<E> iterable, final Splitter<E, R, K, V> splitter) {
final R outerResult = splitter.newResult();
for ( final E e : iterable ) {
doSplit(splitter, outerResult, e);
}
return outerResult;
}
static <E, R, K, V> Collector<E, ?, R> asCollector(final Splitter<E, R, K, V> splitter) {
return Collector.of(
splitter::newResult,
(outerResult, e) -> doSplit(splitter, outerResult, e),
(r1, r2) -> {
throw new UnsupportedOperationException();
},
Function.identity()
);
}
private static <E, R, K, V> void doSplit(final Splitter<E, R, K, V> splitter, final R outerResult, final E e) {
final K elementKey = splitter.elementToKey(e);
final K[] keyGroup = splitter.keyToKeyGroup(elementKey);
R result = outerResult;
final int lastI = keyGroup.length - 1;
for ( int i = 0; i < lastI; i++ ) {
final K innerKey = keyGroup[i];
final R candidateInnerResult = splitter.fromInnerResult(result, innerKey);
if ( candidateInnerResult == null ) {
final R newTargetResult = splitter.newResult();
@SuppressWarnings("unchecked")
final V castNewTargetResult = (V) newTargetResult;
splitter.toInnerResult(result, innerKey, castNewTargetResult);
result = newTargetResult;
} else {
result = candidateInnerResult;
}
}
final V value = splitter.elementToValue(e);
splitter.toInnerResult(result, keyGroup[lastI], value);
}
}
В общем, doSplit(...)
пытается "разбить""ключи для любой последовательности элементов, следовательно, вы можете разделить что угодно, не только карты.
Вот интерфейс, который используется выше.Все его методы используются в doSplit(...)
для выполнения различных задач.
// E - type of elements it can process
// R - the result object type
// K - key type
// K - value type
interface Splitter<E, R, K, V> {
// A factory method to create the outer of an inner result
R newResult();
// A method to extract a key from the element
K elementToKey(E element);
// A method to extract a value from the element
V elementToValue(E element);
// A method to split a key to a key group so we can have a nested objects identitied with
K[] keyToKeyGroup(K key);
// A method to extract an inner result from existing inner result
R fromInnerResult(R innerResult, K innerKey);
// A method to put a key/value pair to the result
void toInnerResult(R innerResult, K innerKey, V value);
// A convenience method similar to Collector.of
static <E, R, K, V> Splitter<E, R, K, V> of(
final Function<? super E, ? extends K> elementToKey,
final Function<? super E, ? extends V> elementToValue,
final Function<? super K, ? extends K[]> keyToKeyGroup,
final Supplier<? extends R> newResult,
final BiFunction<? super R, ? super K, ? extends R> fromInnerResult,
final TriConsumer<? super R, ? super K, ? super V> toInnerResult
) {
return new Splitter<E, R, K, V>() {
@Override
public R newResult() {
return newResult.get();
}
@Override
public K elementToKey(final E element) {
return elementToKey.apply(element);
}
@Override
public V elementToValue(final E element) {
return elementToValue.apply(element);
}
@Override
public K[] keyToKeyGroup(final K key) {
return keyToKeyGroup.apply(key);
}
@Override
public R fromInnerResult(final R innerResult, final K innerKey) {
return fromInnerResult.apply(innerResult, innerKey);
}
@Override
public void toInnerResult(final R innerResult, final K innerKey, final V value) {
toInnerResult.accept(innerResult, innerKey, value);
}
};
}
}
И так как в Java 8 нет трипотребителя:
interface TriConsumer<T, U, V> {
void accept(T t, U u, V v);
}
Теперь, так как этоэто общий подход, вы можете иметь несколько реализаций.Например, разделитель, который может разбить последовательность на карту:
final class MapSplitters {
private MapSplitters() {
}
static <K, V> Splitter<Map.Entry<K, V>, Map<K, V>, K, V> of(final Function<? super K, ? extends K[]> keyToKeyGroup) {
return of(keyToKeyGroup, LinkedTreeMap::new);
}
static <K, V> Splitter<Map.Entry<K, V>, Map<K, V>, K, V> of(final Function<? super K, ? extends K[]> keyToKeyGroup,
final Supplier<? extends Map<K, V>> mapFactory) {
return Splitter.of(
Map.Entry::getKey,
Map.Entry::getValue,
keyToKeyGroup, mapFactory, (innerMap, key) -> {
@SuppressWarnings("unchecked")
final Map<K, V> castInnerMap = (Map<K, V>) innerMap.get(key);
return castInnerMap;
},
Map::put
);
}
}
Или, в частности, Gson JsonElement
, JsonObject
:
final class JsonElementSplitters {
private JsonElementSplitters() {
}
static <V> Splitter<Map.Entry<String, V>, JsonObject, String, V> of(final Function<? super String, ? extends String[]> keyToKeyGroup) {
return of(keyToKeyGroup, JsonElementSplitters::simpleObjectToSimpleJsonElement);
}
static <V> Splitter<Map.Entry<String, V>, JsonObject, String, V> of(final Function<? super String, ? extends String[]> keyToKeyGroup,
final Gson gson) {
return of(keyToKeyGroup, gson::toJsonTree);
}
static <V> Splitter<Map.Entry<String, V>, JsonObject, String, V> of(final Function<? super String, ? extends String[]> keyToKeyGroup,
final Function<? super V, ? extends JsonElement> valueToJsonElement) {
return Splitter.of(
Map.Entry::getKey,
Map.Entry::getValue, keyToKeyGroup,
JsonObject::new,
(innerJsonObject, key) -> {
final JsonElement jsonElement = innerJsonObject.get(key);
return jsonElement != null ? jsonElement.getAsJsonObject() : null;
},
(jsonObject, property, value) -> jsonObject.add(property, valueToJsonElement.apply(value))
);
}
// In simple cases we can do a primitive box value to a simple JSON value
private static JsonElement simpleObjectToSimpleJsonElement(final Object o) {
if ( o == null ) {
return JsonNull.INSTANCE;
}
if ( o instanceof JsonElement ) {
return (JsonElement) o;
}
if ( o instanceof Boolean ) {
return new JsonPrimitive((Boolean) o);
}
if ( o instanceof Number ) {
return new JsonPrimitive((Number) o);
}
if ( o instanceof String ) {
return new JsonPrimitive((String) o);
}
if ( o instanceof Character ) {
return new JsonPrimitive((Character) o);
}
throw new IllegalArgumentException("Cannot convert " + o.getClass());
}
}
Пример использования:
private static final Pattern dotPattern = Pattern.compile("\\.");
public static void main(final String... args) {
final Map<String, Object> map = ImmutableMap.of("stackoverflow.questions.value", "value");
final Splitter<Map.Entry<String, Object>, Map<String, Object>, String, Object> toMapSplitter = MapSplitters.of(dotPattern::split);
final Splitter<Map.Entry<String, Object>, JsonObject, String, Object> toJsonObjectSplitter = JsonElementSplitters.of(dotPattern::split);
// A simple to-inner-maps split example
System.out.println(Split.split(map.entrySet(), toMapSplitter));
// A simple to-nested-JSON-objects split example
System.out.println(Split.split(map.entrySet(), toJsonObjectSplitter));
// Or even use it with Java 8 Stream API
System.out.println(
map.entrySet()
.stream()
.map(e -> new AbstractMap.SimpleImmutableEntry<>(e.getKey().toUpperCase(), e.getValue()))
.collect(Split.asCollector(toMapSplitter))
);
}
Вывод:
{stackoverflow={questions={value=value}}}
{"stackoverflow":{"questions":{"value":"value"}}}
{STACKOVERFLOW={QUESTIONS={VALUE=value}}}
Я не уверен, что какой-либо из встроенных Java 8 Collector
s может сделать это, но это выглядит как групповой сборщик.