为Jackson Object Mapper提供长对象池

时间:2018-02-20 10:19:43

标签: java json jackson objectmapper object-pooling

我有一个转换为POJO的JSON。从GZIPInputStream gis读取JSON。

ObjectMapper mapper = new ObjectMapper();

TypeReference<Map<Long, ConfigMasterAirportData>> typeRef =
                new TypeReference<Map<Long, ConfigMasterAirportData>>() {};

Map<Long, ConfigMasterAirportData> configMasterAirportMap = 
                mapper.readValue(gis, typeRef);

我不希望为每个条目创建新的Long个对象。我希望它从我创建的自定义Long中获取LongPool个对象。有没有办法将这样的LongPool传递给映射器?

如果没有,我可以使用另一个JSON库吗?

2 个答案:

答案 0 :(得分:2)

如果您确定在您的情况下需要对象池,有很多方法可以实现此目的。

首先,Java已经为Long对象池提供了-128到127之间的小范围。请参阅Long.valueOf的源代码。

让我们有2个我们想要反序列化的JSON对象:map1map2

    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]范围的内置池。

Solution1:@JsonAnySetter反序列化

我们可以为地图定义包装器对象,并使用@JsonAnySetter注释拦截所有反序列化的键值对。然后我们可以使用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&amp; 3: Deserializing non-string map keys with Jackson

答案 1 :(得分:0)

不确定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 + '}'; }
    }
}