Установка максимального размера при использовании более быстрого Джексона для десериализации массива

#java #json #jackson #out-of-memory #jackson-databind

Вопрос:

У меня есть приведенный ниже код, в котором я десериализую строку JSON с объектом, содержащим массив пользовательских объектов. Мы используем fasterxml.джексон. Мы видели случаи, когда количество элементов в массиве достаточно велико, чтобы вызвать OutOfMemoryError при десериализации. Каков самый простой способ установить максимальный размер для количества элементов, которые могут быть сериализованы из массива? Я действительно хочу создать исключение или вернуть ошибку в случае превышения лимита.

 class InputMessage {
    private final List<Action> actions;
    
    @JsonCreator
    public InputMessage(@JsonProperty("actions") final List<actions> actions) {
        this.actions = actions;
    }

    List<Action> public getActions() {
        return actions;
    }
}
 

Все решения, которые я видел до сих пор, выполняют проверку размера списка элементов после его сериализации в объекты java. Мне интересно знать, как это можно сделать во время сериализации, чтобы не потреблять лишнюю память в JVM.

Ответ №1:

@JsonCreator и @JsonProperty аннотации являются частью API привязки, который реализует и использует свою собственную логику синтаксического анализа, которая не допускает пользовательских условий, которые вы хотите. Вам нужно переопределить части синтаксического анализа.

Для массивов, которые вы хотите ограничить, используйте пользовательский десериализатор. Вы захотите расширить StdDeserializer и реализовать этот deserialize метод. Этот метод будет использовать потоковый API Джексона для обработки фактических токенов JSON , таких как { или [ или "some string" , и вы будете нести ответственность за выделение объектов (или нет).

Внутри deserialize метода вам не нужно анализировать Action объекты вручную; вы все равно можете полагаться на API привязки, создав ObjectMapper и затем используя objectMapper.readValue(jsonParser, Action.class) .

Обратитесь к своему пользовательскому классу десериализатора через JsonDeserialize:

 public InputMessage(
    @JsonProperty("actions") 
    @JsonDeserialize(using = /* your class */)
    final List<actions> actions) {
 

Некоторые поиски примеров и руководств:

Ответ №2:

Вот что я использовал для решения этой проблемы. Код обладает высокой производительностью, поскольку он проверяет ограничение при анализе входных данных, а не создает экземпляры всех элементов и потенциально испытывает нехватку памяти. Это предотвращает непропорциональное потребление одной строкой JSON большего объема памяти.

 /**
 * Custom deserializer used to convert a list of {@link Input}
 * into a list of {@link ConvertedInput}.
 */
static final class RequestItemsDeserializer extends StdDeserializer<List<ConvertedInput>> {

    // RFC 1149.5
    private static final long serialVersionUID = 4L;
    private static final int INPUT_LIST_LIMIT = 1024;

    private static final ObjectReader READER = new ObjectMapper().readerFor(Input.class);

    public RequestItemsDeserializer() {
        this(List.class);
    }

    public RequestItemsDeserializer(Class<?> clazz) {
        super(clazz);
    }

    @Override
    public List<ConvertedInput> deserialize(final JsonParser jp, final DeserializationContext ctxt)
        throws IOException, JsonProcessingException {

        final List<ConvertedInput> convertedInput = new ArrayList<>();
        if (!jp.isClosed() amp;amp; JsonToken.START_ARRAY == jp.getCurrentToken()) {
            for (int i = 1; !jp.isClosed() amp;amp; (jp.nextToken() != JsonToken.END_ARRAY); i  ) {
                if (i > INPUT_LIST_LIMIT) {
                    throw new RequestInputTooLargeException(""input":[...]",
                            "This request exceeded the item limit of "   INPUT_LIST_LIMIT   ".");
                }
                convertedInput.add(READER.readValue(jp));
            }
        }
        return convertedInput;
    }
}