2017-05-08 4 views
0

Liebe Leute, ich brauche dich beraten.Deserializing JSON eingebetteten Knoten als Wert in Map mit Jackson

Ich versuche, benutzerdefinierte Jackson Deserializer für Karten zu implementieren, aber Schwierigkeiten damit. Als Eingangsdaten habe ich folgendes json:

{ 
    "someMap": { 
     "one_value": "1", 
     "another_value: "2" 
    }, 
    "anotherMap": "{\"100000000\": 360000,\"100000048\": 172800,\"100000036\": 129600,\"100000024\": 86400,\"100000012\": 43200}" 
} 

Wie Sie im zweiten Fall sehen kann json Karte innerhalb Knotenwert hat (ich es auf diese Weise durch die Absicht zu tun, weil ich das ersetzt werden soll. Wert von env Variable: "anotherMap": "${SOME_MAP:-{\"100000000\": 360000,\"100000048\": 172800,\"100000036\": 129600,\"100000024\": 86400,\"100000012\": 43200}}"). Wie ich es verstehe muss ich irgendwie zwischen diesen 2 Karten Deserialisierungsflüssen unterscheiden. Also für die erste Karte muss ich Standard-Karten-Deserializer für den zweiten verwenden, den benutzerdefinierten, um die Karte korrekt aus dem Wert zu analysieren. Im Moment habe ich diesen Code, das zu tun:

// invokation code 
new ObjectMapper().registerModule(new ConfigModule()).readValue(is, ConfigModuleTestConfigWrapper.class); 

// module code 
public class ConfigModule extends SimpleModule { 

@Override 
public void setupModule(SetupContext context) { 
    super.setupModule(context); 
    context.addDeserializers(new Deserializers.Base() { 

      @Override 
      public JsonDeserializer<?> findMapDeserializer(MapType type, DeserializationConfig config, BeanDescription beanDesc, 
                 KeyDeserializer keyDeserializer, TypeDeserializer elementTypeDeserializer, 
                 JsonDeserializer<?> elementDeserializer) throws JsonMappingException { 
       return new MapPropertyDeserializer(type); 
      } 
    }); 
} 

private static class MapPropertyDeserializer extends StdScalarDeserializer<Map<String, Integer>> { 
    MapPropertyDeserializer(MapType type) { 
     super(type); 
    } 
    @Override 
    public Map<String, Integer> deserialize(JsonParser p, DeserializationContext ctxt) throws IOException { 
     JsonNode node = p.readValueAsTree(); 
     if (node == null || node.isContainerNode()) { 
       // return <default jackson deserializer> 
     } 
     System.out.println("isContainerNode ?: " + node.isContainerNode()); 
     System.out.println("isValueNode ?: " + node.isValueNode()); 
     // some parsing flow goes below 
     JsonNode valueNode = node.get(1); 
     valueNode.asText(); 
     return new HashMap<>(); 
    } 
} 

// bean description 
@JsonIgnoreProperties 
public class ConfigSubstitutorModuleTestConfigWrapper { 

    private final Map<String, String> someMap; 
    private final Map<String, Integer> anotherMap; 

    @JsonCreator 
    public ConfigSubstitutorModuleTestConfigWrapper(
      @JsonProperty("someMap") Map<String, String> someMap, 
      @JsonProperty("anotherMap") Map<String, Integer> anotherMap 

) { 
     this.someMap = someMap; 
     this.anotherMap = anotherMap; 
    } 

    public Map<String, String> getSomeMap() { 
     return someMap; 
    } 
    public Map<String, Integer> getAnotherMap() { 
     return anotherMap; 
    } 
} 

Das Problem ist (wie ich sie verstehe :)), dass ich weiß nicht, wie Standard-Karte Deserializer von der deserialize Methode zurückzukehren.

Hat jemand irgendeine Ahnung, was ich dort tun könnte, um das gewünschte Ziel zu erreichen? Schließlich

+0

Hättest du nicht lieber das Teil beheben, die diese hässliche JSON-String innerhalb JSON erzeugt? Das ist der Teil, der für mich gebrochen aussieht. –

+0

