Serialización
La serialización en IPF se realiza con Jackson, y su propia documentación cubrirá mucho más de lo que pretende esta página, y existen innumerables tutoriales y recursos en línea que pueden ayudar a comprender y usar Jackson. Un buen lugar para comenzar es la documentación oficial.
El objetivo de esta página es una breve introducción a la serialización dentro de IPF, incluyendo el comportamiento predeterminado y cómo puede personalizarse, así como algunas consideraciones y recomendaciones.
Conceptos
Esta sección pretende ser breve. Todos los conceptos enumerados aquí se tratan con más detalle en secciones posteriores.
SerializationHelper
SerializationHelper es el punto de entrada principal para la serialización.
Proporciona métodos de conveniencia para serializar a y deserializar desde JSON, y en última instancia delega en su instancia subyacente singleton de Jackson com.fasterxml.jackson.databind.ObjectMapper.
La instancia singleton subyacente de ObjectMapper se crea utilizando ObjectMapperFactory.
Ejemplos
//Accede a la instancia singleton de ObjectMapper
ObjectMapper objectMapper = SerializationHelper.objectMapper();
//p. ej., para convertir entre tipos
objectMapper.convertValue(object, SomeOtherType.class);
//Métodos de conveniencia para serializar a JSON usando el ObjectMapper singleton
String json = SerializationHelper.objectToString(object);
//Y para deserializar desde JSON
MyObject deserialized = SerializationHelper.stringToObject(json, MyObject.class);
ObjectMapperFactory
com.iconsolutions.ipf.core.shared.api.serializer.ObjectMapperFactory es el punto de entrada principal para crear nuevas instancias de ObjectMapper.
Si necesita una instancia diferente a la proporcionada por SerializationHelper.objectMapper(), usaría esta clase. SerializationHelper usa ObjectMapperFactory para crear su propia instancia singleton de ObjectMapper.
Ejemplos
//Crea una nueva instancia de ObjectMapper que se comporta igual que SerializationHelper.objectMapper()
ObjectMapper serializationHelperObjectMapper = ObjectMapperFactory.createObjectMapper();
//Crea una nueva instancia de ObjectMapper con configuración adicional
ObjectMapper customSerializationHelperObjectMapper = ObjectMapperFactory.createObjectMapper(config);
//Crea una nueva instancia de ObjectMapper con nombre que debe configurarse por separado
ObjectMapper customObjectMapper = ObjectMapperFactory.createObjectMapper("my-object-mapper", config);
Akka Jackson
Las instancias de ObjectMapper se configuran utilizando Akka Jackson, lo que significa que el comportamiento de serialización puede personalizarse con configuración Hocon.
Se incluye configuración predeterminada para un serializador llamado serialization-helper, y este serializador puede personalizarse con configuración adicional.
akka.serialization.jackson.serialization-helper {
//Se pueden agregar módulos adicionales para admitir comportamiento de (de)serialización personalizado
jackson-modules += "MyCustomModule"
}
También se pueden definir nuevos serializadores, lo cual es una buena manera de personalizar la serialización sin cambiar el comportamiento de serialization-helper.
akka.serialization.jackson.my-serializer = "akka.serialization.jackson.JacksonJsonSerializer"
//El serializador personalizado puede heredar el comportamiento predeterminado y proporcionar personalización adicional
akka.serialization.jackson.my-serializer = ${akka.serialization.jackson.serialization-helper} {
jackson-modules += "MyCustomModule"
}
Primeros pasos
Usar el SerializationHelper predeterminado
En muchos casos Comportamiento de serialización predeterminado será suficiente, lo que significa que la serialización puede realizarse usando los métodos de conveniencia en SerializationHelper, o accediendo a la instancia subyacente de ObjectMapper.
String json = SerializationHelper.objectToString(object);
MyObject deserialized = SerializationHelper.stringToObject(json, MyObject.class);
Map<String, Object> b = SerializationHelper.objectMapper().convertValue(deserialized, new TypeReference<Map<String, Object>>() {
});
Personalizar el SerializationHelper predeterminado
Por supuesto, habrá casos en los que el comportamiento de serialización predeterminado no sea suficiente, p. ej., cuando los tipos deban serializarse de manera personalizada.
La personalización idealmente debe hacerse mediante configuración. Consulte la Referencia de configuración para el conjunto completo de opciones de configuración.
Evite configurar/mutar programáticamente la instancia predeterminada de ObjectMapper.
Registrar módulos personalizados
Dado el siguiente módulo personalizado
public final class MyModule extends SimpleModule {
@Override
public void setupModule(final SetupContext context) {
super.setupModule(context);
final var serializers = new SimpleSerializers();
serializers.addSerializer(SomeType.class, new JsonSerializer<>() {
@Override
public void serialize(final SomeType value, final JsonGenerator gen, final SerializerProvider serializers) {
//Serialización personalizada para SomeType
}
});
context.addSerializers(serializers);
final var deserializers = new SimpleDeserializers();
deserializers.addDeserializer(SomeType.class, new JsonDeserializer<>() {
@Override
public SomeType deserialize(final JsonParser p, final DeserializationContext ctxt) {
//Deserialización personalizada para SomeType
}
});
context.addDeserializers(deserializers);
}
record SomeType(String name) {
}
}
El módulo se puede agregar al serializador serialization-helper con la siguiente configuración:
akka.serialization.jackson.serialization-helper.jackson-modules += "package.of.my.module.MyModule"
Consideraciones
Cambiar el comportamiento de serialización predeterminado debe abordarse con precaución y probablemente sea mejor evitarlo. En la mayoría de los casos, agregar tipos admitidos personalizados a través de módulos estará bien, pero cambiar el comportamiento de serialización predeterminado afectará a todo el código que usa la serialización predeterminada. Cuando se requiera configuración adicional, considere en su lugar Crear serializadores personalizados.
La configuración programática, es decir, mutar la instancia de ObjectMapper, debe evitarse porque depende de que se invoque esa ruta de código para que la configuración surta efecto, p. ej., evite hacer lo siguiente
SerializationHelper.objectMapper().registerModule(new MyModule())
Usar la serialización IPF en una aplicación Spring
Las aplicaciones web Spring suelen configurar su propio bean ObjectMapper, y Spring viene con sus propios mecanismos para personalizar la instancia ObjectMapper.
Puede preferir usar el comportamiento de serialización de IPF. Para hacerlo, dependa de ipf-serialization-autoconfigure.
<dependency>
<groupId>com.iconsolutions.ipf.core.platform</groupId>
<artifactId>ipf-serialization-autoconfigure</artifactId>
</dependency>
Este módulo de autoconfiguración de Spring se configura antes de cualquier autoconfiguración de Jackson de Spring, proporcionando el mismo ObjectMapper predeterminado (es una instancia diferente) usado por SerializationHelper.
Crear serializadores personalizados
Crear nuevos serializadores, lo cual será necesario cuando el comportamiento de serialización predeterminado no sea suficiente, puede lograrse de diferentes maneras.
Normalmente los proyectos downstream crean su propia instancia de ObjectMapper, la configuran programáticamente y ponen esa instancia a disposición en todos los lugares donde se necesita, pero también es posible definir y configurar serializadores personalizados vía hocon.
El enfoque elegido puede depender de los requisitos, p. ej., si necesita el comportamiento predeterminado con pequeños cambios, o si necesita un ObjectMapper completamente nuevo sin los valores predeterminados de IPF.
Cuando los valores predeterminados casi son suficientes, pero necesita cambiar cómo se serializan los valores nulos y vacíos, podría crear programáticamente una nueva instancia predeterminada y sobrescribir las inclusiones de serialización, p. ej.
//Crea una nueva instancia del ObjectMapper predeterminado de serialización
var mapper ObjectMapperFactory.createObjectMapper();
//Y la sobrescribe para escribir siempre nulos y objetos vacíos al serializar a JSON
mapper.setSerializationInclusion(JsonInclude.Include.ALWAYS);
Alternativamente, puede definir un mapper personalizado con sus propios módulos para personalización, p. ej.
akka {
serializers.my-custom-mapper = "akka.serialization.jackson.JacksonJsonSerializer"
//Hereda de serialization-helper, pero con personalizaciones
serialization.jackson.my-custom-mapper = ${akka.serialization.jackson.serialization-helper} {
jackson-modules += "com.food.bar.MyCustomModule"
}
}
/**
* Sobrescribe la configuración de inclusión de serialización
*/
public final class MyCustomModule extends SimpleModule {
@Override
public void setupModule(final SetupContext context) {
super.setupModule(context);
final ObjectMapper objectMapper = context.getOwner();
objectMapper.setSerializationInclusion(JsonInclude.Include.ALWAYS);
}
}
var mapper = ObjectMapperFactory.createObjectMapper("my-custom-mapper", config);
Cuando se requiera un serializador completamente nuevo, omita el marcador de posición usado para heredar de serialization-helper, p. ej.
akka {
serializers.my-custom-mapper = "akka.serialization.jackson.JacksonJsonSerializer"
}
| Cuando la serialización deba compartirse, p. ej., si produce una aplicación cliente y servidor, considere introducir un módulo de serialización compartido con su propia configuración/configuración programática y clases/métodos auxiliares, y por supuesto, pruebas, en lugar de duplicar la configuración y la inicialización del serializador. |
Comportamiento de serialización predeterminado
La instancia ObjectMapper de serialization-helper predeterminada es una instancia personalizada de lo definido para la serialización Akka Jackson, p. ej., configuración bajo akka.serialization.jackson, que a su vez es una instancia personalizada del ObjectMapper predeterminado, p. ej., new ObjectMapper().
NOTA: Se puede suponer que todas las características y configuraciones no mencionadas aquí son los valores predeterminados de Jackson.
Características de serialización
| Característica | Activada/Desactivada | Descripción |
|---|---|---|
|
Activada |
La duración |
|
Activada |
Se lanzará una excepción al intentar serializar un objeto que no tenga propiedades serializables. |
Características de deserialización
| Característica | Activada/Desactivada | Descripción |
|---|---|---|
|
Activada |
Establece el tipo de dato usado al deserializar números de punto flotante, en este caso |
Módulos
Los siguientes módulos se incluyen en el serializador predeterminado.
| Módulo | Descripción |
|---|---|
Un módulo de Jackson que añade soporte para tipos de Scala |
|
Un módulo de Jackson que añade soporte para acceder a nombres de parámetros; una característica añadida en JDK 8. |
|
Un módulo de Jackson que añade soporte para tipos de jdk8, p. ej. |
|
|
Un módulo de IPF que añade soporte para tipos de tiempo de Java, p. ej. |
|
Un módulo de IPF que compensa un problema con tipos enum con métodos de fábrica |
|
Un módulo de IPF que añade soporte para elementos DOM |
|
Un módulo de IPF que añade soporte para deserializar tipos |
Referencia de configuración
Se utiliza Akka Jackson para configurar los serializadores, por lo que el mejor lugar para comenzar es la documentación oficial. Para opciones de configuración específicas, consulte la sección Características adicionales.
Los detalles de configuración en la sección de Características adicionales, p. ej., todo lo que está bajo akka.serialization.jackson también puede aplicarse al serialization-helper predeterminado akka.serialization.jackson.serialization-helper, y a los serializadores personalizados, p. ej., akka.serialization.jackson.my-custom-serializer.
Akka Jackson no admite configurar las inclusiones de serialización para la instancia ObjectMapper, y normalmente esto se hace programáticamente, p. ej., new ObjectMapper().setSerializationInclusion(JsonIncludes.NON_NUll), pero esto puede lograrse con un módulo personalizado que personalice la instancia subyacente de ObjectMapper, p. ej.
public final class JsonIncludeOverrideModule extends SimpleModule {
@Override
public void setupModule(final SetupContext context) {
super.setupModule(context);
final ObjectMapper objectMapper = context.getOwner();
objectMapper.setSerializationInclusion(JsonInclude.Include.ALWAYS);
}
}
Qué hacer, qué no hacer, sugerencias, opiniones
Soportar tipos personalizados
Evite usar SerializationHelper, o cualquier otra instancia de ObjectMapper dentro de implementaciones personalizadas de com.fasterxml.jackson.databind.JsonSerializer y com.fasterxml.jackson.databind.JsonDeserializer, p. ej., no haga
final class SomeOtherObjectJsonSerializer extends JsonSerializer<SomeOtherObject> {
@Override
public void serialize(final SomeOtherObject value, final JsonGenerator gen, final SerializerProvider serializers) throws IOException {
//Usar SerializationHelper
final var string = SerializationHelper.objectToString(value);
gen.writeString(string);
}
}
Este JsonSerializer personalizado puede usarse dentro de cualquier instancia personalizada de ObjectMapper, pero depende del ObjectMapper predeterminado de serialization-helper, que puede comportarse de manera diferente en tiempo de ejecución, dependiendo de la configuración.
Prefiera el mecanismo proporcionado, p. ej., en el serializador anterior probablemente pueda lograr lo que necesita con los argumentos JsonGenerator y SerializerProvider. El JsonDeserializer proporciona facilidades similares.
Compartir serialización personalizada
Si tiene requisitos específicos de serialización y el serialization-helper predeterminado no es suficiente, considere crear su propio serializador con nombre y colocar su configuración y clases/métodos auxiliares, y por supuesto, pruebas, en un nuevo módulo. Este módulo puede ser usado por otros proyectos downstream.