To avoid manual deserialisation and handling all possible types we can use a fact that all items on the list are also JSON
elements when we wrap them with a quote ("
) char.
So, we can convert John, Tom
to a "John", "Tom"
, 8, 9
to "8", "9"
and so on.
We can use default Jackson
behaviour which allows to handle unexpected tokens. In our case whenever: STRING
token appears when JSON ARRAY
was expected. To handle these cases we can use com.fasterxml.jackson.databind.deser.DeserializationProblemHandler
class. It could look like below:
class ComaSeparatedValuesDeserializationProblemHandler extends DeserializationProblemHandler {
@Override
public Object handleUnexpectedToken(DeserializationContext ctxt, JavaType targetType, JsonToken token, JsonParser parser, String failureMsg) throws IOException {
if (token == JsonToken.VALUE_STRING && targetType.isCollectionLikeType()) {
return deserializeAsList(targetType, parser);
}
return super.handleUnexpectedToken(ctxt, targetType, token, parser, failureMsg);
}
private Object deserializeAsList(JavaType listType, JsonParser parser) throws IOException {
String[] values = readValues(parser);
ObjectMapper mapper = (ObjectMapper) parser.getCodec();
JavaType itemType = listType.getContentType();
List<Object> result = new ArrayList<>();
for (String value : values) {
result.add(convertToItemType(mapper, itemType, value));
}
return result;
}
private Object convertToItemType(ObjectMapper mapper, JavaType contentType, String value) throws IOException {
final String json = "\"" + value.trim() + "\"";
return mapper.readValue(json, contentType);
}
private String[] readValues(JsonParser p) throws IOException {
final String text = p.getText();
return text.split(",");
}
}
Example usage:
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonToken;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.deser.DeserializationProblemHandler;
import com.fasterxml.jackson.databind.json.JsonMapper;
import com.google.common.base.Joiner;
import lombok.Data;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
public class ConvertStringToCollectionApp {
public static void main(String[] args) throws IOException {
File jsonFile = new File("./resource/test.json").getAbsoluteFile();
ObjectMapper mapper = JsonMapper.builder()
.addHandler(new ComaSeparatedValuesDeserializationProblemHandler())
.build();
Bean bean = mapper.readValue(jsonFile, Bean.class);
print(bean.getNames());
print(bean.getValues());
print(bean.getStatuses());
}
private static void print(List<?> values) {
values.stream().findFirst().ifPresent(value -> System.out.print(value.getClass().getSimpleName() + "s: "));
System.out.println(Joiner.on(", ").join(values));
}
}
@Data
class Bean {
private List<String> names;
private List<Integer> values;
private List<StatusEnum> statuses;
}
enum StatusEnum {
yes, no
}
Above app for your JSON
payload prints:
Strings: John, Tom
Integers: 8, 9
StatusEnums: yes, no
I used Lombok and Guava libraries just to make it simple and short but they are not mandatory to make it work.