Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

convert empty string to empty collection or array #54

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions docs/Reference Doc/v0.1.0/restlight_starter/serialization.md
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@ spring.gson.date-format=yyyy-MM-dd HH:mm:ss
```

Restlight同样支持该标准,其作用逻辑如下图所示:
![序列化器兼容springboot逻辑图.png](img/SerializationOfSpringBoot.png)
![序列化器兼容springboot逻辑图.png](../../../img/serialization_of_springboot.png)
如图所示,配置文件中Jackson和Gson相关内容要想生效,需要满足如下条件:
> 1. 用户没有手动定制HttpJsonBodySerializerAdapter
> 2. 用户没有手动注入Jackson或者Gson序列化器(FastJsonHttpBodySerializer和GsonJsonHttpBodySerializer)
Expand Down Expand Up @@ -310,4 +310,4 @@ Restlight内置了ProtoBuf序列化器的实现,对应支持的MediaType为`ap
> - 值为ByteBuf类型时直接将结果写入响应
> - 以上均不符合则使用序列化器进行序列化。
> - 如果Controlelr上未配置`@ResponseBody`,值为基本类型或基本类型包装类将返回该类型的字符串结果(调用String.valueOf())。
> - 以上均不符合则抛异常。
> - 以上均不符合则抛异常。
Binary file added docs/img/serialization_of_springboot.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
import java.util.List;
import java.util.Map;
import java.util.NavigableSet;
import java.util.Optional;
import java.util.Set;
import java.util.SortedSet;
import java.util.StringTokenizer;
Expand All @@ -63,35 +64,39 @@ private ConverterUtils() {
= new HashMap<>(32);

static {
STRING_CONVERTER_MAP.put(Byte.class, Byte::valueOf);
STRING_CONVERTER_MAP.put(byte.class, Byte::parseByte);
STRING_CONVERTER_MAP.put(Byte.class, v -> StringUtils.isEmpty(v) ? null : Byte.valueOf(v));
STRING_CONVERTER_MAP.put(byte.class, v -> v == null ? null : Byte.parseByte(v));

STRING_CONVERTER_MAP.put(Character.class, v -> v.length() > 0 ? v.charAt(0) : v);
STRING_CONVERTER_MAP.put(char.class, v -> v.length() >= 1 ? v.charAt(0) : v);
STRING_CONVERTER_MAP.put(Character.class, v -> StringUtils.isEmpty(v)
? null : v.charAt(0));
STRING_CONVERTER_MAP.put(char.class, v -> v == null ? null : v.length() > 0 ? v.charAt(0) : v);

STRING_CONVERTER_MAP.put(Boolean.class, v -> (Boolean.parseBoolean(v) || "1".equals(v)) ? Boolean.TRUE :
Boolean.FALSE);
STRING_CONVERTER_MAP.put(boolean.class, v -> (Boolean.parseBoolean(v) || "1".equals(v)));
STRING_CONVERTER_MAP.put(Boolean.class, v -> StringUtils.isEmpty(v)
? null : (Boolean.parseBoolean(v) || "1".equals(v)) ? Boolean.TRUE : Boolean.FALSE);
STRING_CONVERTER_MAP.put(boolean.class, v -> v == null ? null : (Boolean.parseBoolean(v) || "1".equals(v)));

STRING_CONVERTER_MAP.put(Short.class, Short::valueOf);
STRING_CONVERTER_MAP.put(short.class, Short::parseShort);
STRING_CONVERTER_MAP.put(Short.class, v -> StringUtils.isEmpty(v) ? null : Short.valueOf(v));
STRING_CONVERTER_MAP.put(short.class, v -> v == null ? null : Short.parseShort(v));

STRING_CONVERTER_MAP.put(Integer.class, Integer::valueOf);
STRING_CONVERTER_MAP.put(int.class, Integer::parseInt);
STRING_CONVERTER_MAP.put(Integer.class, v -> StringUtils.isEmpty(v) ? null : Integer.valueOf(v));
STRING_CONVERTER_MAP.put(int.class, v -> v == null ? null : Integer.parseInt(v));

STRING_CONVERTER_MAP.put(Long.class, Long::valueOf);
STRING_CONVERTER_MAP.put(long.class, Long::parseLong);
STRING_CONVERTER_MAP.put(Long.class, v -> StringUtils.isEmpty(v) ? null : Long.valueOf(v));
STRING_CONVERTER_MAP.put(long.class, v -> v == null ? null : Long.parseLong(v));

STRING_CONVERTER_MAP.put(Double.class, Double::valueOf);
STRING_CONVERTER_MAP.put(double.class, Double::parseDouble);
STRING_CONVERTER_MAP.put(Double.class, v -> StringUtils.isEmpty(v) ? null : Double.valueOf(v));
STRING_CONVERTER_MAP.put(double.class, v -> v == null ? null : Double.parseDouble(v));

STRING_CONVERTER_MAP.put(Float.class, Float::valueOf);
STRING_CONVERTER_MAP.put(float.class, Float::parseFloat);
STRING_CONVERTER_MAP.put(Float.class, v -> StringUtils.isEmpty(v) ? null : Float.valueOf(v));
STRING_CONVERTER_MAP.put(float.class, v -> v == null ? null : Float.parseFloat(v));

STRING_CONVERTER_MAP.put(Void.class, v -> null);
STRING_CONVERTER_MAP.put(void.class, v -> null);

STRING_CONVERTER_MAP.put(BigDecimal.class, v -> BigDecimal.valueOf(Double.parseDouble(v)));
STRING_CONVERTER_MAP.put(BigDecimal.class, v -> StringUtils.isEmpty(v)
? null : new BigDecimal(v));

// convert a empty string to a null is not supported
STRING_CONVERTER_MAP.put(Timestamp.class, Timestamp::valueOf);
STRING_CONVERTER_MAP.put(String.class, v -> v);
STRING_CONVERTER_MAP.put(Object.class, v -> v);
Expand Down Expand Up @@ -139,17 +144,13 @@ public static String normaliseDefaultValue(String value) {
@SuppressWarnings("unchecked")
public static <T> T forceConvertStringValue(String value, Type requiredType) {
Checks.checkNotNull(requiredType, "requiredType");
if (StringUtils.isEmpty(value)) {
return null;
} else {
Function<String, Object> converter = ConverterUtils.str2ObjectConverter(requiredType);
if (converter == null) {
throw new IllegalArgumentException("Could not convert given value '"
+ value
+ "'to target type '" + requiredType + "'");
}
return (T) converter.apply(value);
Function<String, Object> converter = ConverterUtils.str2ObjectConverter(requiredType);
if (converter == null) {
throw new IllegalArgumentException("Could not convert given value '"
+ value
+ "' to target type '" + requiredType + "'");
}
return (T) converter.apply(value);
}

/**
Expand All @@ -172,12 +173,7 @@ public static Function<String, Object> str2ObjectConverter(Type requiredType, Fu
if (str2Object == null) {
return def;
}
return p -> {
if (p == null) {
return null;
}
return str2Object.apply(p);
};
return str2Object;
}

/**
Expand All @@ -193,12 +189,8 @@ public static Function<Collection<String>, Object> strs2ObjectConverter(Type req
if (strs2Object == null) {
return null;
}
return p -> {
if (p == null) {
return null;
}
return strs2Object.apply(p);
};

return strs2Object;
}

private static Function<Collection<String>, Object> strs2ObjectConverter(Class<?> requiredClass,
Expand All @@ -215,6 +207,43 @@ private static Function<Collection<String>, Object> strs2ObjectConverter(Class<?
return null;
}

private static Class<?> retrieveElementType(Type requiredType) {
Class<?> elementType = null;
if (requiredType != null) {
elementType = ClassUtils.retrieveFirstGenericType(requiredType).orElse(null);
}

if (elementType == null) {
elementType = Object.class;
}

return elementType;
}

private static class Str2OptionalConverter implements Function<String, Object> {

private final Function<String, Object> elementConverter;

private Str2OptionalConverter(Function<String, Object> elementConverter) {
this.elementConverter = elementConverter;
}

private static Str2OptionalConverter of(Type requiredType) {
Function<String, Object> elementConverter = getStr2ObjectConverter(retrieveElementType(requiredType),
null);
if (elementConverter == null) {
// we don't know how to convert the elements
return null;
}
return new Str2OptionalConverter(elementConverter);
}

@Override
public Object apply(String s) {
return s == null ? Optional.empty() : Optional.ofNullable(elementConverter.apply(s));
}
}

/**
* Converts a collection of {@link String} values to an array.
*/
Expand All @@ -240,6 +269,9 @@ private static Strs2ArrayConverter of(Class<?> requiredClass) {

@Override
public Object apply(Collection<String> value) {
if (value == null) {
return null;
}
final Object array = Array.newInstance(elementType, value.size());

int i = 0;
Expand Down Expand Up @@ -272,7 +304,7 @@ private static Str2ArrayConverter of(Class<?> requiredClass) {

@Override
public Object apply(String value) {
return strList2ArrayConverter.apply(extractStringFields(value));
return value == null ? null : strList2ArrayConverter.apply(extractStringFields(value));
}
}

Expand Down Expand Up @@ -300,16 +332,8 @@ private static Strs2CollectionConverter of(Class<?> requiredClass, Type required
return null;
}

Class<?> elementType = null;
if (requiredType != null) {
elementType = ClassUtils.retrieveFirstGenericType(requiredType).orElse(null);
}

if (elementType == null) {
elementType = Object.class;
}

Function<String, Object> elementConverter = getStr2ObjectConverter(elementType, null);
Function<String, Object> elementConverter = getStr2ObjectConverter(retrieveElementType(requiredType),
null);
if (elementConverter == null) {
// we don't know how to convert the elements
return null;
Expand All @@ -320,6 +344,9 @@ private static Strs2CollectionConverter of(Class<?> requiredClass, Type required
@SuppressWarnings("unchecked")
@Override
public Object apply(Collection<String> value) {
if (value == null) {
return null;
}
final Collection collection = collectionGenerator.apply(value.size());
for (String v : value) {
// convert to target type and fill in the collection.
Expand Down Expand Up @@ -352,7 +379,7 @@ private static Str2CollectionConverter of(Class<?> requiredClass, Type requiredT

@Override
public Object apply(String value) {
return strs2CollectionConverter.apply(extractStringFields(value));
return value == null ? null : strs2CollectionConverter.apply(extractStringFields(value));
}
}

Expand All @@ -371,6 +398,11 @@ private static Function<String, Object> getStr2ObjectConverter(Class<?> required
return converter;
}

if (Optional.class.isAssignableFrom(requiredClass)
&& (converter = Str2OptionalConverter.of(requiredType)) != null) {
return converter;
}

// have a constructor that accepts a single String argument.
Constructor<?> constructor = getSingleStringParameterConstructor(requiredClass);
if (constructor != null) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,13 +26,15 @@
import java.util.LinkedList;
import java.util.List;
import java.util.NavigableSet;
import java.util.Optional;
import java.util.Set;
import java.util.SortedSet;
import java.util.TreeSet;
import java.util.concurrent.CopyOnWriteArrayList;

import static org.junit.jupiter.api.Assertions.assertArrayEquals;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertSame;
Expand Down Expand Up @@ -65,6 +67,24 @@ void testConvertString2Primitives() {
assertNull(ConverterUtils.str2ObjectConverter(Void.class).apply("10"));
}

@Test
void testConvertString2Wrappers() {
assertNull(ConverterUtils.str2ObjectConverter(Byte.class).apply(""));
assertNull(ConverterUtils.str2ObjectConverter(Character.class).apply(""));
assertNull(ConverterUtils.str2ObjectConverter(Boolean.class).apply(""));
assertNull(ConverterUtils.str2ObjectConverter(Short.class).apply(""));
assertNull(ConverterUtils.str2ObjectConverter(Integer.class).apply(""));
assertNull(ConverterUtils.str2ObjectConverter(Long.class).apply(""));
assertNull(ConverterUtils.str2ObjectConverter(Double.class).apply(""));
assertNull(ConverterUtils.str2ObjectConverter(Float.class).apply(""));
assertNull(ConverterUtils.str2ObjectConverter(Void.class).apply(""));
assertNull(ConverterUtils.str2ObjectConverter(BigDecimal.class).apply(""));
assertThrows(IllegalArgumentException.class,
() -> ConverterUtils.str2ObjectConverter(Timestamp.class).apply(""));
assertNull(ConverterUtils.str2ObjectConverter(String.class).apply(null));
assertNull(ConverterUtils.str2ObjectConverter(Object.class).apply(null));
}

@Test
void testConvertString2Objects() {
assertEquals(new BigDecimal("10.0"), ConverterUtils.str2ObjectConverter(BigDecimal.class).apply("10.0"));
Expand Down Expand Up @@ -165,6 +185,41 @@ void testConvertString2Collection() throws NoSuchMethodException {
assertNull(ConverterUtils.str2ObjectConverter(MyList.class));
}

@Test
void testConvertString2Optional() throws NoSuchMethodException {
final Optional<String> optional0 = (Optional<String>)
ConverterUtils.str2ObjectConverter(Subject.class.getDeclaredMethod("optionalString")
.getGenericReturnType()).apply(null);
assertFalse(optional0.isPresent());

final Optional<Long> optional1 = (Optional<Long>)
ConverterUtils.str2ObjectConverter(Subject.class.getDeclaredMethod("optionalLong")
.getGenericReturnType()).apply(null);
assertFalse(optional1.isPresent());

final Optional<String[]> optional2 = (Optional<String[]>)
ConverterUtils.str2ObjectConverter(Subject.class.getDeclaredMethod("optionalArray")
.getGenericReturnType()).apply(null);
assertFalse(optional2.isPresent());

final Optional<String[]> optional3 = (Optional<String[]>)
ConverterUtils.str2ObjectConverter(Subject.class.getDeclaredMethod("optionalArray")
.getGenericReturnType()).apply("");
assertTrue(optional3.isPresent());
assertEquals(0, optional3.get().length);

final Optional<Collection<String>> optional4 = (Optional<Collection<String>>)
ConverterUtils.str2ObjectConverter(Subject.class.getDeclaredMethod("optionalCollection")
.getGenericReturnType()).apply(null);
assertFalse(optional4.isPresent());

final Optional<Collection<String>> optional5 = (Optional<Collection<String>>)
ConverterUtils.str2ObjectConverter(Subject.class.getDeclaredMethod("optionalCollection")
.getGenericReturnType()).apply("");
assertTrue(optional5.isPresent());
assertTrue(optional5.get().isEmpty());
}

@Test
void testConvertStringCollection2PrimitiveArray() {
final List<String> forTest = Arrays.asList("1", "2", "3", "4");
Expand Down Expand Up @@ -217,6 +272,14 @@ private interface Subject {
LinkedList<Double> doubleLinkedList();

Collection<int[]> intArrayCollection();

Optional<String> optionalString();

Optional<Long> optionalLong();

Optional<String[]> optionalArray();

Optional<Collection<String>> optionalCollection();
}

@Test
Expand Down Expand Up @@ -327,6 +390,11 @@ void testForceConvertStringValue() {
assertNull(ConverterUtils.forceConvertStringValue("", Integer.class));
assertThrows(IllegalArgumentException.class,
() -> ConverterUtils.forceConvertStringValue("foo", MyList.class));

assertNull(ConverterUtils.forceConvertStringValue(null, Collection.class));
assertNull(ConverterUtils.forceConvertStringValue(null, String[].class));
assertNotNull(ConverterUtils.forceConvertStringValue("", Collection.class));
assertNotNull(ConverterUtils.forceConvertStringValue("", String[].class));
}

@Test
Expand Down
Loading