Поставка пула длинных объектов в 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. Я хочу, чтобы он получал Long объектов из LongPool пользовательских объектов, которые я создал. Есть ли способ передать такой LongPool картографу?

Если нет, есть ли другая библиотека JSON, которую я могу использовать для этого?


person Pranav Kapoor    schedule 20.02.2018    source источник
comment
Почему вы хотите этого? Сколько записей у вас есть на этой карте? Звучит как преждевременная оптимизация для меня.   -  person Devstr    schedule 20.02.2018
comment
Длинные идентификаторы используются на нескольких картах — около 25 карт. Это одна из карт. Мы используем длинные пулы для оптимизации пространства. У нас около 200 тысяч записей на карте.   -  person Pranav Kapoor    schedule 20.02.2018
comment
Таким образом, 25 карт по 200 тыс. записей по 16 байт (размер Long) составляют 80 мегабайт. Это максимальная сумма, которую вы можете сэкономить, объединившись. Критична ли для вашего приложения экономия 80 мегабайт? Пожалуйста, учитывайте стоимость поддержки этого кода.   -  person Devstr    schedule 20.02.2018
comment
@Devstr Да, это около 10% нашего объема памяти.   -  person Pranav Kapoor    schedule 21.02.2018
comment
Круто, чуть позже выложу решение. Я предлагаю использовать объект-оболочку для вашей карты и построить его через anysetter   -  person Devstr    schedule 21.02.2018
comment
Используете ли вы Guava в своем проекте? У него уже есть интерны   -  person Devstr    schedule 21.02.2018
comment
В качестве альтернативы вы можете использовать библиотеку trove, чтобы полностью избежать использования длинных объектов github. com/palantir/trove-3.0.3/blob/master/README.md   -  person Devstr    schedule 21.02.2018
comment
И, кажется, для него есть сериализаторы и десериализаторы Джексона /trove/deser/?at=master" rel="nofollow noreferrer">bitbucket.org/marshallpierce/jackson-datatype-trove/src/ Думаю, я напишу правильный ответ   -  person Devstr    schedule 21.02.2018
comment
Мы не используем Guava в нашем коде, кроме Guava BiMap. Хотя выглядит круто. Мы постараемся использовать его больше в нашей кодовой базе.   -  person Pranav Kapoor    schedule 23.02.2018


Ответы (2)


Есть много способов добиться этого, если вы уверены, что в вашем случае требуется объединение объектов.

Во-первых, в Java уже реализовано Long объединение объектов для небольшого диапазона от -128 до 127 включительно. См. исходный код 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].

Решение 1: десериализация @JsonAnySetter

Мы можем определить объект-оболочку для карты и использовать аннотацию @JsonAnySetter для перехвата всех десериализуемых пар ключ-значение. Затем мы можем интернировать каждый объект Long, используя Гуава 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;
    }
}

Мы будем использовать это так:

    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. Используйте собственный KeyDeserializer через аннотацию @JsonDeserialize

Определить объект-оболочку

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 TLongObjectMap, чтобы полностью избежать использования Long объектов

Библиотека Trove реализует карты, которые используют примитивные типы для ключей, чтобы полностью исключить накладные расходы на упакованные объекты. Однако он находится в несколько спящем состоянии.

В вашем случае вам нужно TLongObjectHashMap.

Существует библиотека, которая определяет десериализатор для TIntObjectMap: trove/deser/TIntObjectMapDeserializer.java?at=master&fileviewer=file-view-default" rel="nofollow noreferrer">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: Десериализация нестроковых сопоставить ключи с Джексоном

person Devstr    schedule 21.02.2018

Не уверен насчет 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 + '}'; }
    }
}
person yegodm    schedule 20.02.2018