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.
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.
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}
}
}
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();
}
}
@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();
}
}
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());
}
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
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;
}
}
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.
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.
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;
}
}
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);
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");
}
}