如何在Jackson的JSON到对象映射中忽略枚举字段?

26

我有一个类似于JSON对象的东西:

{"name":"John", "grade":"A"}
或者
{"name":"Mike", "grade":"B"}
或者
{"name":"Simon", "grade":"C"}

我正在尝试将上述JSON映射到:

@JsonIgnoreProperties(ignoreUnknown = true)
public class Employee{
      @JsonIgnoreProperties(ignoreUnknown = true)
      public enum Grade{ A, B, C }
      Grade grade;
      String name;

  public Grade getGrade() {
    return grade;
  }

  public void setGrade(Grade grade) {
    this.grade = grade;
  }

  public String getName() {
    return name;
  }

  public void setName(String name) {
    this.name = name;
  }
}

上述映射功能正常,但未来可能会有更多的“等级”类型,比如D、E等,这将破坏现有的映射并抛出以下异常:

05-08 09:56:28.130: W/System.err(21309): org.codehaus.jackson.map.JsonMappingException: Can not construct instance of Employee from String value 'D': value not one of declared Enum instance names

在枚举类型中是否有一种方法可以忽略未知字段?

谢谢


2
注释@JsonIgnorePropertiesEnum类型上无法按照您期望的方式工作;它目前只允许忽略未知的POJO属性。但我认为这是一种改进的想法。所以,您能否在Jackson databind项目中提出一个问题?如果可以的话,也许它可以加入到Jackson 2.3中? - StaxMan
14
答案有点冗长。对于那些寻求最快和最简单解决方案的人:将READ_UNKNOWN_ENUM_VALUES_AS_NULL反序列化功能设置为true即可。请注意不要修改原始意思。 - Jonik
1
mapper.enable(DeserializationFeature.READ_UNKNOWN_ENUM_VALUES_AS_NULL); - Matt
5个回答

31

我找到了一种实现这个的方法,如下所示:

public static void main(String[] args) throws JsonParseException, JsonMappingException, UnsupportedEncodingException, IOException {
    String json = "{\"name\":\"John\", \"grade\":\"D\"}";

    ObjectMapper mapper = new ObjectMapper();
    mapper.configure(DeserializationFeature.READ_UNKNOWN_ENUM_VALUES_AS_NULL, true);
    Employee employee = mapper.readValue(new ByteArrayInputStream(json.getBytes("UTF-8")), Employee.class);

    System.out.println(employee.getGrade());
}

这将输出:

null

其他类:

import com.fasterxml.jackson.annotation.JsonIgnoreProperties;

@JsonIgnoreProperties(ignoreUnknown = true)
public class Employee {
    private String name;
    private Grade grade;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Grade getGrade() {
        return grade;
    }

    public void setGrade(Grade grade) {
        this.grade = grade;
    }
}



import com.fasterxml.jackson.annotation.JsonIgnoreProperties;

@JsonIgnoreProperties(ignoreUnknown = true)
public enum Grade {A, B, C}

我还没有遇到使用注释来完成这个的方式。

希望这可以帮到你。


@stefaan:这个答案对我有用!我缺少的只是DeserializationFeature.READ_UNKNOWN_ENUM_VALUES_AS_NULL,太棒了! - Arthulia
2
从Jackson 2.8开始,还有READ_UNKNOWN_ENUM_VALUES_USING_DEFAULT_VALUE选项,允许您设置默认值而不是null。https://fasterxml.github.io/jackson-databind/javadoc/2.8/com/fasterxml/jackson/databind/DeserializationFeature.html#READ_UNKNOWN_ENUM_VALUES_USING_DEFAULT_VALUE - Rick

22

我认为你应该为Grade枚举定义外部反序列化器

我向枚举中添加了一个额外的字段 - UNKNOWN:

enum Grade {
    A, B, C, UNKNOWN;

    public static Grade fromString(String value) {
        for (Grade grade : values()) {
            if (grade.name().equalsIgnoreCase(value)) {
                return grade;
            }
        }

        return UNKNOWN;
    }
}
class Employee {

    @JsonDeserialize(using = GradeDeserializer.class)
    private Grade grade;
    private String name;

    public Grade getGrade() {
        return grade;
    }

    public void setGrade(Grade grade) {
        this.grade = grade;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    @Override
    public String toString() {
        return "Employee [grade=" + grade + ", name=" + name + "]";
    }
}

现在,解析器可能看起来像这样:

class GradeDeserializer extends JsonDeserializer<Grade> {
    @Override
    public Grade deserialize(JsonParser parser, DeserializationContext context)
            throws IOException, JsonProcessingException {
        return Grade.fromString(parser.getValueAsString());
    }
}

示例用法:

public class JacksonProgram {

