Come posso includere JSON non elaborato in un object usando Jackson?

Sto cercando di includere JSON non elaborato all’interno di un object Java quando l’object è (de) serializzato usando Jackson. Per testare questa funzionalità, ho scritto il seguente test:

public static class Pojo { public String foo; @JsonRawValue public String bar; } @Test public void test() throws JsonGenerationException, JsonMappingException, IOException { String foo = "one"; String bar = "{\"A\":false}"; Pojo pojo = new Pojo(); pojo.foo = foo; pojo.bar = bar; String json = "{\"foo\":\"" + foo + "\",\"bar\":" + bar + "}"; ObjectMapper objectMapper = new ObjectMapper(); String output = objectMapper.writeValueAsString(pojo); System.out.println(output); assertEquals(json, output); Pojo deserialized = objectMapper.readValue(output, Pojo.class); assertEquals(foo, deserialized.foo); assertEquals(bar, deserialized.bar); } 

Il codice restituisce la riga seguente:

 {"foo":"one","bar":{"A":false}} 

Il JSON è esattamente come voglio che le cose sembrino. Sfortunatamente, il codice fallisce con un’eccezione quando si tenta di leggere di nuovo il JSON nell’object. Ecco l’eccezione:

org.codehaus.jackson.map.JsonMappingException: imansible deserializzare l’istanza di java.lang.String dal token START_OBJECT in [Source: java.io.StringReader@d70d7a; riga: 1, colonna: 13] (attraverso la catena di riferimenti: com.tnal.prism.cobalt.gather.testing.Pojo [“bar”])

Perché Jackson funziona perfettamente in una direzione, ma fallisce quando si va nella direzione opposta? Sembra che dovrebbe essere in grado di prendere di nuovo il proprio output come input. So che quello che sto cercando di fare non è ortodosso (il consiglio generale è di creare un object interno per bar che abbia una proprietà chiamata A ), ma non voglio affatto interagire con questo JSON. Il mio codice funge da pass-through per questo codice: voglio prendere questo JSON e inviarlo di nuovo senza toccare nulla, perché quando cambia il JSON non voglio che il mio codice abbia bisogno di modifiche.

Grazie per il consiglio.

