I'm fetching addresses from an external API. This is the class representing the addresses:
@JsonInclude(JsonInclude.Include.NON_NULL)
public class Address implements Serializable {
private static final long serialVersionUID = -7134571546367230214L;
private String street;
private int houseNumber;
private String district;
private String city;
private String state;
private String zipCode;
}
However, when the given address doesn't have a houseNumber, the API will return a string such as "NO NUMBER"
on the houseNumber
field, causing Jackson to throw a deserialization error, since it was expecting an integer number and got a string.
How can I tell Jackson to convert houseNumber
to 0
when it finds a string value?
CodePudding user response:
You could try with a custom deserializer on the field:
@JsonDeserialize(using = HouseNoDeserializer.class)
private int houseNumber;
The deserializer could look like this:
class HouseNoDeserializer extends JsonDeserializer<Integer> {
@Override
public Integer deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
//read the value as a string since you don't know whether it is a number or a string
String v = p.readValueAs(String.class);
try {
//try to parse the string to an integer, if not return 0 as required (it is a non-numeric string)
return Integer.parseInt(v);
} catch(NumberFormatException nfe) {
return 0;
}
}
}
However, I'd change houseNumber
to take String
anyways because right now you can't support numbers such as "1/c", "123a", etc. which are common at least in some countries.
You could then do without a custom deserializer and simply add some logic to the setter or apply it after parsing the json, i.e. replace "NO NUMBER" with another value as needed.
CodePudding user response:
You can provide custom com.fasterxml.jackson.databind.deser.DeserializationProblemHandler
and handle all these kind of business values generally for all POJO
classes:
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.databind.deser.DeserializationProblemHandler;
import com.fasterxml.jackson.databind.json.JsonMapper;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.IOException;
public class HandleErrorsApp {
private final static JsonMapper JSON_MAPPER = JsonMapper.builder()
.enable(SerializationFeature.INDENT_OUTPUT)
.addModule(new JavaTimeModule())
.addHandler(new ProjectDeserializationProblemHandler())
.build();
public static void main(String[] args) throws Exception {
var json = "{\"houseNumber\":\"NO_ADDRESS\"}";
var address = JSON_MAPPER.readValue(json, Address.class);
System.out.println(address);
}
}
class ProjectDeserializationProblemHandler extends DeserializationProblemHandler {
@Override
public Object handleWeirdStringValue(DeserializationContext ctxt, Class<?> targetType, String valueToConvert, String failureMsg) throws IOException {
if (targetType == int.class && valueToConvert.equals("NO_ADDRESS")) {
return 0;
}
return super.handleWeirdStringValue(ctxt, targetType, valueToConvert, failureMsg);
}
}
@Data
@NoArgsConstructor
@AllArgsConstructor
class Address {
private int houseNumber;
}
Or you can provide custom com.fasterxml.jackson.databind.util.StdConverter
implementation which has a little bit simpler interface than JsonDeserializer
:
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.fasterxml.jackson.databind.json.JsonMapper;
import com.fasterxml.jackson.databind.util.StdConverter;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.apache.commons.lang3.math.NumberUtils;
public class HandleErrorsApp {
private final static JsonMapper JSON_MAPPER = JsonMapper.builder()
.enable(SerializationFeature.INDENT_OUTPUT)
.addModule(new JavaTimeModule())
.build();
public static void main(String[] args) throws Exception {
var json = "{\"houseNumber\":\"NO_ADDRESS\"}";
var address = JSON_MAPPER.readValue(json, Address.class);
System.out.println(address);
}
}
class StringIntConverter extends StdConverter<String, Integer> {
@Override
public Integer convert(String value) {
return NumberUtils.toInt(value, 0);
}
}
@Data
@NoArgsConstructor
@AllArgsConstructor
class Address {
@JsonDeserialize(converter = StringIntConverter.class)
private int houseNumber;
}
In both cases program prints:
Address(houseNumber=0)