    public static void main(String[] args) throws Exception {
        ObjectMapper objectMapper = new ObjectMapper();
        JsonFactory jsonFactory = new JsonFactory();
        JsonParser parser = jsonFactory
                .createJsonParser("{\"name\":\"John\", \"grade\":\"D\"}");
        Employee employee = objectMapper.readValue(parser, Employee.class);
        System.out.println(employee);
    }

}

输出:

Employee [grade=UNKNOWN, name=John]
如果您不想添加额外的字段,您可以返回null作为示例。

1
在枚举类型中使用UNKNOWN实例似乎不是一个好的选择。我更喜欢Stefaan提出的解决方案,即使用READ_UNKNOWN_ENUM_VALUES_AS_NULL功能,这将产生null值。 - Tom
3
UNKNOWN表示该值目前未知,null表示值未设置。在某些情况下,这可能会令人困惑并导致空指针异常。此外,如果枚举值包含方法,我们可以更优雅地实现特殊的null处理。对我来说,使用READ_UNKNOWN_ENUM_VALUES_AS_NULL功能的解决方案也不错。也许比我的解决方案更好。 - Michał Ziober
1
如果您不想让未知值返回null,这是一个很好的解决方案。感谢测试用例示例! - CaptRespect
1
太好了!谢谢! - cristianmiranda
1
请注意,从Jackson 2.8开始,除了READ_UNKNOWN_ENUM_VALUES_AS_NULL功能外,还有一个READ_UNKNOWN_ENUM_VALUES_USING_DEFAULT_VALUE功能,让您可以使用注释来完成上述代码的操作。 - evnafets

13

@JsonCreator 相较于 @JsonDeserialize 提供了更为简洁的解决方案。

其思路是在你的替换方法 valueOf()(在该示例中称为 safeValueOf())上使用@JsonCreator注解,然后Jackson将使用你的实现反序列化字符串。

请注意,实现在枚举内部,你可以在其他对象的字段中使用它而无需修改。

下面的解决方案包含一个单元测试,因此您可以直接运行它。

import static junit.framework.TestCase.assertEquals;

import java.io.IOException;

import org.junit.Test;

import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.databind.ObjectMapper;

public class EmployeeGradeTest {

    public enum Grade {
        A, B, C, OTHER;

        @JsonCreator
        public static Grade safeValueOf(String string) {
            try {
                return Grade.valueOf(string);
            } catch (IllegalArgumentException e) {
                return OTHER;
            }
        }
    }

    @Test
    public void deserialize() throws IOException {
        assertEquals(Grade.A, new ObjectMapper().readValue("\"A\"", Grade.class));
    }

    @Test
    public void deserializeNewValue() throws IOException {
        assertEquals(Grade.OTHER, new ObjectMapper().readValue("\"D\"", Grade.class));
    }
}

1
在我看来,这是最佳解决方案,因为它只需要对Enum类型本身进行最小限度的更改。逻辑可以提取到一个静态助手中,并可轻松地在需要此类容错能力的选择性枚举类型上重用。“READ_UNKNOWN_ENUM_VALUES_AS_NULL”选项的缺点是它是全包式的。你必须准备处理每一个Jackson引用的枚举类型突然静默地反序列化为空值而不是抛出异常。 - Astral

7

我正在使用boot2(尽管这也适用于boot1),但您只需在应用程序属性/ yaml中启用反序列化功能即可。

spring:
  jackson:
    deserialization:
      READ_UNKNOWN_ENUM_VALUES_AS_NULL: true

如果您没有使用yaml: spring.jackson.deserialization.read-unknown-enum-values-as-null = true - spetz83

3

处理此类情况有两种方法:

选项1:将未知的枚举值替换为null

为此,在您的ObjectMapper中启用READ_UNKNOWN_ENUM_VALUES_AS_NULL设置。启用后,当您解析具有未知枚举字段值的对象时,它将被反序列化为null。请参考以下示例代码:

import com.fasterxml.jackson.databind.*;

public static ObjectMapper getMapper() {
    return ObjectMapper().enable(DeserializationFeature.READ_UNKNOWN_ENUM_VALUES_AS_NULL);
}

选项 2: 使用默认的枚举值替换未知的枚举值(如 UNKNOWN

首先在你的 ObjectMapper 中启用READ_UNKNOWN_ENUM_VALUES_USING_DEFAULT_VALUE设置。然后,在你的枚举类中使用@JsonEnumDefaultValue注释要用作默认枚举值的成员。参考下面的代码示例:

import com.fasterxml.jackson.databind.*;

public static ObjectMapper getMapper() {
    return ObjectMapper().enable(DeserializationFeature.READ_UNKNOWN_ENUM_VALUES_USING_DEFAULT_VALUE);
}


public enum YourEnum {
   A,
   B,
   C,
   @JsonEnumDefaultValue UNKNOWN;

}

1
这可能是最好的答案。它提供了两种不同的可能解决方案,而且两者都非常优雅。给它一些爱吧! - dominikbrandon

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接