Comment puis-je inclure raw JSON dans un objet en utilisant Jackson?

j'essaie d'inclure raw JSON dans un objet Java lorsque L'objet est (de)sérialisé en utilisant Jackson. Afin de tester cette fonctionnalité, j'ai écrit le test suivant:

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

le code affiche la ligne suivante:

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

Le JSON est exactement la façon dont je veux que les choses à regarder. Malheureusement, le code échoue avec une exception en essayant de lire le JSON de nouveau à l'objet. Voici l'exception:

org.codehaus.Jackson.cartographie.JsonMappingException: ne peut pas désérialiser l'instance de java.lang.String out of START_OBJECT token à [Source: java.io.StringReader@d70d7a; ligne: 1, colonne: 13] (par chaîne de référence: com.tnal.prisme.cobalt.rassembler.test.Pojo ["bar"])

pourquoi Jackson fonctionne-t-il très bien dans une direction, mais pas dans l'autre? Il semble qu'il devrait être capable de prendre sa propre sortie comme entrée encore. Je sais que ce que j'essaie de faire est peu orthodoxe (le Conseil général est de créer un objet intérieur pour bar qui a une propriété appelée A ), mais je ne veux pas interagir avec ce JSON du tout. Mon code agit comme un pass-through pour ce code -- je veux prendre ce JSON et le renvoyer à nouveau sans rien toucher, parce que quand le JSON change, Je ne veux pas que mon code ait besoin de modifications.

Merci pour le Conseil.

EDIT: a fait de Pojo une classe statique, ce qui causait une erreur différente.

82
demandé sur skaffman 2011-01-24 17:56:18

12 réponses

@JsonRawValue est destiné à la sérialisation-côté seulement, puisque la direction inverse est un peu plus difficile à manipuler. En effet, il a été ajouté pour permettre l'injection de contenu précodé.

je suppose qu'il serait possible d'ajouter un support pour l'inverse, bien que ce serait assez embarrassant: le contenu devra être analysé, puis réécrit à la forme" brute", qui peut être ou non la même (puisque les citations de caractères peuvent différer). Ceci pour cas général. Mais peut-être qu'il serait logique pour un sous-ensemble de problèmes.

mais je pense qu'une solution pour votre cas spécifique serait de spécifier le type comme ' java.lang.Object', puisque cela devrait fonctionner correctement: pour la sérialisation, la chaîne sera sortie telle quelle, et pour la deserialisation, elle sera désérialisée en tant que Map. En fait, vous pourriez vouloir avoir getter/setter séparé si c'est le cas; getter retournerait la chaîne pour la sérialisation (et a besoin de @JsonRawValue); et setter prendrait la carte ou L'objet. Possibilité de ré-encoder pour une Chaîne si cela fait sens.

46
répondu StaxMan 2011-01-24 19:36:10

suite @StaxMan réponse, j'ai fait les travaux suivants comme un charme:

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

et, pour être fidèle à la question initiale, voici le test de travail:

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}
  }
}
46
répondu yves amsellem 2017-05-23 12:03:09

j'ai pu le faire avec un désérialiseur personnalisé (découpé et collé de ici )

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<String> {

    @Override
    public String deserialize(JsonParser jp, DeserializationContext ctxt)
            throws IOException, JsonProcessingException {

        TreeNode tree = jp.getCodec().readTree(jp);
        return tree.toString();
    }
}
30
répondu Roy Truelove 2017-05-23 12:34:59

@JsonSetter peut aider. Voir mon échantillon ('data' est censé contenir un JSON sans précédent):

class Purchase
{
    String data;

    @JsonProperty("signature")
    String signature;

    @JsonSetter("data")
    void setData(JsonNode data)
    {
        this.data = data.toString();
    }
}
14
répondu anagaf 2016-12-05 10:37:07

cela fonctionne même dans une entité 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());
}
4
répondu Georg 2017-07-23 20:06:00

