NOTE: This is not a duplicate. That other question is not about auto-marshalling of Spring request params. It has a solution where you manually marshall objects with jackson.
I want to allow devs to create request objects with enums that can match with case-insensitivity. Other fields/properties may need case-sensitive matching, but the enums should be case-insensitive.
The only way I've found so far (initBinding) requires you to specify the exact enum class at compile time. I am looking for a more generic way to marshall the strings in the JSON request into enums.
The only current way I've found:
#RestController
public class TestController
{
//...elided...
#InitBinder
public void initBinder(final WebDataBinder webdataBinder)
{
webdataBinder.registerCustomEditor( MyEnum.class, new CaseInsensitiveEnumConverter() );
}
}
But this requires compiling with the enums pre-known.
you can see the class org.springframework.core.convert.support.StringToEnumConverterFactory, so you can customize yourself converterFactory like this.
public class MyStringToEnumConverterFactory implements ConverterFactory<String, Enum> {
#Override
public <T extends Enum> Converter<String, T> getConverter(Class<T> targetType) {
return new StringToEnum(getEnumType(targetType));
}
private class StringToEnum<T extends Enum> implements Converter<String, T> {
private final Class<T> enumType;
public StringToEnum(Class<T> enumType) {
this.enumType = enumType;
}
#Override
public T convert(String source) {
if (source.isEmpty()) {
// It's an empty enum identifier: reset the enum value to null.
return null;
}
return (T) Enum.valueOf(this.enumType, source.trim().toUpperCase());
}
}
private static Class<?> getEnumType(Class targetType) {
Class<?> enumType = targetType;
while (enumType != null && !enumType.isEnum()) {
enumType = enumType.getSuperclass();
}
if (enumType == null) {
throw new IllegalArgumentException(
"The target type " + targetType.getName() + " does not refer to an enum");
}
return enumType;
}
}
and add to ConverterRegistry .
#Configuration
public class MyConfiguration {
#Bean
public ConverterRegistry initConverter(ConverterRegistry registry) {
registry.addConverterFactory(new MyStringToEnumConverterFactory());
return registry;
}
}
Hope to help you!
Starting with spring 2.0 it should be enough to set the following in your application.properties:
spring.jackson.mapper.accept-case-insensitive-enums = true
Related
I want to convert between Set of Enum POJO and String[] Database(postgres) column.
and the enum class would be changed by another field type.
So I can say Enum class which's using in fooSet is changable and it's up to field type.
I know it's a messy. but I need a help.
Below are models
public interface A {
enum B implements A {
step1,
step2,
step3
}
enum C implements A {
step4,
step5,
step6
}
}
public abstract class Foo {
private String type;
}
public abstract class FooA {
private Set<B> fooSet;
}
public abstract class FooB {
private Set<C> fooSet;
}
I want to make a Converter like below.
SetOfEnumConverter<U extends Enum<U> & A> implements Converter<String[], Set<U>> {
#Override
public Set<U> from(String[] databaseObject) {
if (databaseObject == null) {
return null;
}
return Arrays.stream(databaseObject)
.map(x -> U.valueOf(U.class, x)). // here's the problem point
.collect(Collectors.toSet());
}
#Override
public String[] to(Set<U> userObject) {
if (userObject == null || userObject.isEmpty()) {
return null;
}
String[] strings = userObject.stream()
.map(Enum::name)
.toArray(String[]::new);
return ArrayUtils.isEmpty(strings) ? new String[0]: strings;
}
#Override
public Class<String[]> fromType() {
return String[].class;
}
#Override
public Class<Set<U>> toType() {
return (Class) TreeSet.class;
}
}
But the problem is I can't point .class attribute from a generic type maybe because of Generic Type erasure.
So, What I want to do is mapping the setEnum class to be used in the field fooSet according to the field type.
Because I have to make a single table for Foo and map from FooA, FooB and FooZ.
You can't do this without passing an actual Class<U> reference to your converter, e.g. like this:
SetOfEnumConverter<U extends Enum<U> & A> implements Converter<String[], Set<U>> {
final Class<U> u;
SetOfEnumConverter(Class<U> u) {
this.u = u;
}
// ...
}
And inside of the converter, you can use:
Enum.valueOf(u, x)
To look up arbitrary enum values by their names. Then, instantiate it with e.g.
new SetOfEnumConverter<>(MyEnum.class);
I have the following string to enum converter factory:
public final class StringToEnumConverterFactory implements ConverterFactory<String, Enum<?>> {
public <T extends Enum<?>> Converter<String, T> getConverter(Class<T> targetType) {
return new StringToEnumConverter(targetType);
}
#RequiredArgsConstructor
private static final class StringToEnumConverter<T extends Enum<T>> implements Converter<String, T> {
private final Class<T> enumType;
public T convert(String source) {
try {
return Enum.valueOf(this.enumType, source.toUpperCase().trim());
} catch (IllegalArgumentException e) {
throw new RuntimeException("Argument invalid " + source);
}
}
}
}
And I've implemented the following controller:
public interface GetGraphsController {
#GetMapping(value = "/graphs", produces = MediaType.APPLICATION_JSON_VALUE)
Graphs getGraphs(#RequestParam GraphType graphType);
}
GraphType corresponds to the following enum:
public enum GraphType {
A,
B;
}
Since graphType is required, I expect Spring to throw an exception when requesting /graphs?graphType= (note no graphType is included). However, passing no graphType is allowed, and no error is thrown.
I've also tried adding the following condition to convert, but the result is the same:
if (source.isBlank()) {
throw new RuntimeException("Argument invalid " + source);
}
I was finally able to solve it by adding #Validated to the controller interface and #NotNull to graphType. Apparently without those annotations validation is not made. #RequestParam has a required field (true by default) which only checks if the query parameter is included in the request and does not care about its value
I have an interface:
public interface ITransformer<S,T>{
public void transform(S source,T target);
default String getTransformerName(){
Class<S> s;
Class<T> t;
return s.getName() + t.getName(); //*********
}
}
the error message the starred line:
The local variable s may not have been initialized
The local variable t may not have been initialized
I would like to use this method to return a string with [S.classname][T.classname] . Please let me know how to achieve this or is this impossible to do at interface ?
Update: Jan 12
My purpose of doing this is due to the fact that this class will be in framework and I want to reduce the human error as much as possible.. I am changing the code as follows:
public interface ITransformer<S,T>{
public void transform(S source,T target);
public FieldEntry<S, T> getTransformerName();
}
public class FieldEntry<S,T> implements Comparable<FieldEntry> {
private Class<S> s;
private Class<T> t;
public FieldEntry(Class<S> s,Class<T> t){
this.s = s;
this.t = t;
}
public String getEntryName(){
return s.getName() + t.getName();
}
#Override
public int compareTo(FieldEntry entry) {
if(entry == null) throw new IllegalArgumentException("The argument to compare cannot be null!");
return entry.getEntryName().compareTo(this.getEntryName());
}
}
In order to demonstrate why this can’t work, you may change your class to
public interface ITransformer<S,T>{
public void transform(S source,T target);
static <In,Out> ITransformer<In,Out> noOp() {
return (source,target) -> {};
}
static void main(String... arg) {
ITransformer<String,Integer> t1 = noOp();
ITransformer<Long,Thread> t2 = noOp();
System.out.println(t1 == (Object)t2);
}
}
Running this will print true. In other words, both functions are represented by the same instances, so there can’t be and property allowing to recognize their different type.
Generally, when two functions (lambda expressions or method references) exhibit the same behavior, a JVM may represent them by the same implementation type or even the same instance.
Even for non-interface classes, this doesn’t work due to Type Erasure. It only works when you have a reifiable (i.e. non-generic) type extending or implementing a generic type.
It's a little bit dangerous and I wouldn't used this in production (because you should cover in your code all possible use cases of your interface), but you can use reflection for it:
public interface ITransformer<S, T> {
public void transform(S source, T target);
default String getTransformerName() {
Type[] genericInterfaces = this.getClass().getGenericInterfaces();
ParameterizedType parameterizedType = null;
for (Type genericInterface : genericInterfaces) {
if (genericInterface instanceof ParameterizedType) {
ParameterizedType paramInterface = (ParameterizedType) genericInterface;
if (paramInterface.getRawType().equals(ITransformer.class)) {
parameterizedType = paramInterface;
break;
}
}
}
if (parameterizedType == null) {
throw new IllegalStateException("!");
}
return parameterizedType.getActualTypeArguments()[0].getTypeName() + parameterizedType.getActualTypeArguments()[1].getTypeName();
}
}
public class StringToIntegerTransfomer implements ITransformer<String, Integer> {
#Override
public void transform(String source, Integer target) {
}
}
public interface StringToNumberTransfomer<T extends Number> extends ITransformer<String, T> {
}
public class StringToLongTransfomer implements StringToNumberTransfomer<Long>, ITransformer<String, Long> {
#Override
public void transform(String source, Long target) {
}
}
#Test
public void test() {
ITransformer<String, Integer> intTransformer = new StringToIntegerTransfomer();
ITransformer<String, Long> longTransformer = new StringToLongTransfomer();
ITransformer<String, String> stringTransformer = new ITransformer<String, String>() {
#Override
public void transform(String source, String target) {
}
};
ITransformer<String, Double> doubleTransformer = new StringToNumberTransfomer<Double>() {
#Override
public void transform(String source, Double target) {
}
};
System.out.println(String.format("intTransformer: %s", intTransformer.getTransformerName()));
System.out.println(String.format("longTransformer: %s", longTransformer.getTransformerName()));
System.out.println(String.format("stringTransformer: %s", stringTransformer.getTransformerName()));
System.out.println(String.format("doubleTransformer: %s", doubleTransformer.getTransformerName()));
}
Output for this snippet:
intTransformer: java.lang.Stringjava.lang.Integer
longTransformer: java.lang.Stringjava.lang.Long
stringTransformer: java.lang.Stringjava.lang.String
java.lang.IllegalStateException: !
This code has one restriction, you should say implements ITransformer<S, T> for all implementations of ITransformer. That why I have got IllegalStateException for this line ITransformer<String, Double> doubleTransformer = new StringToNumberTransfomer<Double>(). But you can improve this code.
Better option is to use some base implementation of interface and pass source and target classes into constructor:
public interface ITransformer<S, T> {
void transform(S source, T target);
String getTransformerName();
}
public abstract class BaseITransformer<S, T> implements ITransformer<S, T> {
private final Class<S> sourceClass;
private final Class<T> targetClass;
public BaseITransformer(Class<S> sourceClass, Class<T> targetClass) {
this.sourceClass = sourceClass;
this.targetClass = targetClass;
}
public String getTransformerName() {
return sourceClass.getName() + targetClass.getName();
}
}
In Java it is impossible to get a Class<S>, unless you already know which class S is, or something else that knows which class S is gives you one.
I want to mock a generic interface:
public interface IModel<T, S> {
public S classify(T entity);
}
This interface is sub-classed by 3 concrete classes: TextModel, ImageModel, ScoringModel. Each of these concrete classes have different T and S parameters.
I wrote a generic method that receives the concrete model class as an argument and generates a mocked version of the model:
private <T extends IModel<?, ?>> T mockModel(Class<T> modelClass) {
return new MockUp<T>() {
#Mock public Object classify(Object entity) { return null; }
}.getMockInstance();
}
I know that IModel::classify has generic types for both its input and output, but I haven't found a way to use the actual generic method within the mockup.
When calling this method I get an IllegalArgumentException:
java.lang.IllegalArgumentException: Value of type com.classificationmanager.model.$Impl_IModel incompatible with return type com.classificationmanager.model.TextModel of com.classificationmanager.model.TextModelFactory#createModel(com.classificationmanager.model.ModelDescriptor)
at com.classificationmanager.model.ModelFetcherTest$5.(ModelFetcherTest.java:110)
at com.classificationmanager.model.ModelFetcherTest.mockAllFactories(ModelFetcherTest.java:109) ....... (spared you the rest)
I thought that getting and returning an Object instead of T and S was the problem, but I get the same exception when removing the mocked method and just mocking the class:
private <T extends IModel<?, ?>> T mockModel(Class<T> modelClass) {
return new MockUp<T>() {
}.getMockInstance();
}
I could do a switch-case and return a concrete class but that would just be nasty.
Any workaround involving the Expectations API would also work for me.
10x
Maybe the following examples can help (although I still don't understand the question - probable case of the XY problem).
public final class ExampleTest {
public interface IModel<T, S> { S classify(T entity); }
static class TextModel implements IModel<Integer, String> {
#Override public String classify(Integer entity) { return "test"; }
}
static class ImageModel implements IModel<String, Image> {
#Override public Image classify(String entity) { return null; }
}
#Test
public void createNonMockedInstanceForAnyModelClass() {
IModel<Integer, String> m1 = mockModel(TextModel.class);
String s = m1.classify(123);
IModel<String, Image> m2 = mockModel(ImageModel.class);
Image img = m2.classify("test");
assertEquals("test", s);
assertNull(img);
}
<T extends IModel<?, ?>> T mockModel(Class<T> modelClass) {
// Or use newUninitializedInstance in case the model class doesn't
// have a no-args constructor.
return Deencapsulation.newInstance(modelClass);
}
#Test
public void mockAllModelImplementationClassesAndInstances(
#Capturing IModel<?, ?> anyModel
) {
IModel<Integer, String> m = new TextModel();
String s = m.classify(123);
assertNull(s);
}
}
Suppose I have an interface like this;
interface Validator<T>{
void validate<T value>
}
And these implementations ;
class StringValidator implements Validator<String>{
void validate<String value>{}
}
class OrderValidator implements Validator<Order>{
void validate<Order value>{}
}
In ValidatorRegisterer class I have a map;
class ValidationRegisterer{
Map<String, Validator> validatorsForPath = new HashMap<String, Validator>();
public Map<String, Validator> registerers(){
return validatorsForPath;
}
public void register(String path, Validator validator){
validatorsForPath.put(path, validator);
}
}
What I want is to iterate over this map in ValidationManager class with type safety;
class ValidationManager<RootObject>{
List<ValidationRegisterer> validationRegisterers;
public ValidationManager(List<ValidationRegisterer> validationRegisterers){
this.validationRegisterers = validationRegisterers;
}
public void validate(RootObject object){
for(ValidationRegisterer validationRegisterer in validationRegisterers){
for(String path : validationRegisterer.keySet()){
Object value = object.getPath(path);
Validator validator = validationRegisterer.get(path);
validator.validate(value);
//this line gets unchecked call to validate(T) warning and I want to get rid of it
//the problem is validationRegisterers map can contain both StringValidator and OrderValidator,
//so the value can be a String or an Order
//do I have to cast the value to the type of validator's T type?
}
}
}
Map<String, Validator> validatorsForPath = new HashMap<String, Validator>();
}
I tried to explain the situation in the last code sample comments.
Declare as follows to remove warnings :
Validator<Object> validator = validationRegisterer.get(path);
In this case you are declaring the validator reference that would work on Object type.
later you can typecast to Order or String after doing an instanceof test.
You need to make ValidationRegisterer class generic like this:
class ValidationRegisterer<T extends Validator> {
Map<String, T> validatorsForPath = new HashMap<String, T>();
public Map<String, T> registerers(){
return validatorsForPath;
}
public void register(String path, T validator){
validatorsForPath.put(path, validator);
}
}
And then maintain separate lists for these two types of ValidationRegisterer
class ValidationManager {
List<ValidationRegisterer<StringValidator>> strValidationRegisterers;
List<ValidationRegisterer<OrderValidator>> ordValidationRegisterers;
....
}
I will assume that with "type safety" you mean that you want to be certain that the object returned for a certain path is really of the type that the associated Validator accepts.
One problem is that the type parameter for the Validator is not available at compile time since, as you say yourself, any kind of Validator can be in the map.
Also, object.getPath(path) will always return an Object which will always need casting at runtime, so the fact that the validate method limits its argument to type T is of little use.
So the best you can do is make validation fail fast in case the object is not of the correct type.
A solution would be to
1. store the Class object for the Validator,
2. let validate accept an Object as parameter and dynamically cast the object to the validator type at the beginning of the validate method. This can be done in an abstract base class.
Example:
interface Validator<T> {
void validate(Object value);
Class<T> getType();
}
abstract class BaseValidator<T> implements Validator<T> {
private final Class<T> type;
public BaseValidator(Class<T> type) {
this.type = type;
}
public final void validate(Object o) {
doValidate(type.cast(o)); // wrong type will fail fast here
}
public final Class<T> getType() {
return type;
}
protected abstract void doValidate(T value);
}
class StringValidator extends BaseValidator<String> {
public StringValidator() {
super(String.class);
}
protected void doValidate(String value) {
// do actual string validation here
}
}
An alternative solution if you want to keep Object out of the Validator interface would be to let the path be resolved by a type parameterized object that has a reference the validator and performs the dynamic cast, and keep that in your registry map as value:
interface Validator<T> {
void validate(final T value);
}
class PathValidator<T> {
private final Class<T> type;
private final Validator<T> validator;
public PathValidator(final Class<T> type, final Validator<T> validator) {
this.type = type;
this.validator = validator;
}
public void validate(final RootObject object, final String path) {
T value = type.cast(object.getPath(path)); // throws ClassCastException here if not the correct type
validator.validate(value);
}
}
You would then have a Map<String, PathValidator<?> in your ValidationRegisterer class.
I'd personally prefer this alternative solution.