Jackson ObjectMapper: especifique el orden de serialización de las propiedades del objeto

84

Estoy implementando un servicio web RESTful donde el usuario tiene que enviar un token de verificación firmado junto con la solicitud para poder asegurarme de que la solicitud no ha sido manipulada por un intermediario. Mi implementación actual es la siguiente.

El token de verificación es un objeto VerifData serializado en una cadena y luego codificado y cifrado.

class VerifData {
    int prop1;
    int prop2;
}

En mi servicio, coloco los datos para serializarlos en una instancia de VerifData y luego los serializo usando Jackson ObjectMapper y los paso al motor de verificación junto con el token de verificación.

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

Pero parece que cada vez que se inicia el contenedor de la aplicación, cambia el orden de las propiedades que ObjectMapper asigna a una cadena.

Ej: una vez sería

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

y en otra ocasión sería

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

Entonces, si el cliente ha serializado la instancia VerifData como en la primera cadena, hay un 50% de posibilidades de que falle aunque sea correcta.

¿Hay alguna forma de evitar esto? ¿Puedo especificar el orden de las propiedades para mapear por ObjectMapper (como en orden ascendente)? ¿O hay alguna otra forma de implementar mejor este paso de verificación? Tanto las implementaciones de cliente como de servidor las desarrollo yo. Utilizo la API de seguridad de Java para firmar y verificar.

Lizzy
fuente

Respuestas:

82

De la documentación de 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
fuente
desde el enlace "se han cancelado todos los servicios de codehaus"
Andrew Norman
2
Como se menciona a continuación, si su objetivo es aplicar el orden en todas partes en lugar de al serializar un objeto específico, consulte stackoverflow.com/a/46267506/2089674 .
user2089674
91

Las anotaciones son útiles, pero puede resultar complicado aplicarlas en todas partes. Puede configurar todo su ObjectMapper para que funcione de esta manera con

Versiones actuales de Jackson: objectMapper.configure(MapperFeature.SORT_PROPERTIES_ALPHABETICALLY, true)

Versiones anteriores de Jackson: objectMapper.configure(SerializationConfig.Feature.SORT_PROPERTIES_ALPHABETICALLY, true);

Duncan McGregor
fuente
Esto no es del todo correcto (o al menos no funciona en todos los escenarios); consulte stackoverflow.com/a/46267506/2089674 para obtener un ejemplo completo de algo que funciona.
user2089674
2
@ user2089674: su ejemplo solo funciona para las teclas del mapa. El OP solicitó una solución para los campos de objeto.
Dave
10

En Jackson 2.x, que probablemente esté usando hoy, use:

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

Si te importa la apariencia, también puedes considerarlo SerializationFeature.INDENT_OUTPUT.

Tenga en cuenta que debe serializar mapas u objetos para que esto se ordene correctamente. Si serializa un JsonNodepor ejemplo (from readTree), no se sangrará correctamente.

Ejemplo

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));

resulta en:

{
  "hello" : {
    "cruel" : "world"
  }
}
user2089674
fuente
6
Solo hay una entrada de mapa. Por tanto, no veo su orden.
Ben
9

En Spring Boot, puede agregar este comportamiento globalmente agregando lo siguiente a su Applicationclase de punto de entrada:

  @Bean
  public Jackson2ObjectMapperBuilder objectMapperBuilder() {

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

    return builder;
  }
Gary Rowe
fuente
9

Hay una forma más fácil en Spring Boot especificando una propiedad (en, application.propertiespor ejemplo:

spring.jackson.mapper.sort_properties_alphabetically=true
Wim Deblauwe
fuente
7

Se requiere la siguiente configuración de 2 ObjectMapper:

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

define el orden de serialización de propiedad utilizado para los campos POJO
Nota : ¡no se aplica a la java.util.Mapserialización!

y

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

Característica que determina si las java.util.Mapentradas se ordenan primero por clave antes de la serialización


Ejemplo de configuración de Spring Boot (yaml):

spring:
  jackson:
    mapper:
      SORT_PROPERTIES_ALPHABETICALLY: true
    serialization:
      ORDER_MAP_ENTRIES_BY_KEYS: true
magico
fuente
3

De la respuesta de Duncan McGregor: Es mejor usarlo así:

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

ya que MapperFeature es para XML y viene con jackson-databind que no es necesario ...

Nemes Nisano
fuente
0

En lugar de usar el argumento de la bandera:

objectMapper.enable(MapperFeature.SORT_PROPERTIES_ALPHABETICALLY);
León
fuente
0

Puede utilizar la combinación y especificar el orden de las propiedades como desee:

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
fuente
0

Me doy cuenta de que este es un hilo antiguo, pero como estaba buscando una respuesta y llegué aquí, alguna información adicional podría ser útil para otras personas.
La anotación @JsonProperty que estoy usando actualmente (jackson-annotations-2.11.2) acepta, además del argumento "valor", un argumento "índice" (numérico) que especifica el orden de los campos durante la serialización.

PaulN
fuente