¿Cómo puedo incluir JSON sin formato en un objeto usando Jackson?

Resuelto bhilstrom asked hace 13 años • 12 respuestas

Estoy intentando incluir JSON sin formato dentro de un objeto Java cuando el objeto se (des)serializa usando Jackson. Para probar esta funcionalidad, escribí la siguiente prueba:

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);
}

El código genera la siguiente línea:

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

El JSON es exactamente como quiero que se vean las cosas. Desafortunadamente, el código falla con una excepción al intentar volver a leer el JSON en el objeto. Aquí está la excepción:

org.codehaus.jackson.map.JsonMappingException: no se puede deserializar la instancia de java.lang.String fuera del token START_OBJECT en [Fuente: java.io.StringReader@d70d7a; línea: 1, columna: 13] (a través de la cadena de referencia: com.tnal.prism.cobalt.gather.testing.Pojo["bar"])

¿Por qué Jackson funciona bien en una dirección pero falla en la otra dirección? Parece que debería poder volver a tomar su propia salida como entrada. Sé que lo que estoy tratando de hacer es poco ortodoxo (el consejo general es crear un objeto interno que bartenga una propiedad llamada A), pero no quiero interactuar con este JSON en absoluto. Mi código actúa como un paso para este código. Quiero tomar este JSON y enviarlo nuevamente sin tocar nada, porque cuando el JSON cambia no quiero que mi código necesite modificaciones.

Gracias por el consejo.

EDITAR: convirtió a Pojo en una clase estática, lo que estaba causando un error diferente.

bhilstrom avatar Jan 24 '11 21:01 bhilstrom
Aceptado

@JsonRawValue está destinado únicamente al lado de la serialización, ya que la dirección inversa es un poco más complicada de manejar. De hecho, se agregó para permitir inyectar contenido precodificado.

Supongo que sería posible agregar soporte para revertir, aunque eso sería bastante incómodo: el contenido tendrá que ser analizado y luego reescrito nuevamente a su forma "sin procesar", que puede o no ser la misma (ya que las comillas puede diferenciarse). Esto para el caso general. Pero tal vez tendría sentido para algún subconjunto de problemas.

Pero creo que una solución para su caso específico sería especificar el tipo como 'java.lang.Object', ya que esto debería funcionar bien: para la serialización, String se generará tal cual, y para la deserialización, se deserializará como un mapa. En realidad, es posible que desee tener un captador/definidor separado si es así; getter devolvería String para la serialización (y necesita @JsonRawValue); y el configurador tomaría Mapa u Objeto. Podrías volver a codificarlo en una cadena si eso tiene sentido.

StaxMan avatar Jan 24 '2011 19:01 StaxMan

Siguiendo la respuesta de @StaxMan , hice que lo siguiente funcione de maravilla:

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;
  }
}

Y, para ser fiel a la pregunta inicial, aquí tenéis la prueba de trabajo:

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}
  }
}
yves amsellem avatar Jul 12 '2012 13:07 yves amsellem