Jackson ObjectMapper - określ kolejność serializacji właściwości obiektu

84

Wdrażam usługę sieciową RESTful, w której użytkownik musi wysłać podpisany token weryfikacyjny wraz z żądaniem, aby upewnić się, że żądanie nie zostało zmodyfikowane przez pośrednika. Moja obecna realizacja jest następująca.

Token weryfikacyjny to obiekt VerifData serializowany w łańcuch, a następnie zaszyfrowany i zaszyfrowany.

class VerifData {
    int prop1;
    int prop2;
}

W mojej usłudze umieszczam dane do serializacji w instancji VerifData, a następnie serializuję je za pomocą Jackson ObjectMapper i przesyłam do silnika weryfikacji wraz z tokenem weryfikacyjnym.

VerfiData verifData = new VerifData(12345, 67890);
ObjectMapper mapper = new ObjectMapper();
String verifCodeGenerated = mapper.writeValueAsString(verifData);

Wydaje się jednak, że za każdym razem, gdy uruchamiany jest kontener aplikacji, kolejność właściwości mapowanych na ciąg znaków przez ObjectMapper zmienia się.

Np .: kiedyś to będzie

{"prop1":12345,"prop2":67890}

i innym razem będzie

{"prop2":67890,"prop1":12345}

Jeśli więc klient zserializował wystąpienie VerifData w pierwszym ciągu, istnieje 50% szans na niepowodzenie, mimo że jest poprawne.

Czy można to obejść? Czy mogę określić kolejność właściwości do mapowania przez ObjectMapper (jak w porządku rosnącym)? Czy jest inny sposób, aby najlepiej wdrożyć ten krok weryfikacji. Zarówno implementacje klienta, jak i serwera są opracowywane przeze mnie. Do podpisywania i weryfikacji używam Java Security API.

Lizzy
źródło

Odpowiedzi:

82

Z dokumentacji Jackson Annotations :

// ensure that "id" and "name" are output before other properties
@JsonPropertyOrder({ "id", "name" })

// order any properties that don't have explicit setting using alphabetic order
@JsonPropertyOrder(alphabetic=true)
wgitscht
źródło
z linku "wszystkie usługi Codehaus zostały zakończone"
Andrew Norman
2
Jak wspomniano poniżej, jeśli Twoim celem jest zastosowanie kolejności wszędzie, a nie podczas serializacji określonego obiektu, zobacz stackoverflow.com/a/46267506/2089674 .
user2089674
91

Adnotacje są przydatne, ale ich zastosowanie wszędzie może być trudne. Możesz skonfigurować cały ObjectMapper do pracy w ten sposób

Aktualne wersje Jacksona: objectMapper.configure(MapperFeature.SORT_PROPERTIES_ALPHABETICALLY, true)

Starsze wersje Jacksona: objectMapper.configure(SerializationConfig.Feature.SORT_PROPERTIES_ALPHABETICALLY, true);

Duncan McGregor
źródło
To nie jest do końca poprawne (lub przynajmniej nie działa we wszystkich scenariuszach) - zobacz stackoverflow.com/a/46267506/2089674, aby uzyskać pełny przykład czegoś, co działa.
user2089674
2
@ user2089674 - Twój przykład działa tylko dla kluczy mapy. OP zażądał rozwiązania dla pól obiektu.
Dave
10

W Jackson 2.x, którego prawdopodobnie używasz dzisiaj, użyj:

ObjectMapper mapper = new ObjectMapper();
mapper.configure(SerializationFeature.ORDER_MAP_ENTRIES_BY_KEYS, true);

Jeśli zależy Ci na wyglądzie, możesz również rozważyć SerializationFeature.INDENT_OUTPUT.

Zauważ, że musisz serializować mapy lub obiekty, aby to posortowało poprawnie. Jeśli JsonNodena przykład serializujesz (from readTree), nie będzie to odpowiednio wcięte.

Przykład

import com.fasterxml.jackson.databind.*;