C'est un problème avec vos classes intérieures. La classe Pojo est une non-static inner class de votre classe d'essai, et Jackson ne peut pas instancier cette classe. Donc il peut sérialiser, mais pas désérialiser.

redéfinissez votre classe comme ceci:

public static class Pojo {
    public String foo;

    @JsonRawValue
    public String bar;
}

Note l'ajout de static

3
répondu skaffman 2011-01-24 15:08:48

ajouter à Roy Truelove L 'Grand réponse , voici comment injecter le custom deserialiseren réponse à l'apparition de @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<SettableBeanProperty> 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;
    }
}
2
répondu Amir Abiri 2017-05-23 11:33:26

Cette solution facile a fonctionné pour moi:

public class MyObject {
    private Object rawJsonValue;

    public Object getRawJsonValue() {
        return rawJsonValue;
    }

    public void setRawJsonValue(Object rawJsonValue) {
        this.rawJsonValue = rawJsonValue;
    }
}

ainsi j'ai pu stocker la valeur brute de JSON dans la variable rawJsonValue et puis il n'y avait aucun problème pour la deserialiser (comme objet) avec d'autres champs de retour à JSON et l'envoyer via Mon Repos. L'utilisation de @JsonRawValue ne m'a pas aidé parce que JSON stocké a été désérialisé comme chaîne de caractères, pas comme objet, et ce n'était pas ce que je voulais.

2
répondu Pavol Golias 2017-01-30 08:05:04

j'ai eu exactement le même problème. J'ai trouvé la solution dans ce post : Parser JSON arbre à la plaine de classe à l'aide de Jackson ou de ses alternatives

cochez la dernière réponse. En définissant un setter personnalisé pour la propriété qui prend un jsonnode comme paramètre et appelle la méthode toString sur le jsonNode pour définir la propriété String, tout fonctionne.

1
répondu Alex Kubity 2017-05-23 12:10:54

utiliser un objet fonctionne dans les deux sens... Cette méthode a un peu de sursérialisation de la valeur brute en deux temps.

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;
    }
}
1
répondu Vozzie 2016-05-23 01:39:24

voici un exemple complet d'utilisation des modules Jackson pour faire fonctionner @JsonRawValue dans les deux sens (sérialisation et désérialisation):

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<String> {
        private static final JsonDeserializer<String> INSTANCE = new JsonRawValueDeserializer();

        @Override
        public String deserialize(JsonParser p, DeserializationContext ctxt) throws IOException, JsonProcessingException {
            return p.readValueAsTree().toString();
        }
    }
}

ensuite vous pouvez enregistrer le module après avoir créé le ObjectMapper :

ObjectMapper objectMapper = new ObjectMapper();
objectMapper.registerModule(new JsonRawValueDeserializerModule());

String json = "{\"foo\":\"one\",\"bar\":{\"A\":false}}";
Pojo deserialized = objectMapper.readValue(json, Pojo.class);
1
répondu Helder Pereira 2017-11-08 19:15:56

j'ai eu un problème similaire, mais en utilisant une liste avec beaucoup de JSON itens ( List<String> ).

public class Errors {
    private Integer status;
    private List<String> jsons;
}

j'ai géré la sérialisation en utilisant l'annotation @JsonRawValue . Mais pour la desérialisation j'ai dû créer une coutume deserializer basé sur la suggestion de Roy.

public class Errors {

    private Integer status;

    @JsonRawValue
    @JsonDeserialize(using = JsonListPassThroughDeserialzier.class)
    private List<String> jsons;

}

ci-dessous, vous pouvez voir mon" liste " désérialiseur.

public class JsonListPassThroughDeserializer extends JsonDeserializer<List<String>> {

    @Override
    public List<String> deserialize(JsonParser jp, DeserializationContext cxt) throws IOException, JsonProcessingException {
        if (jp.getCurrentToken() == JsonToken.START_ARRAY) {
            final List<String> 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");
    }
}
1
répondu Biga 2018-06-19 20:33:45