EDIT: Made Pojo una class statica, che stava causando un errore diverso.

    @JsonRawValue è destinato esclusivamente al lato di serializzazione, poiché la direzione inversa è un po ‘più complicata da gestire. In effetti è stato aggiunto per consentire l’iniezione di contenuti pre-codificati.

    Suppongo che sarebbe ansible aggiungere il supporto per il reverse, anche se ciò sarebbe abbastanza imbarazzante: il contenuto dovrà essere analizzato e quindi riscritto in forma “raw”, che può essere o non essere lo stesso (dal momento che il carattere cita può differire). Questo per caso generale. Ma forse avrebbe senso per alcuni sottogruppi di problemi.

    Ma penso che una soluzione per il tuo caso specifico sarebbe quella di specificare il tipo ‘java.lang.Object’, poiché dovrebbe funzionare bene: per la serializzazione, String verrà emesso così com’è, e per la deserializzazione, sarà deserializzato come una cartina. In realtà si potrebbe desiderare di avere getter / setter separati in caso affermativo; getter restituirebbe String per la serializzazione (e ha bisogno di @JsonRawValue); e setter prenderebbe sia una mappa che un object. Potresti ricodificarlo in una stringa se questo ha senso.

    Seguendo la risposta @StaxMan , ho reso il seguente lavoro come un incantesimo:

     public class Pojo { Object json; @JsonRawValue public String getJson() { // default raw value: null or "[]" return json == null ? null : json.toString(); } public void setJson(JsonNode node) { this.json = node; } } 

    E, per essere fedele alla domanda iniziale, ecco il test di lavoro:

     public class PojoTest { ObjectMapper mapper = new ObjectMapper(); @Test public void test() throws IOException { Pojo pojo = new Pojo("{\"foo\":18}"); String output = mapper.writeValueAsString(pojo); assertThat(output).isEqualTo("{\"json\":{\"foo\":18}}"); Pojo deserialized = mapper.readValue(output, Pojo.class); assertThat(deserialized.json.toString()).isEqualTo("{\"foo\":18}"); // deserialized.json == {"foo":18} } } 

    Sono stato in grado di farlo con un deserializzatore personalizzato (tagliato e incollato da qui )

     package etc; import java.io.IOException; import com.fasterxml.jackson.core.JsonParser; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.core.TreeNode; import com.fasterxml.jackson.databind.DeserializationContext; import com.fasterxml.jackson.databind.JsonDeserializer; /** * Keeps json value as json, does not try to deserialize it * @author roytruelove * */ public class KeepAsJsonDeserialzier extends JsonDeserializer { @Override public String deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException, JsonProcessingException { TreeNode tree = jp.getCodec().readTree(jp); return tree.toString(); } } 

    @ Jeffson potrebbe aiutare. Guarda il mio esempio (si suppone che ‘data’ contenga un JSON non analizzato):

     class Purchase { String data; @JsonProperty("signature") String signature; @JsonSetter("data") void setData(JsonNode data) { this.data = data.toString(); } } 

    Funziona anche in un’ quadro JPA:

     private String json; @JsonRawValue public String getJson() { return json; } public void setJson(final String json) { this.json = json; } @JsonProperty(value = "json") public void setJsonRaw(JsonNode jsonNode) { setJson(jsonNode.toString()); } 

    Questo è un problema con le tue classi interne. La class Pojo è una class interna non statica della tua class di test, e Jackson non può istanziare quella class. Quindi può serializzare, ma non deserializzare.

    Ridefinisci la tua class in questo modo:

     public static class Pojo { public String foo; @JsonRawValue public String bar; } 

    Nota l’aggiunta di static

    Aggiunta alla grande risposta di Roy Truelove , ecco come iniettare il deserializzatore personalizzato in risposta all’aspetto di @JsonRawValue :

     import com.fasterxml.jackson.databind.Module; @Component public class ModuleImpl extends Module { @Override public void setupModule(SetupContext context) { context.addBeanDeserializerModifier(new BeanDeserializerModifierImpl()); } } 

     import java.util.Iterator; import com.fasterxml.jackson.annotation.JsonRawValue; import com.fasterxml.jackson.databind.BeanDescription; import com.fasterxml.jackson.databind.DeserializationConfig; import com.fasterxml.jackson.databind.deser.BeanDeserializerBuilder; import com.fasterxml.jackson.databind.deser.BeanDeserializerModifier; import com.fasterxml.jackson.databind.deser.SettableBeanProperty; public class BeanDeserializerModifierImpl extends BeanDeserializerModifier { @Override public BeanDeserializerBuilder updateBuilder(DeserializationConfig config, BeanDescription beanDesc, BeanDeserializerBuilder builder) { Iterator it = builder.getProperties(); while (it.hasNext()) { SettableBeanProperty p = it.next(); if (p.getAnnotation(JsonRawValue.class) != null) { builder.addOrReplaceProperty(p.withValueDeserializer(KeepAsJsonDeserialzier.INSTANCE), true); } } return builder; } } 

    Questa semplice soluzione ha funzionato per me:

     public class MyObject { private Object rawJsonValue; public Object getRawJsonValue() { return rawJsonValue; } public void setRawJsonValue(Object rawJsonValue) { this.rawJsonValue = rawJsonValue; } } 

    Quindi sono stato in grado di memorizzare il valore raw di JSON nella variabile rawJsonValue e quindi non era un problema deserializzare (come object) con altri campi di nuovo in JSON e inviarlo tramite il mio REST. L’uso di @JsonRawValue non mi ha aiutato perché il JSON memorizzato è stato deserializzato come String, non come object, e non era quello che volevo.

    Ho avuto lo stesso identico problema. Ho trovato la soluzione in questo post: Analizzare l’albero JSON in semplice class usando Jackson o le sue alternative

    Controlla l’ultima risposta. Definendo un setter personalizzato per la proprietà che accetta un parametro JsonNode come parametro e chiama il metodo toString su jsonNode per impostare la proprietà String, tutto funziona.

    Usare un object funziona bene in entrambi i modi … Questo metodo ha un po ‘di overhead che deserializza il valore grezzo in due volte.

     ObjectMapper mapper = new ObjectMapper(); RawJsonValue value = new RawJsonValue(); value.setRawValue(new RawHello(){{this.data = "universe...";}}); String json = mapper.writeValueAsString(value); System.out.println(json); RawJsonValue result = mapper.readValue(json, RawJsonValue.class); json = mapper.writeValueAsString(result.getRawValue()); System.out.println(json); RawHello hello = mapper.readValue(json, RawHello.class); System.out.println(hello.data); 

    RawHello.java

     public class RawHello { public String data; } 

    RawJsonValue.java

     public class RawJsonValue { private Object rawValue; public Object getRawValue() { return rawValue; } public void setRawValue(Object value) { this.rawValue = value; } } 

    Ecco un esempio completo di come utilizzare i moduli Jackson per rendere @JsonRawValue funzionante in entrambi i modi (serializzazione e deserializzazione):

     public class JsonRawValueDeserializerModule extends SimpleModule { public JsonRawValueDeserializerModule() { setDeserializerModifier(new JsonRawValueDeserializerModifier()); } private static class JsonRawValueDeserializerModifier extends BeanDeserializerModifier { @Override public BeanDeserializerBuilder updateBuilder(DeserializationConfig config, BeanDescription beanDesc, BeanDeserializerBuilder builder) { builder.getProperties().forEachRemaining(property -> { if (property.getAnnotation(JsonRawValue.class) != null) { builder.addOrReplaceProperty(property.withValueDeserializer(JsonRawValueDeserializer.INSTANCE), true); } }); return builder; } } private static class JsonRawValueDeserializer extends JsonDeserializer { private static final JsonDeserializer INSTANCE = new JsonRawValueDeserializer(); @Override public String deserialize(JsonParser p, DeserializationContext ctxt) throws IOException, JsonProcessingException { return p.readValueAsTree().toString(); } } } 

    Quindi puoi registrare il modulo dopo aver creato l’ ObjectMapper :

     ObjectMapper objectMapper = new ObjectMapper(); objectMapper.registerModule(new JsonRawValueDeserializerModule()); String json = "{\"foo\":\"one\",\"bar\":{\"A\":false}}"; Pojo deserialized = objectMapper.readValue(json, Pojo.class); 

    Ho avuto un problema simile, ma usando una lista con molti itens JSON ( List ).

     public class Errors { private Integer status; private List jsons; } 

    Ho gestito la serializzazione usando l’annotazione @JsonRawValue . Ma per la deserializzazione ho dovuto creare un deserializzatore personalizzato basato sul suggerimento di Roy.

     public class Errors { private Integer status; @JsonRawValue @JsonDeserialize(using = JsonListPassThroughDeserialzier.class) private List jsons; } 

    Qui sotto puoi vedere il mio deserializzatore “Elenco”.

     public class JsonListPassThroughDeserializer extends JsonDeserializer> { @Override public List deserialize(JsonParser jp, DeserializationContext cxt) throws IOException, JsonProcessingException { if (jp.getCurrentToken() == JsonToken.START_ARRAY) { final List list = new ArrayList<>(); while (jp.nextToken() != JsonToken.END_ARRAY) { list.add(jp.getCodec().readTree(jp).toString()); } return list; } throw cxt.instantiationException(List.class, "Expected Json list"); } }