Как я могу включить raw JSON в объект с помощью Jackson?
Я пытаюсь включить raw JSON внутри объекта Java, когда объект (de)сериализуется с помощью Jackson. Чтобы проверить эту функциональность, я написал следующий тест:
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);
}
этот код выводит следующую строку:
{"foo":"one","bar":{"A":false}}
JSON-это именно то, как я хочу, чтобы все выглядело. К сожалению, код не выполняется с исключением при попытке прочитать JSON обратно в объект. Вот исключение:
орг.codehaus.Джексон.карта.JsonMappingException: не удается десериализовать экземпляр java.ленг.Строка из маркера START_OBJECT в [источник: java.io.StringReader@d70d7a; строка: 1, столбец: 13] (через цепочку ссылок: com.тнал.призма.кобальт.собирать.тестирование.Pojo ["бар"])
Почему Джексон функционирует просто отлично в одном направлении, но терпит неудачу при движении в другом направлении? Похоже, что он должен быть в состоянии снова взять свой собственный выход в качестве входа. Я знаю, что я пытаюсь сделать, это неортодоксальный (общий совет-создать внутренний объект для bar
, который имеет свойство с именем A
), но я вообще не хочу взаимодействовать с этим JSON. Мой код действует как сквозной для этого кода - я хочу взять этот JSON и отправить его обратно, не касаясь ничего, потому что когда JSON изменяется, я не хочу, чтобы мой код нуждался в изменениях.
Спасибо за совет.
EDIT: сделал Pojo статическим классом, который вызывал другой ошибка.
12 ответов:
@JsonRawValue предназначен только для сериализации-сторона, так как обратное направление немного сложнее обрабатывать. Фактически он был добавлен, чтобы позволить вводить предварительно закодированный контент.
Я думаю, что можно было бы добавить поддержку обратного, хотя это было бы довольно неудобно: контент должен быть проанализирован, а затем переписан обратно в "сырую" форму, которая может быть или не быть одинаковой (поскольку цитирование символов может отличаться). Это для общего случая. Но, возможно, это имело бы смысл для некоторое подмножество проблем.
но я думаю, что обходной путь для вашего конкретного случая будет указывать тип как 'java.ленг.Object', так как это должно работать нормально: для сериализации строка будет выводиться как есть, а для десериализации она будет десериализована как карта. На самом деле вы можете захотеть иметь отдельный геттер/сеттер, если это так; геттер вернет строку для сериализации (и нуждается в @JsonRawValue); и сеттер возьмет либо карту, либо объект. Вы можете перекодировать его в строку, если это обретать смысл.
после @StaxMan ответ, я сделал следующие работы, как шарм:
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; } }
и, чтобы быть верным первоначальному вопросу, вот рабочий тест:
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} } }
я смог сделать это с помощью пользовательского десериализатора (вырезать и вставить из здесь)
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 может помочь. См. мой пример ("данные" должны содержать unparsed JSON):
class Purchase { String data; @JsonProperty("signature") String signature; @JsonSetter("data") void setData(JsonNode data) { this.data = data.toString(); } }
Это даже работает в сущности 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()); }
это проблема с внутренними классами. Элемент
Pojo
класс нестатический внутренний класс вашего тестового класса, и Джексон не может создать экземпляр этого класса. Таким образом, он может сериализовать, но не десериализовать.переопределите свой класс следующим образом:
public static class Pojo { public String foo; @JsonRawValue public String bar; }
обратите внимание на добавление
static
добавлять к Рой Трулавздорово ответ, это как ввести пользовательский десериализатор в ответ на появление
@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; } }
Это простое решение работало для меня:
public class MyObject { private Object rawJsonValue; public Object getRawJsonValue() { return rawJsonValue; } public void setRawJsonValue(Object rawJsonValue) { this.rawJsonValue = rawJsonValue; } }
таким образом, я смог сохранить необработанное значение JSON в переменной rawJsonValue, а затем не было проблем десериализовать его (как объект) с другими полями обратно в JSON и отправить через мой REST. Использование @JsonRawValue не помогло мне, потому что сохраненный JSON был десериализован как строка, а не как объект, и это было не то, что я хотел.
У меня была точно такая же проблема. Я нашел решение в этом посте : разбор дерева JSON в обычный класс с использованием Jackson или его альтернатив
Проверьте последний ответ. Определив пользовательский сеттер для свойства, которое принимает JsonNode в качестве параметра и вызывает метод toString на jsonNode для установки строкового свойства, все это работает.
использование объекта отлично работает в обоих направлениях... Этот метод имеет немного накладных расходов десериализации необработанного значения в два раза.
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; } }
вот полный рабочий пример того, как использовать модули Джексона, чтобы сделать
@JsonRawValue
работа в обоих направлениях (сериализация и десериализация):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(); } } }
затем вы можете зарегистрировать модуль после создания
ObjectMapper
:ObjectMapper objectMapper = new ObjectMapper(); objectMapper.registerModule(new JsonRawValueDeserializerModule()); String json = "{\"foo\":\"one\",\"bar\":{\"A\":false}}"; Pojo deserialized = objectMapper.readValue(json, Pojo.class);
у меня была аналогичная проблема, но с помощью списка с большим количеством JSON itens (
List<String>
).public class Errors { private Integer status; private List<String> jsons; }
Я сериализации с помощью
@JsonRawValue
Примечание. Но для десериализации мне пришлось создать пользовательский десериализатор на основе предложения Роя.public class Errors { private Integer status; @JsonRawValue @JsonDeserialize(using = JsonListPassThroughDeserialzier.class) private List<String> jsons; }
ниже вы можете увидеть мой "список" десериализатор.
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"); } }