【问题标题】:How to ignore enum fields in Jackson JSON-to-Object mapping?如何忽略杰克逊 JSON 到对象映射中的枚举字段?
【发布时间】:2013-05-08 17:20:48
【问题描述】:

我有一个类似的 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

有没有办法忽略枚举类型中的未知字段?

谢谢

【问题讨论】:

  • 注解@JsonIgnoreProperties 不能按照您对Enum 类型的预期方式工作;它仅(当前)允许忽略未知的 POJO 属性。但我认为这是一个有意义的改进想法。那么您可以在Jackson databind 项目中提交问题吗?如果是这样,也许它可以添加到 Jackson 2.3 中?
  • 答案有点冗长。对于那些寻求最快和最简单解决方案的人:将 READ_UNKNOWN_ENUM_VALUES_AS_NULL deserialization feature 设置为 true。
  • mapper.enable(DeserializationFeature.READ_UNKNOWN_ENUM_VALUES_AS_NULL);

标签: java jackson


【解决方案1】:

我找到了一种方法,如下所示:

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());
}

这个输出:

空

其他类:

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}

我还没有找到使用注释的方法。

我希望这会有所帮助。

【讨论】:

【解决方案2】:

我认为您应该为 Grade 枚举定义外部 deserializer。

我向枚举添加了额外的字段 - 未知:

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 例如。

【讨论】:

  • 拥有一个 UNKNOWN 枚举实例对我来说似乎是一个糟糕的选择。我更喜欢 Stefaan 的具有 READ_UNKNOWN_ENUM_VALUES_AS_NULL 功能的解决方案,它会产生 null。
  • UNKNOWN 表示此时值未知。 null 表示 - 未设置值。在某些情况下可能会造成混淆,并可能导致 NPE。此外,如果枚举值包含方法,我们可以以更优雅的方式实现特殊的 null 处理。对我来说,带有 READ_UNKNOWN_ENUM_VALUES_AS_NULL 功能的解决方案也很好。也许,比我的...
  • 如果您不希望未知值返回 null,这是一个很好的解决方案。也感谢您提供测试用例示例!
  • 太棒了!谢谢!
  • 请注意,从 Jackson 2.8 开始,有一个 READ_UNKNOWN_ENUM_VALUES_USING_DEFAULT_VALUE 功能以及 READ_UNKNOWN_ENUM_VALUES_AS_NULL 可以让您使用注释来完成上述代码完成的工作。
【解决方案3】:

@JsonCreator 与@JsonDeserialize 相比提供了更简洁的解决方案。

这个想法是用@JsonCreator 注释您的valueOf() 替换(在本例中称为safeValueOf()),然后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));
    }
}

【讨论】:

  • IMO 这是最好的解决方案,因为它只需要很少的更改:只有 Enum 类型本身。可以将逻辑提取到静态帮助程序并轻松地在选择的枚举类型上重用,您需要这种容忍度。 READ_UNKNOWN_ENUM_VALUES_AS_NULL 选项的缺点是它是一个全有或全无的命题。您必须准备好处理每一个 Jackson 引用的 enum 类型突然默默地反序列化为 null 而不是抛出异常。
【解决方案4】:

我正在使用 boot2(虽然这也可以在 boot 1 中使用),但您可以在应用程序属性/yaml 中启用反序列化功能;

spring:
  jackson:
    deserialization:
      READ_UNKNOWN_ENUM_VALUES_AS_NULL: true

【讨论】:

  • 如果你不使用 yaml:spring.jackson.deserialization.read-unknown-enum-values-as-null = true
【解决方案5】:

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

选项 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;

}

【讨论】:

  • 这可能是最好的答案。它提出了两种不同的可能解决方案,并且都尽可能优雅。给它一些爱!
猜你喜欢
  • 1970-01-01
  • 2015-05-23
  • 2016-06-06
  • 2022-06-22
  • 1970-01-01
  • 2015-03-20
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多