web-dev-qa-db-fra.com

Deserializing une enum avec Jackson

J'essaie et je ne parviens pas à désérialiser une énumération avec Jackson 2.5.4, et je ne vois pas très bien mon cas. Mes chaînes d'entrée sont des cas chameaux et je souhaite simplement mapper les conventions Enum standard.

@JsonFormat(shape = JsonFormat.Shape.STRING)
public enum Status {
    READY("ready"),
    NOT_READY("notReady"),
    NOT_READY_AT_ALL("notReadyAtAll");

    private static Map<String, Status> FORMAT_MAP = Stream
            .of(Status.values())
            .collect(toMap(s -> s.formatted, Function.<Status>identity()));

    private final String formatted;

    Status(String formatted) {
        this.formatted = formatted;
    }

    @JsonCreator
    public Status fromString(String string) {
        Status status = FORMAT_MAP.get(string);
        if (status == null) {
            throw new IllegalArgumentException(string + " has no corresponding value");
        }
        return status;
    }
}

J'ai aussi essayé @JsonValue sur un getter en vain, une option que j’ai vue rapportée ailleurs. Ils explosent tous avec:

com.fasterxml.jackson.databind.exc.InvalidFormatException: Can not construct instance of ...Status from String value 'ready': value not one of declared Enum instance names: ...

Qu'est-ce que je fais mal?

46
jwilner

EDIT: À partir de Jackson 2.6, vous pouvez utiliser @JsonProperty sur chaque élément de l’énumération pour spécifier sa valeur de sérialisation/désérialisation ( voir ici ):

public enum Status {
    @JsonProperty("ready")
    READY,
    @JsonProperty("notReady")
    NOT_READY,
    @JsonProperty("notReadyAtAll")
    NOT_READY_AT_ALL;
}

(Le reste de cette réponse est toujours valable pour les anciennes versions de Jackson)

Tu devrais utiliser @JsonCreator pour annoter une méthode statique qui reçoit un argument String. C'est ce que Jackson appelle une méthode d'usine:

public enum Status {
    READY("ready"),
    NOT_READY("notReady"),
    NOT_READY_AT_ALL("notReadyAtAll");

    private static Map<String, Status> FORMAT_MAP = Stream
        .of(Status.values())
        .collect(Collectors.toMap(s -> s.formatted, Function.identity()));

    private final String formatted;

    Status(String formatted) {
        this.formatted = formatted;
    }

    @JsonCreator // This is the factory method and must be static
    public static Status fromString(String string) {
        return Optional
            .ofNullable(FORMAT_MAP.get(string))
            .orElseThrow(() -> new IllegalArgumentException(string));
    }
}

C'est le test:

ObjectMapper mapper = new ObjectMapper();

Status s1 = mapper.readValue("\"ready\"", Status.class);
Status s2 = mapper.readValue("\"notReadyAtAll\"", Status.class);

System.out.println(s1); // READY
System.out.println(s2); // NOT_READY_AT_ALL

Comme la méthode d'usine attend un String, vous devez utiliser une syntaxe JSON valide pour les chaînes, qui consiste à indiquer la valeur entre guillemets.

C'est probablement un moyen plus rapide de le faire:

public enum Status {
 READY("ready"),
 NOT_READY("notReady"),
 NOT_READY_AT_ALL("notReadyAtAll");

 private final String formatted;

 Status(String formatted) {
   this.formatted = formatted;
 }

 @Override
 public String toString() {
   return formatted;
 }
}

public static void main(String[] args) throws IOException {
  ObjectMapper mapper = new ObjectMapper();
  ObjectReader reader = mapper.reader(Status.class);
  Status status = reader.with(DeserializationFeature.READ_ENUMS_USING_TO_STRING).readValue("\"notReady\"");
  System.out.println(status.name());  // NOT_READY
}
8
Konstantin Kulagin

Les solutions de cette page ne fonctionnent que pour un seul champ et @JsonFormat (forme = JsonFormat.Shape.NATURAL) (format par défaut)

cela fonctionne pour plusieurs champs et @JsonFormat (shape = JsonFormat.Shape.OBJECT)

@JsonFormat(shape = JsonFormat.Shape.OBJECT)
public enum PinOperationMode {
    INPUT("Input", "I"),
    OUTPUT("Output", "O")
    ;

    private final String mode;
    private final String code;

    PinOperationMode(String mode, String code) {
        this.mode = mode;
        this.code = code;
    }

    public String getMode() {
        return mode;
    }

    public String getCode() {
        return code;
    }

    @JsonCreator
    static PinOperationMode findValue(@JsonProperty("mode") String mode, @JsonProperty("code") String code) {
        return Arrays.stream(PinOperationMode.values()).filter(pt -> pt.mode.equals(mode) && pt.code.equals(code)).findFirst().get();
    }
}
2
Stefan

Pour ceux qui recherchent des enums avec des propriétés json entières. Voici ce qui a fonctionné pour moi:

enum class Status (private val code: Int) {
    PAST(0),
    LIVE(2),
    UPCOMING(1);
    companion object {
        private val codes = Status.values().associateBy(Status::code)
        @JvmStatic @JsonCreator fun from (value: Int) = codes[value]
    }
}
1
Sileria
@JsonCreator
public static Status forValue(String name)
{
    return EnumUtil.getEnumByNameIgnoreCase(Status.class, name);
}

Ajouter cette méthode statique résoudrait votre problème de désérialisation

0
Arun Kumar