Skip to content

infobip/infobip-jackson-extension

Repository files navigation

Infobip Jackson Extension

Maven Central Coverage Status Known Vulnerabilities

Library which provides new features for (de)serialization on top of Jackson library.

Contents

  1. Changelog
  2. Setup
  3. Features

Changelog

For changes check the changelog.

Setup

All examples have corresponding tests and additional usage examples can be found in tests.

In examples, tests and Single Argument Property Creator annotationless support it's required that the code is compiled with -parameters compiler option and that jackson-module-parameter-names module is used.

Parameter names module:

<dependency>
   <groupId>com.fasterxml.jackson.module</groupId>
   <artifactId>jackson-module-parameter-names</artifactId>
</dependency>

Compiler plugin setup:

<plugin>
	<groupId>org.apache.maven.plugins</groupId>
	<artifactId>maven-compiler-plugin</artifactId>
	<version>${maven-compiler-plugin.version}</version>
	<configuration>
       ...
		<compilerArgument>-parameters</compilerArgument>
		<testCompilerArgument>-parameters</testCompilerArgument>
       ...
	</configuration>
</plugin>

Parameter names module makes parameter names visible to the Jackson meaning it can map json properties to constructor parameter names so there's no redundant Jackson annotation to redeclare them. There's an important catch here: JSON field name, Java accessors and constructor parameter name all must match!

Spring Boot

Just include the following dependency:

<dependency>
    <groupId>com.infobip</groupId>
    <artifactId>infobip-jackson-extension-spring-boot-starter</artifactId>
    <version>${infobip-jackson-extension.version}</version>
</dependency>

Without Spring Boot

Include the following dependency:

<dependency>
    <groupId>com.infobip</groupId>
    <artifactId>infobip-jackson-extension-module</artifactId>
    <version>${infobip-jackson-extension.version}</version>
</dependency>

Register the module with ObjectMapper:

objectMapper.registerModule(new InfobipJacksonModule());     

Features

Simple json hierarchy

For models that have a type represented by an enum you can use simple typed json approach:

interface FooBar extends SimpleJsonHierarchy<FooBarType> {
}

record Foo(String foo) implements FooBar {

   @Override
   public FooBarType getType() {
      return FooBarType.FOO;
   }

}

record Bar(String bar) implements FooBar {

   @Override
   public FooBarType getType() {
      return FooBarType.BAR;
   }

}

@Getter
@AllArgsConstructor
enum FooBarType implements TypeProvider<FooBar> {
   FOO(Foo.class),
   BAR(Bar.class);

   private final Class<? extends FooBar> type;
}

Showcase

Overriding type json property name

Name of the property can be overridden:

Showcase

Lower case type value

Casing of the property type value can be overridden:

Showcase

Multi level hierarchies

If you have multiple levels of hierarchy following approach can be used:

@JsonTypeResolveWith(AnimalJsonTypeResolver.class)
interface Animal {
    AnimalType getAnimalType();
}

static class AnimalJsonTypeResolver extends SimpleJsonTypeResolver<AnimalType> {
    public AnimalJsonTypeResolver() {
        super(AnimalType.class, "animalType");
    }
}

@JsonTypeResolveWith(MammalJsonTypeResolver.class)
interface Mammal extends Animal {
    MammalType getMammalType();
}

static class MammalJsonTypeResolver extends SimpleJsonTypeResolver<MammalType> {

    public MammalJsonTypeResolver() {
        super(MammalType.class, "mammalType");
    }
}

record Human(String name) implements Mammal {

   @Override
   public AnimalType getAnimalType() {
      return AnimalType.MAMMAL;
   }

   @Override
   public MammalType getMammalType() {
      return MammalType.HUMAN;
   }

}

@Getter
@AllArgsConstructor
enum AnimalType implements TypeProvider<Animal> {
   MAMMAL(Mammal.class);

   private final Class<? extends Animal> type;
}

@Getter
@AllArgsConstructor
enum MammalType implements TypeProvider<Mammal> {
   HUMAN(Human.class);

   private final Class<? extends Mammal> type;
}

Showcase

Parallel hierarchies

In case you have multiple hierarchies that reuse the same enum TypeProvider can present an issue, use this approach to as a guide to possible solution:

Showcase.

Typeless (present property)

In case you don't want to (or can't - third party API) include type information in json, you can use this approach:

interface Bike extends PresentPropertyJsonHierarchy<BikeType> {
}

record RoadBike(String roadBike) implements Bike {
}

record Bmx(String bmx) implements Bike {
}

@Getter
@AllArgsConstructor
enum BikeType implements TypeProvider<Bike> {
   ROAD_BIKE(RoadBike.class),
   BMX(Bmx.class);

   private final Class<? extends Bike> type;
}

Showcase.

Notice that by default snake cased type names are translated to camel case properties (e.g. ROAD_BIKE -> roadBike). If you want to use different casing you can provide your own resolver by extending PresentPropertyJsonTypeResolver:

static class LowerUnderscorePresentPropertyJsonTypeResolver<E extends Enum<E> & TypeProvider> extends PresentPropertyJsonTypeResolver<E> {

    public LowerUnderscorePresentPropertyJsonTypeResolver(Class<E> type) {
        super(type, CaseFormat.LOWER_UNDERSCORE);
    }
}

Then your model may look as follows:

@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class)
@JsonTypeResolveWith(LowerUnderscorePresentPropertyJsonTypeResolver.class)
interface Bike extends PresentPropertyJsonHierarchy<BikeType> {

}

static class LowerUnderscorePresentPropertyJsonTypeResolver<E extends Enum<E> & TypeProvider<E>>
        extends PresentPropertyJsonTypeResolver<E> {

   public LowerUnderscorePresentPropertyJsonTypeResolver(Class<E> type) {
      super(type, CaseFormat.LOWER_UNDERSCORE);
   }
}

record RoadBike(String roadBike) implements Bike {

}

record MountainBike(String mountainBike) implements Bike {

}

Notice standard jackson @JsonNaming annotation in Bike interface.

Showcase.

Dynamic hierarchy

In case root of the hierarchy is decoupled from the leaves in different compilation units. Also it can be seen as annotationless alternative.

Showcase without type provider.

Showcase with type provider.

In case Spring Boot starter is used you only need to register DynamicHierarchyDeserializer as a @Bean, starter handles wiring with Jackson.

Single Argument Property Creator annotationless support

This module also adds support for deserializing single property value objects when using parameter names module:

class Foo {
    private final Bar bar;

    Foo(Bar bar) {
        this.bar = bar;
    }
}

without any additional configuration or annotations. Related issues: FasterXML/jackson-databind#1498, spring-projects/spring-boot#26023.

Requirements:

  • Java 17

Contributing

If you have an idea for a new feature or want to report a bug please use the issue tracker.

Pull requests are welcome!

License

This library is licensed under the Apache License, Version 2.0.

About

Library which provides new features for (de)serialization on top of Jackson library.

Topics

Resources

License

Stars

Watchers

Forks

Packages

No packages published

Languages