Home > Net >  How to convert string values to 0 with Jackson?
How to convert string values to 0 with Jackson?

Time:01-11

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)
  • Related