2016-07-14 6 views
2

는 다음과 같은 예를 생각해기본 입력으로 명명 된 형식을 deserialize하도록 Jackson을 구성하는 방법은 무엇입니까?

package com.example; 

import com.fasterxml.jackson.annotation.JsonTypeInfo; 
import com.fasterxml.jackson.annotation.JsonTypeInfo.Id; 
import com.fasterxml.jackson.annotation.JsonTypeName; 
import com.fasterxml.jackson.databind.ObjectMapper; 
import com.fasterxml.jackson.databind.ObjectMapper.DefaultTyping; 

public class JacksonDeserializationOfNamedTypes { 
    public static void main(String[] args) throws Exception { 
     ObjectMapper jackson = new ObjectMapper(); 
     jackson.enableDefaultTypingAsProperty(DefaultTyping.JAVA_LANG_OBJECT, "@type"); 

     Balloon redBalloon = new Balloon("red"); 

     String json = jackson.writeValueAsString(redBalloon); //{"@type":"Balloon","color":"red"} 
     //assume the JSON could be anything 
     Object deserialized = jackson.readValue(json, Object.class); 

     assert deserialized instanceof Balloon; 
     assert redBalloon.equals(deserialized); 
    } 

    @JsonTypeName("Balloon") 
    @JsonTypeInfo(use = Id.NAME) 
    public static final class Balloon { 
     private final String color; 

     //for deserialization 
     private Balloon() { 
      this.color = null; 
     } 

     public Balloon(final String color) { 
      this.color = color; 
     } 

     public String getColor() { 
      return color; 
     } 

     @Override 
     public boolean equals(final Object obj) { 
      if (this == obj) return true; 
      if (obj == null || getClass() != obj.getClass()) return false; 
      final Balloon other = (Balloon) obj; 
      return this.color.equals(other.color); 
     } 

     @Override 
     public int hashCode() { 
      int result = color.hashCode(); 
      result = 31 * result + color.hashCode(); 
      return result; 
     } 

     @Override 
     public String toString() { 
      return color + " balloon"; 
     } 
    } 
} 
직렬화 복원은 다음을 제외하고 런타임에 실패

: Exception in thread "main" java.lang.IllegalArgumentException: Invalid type id 'Balloon' (for id type 'Id.class'): no such class found

생성 된 JSON 확실히 잭슨이 제대로 유형을 결정하는 데 필요한 모든 정보를 가지고, 어떻게 내가 할 수있는 "Balloon"com.example.JacksonDeserializationOfNamedTypes$Balloon에 올바르게 매핑하도록 ObjectMapper를 구성 하시겠습니까?

답변

3

내 현재의 솔루션은 사용자 정의 디시리얼라이저의 조합 및 Java 유형 유형 이름의 수동으로 형성 맵을 포함한다 :

package com.example.jackson; 

import com.fasterxml.jackson.annotation.JsonTypeInfo; 
import com.fasterxml.jackson.annotation.JsonTypeName; 
import com.fasterxml.jackson.core.JsonParser; 
import com.fasterxml.jackson.core.JsonProcessingException; 
import com.fasterxml.jackson.core.ObjectCodec; 
import com.fasterxml.jackson.databind.DeserializationContext; 
import com.fasterxml.jackson.databind.JsonMappingException; 
import com.fasterxml.jackson.databind.JsonNode; 
import com.fasterxml.jackson.databind.ObjectMapper; 
import com.fasterxml.jackson.databind.deser.std.StdDeserializer; 
import com.fasterxml.jackson.databind.module.SimpleModule; 

import java.io.IOException; 
import java.util.Collections; 
import java.util.HashMap; 
import java.util.Map; 
import java.util.Optional; 

public class JacksonDeserializerOfNamedTypes extends StdDeserializer<Object> { 
    private final Map<String, Class<?>> typesByName; 
    private final String typeProperty; 

    private JacksonDeserializerOfNamedTypes(final Map<String, Class<?>> typesByName, final String typeProperty) { 
     super(Object.class); 

     this.typesByName = typesByName; 
     this.typeProperty = typeProperty; 
    } 

    @Override 
    public Object deserialize(final JsonParser parser, final DeserializationContext context) throws IOException, JsonProcessingException { 
     final ObjectCodec codec = parser.getCodec(); 
     final JsonNode root = parser.readValueAsTree(); 
     final JsonNode typeNameNodeOrNull = root.get(typeProperty); 
     if (typeNameNodeOrNull == null) { 
      throw new JsonMappingException(parser, "Unable to determine Java type of JSON: " + root); 
     } else { 
      final String typeName = typeNameNodeOrNull.asText(); 
      return Optional 
       .ofNullable(typesByName.get(typeName)) 
       .map(type -> parseOrNull(root, type, codec)) 
       .orElseThrow(() -> 
        new JsonMappingException(parser, String.format(
         "Unsupported type name '%s' in JSON: %s", typeName, root))); 
     } 
    } 

    private <T> T parseOrNull(final JsonNode root, final Class<T> type, final ObjectCodec codec) { 
     try { 
      return root.traverse(codec).readValueAs(type); 
     } catch (IOException e) { 
      return null; 
     } 
    } 

    public static void main(String[] args) throws Exception { 
     final Map<String, Class<?>> typesByName = scanForNamedTypes(); 

     final SimpleModule namedTypesModule = new SimpleModule("my-named-types-module"); 
     namedTypesModule.addDeserializer(Object.class, new JacksonDeserializerOfNamedTypes(typesByName, JsonTypeInfo.Id.NAME.getDefaultPropertyName())); 

     final Car pinto = new Car("Ford", "Pinto", 1971); 
     final Balloon sharik = new Balloon("blue"); 
     final ObjectMapper mapper = new ObjectMapper().registerModule(namedTypesModule); 
     System.out.println(mapper.readValue(mapper.writeValueAsString(pinto), Object.class).getClass()); 
     System.out.println(mapper.readValue(mapper.writeValueAsString(sharik), Object.class).getClass()); 
    } 

    @JsonTypeName("Balloon") 
    @JsonTypeInfo(use = JsonTypeInfo.Id.NAME) 
    public static final class Balloon { 
     public String color; 

     private Balloon() {} 

     public Balloon(final String color) { 
      this.color = color; 
     } 
    } 

    @JsonTypeName("Car") 
    @JsonTypeInfo(use = JsonTypeInfo.Id.NAME) 
    public static final class Car { 
     public String make; 
     public String model; 
     public int year; 

     private Car() {} 

     public Car(final String make, final String model, final int year) { 
      this.make = make; 
      this.model = model; 
      this.year = year; 
     } 
    } 

    static Map<String, Class<?>> scanForNamedTypes() { 
     //in reality, i'd be using a framework (e.g. Reflections) to scan the classpath 
     //for classes tagged with @JsonTypeName to avoid maintaining manual mappings 
     final Map<String, Class<?>> typesByName = new HashMap<>(); 
     typesByName.put("Balloon", Balloon.class); 
     typesByName.put("Car", Car.class); 
     return Collections.unmodifiableMap(typesByName); 
    } 
} 
2

는 생성 된 JSON은 확실히 당신은 다음과 같은

Object deserialized = jackson.readValue(json, Object.class); 

Object와 잭슨을 제공 한 유형을 올바르게

을 결정 잭슨이 에 필요한 모든 정보가 모든 Java의 슈퍼 타입입니다 참조 유형. 이것은 알려져있다. 잭슨에게별로 도움이되지 않습니다. 귀하의 JSON은

{"@type":"Balloon","color":"red"} 

잭슨이이 @type 요소에 대한 값을 사용할 수 있음을 추론 할 수

jackson.enableDefaultTypingAsProperty(DefaultTyping.JAVA_LANG_OBJECT, "@type"); 

해당 감사를 감안할 때 포함되어 있습니다. 그러나 Balloon이란 이름으로 무엇을 할 수 있습니까? Jackson은 클래스 패스의 모든 유형에 대해 알지 못합니다. 정규화 된 이름이 Balloon 인 유형이 있습니까? com.example.Balloon 또는 org.company.toys.Balloon이라는 유형이 있습니까? 잭슨은 어떻게 선택해야합니까?

@JsonTypeInfo 및 주석 군은 일반적으로 상속 계층 구조의 비 직렬화에 사용됩니다. 예를 들어

@JsonTypeInfo(use = Id.NAME) 
@JsonSubTypes(value = @Type(value = Balloon.class)) 
public abstract static class Toy { 

} 

@JsonTypeName("Balloon") 
public static final class Balloon extends Toy { 

Object deserialized = jackson.readValue(json, Toy.class); 

이제 잭슨은 또한 이름 Balloon를 확인할 수 있습니다 Balloon으로 서브 클래스 (들)을 식별하는 Toy 클래스와 메타 데이터를 찾아 볼 수 있습니다. 당신이 상속 계층 구조, 간단한 솔루션, 여기 모델을 시도하지 않는 경우

@JsonTypeName 주석에 Balloon 클래스의 완전한 이름을 사용하는 것입니다.

@JsonTypeName("com.example.JacksonDeserializationOfNamedTypes$Balloon") 
@JsonTypeInfo(use = Id.NAME) 
public static final class Balloon { 

그 이름은 JSON에 표시되며 Jackson은이를 사용하여 대상 클래스를 결정합니다.에서

+0

'는 이름을 풍선으로 무엇을 할 수'글쎄, 난 '? m 클래스의 클래스 경로를 검사하여 * 자신의 * 주석으로 태그가 붙은 잭슨을 구성하고 Balloon 클래스에 @JsonTypeName ("Balloon") 태그가 붙어 있는지 확인하는 방법이 있기를 바랍니다. – Andrey

+0

나는 또한 시도했습니다. 'NamedType (Balloon.class, "Balloon")'을 전달함으로써'ObjectMapper # registerSubtypes'를 가지고 놀았습니다. 그것은 또한 작동하지 않았다. 다시 말하면, 질문의 주요 목표는 기본 유형 * (즉, Object.class 지정)을 모른 채 *를 역 직렬화하는 방법입니다. – Andrey

-1

짧은 (과 : 너무 늦게)

당신은해야 하나 :

@JsonTypeName("com.example.JacksonDeserializationOfNamedTypes$Balloon") 
: 잭슨에 역 직렬화하는 클래스를 찾을 수 있도록

+0

사용자 정의 디시리얼라이저를 추가하는 것이 확장 가능하지 않습니다 - 많은 pojo 메시지 클래스 (모두 JsonTypeName 태그)가있는 코드베이스를 상상해보십시오. 개념적으로, 내가 정말로해야 할 일은 잭슨에게 "이봐, ObjectMapper에게 @JsonTypeName이라는 주석이 어떻게 있는지 알 수있다. 좋은, 좋은데, 여기에 태그가 붙은 클래스의 긴 목록이있다. 나는 당신이 각 클래스를 스캔하기를 원한다. 그리고 나중에 'readValue'를 호출하는 동안 JSON 필드를 찾을 수 있고 사용할 자바 클래스를 파악할 수 있도록 자바 타입 (java.lang.Class)에 타입 이름 (String)의 내부 매핑을 구축하십시오. 직렬화 복원 용 " – Andrey

관련 문제