java - 如何将具有复合键的键值结构映射到Java中的json对象
问题描述
我有一个保存键值对象的数据库。键可能与分隔符复合,例如stackoverflow.questions.ask
. 我想将这样的对象映射到 JSON。因此,对于stackoverflow.questions.ask
我想要得到的密钥
{
'stackoverflow' : {
'questions' : {
'ask' : 'value'
}
}
}
有什么简单的方法可以自动完成吗?
解决方案
我不认为 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 8Collector
可以做到这一点,但它看起来像一个分组收集器。
推荐阅读
- c# - 我需要帮助将 SQL 语句转换为 C# LINQ
- oracle - Oracle 12cr2 编译指示已弃用
- windows - UWP 包安装脚本未正确安装
- sql-server - sql-server 中具有时间序列的最佳性能设计
- spring-boot - 如何在 Spring Boot 测试中模拟 BindingResult?
- pandas - 如何为通过 IntervalIndex 定义的列范围计算二维 DataFrame 的统计值?
- scala - Scala:哪个隐式参数优先?在定义方法的地方声明的那个?或者在哪里调用该方法?
- php - Woocommerce:在购物车更新时运行 PHP 函数
- c++ - 无法获得乘法输出
- cuda - cudaMallocManaged and cudaDeviceSynchronize()