Skip to content

Instantly share code, notes, and snippets.

@rnkoaa
Last active November 20, 2021 14:45
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save rnkoaa/c937c6dd35c30a4e7bde52de0d02f4d3 to your computer and use it in GitHub Desktop.
Save rnkoaa/c937c6dd35c30a4e7bde52de0d02f4d3 to your computer and use it in GitHub Desktop.
plugins {
id 'java'
id 'application'
}
group 'org.example'
version '1.0-SNAPSHOT'
repositories {
mavenLocal()
mavenCentral()
}
application {
mainClass = 'com.richard.simple.MainApplication'
}
dependencies {
implementation("io.vavr:vavr:0.10.4")
implementation("io.projectreactor:reactor-core:3.4.12")
annotationProcessor("org.immutables:value:2.8.2")
implementation("org.immutables:value:2.8.2")
implementation("com.fasterxml.jackson.core:jackson-core:2.13.0")
implementation("com.fasterxml.jackson.core:jackson-databind:2.13.0")
implementation("com.fasterxml.jackson.core:jackson-annotations:2.13.0")
implementation("com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.13.0")
testImplementation("org.junit.jupiter:junit-jupiter-api:5.8.1")
testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:5.8.1")
}
test {
useJUnitPlatform()
testLogging {
// events "passed", "skipped", "failed"
showStandardStreams = true
}
}
@JsonDeserialize(using = CustomDeserializer.class)
public abstract class BaseClass {
private String commonProp;
}
// Important to override the base class' usage of CustomDeserializer which produces an infinite loop
@JsonDeserialize(using = JsonDeserializer.None.class)
public class ClassA extends BaseClass {
private String classAProp;
}
@JsonDeserialize(using = JsonDeserializer.None.class)
public class ClassB extends BaseClass {
private String classBProp;
}
public class CustomDeserializer extends StdDeserializer<BaseClass> {
protected CustomDeserializer() {
super(BaseClass.class);
}
@Override
public BaseClass deserialize(JsonParser p, DeserializationContext ctxt) throws IOException, JsonProcessingException {
TreeNode node = p.readValueAsTree();
// Select the concrete class based on the existence of a property
if (node.get("classAProp") != null) {
return p.getCodec().treeToValue(node, ClassA.class);
}
return p.getCodec().treeToValue(node, ClassB.class);
}
}
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface EventMetadata {
int version() default 1;
String name() default "";
}
public static void main(String[] args) throws JsonProcessingException {
var objectMapper = provideObjectMapper();
var list = new VersionedEventList();
var event = ImmutableProductCreatedEvent.builder()
.name("Hane's T-Shirts")
.build();
list.add(event);
var updatedEvent = ImmutableProductUpdatedEvent.builder()
.active(true)
.name("Hane's Blue T-Shirts")
.build();
list.add(updatedEvent);
System.out.println(objectMapper.writeValueAsString(event));
System.out.println(objectMapper.writeValueAsString(updatedEvent));
String value = objectMapper.writeValueAsString(list);
System.out.println(value);
VersionedEventList deserializedEvents = objectMapper.readValue(value, VersionedEventList.class);
System.out.println(deserializedEvents);
}
static ObjectMapper provideObjectMapper() {
var objectMapper = new ObjectMapper().findAndRegisterModules();
objectMapper.setPropertyNamingStrategy(new PropertyNamingStrategies.SnakeCaseStrategy());
// Ignore null values when writing json.
objectMapper.setSerializationInclusion(Include.NON_DEFAULT);
objectMapper.configure(DeserializationFeature.USE_BIG_DECIMAL_FOR_FLOATS, true);
// objectMapper.configure(SerializationFeature., true);
// Write times as a String instead of a Long so its human readable.
objectMapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false);
objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
// objectMapper.configure(DeserializationFeature.UNWRAP_ROOT_VALUE)
objectMapper.registerModule(new JavaTimeModule());
return objectMapper;
}
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.richard.simple.base.EventMetadata;
import com.richard.simple.base.VersionedEvent;
import org.immutables.value.Value.Immutable;
@Immutable
@JsonSerialize
@JsonDeserialize(builder = ImmutableProductCreatedEvent.Builder.class)
@EventMetadata(name = "ProductCreatedEvent")
public interface ProductCreatedEvent extends VersionedEvent {
String getName();
}
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.richard.simple.base.EventMetadata;
import com.richard.simple.base.VersionedEvent;
import org.immutables.value.Value.Immutable;
@Immutable
@JsonSerialize
@JsonDeserialize(builder = ImmutableProductUpdatedEvent.Builder.class)
@EventMetadata(name = "ProductUpdatedEvent")
public interface ProductUpdatedEvent extends VersionedEvent {
String getName();
boolean active();
}
package com.richard.simple.base;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import com.fasterxml.jackson.databind.annotation.JsonTypeIdResolver;
import java.time.Instant;
import java.util.UUID;
import org.immutables.value.Value.Default;
@JsonTypeInfo(use = JsonTypeInfo.Id.CUSTOM, property = "type")
@JsonTypeIdResolver(VersionedEventTypeIdResolver.class)
public interface VersionedEvent {
default UUID getId() {
return UUID.randomUUID();
}
@Default
default Instant getCreated() {
return Instant.now();
}
@Default
default long getVersion() {
return 1L;
}
}
import com.richard.simple.base.VersionedEvent;
import java.util.ArrayList;
public class VersionedEventList extends ArrayList<VersionedEvent> {
}
package com.richard.simple.base;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import com.fasterxml.jackson.databind.DatabindContext;
import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.jsontype.impl.TypeIdResolverBase;
import com.fasterxml.jackson.databind.type.TypeFactory;
public class VersionedEventTypeIdResolver extends TypeIdResolverBase {
private static final ClassLoader classLoader = VersionedEventTypeIdResolver.class.getClassLoader();
private JavaType mBaseType;
@Override
public void init(JavaType baseType) {
this.mBaseType = baseType;
}
@Override
public String idFromValue(Object value) {
return idFromValueAndType(value, value.getClass());
}
@Override
public String idFromValueAndType(Object value, Class<?> suggestedType) {
return suggestedType.getName();
}
@Override
public String idFromBaseType() {
return idFromValueAndType(null, mBaseType.getRawClass());
}
@Override
public JavaType typeFromId(DatabindContext context, String id) {
Class<?> clazz;
try {
clazz = classLoader.loadClass(id);
} catch (ClassNotFoundException e) {
throw new IllegalStateException("cannot find class '" + id + "'");
}
return TypeFactory.defaultInstance().constructSpecializedType(mBaseType, clazz);
}
@Override
public JsonTypeInfo.Id getMechanism() {
return JsonTypeInfo.Id.CUSTOM;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment