web-dev-qa-db-ja.com

Jacksonに特定のプリミティブ型への逆シリアル化を強制する

Jackson 1.8.3を使用して、次のドメインオブジェクトをJSONにシリアル化および逆シリアル化します

public class Node {
    private String key;
    private Object value;
    private List<Node> children = new ArrayList<Node>();
    /* getters and setters omitted for brevity */
}

次に、オブジェクトは次のコードを使用してシリアル化および逆シリアル化されます

ObjectMapper mapper = new ObjectMapper();
mapper.writeValue(destination, rootNode);

そして後でデシリアライズされた

mapper.readValue(destination, Node.class);

オブジェクトの元の値は、Strings、Doubles、Longs、またはBooleansのいずれかです。ただし、シリアル化および逆シリアル化中に、JacksonはLong値(4など)を整数に変換します。

Jacksonに10進数以外の数値を整数ではなくLongに逆シリアル化するように「強制」するにはどうすればよいですか?

15
Peders

タイプがJava.lang.Objectとして宣言されている場合、Jacksonは値が32ビットに収まる場合に整数を使用する「自然な」マッピングを使用します。カスタムハンドラーとは別に、タイプ情報を強制的に含める必要があります(field/getterの横に@JsonTypeInfoを追加するか、いわゆる「デフォルトの入力」を有効にする)。

9
StaxMan

特にこの場合のために、Jackson2.6には新しい機能があります。

DeserializationFeature.USE_LONG_FOR_INTSを使用するようにObjectMapperを構成します

https://github.com/FasterXML/jackson-databind/issues/504 を参照してください

cowtowncoderは、2015年5月19日にこの問題を解決するコミットをプッシュしました修正#504および#797

25
Fabian Lange

アプリケーションロジックには値のタイプが4つしかないため(DoubleLongIntegerString)、カスタムデシリアライザーを作成することになりました。 。

これが最善の解決策かどうかはわかりませんが、今のところは機能します。

public class MyDeserializer extends JsonDeserializer<Object> {

@Override
public Object deserialize(JsonParser p, DeserializationContext ctxt)
        throws IOException, JsonProcessingException {
    try {
        Long l = Long.valueOf(p.getText());
        return l;
    } catch (NumberFormatException nfe) {
      // Not a Long
    }
    try {
      Double d = Double.valueOf(p.getText());
      return d;
    } catch (NumberFormatException nfe) {
      // Not a Double
    }
    if ("TRUE".equalsIgnoreCase(p.getText())
          || "FALSE".equalsIgnoreCase(p.getText())) {
      // Looks like a boolean
      return Boolean.valueOf(p.getText());
    }
    return String.valueOf(p.getText());
  }
}
4
Peders

この問題を回避するために、以下のようなものを使用しました。

@JsonIgnoreProperties(ignoreUnknown = true)
public class Message {
    public Long ID;

    @JsonCreator
    private Message(Map<String,Object> properties) {
        try {
            this.ID = (Long) properties.get("id");
        } catch (ClassCastException e) {
            this.ID = ((Integer) properties.get("id")).longValue();
        }
    }
}
1
ostergaard

私の場合、ObjectMapperにDeserializationFeature.USE_LONG_FOR_INTSを使用したくありませんでした。これは、すべてのプロジェクトに影響するためです。私は次のソリューションを使用しました:カスタムデシリアライザーを使用します:

import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.ObjectCodec;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonDeserializer;
import com.fasterxml.jackson.databind.JsonNode;

import Java.io.IOException;

public class LongInsteadOfIntegerDeserializer extends JsonDeserializer<Object> {

    @Override
    public Object deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException {
        ObjectCodec codec = jsonParser.getCodec();
        JsonNode jsonNode = codec.readTree(jsonParser);
        if (jsonNode.isInt()) {
            return jsonNode.asLong();
        }
        return codec.treeToValue(jsonNode, Object.class);
    }
}

それをObject型のフィールドに追加します:public class SomeTOWithObjectField {

    //...  other fields

    @JsonDeserialize(using = LongInsteadOfIntegerDeserializer.class)
    private Object value;

    //...  other fields
}

また、整数をlongとして逆シリアル化しましたが、String、boolean、doubleなどの他のタイプは、デフォルトであるはずのように逆シリアル化されました。

1

プリミティブを特定のクラスにラップする場合は、次のようにします(Kotlinの例)。

data class Age(
    @JsonValue
    val value: Int
)

そして今、あなたのIntプリミティブは、Ageクラスに、またはその逆に解析されます。

0
Pavel Shorokhov

ジャクソン2では、 TypeReference を使用してジェネリック型を詳細に指定できます。 TypeReferenceを2番目のパラメーターとして受け取るreadValue()のオーバーロードメソッドがあります。

readValue([File | String | etc]、com.fasterxml.jackson.core.type.TypeReference))

Longの代わりにIntegerのリストを取得したい場合は、次のようにすることができます。

ObjectMapper mapper = new ObjectMapper();
TypeReference ref = new TypeReference<List<Integer>>() { };
List<Integer> list = mapper.readValue(<jsonString>, ref);

これはマップでも機能します。

TypeReference ref = new TypeReference<Map<String,Long>>() { };
Map<String, Long> map = mapper.readValue(<jsonString>, ref);

あなたの場合、クラスをジェネリッククラスに変換できます。つまりNode<T>。ノードを作成するときは、Node<String/Integer/etc>そして、型参照を使用して値を読み取ります。

0