Как отобразить структуру ключ-значение с составным ключом на объект json в Java - PullRequest
0 голосов
/ 24 мая 2018

У меня есть база данных, хранящая объекты ключ-значение.Ключ может быть составным с разделителем, например stackoverflow.questions.ask.Я хочу сопоставить такой объект с JSON.Таким образом, для клавиши stackoverflow.questions.ask я хочу получить

{
    'stackoverflow' : {
        'questions' : {
                'ask' : 'value'
            }
        }
}

Есть ли простой способ сделать это автоматически?

1 Ответ

0 голосов
/ 24 мая 2018

Я не думаю, что 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 может сделать это, но это выглядит как групповой сборщик.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...