I have a JSON that I convert into POJOs. The JSON is read from a GZIPInputStream gis
.
ObjectMapper mapper = new ObjectMapper();
TypeReference<Map<Long, ConfigMasterAirportData>> typeRef =
new TypeReference<Map<Long, ConfigMasterAirportData>>() {};
Map<Long, ConfigMasterAirportData> configMasterAirportMap =
mapper.readValue(gis, typeRef);
I do not want new Long
objects to be created for each entry. I want it to get Long
objects from a custom LongPool
I have created. Is there a way to pass such a LongPool
to the mapper?
If not, is there another JSON library I can use to do that?
There are many ways to achieve this if you are sure that object pooling is required in your case.
First of all, Java already does Long
object pooling for a small range between -128 and 127 inclusive. See source code of Long.valueOf
.
Let us have 2 JSON objects that we want to deserialize: map1
and map2
:
final String map1 = "{\"1\": \"Hello\", \"10000000\": \"world!\"}";
final String map2 = "{\"1\": \"You\", \"10000000\": \"rock!\"}";
If we use standard deserialization:
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);
Where printMap
is defined as
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);
});
}
we get the following output:
key object id 1635756693 1 -> Hello key object id 504527234 10000000 -> world! key object id 1635756693 1 -> You key object id 101478235 10000000 -> rock!
Note that key 1
is the same object with hashcode 1635756693
in both maps. This is due to built-in pool for [-128,127] range.
We can define a wrapper object for the map and use @JsonAnySetter
annotation to intercept all key-value pairs being deserialized. Then we can intern each Long
object using Guava StrongInterner
:
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;
}
}
We will use it like this:
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();
Output:
key object id 1635756693 1 -> Hello key object id 1596467899 10000000 -> world! key object id 1635756693 1 -> You key object id 1596467899 10000000 -> rock!
Now you can see that key 10000000
is also the same object in both maps with hashcode 1596467899
Define custom 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));
}
}
And register it with the 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);
@JsonDeserialize
annotationDefine a wrapper object
static class MapWrapper {
@JsonDeserialize(keyUsing = MyCustomKeyDeserializer.class)
private Map<Long, String> map1;
@JsonDeserialize(keyUsing = MyCustomKeyDeserializer.class)
private Map<Long, String> map2;
}
And deserialize it:
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;
TLongObjectMap
to avoid using Long
objects entirelyTrove library implements maps that use primitive types for keys to remove overhead of boxed objects entirely. It's in somewhat dormant state however.
You need in your case TLongObjectHashMap.
There is a library that defines a deserializer for 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
I think it will be quite easy to adapt it for TLongObjectMap
.
Full code for this answer can be found here: https://gist.github.com/shtratos/f0a81515d19b858dafb71e86b62cb474
I've used answers to this question for solutions 2 & 3: Deserializing non-string map keys with Jackson