Ich kann das nicht tun. Die Idee hier ist, den hässlichen JSON mit einem Wert von der Umgebungsvariablen wie dem zu ersetzen "" anotherMap ":" $ {SOME_MAP: - {\ "100000000 \": 360000, \ "100000048 \": 172800, \ "100000036 \": 129600, "100000024": 86400, "100000012": 43200}} ". Also mache ich es mit Absicht. –

+0

möglich duplizieren von http://stackoverflow.com/questions/18313323/how-do-i-call-the-default-deserializer-from-a-custom-deserializer-in-jackson –

Antwort

0

akzeptiert, dass Lösung es zu lösen:

1) Erstellen Deserializer Klasse:

/** 
* The target of that deserializer is to do two-step deserialization. 
* At first it just reads string and then does second deserialization in the proper {@link Map} type once string substitution done. 
* <p> 
* Note! In order to get object mapper reference you have to set it first on object mapper initialization stage: 
* </p> 
* <pre> 
*  objectMapper.setInjectableValues(new InjectableValues.Std().addValue(OBJECT_MAPPER_VALUE_ID, objectMapper)); 
* </pre> 
*/ 
public class ValueAsMapDeserializer extends JsonDeserializer<Map> implements ContextualDeserializer { 
    public static final String OBJECT_MAPPER_VALUE_ID = "objectMapper"; 
    static final String VALUE_PREFIX = "$|"; 
    static final String VALUE_SUFFIX = "|"; 

    private JavaType keyType; 
    private JavaType valueType; 

    @Override 
    public JsonDeserializer<?> createContextual(final DeserializationContext ctxt, 
               final BeanProperty property) throws JsonMappingException { 
     JavaType filedType = property.getType(); 
     this.keyType = filedType.getKeyType(); 
     this.valueType = filedType.getContentType(); 
     return this; 
    } 

    @Override 
    public Map deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException { 
     // Can't use constructor init there because of intention to use that deserializer using annotation 
     // Also such tricky thing as 'injectable values' was used cause of no way to get the reference to object mapper from deserialization context out of the box 
     ObjectMapper objectMapper = (ObjectMapper) ctxt.findInjectableValue(OBJECT_MAPPER_VALUE_ID, null, null); 
     final Optional<String> substitutedValue = Substitutor.create(jp, VALUE_PREFIX, VALUE_SUFFIX).substitute(); 
     MapType mapType = objectMapper.getTypeFactory().constructMapType(Map.class, keyType, valueType); 
     return objectMapper.readValue(substitutedValue.orElseThrow(() -> new RuntimeException("Failed to parse the value as map")), mapType); 
    } 
} 

2) Mark Bean Feld die Deserializer zu verwenden:

@JsonDeserialize(using = ValueAsMapDeserializer.class) 
private final Map<String, Integer> anotherMap; 
0

Versuchen Sie, Führen Sie die Deserialisierung in zwei Schritten durch:

package stack43844461; 

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

import org.junit.Test; 

import com.fasterxml.jackson.core.JsonParseException; 
import com.fasterxml.jackson.databind.JsonMappingException; 
import com.fasterxml.jackson.databind.ObjectMapper; 

public class HowToConvertJsonStringToMap { 

    @Test 
    public void json() throws JsonParseException, JsonMappingException, IOException { 

     String jsonInString = "{\"someMap\":" 
         + " {\"one_value\": \"1\"," 
         + "\"another_value\": \"2\"}," 
         + "\"anotherMap\": " 
         + "\"{\\\"100000000\\\": 360000," 
         + "\\\"100000048\\\": 172800," 
         + "\\\"100000036\\\": 129600," 
         + "\\\"100000024\\\": 86400," 
         + "\\\"100000012\\\": 43200}\"}"; 

     ObjectMapper mapper = new ObjectMapper(); 
     // Step 1: Read everything into one object. 
     Map<String, Object> all = mapper.readValue(jsonInString, Map.class); 
     // Step 2: Get your "normal" data into one object 
     Map<String, Object> someMap=(Map<String, Object>) all.get("someMap"); 
     // Step 3: Get your "embedded" data from your object 
     String anotherMapStr = (String) all.get("anotherMap"); 
     // Step 4: Deserialize embedded data 
     Map<String, Object> anotherMap = mapper.readValue(anotherMapStr, Map.class); 
     System.out.println(anotherMap); 
     System.out.println(someMap); 
    } 

} 

Drucke:

{100000000=360000, 100000048=172800, 100000036=129600, 100000024=86400, 100000012=43200} 
{one_value=1, another_value=2} 
+0

Ja, es hätte in meinem Fall funktioniert. Aber ich muss mit dem Modul arbeiten, das für objectMapper registriert wird, also muss ich das auch im Deserializer tun, anstatt 'jsonInString' zu haben. Ich habe Felder in POJO mit dem Typ' Map' und kenne keinen genauen Feldnamen wie 'anotherMap' –

Verwandte Themen