Skip to content

Instantly share code, notes, and snippets.

@libetl
Last active July 11, 2016 19:57
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 libetl/9c6ec1820444370c3c55670058f57426 to your computer and use it in GitHub Desktop.
Save libetl/9c6ec1820444370c3c55670058f57426 to your computer and use it in GitHub Desktop.
package com.fasterxml.jackson.databind.jsontype.ext;
import java.io.IOException;
import java.util.UUID;
import org.junit.Test;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonPropertyOrder;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import com.fasterxml.jackson.annotation.JsonTypeInfo.Id;
import com.fasterxml.jackson.core.JsonParseException;
import com.fasterxml.jackson.databind.DatabindContext;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.PropertyNamingStrategy;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.fasterxml.jackson.databind.annotation.JsonPOJOBuilder;
import com.fasterxml.jackson.databind.annotation.JsonTypeIdResolver;
import com.fasterxml.jackson.databind.jsontype.TypeIdResolver;
import com.fasterxml.jackson.databind.type.TypeFactory;
public class ExternalTypeIdTest1288 {
public static class ClassesWithoutBuilder {
public static class CreditCardDetails implements PaymentDetails {
private String cardHolderFirstName;
private String cardHolderLastName;
private String number;
private String expiryDate;
private int csc;
private String address;
private String zipCode;
private String city;
private String province;
private String countryCode;
private String description;
public void setCardHolderFirstName (String cardHolderFirstName) {
this.cardHolderFirstName = cardHolderFirstName;
}
public void setCardHolderLastName (String cardHolderLastName) {
this.cardHolderLastName = cardHolderLastName;
}
public void setNumber (String number) {
this.number = number;
}
public void setExpiryDate (String expiryDate) {
this.expiryDate = expiryDate;
}
public void setCsc (int csc) {
this.csc = csc;
}
public void setAddress (String address) {
this.address = address;
}
public void setZipCode (String zipCode) {
this.zipCode = zipCode;
}
public void setCity (String city) {
this.city = city;
}
public void setProvince (String province) {
this.province = province;
}
public void setCountryCode (String countryCode) {
this.countryCode = countryCode;
}
public void setDescription (String description) {
this.description = description;
}
}
public static class EncryptedCreditCardDetails implements PaymentDetails {
private UUID paymentInstrumentID;
private String name;
public void setPaymentInstrumentID (UUID paymentInstrumentID) {
this.paymentInstrumentID = paymentInstrumentID;
}
public void setName (String name) {
this.name = name;
}
}
public enum FormOfPayment {
INDIVIDUAL_CREDIT_CARD (CreditCardDetails.class), COMPANY_CREDIT_CARD (
CreditCardDetails.class), INSTRUMENTED_CREDIT_CARD (EncryptedCreditCardDetails.class);
private final Class<? extends PaymentDetails> clazz;
FormOfPayment (final Class<? extends PaymentDetails> clazz) {
this.clazz = clazz;
}
@SuppressWarnings ("unchecked")
public <T extends PaymentDetails> Class<T> getDetailsClass () {
return (Class<T>) this.clazz;
}
public static FormOfPayment fromDetailsClass (Class<PaymentDetails> detailsClass) {
for (FormOfPayment fop : FormOfPayment.values ()) {
if (fop.clazz == detailsClass) {
return fop;
}
}
throw new IllegalArgumentException ("not found");
}
}
public interface PaymentDetails {
public interface Builder {
PaymentDetails build ();
}
}
public static class PaymentMean {
private FormOfPayment formOfPayment;
private PaymentDetails paymentDetails;
public void setFormOfPayment (FormOfPayment formOfPayment) {
this.formOfPayment = formOfPayment;
}
@JsonTypeInfo (use = JsonTypeInfo.Id.CUSTOM, include = JsonTypeInfo.As.EXTERNAL_PROPERTY, property = "form_of_payment", visible = true)
@JsonTypeIdResolver (PaymentDetailsTypeIdResolver.class)
public void setPaymentDetails (PaymentDetails paymentDetails) {
this.paymentDetails = paymentDetails;
}
}
public static class PaymentDetailsTypeIdResolver implements TypeIdResolver {
@Override
public void init (JavaType baseType) {
}
@SuppressWarnings ("unchecked")
@Override
public String idFromValue (Object value) {
if (! (value instanceof PaymentDetails)) {
return null;
}
return FormOfPayment.fromDetailsClass ((Class<PaymentDetails>) value.getClass ()).name ();
}
@Override
public String idFromValueAndType (Object value, Class<?> suggestedType) {
return this.idFromValue (value);
}
@Override
public String idFromBaseType () {
return null;
}
@Override
public JavaType typeFromId (DatabindContext context, String id) throws IOException {
return TypeFactory.defaultInstance ().constructType (FormOfPayment.valueOf (id).getDetailsClass ());
}
@Override
public String getDescForKnownTypeIds () {
return "PaymentDetails";
}
@Override
public Id getMechanism () {
return JsonTypeInfo.Id.CUSTOM;
}
}
}
public static class ClassesWithBuilder {
@JsonDeserialize (builder = CreditCardDetails.IndividualCreditCardDetailsBuilder.class)
public static class CreditCardDetails implements PaymentDetails {
@JsonPOJOBuilder (withPrefix = "")
public static class CompanyCreditCardDetailsBuilder implements Builder {
private String cardHolderFirstName;
private String cardHolderLastName;
private String number;
private String expiryDate;
private int csc;
private String address;
private String zipCode;
private String city;
private String province;
private String countryCode;
public CompanyCreditCardDetailsBuilder address (final String address) {
this.address = address;
return this;
}
@Override
public CreditCardDetails build () {
return new CreditCardDetails (this.cardHolderFirstName, this.cardHolderLastName, this.number, this.expiryDate, this.csc, this.address, this.zipCode, this.city,
this.province, this.countryCode, "COMPANY CREDIT CARD");
}
public CompanyCreditCardDetailsBuilder cardHolderFirstName (final String cardHolderFirstName) {
this.cardHolderFirstName = cardHolderFirstName;
return this;
}
public CompanyCreditCardDetailsBuilder cardHolderLastName (final String cardHolderLastName) {
this.cardHolderLastName = cardHolderLastName;
return this;
}
public CompanyCreditCardDetailsBuilder city (final String city) {
this.city = city;
return this;
}
public CompanyCreditCardDetailsBuilder countryCode (final String countryCode) {
this.countryCode = countryCode;
return this;
}
public CompanyCreditCardDetailsBuilder csc (final int csc) {
this.csc = csc;
return this;
}
public CompanyCreditCardDetailsBuilder expiryDate (final String expiryDate) {
this.expiryDate = expiryDate;
return this;
}
public CompanyCreditCardDetailsBuilder number (final String number) {
this.number = number;
return this;
}
public CompanyCreditCardDetailsBuilder province (final String province) {
this.province = province;
return this;
}
public CompanyCreditCardDetailsBuilder zipCode (final String zipCode) {
this.zipCode = zipCode;
return this;
}
}
@JsonPOJOBuilder (withPrefix = "")
public static class IndividualCreditCardDetailsBuilder implements Builder {
private String cardHolderFirstName;
private String cardHolderLastName;
private String number;
private String expiryDate;
private int csc;
private String address;
private String zipCode;
private String city;
private String province;
private String countryCode;
private String description;
public IndividualCreditCardDetailsBuilder address (final String address) {
this.address = address;
return this;
}
@Override
public CreditCardDetails build () {
return new CreditCardDetails (this.cardHolderFirstName, this.cardHolderLastName, this.number, this.expiryDate, this.csc, this.address, this.zipCode, this.city,
this.province, this.countryCode, this.description);
}
public IndividualCreditCardDetailsBuilder cardHolderFirstName (final String cardHolderFirstName) {
this.cardHolderFirstName = cardHolderFirstName;
return this;
}
public IndividualCreditCardDetailsBuilder cardHolderLastName (final String cardHolderLastName) {
this.cardHolderLastName = cardHolderLastName;
return this;
}
public IndividualCreditCardDetailsBuilder city (final String city) {
this.city = city;
return this;
}
public IndividualCreditCardDetailsBuilder countryCode (final String countryCode) {
this.countryCode = countryCode;
return this;
}
public IndividualCreditCardDetailsBuilder csc (final int csc) {
this.csc = csc;
return this;
}
public IndividualCreditCardDetailsBuilder description (final String description) {
this.description = description;
return this;
}
public IndividualCreditCardDetailsBuilder expiryDate (final String expiryDate) {
this.expiryDate = expiryDate;
return this;
}
public IndividualCreditCardDetailsBuilder number (final String number) {
this.number = number;
return this;
}
public IndividualCreditCardDetailsBuilder province (final String province) {
this.province = province;
return this;
}
public IndividualCreditCardDetailsBuilder zipCode (final String zipCode) {
this.zipCode = zipCode;
return this;
}
}
private final String cardHolderFirstName;
private final String cardHolderLastName;
private final String number;
private final String expiryDate;
private final int csc;
private final String address;
private final String zipCode;
private final String city;
private final String province;
private final String countryCode;
private final String description;
public CreditCardDetails (final String cardHolderFirstName, final String cardHolderLastName, final String number, final String expiryDate, final int csc,
final String address, final String zipCode, final String city, final String province, final String countryCode, final String description) {
super ();
this.cardHolderFirstName = cardHolderFirstName;
this.cardHolderLastName = cardHolderLastName;
this.number = number;
this.expiryDate = expiryDate;
this.csc = csc;
this.address = address;
this.zipCode = zipCode;
this.city = city;
this.province = province;
this.countryCode = countryCode;
this.description = description;
}
}
@JsonDeserialize (builder = EncryptedCreditCardDetails.InstrumentedCreditCardBuilder.class)
public static class EncryptedCreditCardDetails implements PaymentDetails {
@JsonPOJOBuilder (withPrefix = "")
public static class InstrumentedCreditCardBuilder implements Builder {
private UUID paymentInstrumentID;
private String name;
@Override
public EncryptedCreditCardDetails build () {
return new EncryptedCreditCardDetails (this.paymentInstrumentID, this.name);
}
public InstrumentedCreditCardBuilder name (final String name) {
this.name = name;
return this;
}
public InstrumentedCreditCardBuilder paymentInstrumentID (final UUID paymentInstrumentID) {
this.paymentInstrumentID = paymentInstrumentID;
return this;
}
}
private final UUID paymentInstrumentID;
private final String name;
private EncryptedCreditCardDetails (final UUID paymentInstrumentID, final String name) {
super ();
this.paymentInstrumentID = paymentInstrumentID;
this.name = name;
}
}
public enum FormOfPayment {
INDIVIDUAL_CREDIT_CARD (CreditCardDetails.IndividualCreditCardDetailsBuilder.class), COMPANY_CREDIT_CARD (
CreditCardDetails.CompanyCreditCardDetailsBuilder.class), INSTRUMENTED_CREDIT_CARD (EncryptedCreditCardDetails.InstrumentedCreditCardBuilder.class);
private final Class<? extends PaymentDetails.Builder> builderClass;
FormOfPayment (final Class<? extends PaymentDetails.Builder> builderClass) {
this.builderClass = builderClass;
}
@SuppressWarnings ("unchecked")
public <T extends PaymentDetails> Class<T> getDetailsClass () {
return (Class<T>) this.builderClass.getEnclosingClass ();
}
public static FormOfPayment fromDetailsClass (Class<PaymentDetails> detailsClass) {
for (FormOfPayment fop : FormOfPayment.values ()) {
if (fop.builderClass.getEnclosingClass () == detailsClass) {
return fop;
}
}
throw new IllegalArgumentException ("not found");
}
}
public interface PaymentDetails {
public interface Builder {
PaymentDetails build ();
}
}
@JsonDeserialize (builder = PaymentMean.Builder.class)
public static class PaymentMean {
@JsonPOJOBuilder (withPrefix = "")
@JsonPropertyOrder ({ "form_of_payment", "payment_details" })
public static class Builder {
private FormOfPayment formOfPayment;
private PaymentDetails paymentDetails;
public PaymentMean build () {
return new PaymentMean (this.formOfPayment, this.paymentDetails);
}
// if you annotate with @JsonIgnore, it works, but the value
// disappears in the constructor
public Builder formOfPayment (final FormOfPayment val) {
this.formOfPayment = val;
return this;
}
@JsonTypeInfo (use = JsonTypeInfo.Id.CUSTOM, include = JsonTypeInfo.As.EXTERNAL_PROPERTY, property = "form_of_payment", visible = true)
@JsonTypeIdResolver (PaymentDetailsTypeIdResolver.class)
public Builder paymentDetails (final PaymentDetails val) {
this.paymentDetails = val;
return this;
}
}
public static Builder create () {
return new Builder ();
}
private final FormOfPayment formOfPayment;
private final PaymentDetails paymentDetails;
private PaymentMean (final FormOfPayment formOfPayment, final PaymentDetails paymentDetails) {
super ();
this.formOfPayment = formOfPayment;
this.paymentDetails = paymentDetails;
}
}
public static class PaymentDetailsTypeIdResolver implements TypeIdResolver {
@Override
public void init (JavaType baseType) {
}
@SuppressWarnings ("unchecked")
@Override
public String idFromValue (Object value) {
if (! (value instanceof PaymentDetails)) {
return null;
}
return FormOfPayment.fromDetailsClass ((Class<PaymentDetails>) value.getClass ()).name ();
}
@Override
public String idFromValueAndType (Object value, Class<?> suggestedType) {
return this.idFromValue (value);
}
@Override
public String idFromBaseType () {
return null;
}
@Override
public JavaType typeFromId (DatabindContext context, String id) throws IOException {
return TypeFactory.defaultInstance ().constructType (FormOfPayment.valueOf (id).getDetailsClass ());
}
@Override
public String getDescForKnownTypeIds () {
return "PaymentDetails";
}
@Override
public Id getMechanism () {
return JsonTypeInfo.Id.CUSTOM;
}
}
}
@Test
public void tryToDeserialize () throws JsonParseException, JsonMappingException, IOException {
// given
final String asJson1 = "{\"form_of_payment\":\"INDIVIDUAL_CREDIT_CARD\", \"payment_details\":{\"card_holder_first_name\":\"John\", \"card_holder_last_name\":\"Doe\", \"number\":\"XXXXXXXXXXXXXXXX\", \"expiry_date\":\"MM/YY\","
+ "\"csc\":666,\"address\":\"10 boulevard de Sebastopol\",\"zip_code\":\"75001\",\"city\":\"Paris\",\"province\":\"Ile-de-France\",\"country_code\":\"FR\",\"description\":\"John Doe personal credit card\"}}";
final String asJson2 = "{\"form_of_payment\":\"INSTRUMENTED_CREDIT_CARD\",\"payment_details\":{\"payment_instrument_id\":\"00000000-0000-0000-0000-000000000000\", \"name\":\"Mr John Doe encrypted credit card\"}}";
final ObjectMapper objectMapper = new ObjectMapper ().setPropertyNamingStrategy (PropertyNamingStrategy.SNAKE_CASE)
.disable (DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
// when
final ClassesWithoutBuilder.PaymentMean paymentMean1 = objectMapper.readValue (asJson1, ClassesWithoutBuilder.PaymentMean.class);
final ClassesWithBuilder.PaymentMean paymentMean2 = objectMapper.readValue (asJson2, ClassesWithBuilder.PaymentMean.class);
// then paymentMean2 should be unmarshalled (payment1 will be naturally)
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment