2016-01-20 33 views
0

我的JSON是這樣的:動態傑克遜自定義解串器

{"typeName":"test","field":{"name":"42"}} 

我有兩個解串器。第一個(JsonDeserializer<EntityImpl>)將檢查JSON並提取由typeName屬性提供的類型信息。

第二個解串器(JsonDeserializer<TestField>)用於反序列化field屬性。這個解串器需要知道之前提取的typeName值才能正常工作。

我該如何將類型信息從一個反序列化器傳遞到其他反序列化器?我試圖用DeserializationContext但我不知道如何相處的背景下,從解串器的傳球到B

我當前的代碼如下所示:

EntityImpl.java:

package de.jotschi.test; 

public class EntityImpl implements Entity { 

    private String typeName; 

    private TestField field; 

    public String getTypeName() { 
     return typeName; 
    } 

    public void setTypeName(String typeName) { 
     this.typeName = typeName; 
    } 

    public TestField getField() { 
     return field; 
    } 

    public void setField(TestField field) { 
     this.field = field; 
    } 
} 

TestField的.java:

package de.jotschi.test; 

public class TestField { 

    String name; 

    public String getName() { 
     return name; 
    } 

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

測試:

package de.jotschi.test; 

import java.io.IOException; 

import java.util.HashMap; 
import java.util.Map; 

import org.junit.Test; 

import com.fasterxml.jackson.annotation.JsonInclude.Include; 
import com.fasterxml.jackson.core.JsonParser; 
import com.fasterxml.jackson.core.JsonProcessingException; 
import com.fasterxml.jackson.core.ObjectCodec; 
import com.fasterxml.jackson.core.Version; 
import com.fasterxml.jackson.databind.BeanProperty; 
import com.fasterxml.jackson.databind.DeserializationContext; 
import com.fasterxml.jackson.databind.DeserializationFeature; 
import com.fasterxml.jackson.databind.InjectableValues; 
import com.fasterxml.jackson.databind.JsonDeserializer; 
import com.fasterxml.jackson.databind.JsonNode; 
import com.fasterxml.jackson.databind.ObjectMapper; 
import com.fasterxml.jackson.databind.module.SimpleModule; 

import de.jotschi.test.EntityImpl; 
import de.jotschi.test.TestField; 

public class TestMapper2 { 

    private InjectableValues getInjectableValue() { 

     InjectableValues values = new InjectableValues() { 

      @Override 
      public Object findInjectableValue(Object valueId, DeserializationContext ctxt, BeanProperty forProperty, Object beanInstance) { 
       if ("data".equals(valueId.toString())) { 
        return new HashMap<String, String>(); 
       } 
       return null; 
      } 
     }; 
     return values; 

    } 

    @Test 
    public void testMapper() throws IOException { 
     ObjectMapper mapper = new ObjectMapper(); 
     SimpleModule idAsRefModule = new SimpleModule("ID-to-ref", new Version(1, 0, 0, null)); 

     idAsRefModule.addDeserializer(EntityImpl.class, new JsonDeserializer<EntityImpl>() { 
      @Override 
      public EntityImpl deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException, JsonProcessingException { 

       Map<String, String> dataMap = (Map) ctxt.findInjectableValue("data", null, null); 
       System.out.println("Value: " + dataMap.get("test")); 

       ObjectCodec codec = jp.getCodec(); 
       JsonNode node = codec.readTree(jp); 
       String type = node.get("typeName").asText(); 
       dataMap.put("typeName", type); 

       // How to pass on type information to TestField deserializer? The context is not reused for the next deserializer. 
       // I assume that readValueAs fails since the codec.readTree method has already been invoked. 
       //return jp.readValueAs(EntityImpl.class); 

       // Alternatively the treeToValue method can be invoked in combination with the node. Unfortunately all information about the DeserializationContext is lost. I assume new context will be created. 
       // How to reuse the old context? 
       return codec.treeToValue(node, EntityImpl.class); 

      } 
     }); 

     idAsRefModule.addDeserializer(TestField.class, new JsonDeserializer<TestField>() { 
      @Override 
      public TestField deserialize(JsonParser p, DeserializationContext ctxt) throws IOException, JsonProcessingException { 
       // Access type from context 
       Map<String, String> dataMap = (Map) ctxt.findInjectableValue("data", null, null); 
       System.out.println(dataMap.get("typeName")); 
       ObjectCodec codec = p.getCodec(); 
       JsonNode node = codec.readTree(p); 
       return codec.treeToValue(node, TestField.class); 
      } 

     }); 

     mapper.registerModule(idAsRefModule); 
     mapper.setSerializationInclusion(Include.NON_NULL); 
     mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); 

     // Setup the pojo 
     EntityImpl impl = new EntityImpl(); 
     impl.setTypeName("test"); 
     TestField testField = new TestField(); 
     testField.setName("42"); 
     impl.setField(testField); 

     // POJO -> JSON 
     String json = mapper.writeValueAsString(impl); 
     System.out.println(json); 

     // JSON -> POJO 
     Entity obj = mapper.reader(getInjectableValue()).forType(EntityImpl.class).readValue(json); 
     System.out.println(obj.getClass().getName()); 

    } 

} 
+0

這種類型的信息手工處理重新確立的輪子。傑克遜已經可以做到了。看到這篇文章http://www.cowtowncoder.com/blog/archives/2010/03/entry_372.html – AdamSkywalker

+0

我的類型信息不是指一個類。類型信息基本上用於在TestField derserializer內動態構建POJO。因此我不能使用傑克遜的類型系統。 – Jotschi

回答

0

我目前的解決辦法是撥打以下映射是這樣的:

 return mapper.setInjectableValues(getInjectableValue(dataMap)).treeToValue(obj, EntityImpl.class); 

這樣,以前加載的上下文數據映射投入被用於以下解析過程一個新的上下文。

完整例如:

package de.jotschi.test; 

import static org.junit.Assert.assertEquals; 
import static org.junit.Assert.assertNotNull; 

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

import org.junit.Test; 

import com.fasterxml.jackson.annotation.JsonInclude.Include; 
import com.fasterxml.jackson.core.JsonParser; 
import com.fasterxml.jackson.core.JsonProcessingException; 
import com.fasterxml.jackson.core.Version; 
import com.fasterxml.jackson.databind.BeanProperty; 
import com.fasterxml.jackson.databind.DeserializationContext; 
import com.fasterxml.jackson.databind.DeserializationFeature; 
import com.fasterxml.jackson.databind.InjectableValues; 
import com.fasterxml.jackson.databind.JsonDeserializer; 
import com.fasterxml.jackson.databind.ObjectMapper; 
import com.fasterxml.jackson.databind.module.SimpleModule; 
import com.fasterxml.jackson.databind.node.ObjectNode; 

public class TestMapper2 { 

    private InjectableValues getInjectableValue(final Map<String, String> dataMap) { 

     InjectableValues values = new InjectableValues() { 

      @Override 
      public Object findInjectableValue(Object valueId, DeserializationContext ctxt, BeanProperty forProperty, Object beanInstance) { 
       if ("data".equals(valueId.toString())) { 
        return dataMap; 
       } 
       return null; 
      } 
     }; 
     return values; 

    } 

    @Test 
    public void testMapper() throws IOException { 
     ObjectMapper mapper = new ObjectMapper(); 
     SimpleModule idAsRefModule = new SimpleModule("ID-to-ref", new Version(1, 0, 0, null)); 

     idAsRefModule.addDeserializer(Entity.class, new JsonDeserializer<Entity>() { 
      @Override 
      public Entity deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException, JsonProcessingException { 

       Map<String, String> dataMap = (Map) ctxt.findInjectableValue("data", null, null); 
       ObjectMapper mapper = (ObjectMapper) jp.getCodec(); 
       ObjectNode obj = (ObjectNode) mapper.readTree(jp); 
       String type = obj.get("typeName").asText(); 
       dataMap.put("typeName", type); 
       return mapper.setInjectableValues(getInjectableValue(dataMap)).treeToValue(obj, EntityImpl.class); 
      } 
     }); 

     idAsRefModule.addDeserializer(TestField.class, new JsonDeserializer<TestField>() { 
      @Override 
      public TestField deserialize(JsonParser p, DeserializationContext ctxt) throws IOException, JsonProcessingException { 
       // Access type from context 
       Map<String, String> dataMap = (Map) ctxt.findInjectableValue("data", null, null); 
       System.out.println("Type name: " + dataMap.get("typeName")); 

       ObjectMapper mapper = (ObjectMapper) p.getCodec(); 
       ObjectNode obj = (ObjectNode) mapper.readTree(p); 

       // Custom deserialisation 
       TestField field = new TestField(); 
       field.setName(obj.get("name").asText()); 
       // Delegate further deserialisation to other mapper 
       field.setSubField(mapper.setInjectableValues(getInjectableValue(dataMap)).treeToValue(obj.get("subField"), SubField.class)); 
       return field; 
      } 
     }); 

     mapper.registerModule(idAsRefModule); 
     mapper.setSerializationInclusion(Include.NON_NULL); 
     mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); 

     // Setup the pojo 
     EntityImpl impl = new EntityImpl(); 
     impl.setTypeName("test"); 
     TestField testField = new TestField(); 
     testField.setName("42"); 
     SubField subField = new SubField(); 
     subField.setName("sub"); 
     testField.setSubField(subField); 
     impl.setField(testField); 

     // POJO -> JSON 
     String json = mapper.writeValueAsString(impl); 
     System.out.println(json); 

     // JSON -> POJO 
     Entity obj = mapper.reader(getInjectableValue(new HashMap<String, String>())).forType(Entity.class).readValue(json); 
     assertNotNull("The enity must not be null", obj); 
     assertNotNull(((EntityImpl) obj).getField()); 
     assertEquals("42", ((EntityImpl) obj).getField().getName()); 
     assertNotNull(((EntityImpl) obj).getField().getSubField()); 
     assertEquals("sub", ((EntityImpl) obj).getField().getSubField().getName()); 
     System.out.println(obj.getClass().getName()); 

    } 

}