Usando Jackson ObjectMapper con Java 8 Valores opcionales


Estaba tratando de usar Jackson para escribir un valor de clase en JSON que tiene campos opcionales como:

public class Test {
    Optional<String> field = Optional.of("hello, world!");

    public Optional<String> getField() {
        return field;
    }

    public static void main(String[] args) throws JsonProcessingException {
        ObjectMapper mapper = new ObjectMapper();
        System.out.println(mapper.writeValueAsString(new Test()));
    }
}

Cuando se ejecuta, esta clase genera la siguiente salida:

{"field":{"present":true}}

Entiendo que se incluye el campo presente/no presente y podría evitarlo al leer los datos JSON, sin embargo, no puedo evitar el hecho de que el contenido real del opcional nunca se escribe en la salida. :(

¿Alguna solución aquí excepto no usar ObjectMapper en absoluto?

Author: Sotirios Delimanolis, 2014-09-06

7 answers

Puedes usar jackson-datatype-jdk8 que se describe como:

Soporte para nuevos tipos específicos de JDK8, como

 47
Author: Jonas,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-12-03 12:32:13

La clase Optional tiene un campo value, pero no un getter/setter estándar para él. Por defecto, Jackson busca getters / setters para encontrar propiedades de clase.

Puede agregar un Mixin personalizado para identificar el campo como una propiedad

final class OptionalMixin {
    private Mixin(){}
    @JsonProperty
    private Object value;
}

Y registrarlo con su ObjectMapper.

ObjectMapper mapper = new ObjectMapper();
mapper.addMixInAnnotations(Optional.class, OptionalMixin.class);

Ahora puede serializar su objeto.

System.out.println(mapper.writeValueAsString(new Test()));

Se imprimirá

{"field":{"value":"hello, world!","present":true}}

Considere también mirar jackson-datatype-guava. Hay una implementación de Jackson Module para los tipos de guayaba incluidos sus Optional. Es posiblemente más completo de lo que he mostrado anteriormente.

 12
Author: Sotirios Delimanolis,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-09-06 17:42:16

Similar a la respuesta de @Manikandan, pero agrega @JsonProperty al campo privado en lugar de un getter para que no expongas tu trabajo en la api pública.

public class Test {

    @JsonProperty("field")
    private String field;

    @JsonIgnore
    public Optional<String> getField() {
        return Optional.of(field); // or Optional.ofNullable(field);
    }
}
 8
Author: mjj1409,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-07-31 00:41:57

Definir nuevo getter que devolverá Cadena en lugar de Opcional.

public class Test {
    Optional<String> field = Optional.of("hello, world!");

    @JsonIgnore
    public Optional<String> getField() {
        return field;
    }

    @JsonProperty("field")
    public String getFieldName() {
        return field.orElse(null);
    }

}
 3
Author: Manikandan,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-09-06 17:34:47

Intente pasar las opciones a @JsonInclude anotación.
Por ejemplo, si no desea mostrar field cuando el valor es null. Es posible que necesite usar Jackson-Modules >2.8.5.

import com.fasterxml.jackson.annotation.JsonInclude;

public class Test {
    @JsonInclude(JsonInclude.Include.NON_NULL)
    Optional<String> field;
}
 3
Author: jiantongc,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-02-16 07:08:05

Si está utilizando la última versión de Spring-boot, podría lograr esto agregando la siguiente dependencia en el archivo pom

<dependency>
    <groupId>com.fasterxml.jackson.datatype</groupId>
    <artifactId>jackson-datatype-jdk8</artifactId>
</dependency>

Y auto wire el JacksonObjectMapper.

@Autowired
private ObjectMapper jacksonObjectMapper;

Luego use la instancia mapper del contenedor Spring anterior para convertir el objeto en Cadena

jacksonObjectMapper.writeValueAsString(user);

El blog de primavera dice:

Algunos módulos bien conocidos de Jackson se registran automáticamente si se detectan en el classpath:

  • jackson-datatype-jdk7: Java 7 tipos como java.nio.file.Path (a partir de la versión 4.2.1)
  • jackson-datatype-joda: Joda-Time types{[18]]}
  • jackson-datatype-jsr310: Java 8 Tipos de datos API de fecha y hora
  • jackson-datatype-jdk8: otros tipos de Java 8 como Opcional (a partir de la versión 4.2.0)
 0
Author: seenimurugan,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-01-18 17:44:10

Solo necesita registrar el módulo Jdk8Module. Luego serializará cualquier Optional<T> como T si está presente o null de lo contrario.

ObjectMapper objectMapper = new ObjectMapper();
objectMapper.registerModule(new Jdk8Module());

Modifiquemos un poco la clase Test para que podamos probar un valor Optional.empty(). Esto es similar a la clase original Test cuando se serializa porque el mapeador de objetos está buscando el getter (ya que el campo es private). Sin embargo, usar la clase original Test también funcionará.

class Test {
    private final String field;

    public Test(String field) {
        this.field = field;
    }

    public Optional<String> getField() {
        return Optional.ofNullable(field);
    }
}

Luego en nuestra clase principal:

Test testFieldNull = new Test(null);
Test testFieldNotNull = new Test("foo");

// output: {"field":null}
System.out.println(objectMapper.writeValueAsString(testFieldNull)); 

// output: {"field":"foo"}
System.out.println(objectMapper.writeValueAsString(testFieldNotNull)); 
 0
Author: cakraww,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-03-11 02:00:09