Micronaut fails to load and use my TypeConverter implementation

950 Views Asked by At

I have a controller method, that takes in a POJO.

@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor

public class Dto {    
    private LocalDate endDate;
    private String token;
    private TransactionType type;   
}

Transaction type is a simple enum, but I want to use a custom conversion from the inbound value to the transaction type.

@Slf4j
@Controller("/api/transactions")
public class IssuerTransactionController {
    @Get(value = "/{?tr*}", produces = APPLICATION_JSON)
    public List<String> get(Dto tr) {
        return new ArrayList<>();
    }
}

I have written a converter:

@Slf4j
@Singleton
public class TransactionTypeConverter implements TypeConverter<String, TransactionType> {
    @Override
    public Optional<TransactionType> convert(String value, Class<TransactionType> targetType, ConversionContext context) {
        return Arrays.stream(TransactionType.values())
                .filter(txnType -> StringUtils.equals(txnType.getTransactionType(), value) || StringUtils.equals(txnType.name(), value))
                .findFirst();
    }
}

Micronaut is not using the type converter to transform the inbound value?
Is some special registration process needed in order for Micronaut to know that it should be using the converter?

If I add a constructor to TransactionTypeConverter I can see that the class is never actually created by Micronaut at all.

If I add it as a regular dependency to the controller, it's loaded (no surprise there), but still not used. Is there a step I am missing?

2

There are 2 best solutions below

2
On BEST ANSWER

Seems you are using the Binding from Multiple Query values functionality which under the hood is just creating the map of the query parameters you passed in and uses the Jackson to convert the map into your own POJO. So it does not rely on the system converters but only on the Jackson itself.

What you can do is just use Jacksons @JsonCreator annotation to customize the conversation.

Something like this should work.

public enum TransactionType {
    A ("A"),
    B ("B");

    private final String transactionType;

    TransactionType(String transactionType){
        this.transactionType = transactionType;
    }

    public String getTransactionType() {
        return transactionType;
    }

    @JsonCreator
    public static TransactionType forValue(Collection<String> values) {           
        if(values == null || values.isEmpty()){
            return null;
        }
        String value = values.get(0);
        return Arrays.stream(TransactionType.values())
            .filter(txnType -> StringUtils.equals(txnType.getTransactionType(), value) || StringUtils.equals(txnType.name(), value))
            .findFirst().orElse(null);
    }
}
0
On

An alternative way to do this, if you cannot change the sourcecode of the class you need to load, would be to create a Jackson module. By default micronaut will scan and load modules into jackson if they are defined as beans. Example:

import com.fasterxml.jackson.core.JacksonException;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonNode;
import jakarta.inject.Singleton;

import java.io.IOException;

@Singleton
public class TransactionTypeDeserializer extends StdDeserializer<TransactionType> {

    public TransactionTypeDeserializer() {
        this(null);
    }

    public TransactionTypeDeserializer(Class<?> vc) {
        super(vc);
    }

    @Override
    public TransactionType deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException, JacksonException {
        JsonNode node = jsonParser.getCodec().readTree(jsonParser);
        // Do conversion
        return result;
    }
}