ObjectMapper mapper = new ObjectMapper();
mapper.configure(SerializationFeature.ORDER_MAP_ENTRIES_BY_KEYS, true);
mapper.configure(SerializationFeature.INDENT_OUTPUT, true);

String input = "{\"hello\": {\"cruel\" : \"world\"} }";
Object pojo = mapper.readValue(input, Object.class);
System.out.println(mapper.writeValueAsString(pojo));

prowadzi do:

{
  "hello" : {
    "cruel" : "world"
  }
}
user2089674
źródło
6
Jest tylko jeden wpis na mapie. Dlatego nie widzę porządku.
Ben
9

W Spring Boot możesz dodać to zachowanie globalnie, dodając następujące elementy do swojej Applicationklasy punktu wejścia:

  @Bean
  public Jackson2ObjectMapperBuilder objectMapperBuilder() {

    Jackson2ObjectMapperBuilder builder = new Jackson2ObjectMapperBuilder();
    builder.featuresToEnable(MapperFeature.SORT_PROPERTIES_ALPHABETICALLY);

    return builder;
  }
Gary Rowe
źródło
9

W Spring Boot jest łatwiejszy sposób poprzez określenie właściwości ( application.propertiesna przykład:

spring.jackson.mapper.sort_properties_alphabetically=true
Wim Deblauwe
źródło
7

Następujące 2 konfiguracje ObjectMapper:

ObjectMapper.configure(MapperFeature.SORT_PROPERTIES_ALPHABETICALLY, true)
lub
ObjectMapper.enable(MapperFeature.SORT_PROPERTIES_ALPHABETICALLY)

definiuje kolejność serializacji właściwości używaną dla pól POJO
Uwaga : nie dotyczy java.util.Mapserializacji!

i

ObjectMapper.configure(SerializationFeature.ORDER_MAP_ENTRIES_BY_KEYS, true)
lub
ObjectMapper.enable(SerializationFeature.ORDER_MAP_ENTRIES_BY_KEYS)

Funkcja, która określa, czy java.util.Mapwpisy są najpierw sortowane według klucza przed serializacją


Przykład konfiguracji Spring Boot (yaml):

spring:
  jackson:
    mapper:
      SORT_PROPERTIES_ALPHABETICALLY: true
    serialization:
      ORDER_MAP_ENTRIES_BY_KEYS: true
magiccrafter
źródło
3

Z odpowiedzi Duncana McGregora: lepiej go używać w ten sposób:

objectMapper.configure(SerializationConfig.Feature.SORT_PROPERTIES_ALPHABETICALLY, true);

ponieważ MapperFeature jest przeznaczony dla XML i jest dostarczany z jackson-databind, który nie jest wymagany ...

Nemes Nisano
źródło
0

Zamiast używać argumentu flag:

objectMapper.enable(MapperFeature.SORT_PROPERTIES_ALPHABETICALLY);
Leon
źródło
0

Możesz użyć mieszania i określić kolejność właściwości, jak chcesz:

import com.fasterxml.jackson.annotation.JsonPropertyOrder;
import com.fasterxml.jackson.databind.ObjectMapper;

import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Component;

@Component
public final class ObjectMapperUtils {

    private static final ObjectMapper MAPPER = new ObjectMapper();

    static {
        MAPPER.addMixIn(Object.class, IdFirst.class);
    }

    @Bean
    public ObjectMapper objectMapper() {
        return MAPPER;
    }

    @JsonPropertyOrder({"id", "...", "..."})
    private abstract static class IdFirst {}

}
Marcus Vinícius Voltolim
źródło
0

Zdaję sobie sprawę, że to stary wątek, ale ponieważ szukałem odpowiedzi i wylądowałem tutaj, dodatkowe informacje mogą być przydatne dla innych osób.
Adnotacja @JsonProperty, której obecnie używam (jackson-annotations-2.11.2) akceptuje, oprócz argumentu „wartość”, argument „indeks” (numeryczny), który określa kolejność pól podczas serializacji.

PaulN
źródło