为 Jackson 对象映射器提供长对象池
Supply Long Object Pool to Jackson Object Mapper
我有一个 JSON 可以转换成 POJO。 JSON 从 GZIPInputStream gis
中读取。
ObjectMapper mapper = new ObjectMapper();
TypeReference<Map<Long, ConfigMasterAirportData>> typeRef =
new TypeReference<Map<Long, ConfigMasterAirportData>>() {};
Map<Long, ConfigMasterAirportData> configMasterAirportMap =
mapper.readValue(gis, typeRef);
我不想为每个条目创建新的 Long
对象。我希望它从我创建的自定义 LongPool
中获取 Long
对象。有没有办法将这样的 LongPool
传递给映射器?
如果没有,我可以使用另一个 JSON 库吗?
不确定 Jackson
库,但是使用 Google Gson
你可以很简单地通过注册一个自定义类型适配器来做到这一点,它的职责是按照你想要的方式解析每个键它:
public class DeserializeJsonMapWithCustomKeyResolver {
public static void main(String[] args) {
final String JSON = "{ \"1\" : { \"value\" :1 }, \"2\" : { \"value\" : 2} }";
final Type mapType = new TypeToken<Map<Long, ConfigMasterAirportData>>() {}.getType();
final Map<String, ConfigMasterAirportData> map =
new GsonBuilder().registerTypeAdapter(mapToken, new PooledLongKeyDeserializer())
.create()
.fromJson(JSON, mapType);
System.out.println(map);
}
static Long longFromString(String value)
{
System.out.println("Resolving value : " + value);
// TODO: replace with your LongPool call here instead; may need to convert from String
return Long.valueOf(value);
}
static class PooledLongKeyDeserializer implements
JsonDeserializer<Map<Long, ConfigMasterAirportData>>
{
@Override
public Map<Long, ConfigMasterAirportData> deserialize(
JsonElement json,
Type typeOfT,
JsonDeserializationContext context)
throws JsonParseException
{
final Map<Long, ConfigMasterAirportData> map = json.getAsJsonObject()
.entrySet()
.stream()
.collect(
Collectors.toMap(
e -> longFromString(e.getKey()),
e -> context.deserialize(e.getValue(),
TypeToken.get(ConfigMasterAirportData.class).getType())
));
return map;
}
}
static class ConfigMasterAirportData {
public int value;
@Override
public String toString() { return "ConfigMasterAirportData{value=" + value + '}'; }
}
}
如果您确定在您的情况下需要对象池,有很多方法可以实现这一点。
首先,Java 已经在 -128 到 127(含)之间的小范围内进行了 Long
对象池化。请参阅 Long.valueOf
.
的源代码
让我们有 2 个要反序列化的 JSON 对象:map1
和 map2
:
final String map1 = "{\"1\": \"Hello\", \"10000000\": \"world!\"}";
final String map2 = "{\"1\": \"You\", \"10000000\": \"rock!\"}";
标准反序列化
如果我们使用标准反序列化:
final ObjectMapper mapper = new ObjectMapper();
final TypeReference<Map<Long, String>> typeRef = new TypeReference<Map<Long, String>>() {};
final Map<Long, String> deserializedMap1 = mapper.readValue(map1, typeRef);
final Map<Long, String> deserializedMap2 = mapper.readValue(map2, typeRef);
printMap(deserializedMap1);
printMap(deserializedMap2);
其中 printMap
定义为
private static void printMap(Map<Long, String> longStringMap) {
longStringMap.forEach((Long k, String v) -> {
System.out.printf("key object id %d \t %s -> %s %n", System.identityHashCode(k), k, v);
});
}
我们得到以下输出:
key object id 1635756693 1 -> Hello
key object id 504527234 10000000 -> world!
key object id 1635756693 1 -> You
key object id 101478235 10000000 -> rock!
请注意,在两个映射中,键 1
是 具有哈希码 1635756693
的同一对象。这是由于 [-128,127] 范围的 built-in 池。
解决方案一:@JsonAnySetter反序列化
我们可以为地图定义一个包装器对象,并使用@JsonAnySetter
注解拦截所有被反序列化的key-value对。然后我们可以使用 Guava StrongInterner
:
来实习每个 Long
对象
static class CustomLongPoolingMap {
private static final Interner<Long> LONG_POOL = Interners.newStrongInterner();
private final Map<Long, String> map = new HashMap<>();
@JsonAnySetter
public void addEntry(String key, String value) {
map.put(LONG_POOL.intern(Long.parseLong(key)), value);
}
public Map<Long, String> getMap() {
return map;
}
}
我们将这样使用它:
final ObjectMapper mapper = new ObjectMapper();
final Map<Long, String> deserializedMap1 = mapper.readValue(map1, CustomLongPoolingMap.class).getMap();
final Map<Long, String> deserializedMap2 = mapper.readValue(map2, CustomLongPoolingMap.class).getMap();
输出:
key object id 1635756693 1 -> Hello
key object id 1596467899 10000000 -> world!
key object id 1635756693 1 -> You
key object id 1596467899 10000000 -> rock!
现在您可以看到键 10000000
在两个映射中也是同一个对象,哈希码 1596467899
解决方案 2:注册自定义 KeyDeserializer
定义自定义 KeySerializer
:
public static class MyCustomKeyDeserializer extends KeyDeserializer {
private static final Interner<Long> LONG_POOL = Interners.newStrongInterner();
@Override
public Long deserializeKey(String key, DeserializationContext ctxt) {
return LONG_POOL.intern(Long.parseLong(key));
}
}
并用ObjectMapper
注册:
final SimpleModule module = new SimpleModule();
module.addKeyDeserializer(Long.class, new MyCustomKeyDeserializer());
final ObjectMapper mapper = new ObjectMapper().registerModule(module);
final TypeReference<Map<Long, String>> typeRef = new TypeReference<Map<Long, String>>() {};
final Map<Long, String> deserializedMap1 = mapper.readValue(map1, typeRef);
final Map<Long, String> deserializedMap2 = mapper.readValue(map2, typeRef);
解决方案 3:通过 @JsonDeserialize
注释使用自定义 KeyDeserializer
定义包装器对象
static class MapWrapper {
@JsonDeserialize(keyUsing = MyCustomKeyDeserializer.class)
private Map<Long, String> map1;
@JsonDeserialize(keyUsing = MyCustomKeyDeserializer.class)
private Map<Long, String> map2;
}
并反序列化它:
final ObjectMapper mapper = new ObjectMapper();
final String json = "{\"map1\": " + map1 + ", \"map2\": " + map2 + "}";
final MapWrapper wrapper = mapper.readValue(json, MapWrapper.class);
final Map<Long, String> deserializedMap1 = wrapper.map1;
final Map<Long, String> deserializedMap2 = wrapper.map2;
解决方案 4:使用 Trove library TLongObjectMap
来避免完全使用 Long
个对象
Trove 库实现了使用基本类型作为键的映射,以完全消除装箱对象的开销。然而,它处于休眠状态。
你的情况需要TLongObjectHashMap。
有一个库为 TIntObjectMap
定义了反序列化器:
https://bitbucket.org/marshallpierce/jackson-datatype-trove/src/d7386afab0eece6f34a0af69b76b478f417f0bd4/src/main/java/com/palominolabs/jackson/datatype/trove/deser/TIntObjectMapDeserializer.java?at=master&fileviewer=file-view-default
我认为它很容易适应 TLongObjectMap
。
此答案的完整代码可在此处找到:https://gist.github.com/shtratos/f0a81515d19b858dafb71e86b62cb474
我已将此问题的答案用于解决方案 2 和 3:
Deserializing non-string map keys with Jackson
我有一个 JSON 可以转换成 POJO。 JSON 从 GZIPInputStream gis
中读取。
ObjectMapper mapper = new ObjectMapper();
TypeReference<Map<Long, ConfigMasterAirportData>> typeRef =
new TypeReference<Map<Long, ConfigMasterAirportData>>() {};
Map<Long, ConfigMasterAirportData> configMasterAirportMap =
mapper.readValue(gis, typeRef);
我不想为每个条目创建新的 Long
对象。我希望它从我创建的自定义 LongPool
中获取 Long
对象。有没有办法将这样的 LongPool
传递给映射器?
如果没有,我可以使用另一个 JSON 库吗?
不确定 Jackson
库,但是使用 Google Gson
你可以很简单地通过注册一个自定义类型适配器来做到这一点,它的职责是按照你想要的方式解析每个键它:
public class DeserializeJsonMapWithCustomKeyResolver {
public static void main(String[] args) {
final String JSON = "{ \"1\" : { \"value\" :1 }, \"2\" : { \"value\" : 2} }";
final Type mapType = new TypeToken<Map<Long, ConfigMasterAirportData>>() {}.getType();
final Map<String, ConfigMasterAirportData> map =
new GsonBuilder().registerTypeAdapter(mapToken, new PooledLongKeyDeserializer())
.create()
.fromJson(JSON, mapType);
System.out.println(map);
}
static Long longFromString(String value)
{
System.out.println("Resolving value : " + value);
// TODO: replace with your LongPool call here instead; may need to convert from String
return Long.valueOf(value);
}
static class PooledLongKeyDeserializer implements
JsonDeserializer<Map<Long, ConfigMasterAirportData>>
{
@Override
public Map<Long, ConfigMasterAirportData> deserialize(
JsonElement json,
Type typeOfT,
JsonDeserializationContext context)
throws JsonParseException
{
final Map<Long, ConfigMasterAirportData> map = json.getAsJsonObject()
.entrySet()
.stream()
.collect(
Collectors.toMap(
e -> longFromString(e.getKey()),
e -> context.deserialize(e.getValue(),
TypeToken.get(ConfigMasterAirportData.class).getType())
));
return map;
}
}
static class ConfigMasterAirportData {
public int value;
@Override
public String toString() { return "ConfigMasterAirportData{value=" + value + '}'; }
}
}
如果您确定在您的情况下需要对象池,有很多方法可以实现这一点。
首先,Java 已经在 -128 到 127(含)之间的小范围内进行了 Long
对象池化。请参阅 Long.valueOf
.
让我们有 2 个要反序列化的 JSON 对象:map1
和 map2
:
final String map1 = "{\"1\": \"Hello\", \"10000000\": \"world!\"}";
final String map2 = "{\"1\": \"You\", \"10000000\": \"rock!\"}";
标准反序列化
如果我们使用标准反序列化:
final ObjectMapper mapper = new ObjectMapper();
final TypeReference<Map<Long, String>> typeRef = new TypeReference<Map<Long, String>>() {};
final Map<Long, String> deserializedMap1 = mapper.readValue(map1, typeRef);
final Map<Long, String> deserializedMap2 = mapper.readValue(map2, typeRef);
printMap(deserializedMap1);
printMap(deserializedMap2);
其中 printMap
定义为
private static void printMap(Map<Long, String> longStringMap) {
longStringMap.forEach((Long k, String v) -> {
System.out.printf("key object id %d \t %s -> %s %n", System.identityHashCode(k), k, v);
});
}
我们得到以下输出:
key object id 1635756693 1 -> Hello key object id 504527234 10000000 -> world! key object id 1635756693 1 -> You key object id 101478235 10000000 -> rock!
请注意,在两个映射中,键 1
是 具有哈希码 1635756693
的同一对象。这是由于 [-128,127] 范围的 built-in 池。
解决方案一:@JsonAnySetter反序列化
我们可以为地图定义一个包装器对象,并使用@JsonAnySetter
注解拦截所有被反序列化的key-value对。然后我们可以使用 Guava StrongInterner
:
Long
对象
static class CustomLongPoolingMap {
private static final Interner<Long> LONG_POOL = Interners.newStrongInterner();
private final Map<Long, String> map = new HashMap<>();
@JsonAnySetter
public void addEntry(String key, String value) {
map.put(LONG_POOL.intern(Long.parseLong(key)), value);
}
public Map<Long, String> getMap() {
return map;
}
}
我们将这样使用它:
final ObjectMapper mapper = new ObjectMapper();
final Map<Long, String> deserializedMap1 = mapper.readValue(map1, CustomLongPoolingMap.class).getMap();
final Map<Long, String> deserializedMap2 = mapper.readValue(map2, CustomLongPoolingMap.class).getMap();
输出:
key object id 1635756693 1 -> Hello key object id 1596467899 10000000 -> world! key object id 1635756693 1 -> You key object id 1596467899 10000000 -> rock!
现在您可以看到键 10000000
在两个映射中也是同一个对象,哈希码 1596467899
解决方案 2:注册自定义 KeyDeserializer
定义自定义 KeySerializer
:
public static class MyCustomKeyDeserializer extends KeyDeserializer {
private static final Interner<Long> LONG_POOL = Interners.newStrongInterner();
@Override
public Long deserializeKey(String key, DeserializationContext ctxt) {
return LONG_POOL.intern(Long.parseLong(key));
}
}
并用ObjectMapper
注册:
final SimpleModule module = new SimpleModule();
module.addKeyDeserializer(Long.class, new MyCustomKeyDeserializer());
final ObjectMapper mapper = new ObjectMapper().registerModule(module);
final TypeReference<Map<Long, String>> typeRef = new TypeReference<Map<Long, String>>() {};
final Map<Long, String> deserializedMap1 = mapper.readValue(map1, typeRef);
final Map<Long, String> deserializedMap2 = mapper.readValue(map2, typeRef);
解决方案 3:通过 @JsonDeserialize
注释使用自定义 KeyDeserializer
定义包装器对象
static class MapWrapper {
@JsonDeserialize(keyUsing = MyCustomKeyDeserializer.class)
private Map<Long, String> map1;
@JsonDeserialize(keyUsing = MyCustomKeyDeserializer.class)
private Map<Long, String> map2;
}
并反序列化它:
final ObjectMapper mapper = new ObjectMapper();
final String json = "{\"map1\": " + map1 + ", \"map2\": " + map2 + "}";
final MapWrapper wrapper = mapper.readValue(json, MapWrapper.class);
final Map<Long, String> deserializedMap1 = wrapper.map1;
final Map<Long, String> deserializedMap2 = wrapper.map2;
解决方案 4:使用 Trove library TLongObjectMap
来避免完全使用 Long
个对象
Trove 库实现了使用基本类型作为键的映射,以完全消除装箱对象的开销。然而,它处于休眠状态。
你的情况需要TLongObjectHashMap。
有一个库为 TIntObjectMap
定义了反序列化器:
https://bitbucket.org/marshallpierce/jackson-datatype-trove/src/d7386afab0eece6f34a0af69b76b478f417f0bd4/src/main/java/com/palominolabs/jackson/datatype/trove/deser/TIntObjectMapDeserializer.java?at=master&fileviewer=file-view-default
我认为它很容易适应 TLongObjectMap
。
此答案的完整代码可在此处找到:https://gist.github.com/shtratos/f0a81515d19b858dafb71e86b62cb474
我已将此问题的答案用于解决方案 2 和 3: Deserializing non-string map keys with Jackson