Jackson deserialize string value that contains valid json - java

Suppose I have the following JSON data:
{
"header": "some value",
"message": "{\"field1\": \"abc\", \"field2\": 123}"
}
Is it possible to adjust the annotations on OuterClass so the message field will be parsed as an InnerClass object?
public class InnerClass {
#JsonProperty("field1")
public void setField1(String value) {/* do stuff */}
#JsonProperty("field2")
public void setField2(Integer value) {/* do stuff */}
}
public class OuterClass {
#JsonProperty("message")
public void setMessage(InnerClass obj) {/* do stuff */}
}
Ideally I would like the calling code to look something like:
ObjectMapper mapper = new ObjectMapper();
OuterClass obj = mapper.readValue(jsonStr, OuterClass.class);

Structure your outer class to have a property of the other class that represents the nested JSON, like:
public class OuterClass {
#JsonProperty("header")
private String header;
#JsonProperty("message")
private InnerClass message;
//getters & setters
}

Once I added some complexity, the accepted answer would not work. I kept getting the error:
Can not instantiate value of type [simple type, InnerClass] from JSON String; no single-String constructor/factory method (through reference chain: InnerClass)
I ended up using the following approach
public class OuterClass {
public InnerClass message;
#JsonCreator
public OuterClass (Map<String,Object> delegate) throws IOException {
String json = (String)delegate.get("Message");
ObjectMapper mapper = new ObjectMapper();
this.message = mapper.readValue(json, InnerClass.class);
}
}

Related

Johnzon sub dynamic json tree mapping

In Apache Johnzon, is there a way to hava a generic field that contains dynamic JSON data, not mappable to a pre-defined POJO?
In Jackson you can simply use ObjectNode as a generic container, have some JSON processing on it, and then write the whole object in JSON format.
In Jackson it works as expected using ObjectNode, here is my code:
public class JsonTest {
private String myStaticKey = "foo";
private ObjectNode jsonData;
//code to initialize ObjectNode + getters + setters
#JsonIgnore
public void addValue(String key, String value) {
jsonData.put(key, value);
}
#JsonIgnore
public String toJson() {
return new ObjectMapper().writeValueAsString(this);
}
}
public class MainTest {
public static void main(String[] args) {
JsonTest t = new JsonTest();
t.addValue("myDynamicKey", "bar");
System.out.println(t.toJson());
}
}
Expected result:
{
"myStaticKey": "foo",
"jsonData": {
"myDynamicKey": "bar"
}
}

How to deserialize a List of String to a single arg value class?

Basically I always want to unwrap my Id class to the parent object but in case of a List<> I can not use the JsonUnwrapped Annotation from the jackson library.
#lombok.Value
public class Response {
List<MyId> ids;
// ... other fields
}
#lombok.Value
public class MyId {
String id;
}
{
"ids": ["id1", "id2"]
"otherField": {}
}
Working solution with jackson-databind 2.11
#lombok.Value
public class MyId {
#JsonValue
String id;
public MyId(final String id) {
this.id = id;
}
}
You can use #JsonValue. From docs:
Marker annotation that indicates that the value of annotated accessor (either field or "getter" method [a method with non-void return type, no args]) is to be used as the single value to serialize for the instance, instead of the usual method of collecting properties of value. Usually value will be of a simple scalar type (String or Number), but it can be any serializable type (Collection, Map or Bean).
Usage:
#Value
public class MyId {
#JsonValue
String id;
}
Complete code:
public class JacksonExample {
public static void main(String[] args) throws JsonProcessingException {
ObjectMapper objectMapper = new ObjectMapper();
List<MyId> myIds = new ArrayList<>();
MyId id1 = new MyId("one");
MyId id2 = new MyId("two");
myIds.add(id1);
myIds.add(id2);
Response response = new Response(myIds, "some other field value");
System.out.println(objectMapper.writeValueAsString(response));
}
}
#Value
class Response {
List<MyId> ids;
String otherField;
}
#Value
class MyId {
#JsonValue
String id;
}
Output:
{
"ids": [
"one",
"two"
],
"otherField": "some other field value"
}

Jackson Deserialiser : use #JsonCreator AND #JsonProperty on field?

I'm trying to parse some Json into an Java object.
Some fields require custom behavior so i tried to use #JsonCreator on a constructor.
Well, it work, but for the other field annotate with #JsonProperty are not populated.
Didn't check yet, but i guess my object annotate with #JsonUnwrappedare not populated either.
In my search, i saw a comment that indicate that it is possible, but i can't figure how, if it is indeed possible.
There is around 400 field in the json, and only 5 or 6 that require custom behavior. So if i can avoid rewriting all constructor... that would be nice !
Exemple of what i tried :
public class MyObjectA {
#JsonProperty("keyField1")
private String myField1;
#JsonUnwrapped
private MyObjectB;
private String[] myField2;
#JsonCreator
public MyObjectA(final Map<String, Object> properties){
myField2 = ... //some Business logic
}
}
Junit :
ObjectMapper mapper = new ObjectMapper();
MyObjectA result = mapper.readValue(getJsonInputStream(JSON_FILE_PATH),MyObjectA.class);
Assert.notNull(result.getMyField1(),"should be populated")
Assert.notNull(result.getMyField2(),"should be populated")
Assert.notNull(result.getMyObjectB(),"should be populated")
Note : without the constructor, the other field are well populated
Here it is. See the difference between commented and non commented #JsonConstructor usage. I am handling property something as custom handling and leaving name to be called using setName. Hope that helps
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.util.Map;
public class Jackson2 {
public static void main(String[] args) throws Exception {
final ObjectMapper mapper = new ObjectMapper();
final String jsonInString = "{\"name\":\"hello world\",\"something\":\"from string\"}";
System.out.println(jsonInString);
Foo newFoo = mapper.readValue(jsonInString, Foo.class);
System.out.println(newFoo.getName());
System.out.println(newFoo.getSomething());
}
}
class Foo {
#JsonProperty
private String name;
private String something;
public String getSomething() {
return something;
}
public void setSomething(String something) {
this.something = something;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
Foo() {
}
// #JsonCreator
public Foo(final Map<String, Object> properties) {
System.out.println("printing.."+properties);
something = "Something from constructor";
}
#JsonCreator
public Foo(#JsonProperty("something") String something ) {
System.out.println("printing.."+name);
this.something = "Something from constructor appended"+something;
}
}
So idea is that you use #JsonProperty in the constructor argument for properties you want to customize. :)

Instruct Jackson to serialize from given method

I'm using Jackson library vs 2.6.3. I would like to define the serialization method inside a class and I would like to instruct Jackson to call this method when an object is serialized.
E.g.
public interface AClass {
default String toJSON(){
return "{JSON}";
}
}
and then I have
public class AnotherClass {
AClass aClass;
}
When I serialize AnotherClass, I "pretend" that method toJson from AClass is called to return the JSON view of the object.
Is there any annotation that I can use on AClass?
Yes, this is possible using #JsonValue and #JsonRawValue annotations. Here is an example:
public class JacksonValue {
public interface AClass {
#JsonValue
#JsonRawValue
default String toJSON(){
return "{\"JSON\":true}";
}
}
public static class AnotherClass {
#JsonProperty
AClass aClass = new AClass() {};
}
public static void main(String[] args) throws JsonProcessingException {
final ObjectMapper mapper = new ObjectMapper();
System.out.println(mapper.writeValueAsString(new AnotherClass()));
}
}
Output:
{"aClass":{"JSON":true}}

How can I include raw JSON in an object using Jackson?

I am trying to include raw JSON inside a Java object when the object is (de)serialized using Jackson. In order to test this functionality, I wrote the following 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);
}
The code outputs the following line:
{"foo":"one","bar":{"A":false}}
The JSON is exactly how I want things to look. Unfortunately, the code fails with an exception when attempting to read the JSON back in to the object. Here is the exception:
org.codehaus.jackson.map.JsonMappingException: Can not deserialize instance of java.lang.String out of START_OBJECT token
at [Source: java.io.StringReader#d70d7a; line: 1, column: 13] (through reference chain: com.tnal.prism.cobalt.gather.testing.Pojo["bar"])
Why does Jackson function just fine in one direction but fail when going the other direction? It seems like it should be able to take its own output as input again. I know what I'm trying to do is unorthodox (the general advice is to create an inner object for bar that has a property named A), but I don't want to interact with this JSON at all. My code is acting as a pass-through for this code -- I want to take in this JSON and send it back out again without touching a thing, because when the JSON changes I don't want my code to need modifications.
Thanks for the advice.
EDIT: Made Pojo a static class, which was causing a different error.
#JsonRawValue is intended for serialization-side only, since the reverse direction is a bit trickier to handle. In effect it was added to allow injecting pre-encoded content.
I guess it would be possible to add support for reverse, although that would be quite awkward: content will have to be parsed, and then re-written back to "raw" form, which may or may not be the same (since character quoting may differ).
This for general case. But perhaps it would make sense for some subset of problems.
But I think a work-around for your specific case would be to specify type as 'java.lang.Object', since this should work ok: for serialization, String will be output as is, and for deserialization, it will be deserialized as a Map. Actually you might want to have separate getter/setter if so; getter would return String for serialization (and needs #JsonRawValue); and setter would take either Map or Object. You could re-encode it to a String if that makes sense.
Following #StaxMan answer, I've made the following works like a charm:
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;
}
}
And, to be faithful to the initial question, here is the working test:
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}
}
}
I was able to do this with a custom deserializer (cut and pasted from here)
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 KeepAsJsonDeserializer extends JsonDeserializer<String> {
#Override
public String deserialize(JsonParser jp, DeserializationContext ctxt)
throws IOException {
TreeNode tree = jp.getCodec().readTree(jp);
return tree.toString();
}
}
Use it by annotating the desired member like this:
#JsonDeserialize(using = KeepAsJsonDeserializer.class)
private String value;
#JsonSetter may help. See my sample ('data' is supposed to contain unparsed JSON):
class Purchase
{
String data;
#JsonProperty("signature")
String signature;
#JsonSetter("data")
void setData(JsonNode data)
{
this.data = data.toString();
}
}
This is a problem with your inner classes. The Pojo class is a non-static inner class of your test class, and Jackson cannot instantiate that class. So it can serialize, but not deserialize.
Redefine your class like this:
public static class Pojo {
public String foo;
#JsonRawValue
public String bar;
}
Note the addition of static
Adding to Roy Truelove's great answer, this is how to inject the custom deserialiser in response to appearance of #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;
}
}
This easy solution worked for me:
public class MyObject {
private Object rawJsonValue;
public Object getRawJsonValue() {
return rawJsonValue;
}
public void setRawJsonValue(Object rawJsonValue) {
this.rawJsonValue = rawJsonValue;
}
}
So I was able to store raw value of JSON in rawJsonValue variable and then it was no problem to deserialize it (as object) with other fields back to JSON and send via my REST. Using #JsonRawValue didnt helped me because stored JSON was deserialized as String, not as object, and that was not what I wanted.
This even works in a JPA entity:
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) {
// this leads to non-standard json, see discussion:
// setJson(jsonNode.toString());
StringWriter stringWriter = new StringWriter();
ObjectMapper objectMapper = new ObjectMapper();
JsonGenerator generator =
new JsonFactory(objectMapper).createGenerator(stringWriter);
generator.writeTree(n);
setJson(stringWriter.toString());
}
Ideally the ObjectMapper and even JsonFactory are from the context and are configured so as to handle your JSON correctly (standard or with non-standard values like 'Infinity' floats for example).
Here is a full working example of how to use Jackson modules to make #JsonRawValue work both ways (serialization and deserialization):
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();
}
}
}
Then you can register the module after creating the ObjectMapper:
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.registerModule(new JsonRawValueDeserializerModule());
String json = "{\"foo\":\"one\",\"bar\":{\"A\":false}}";
Pojo deserialized = objectMapper.readValue(json, Pojo.class);
I had the exact same issue.
I found the solution in this post :
Parse JSON tree to plain class using Jackson or its alternatives
Check out the last answer.
By defining a custom setter for the property that takes a JsonNode as parameter and calls the toString method on the jsonNode to set the String property, it all works out.
Using an object works fine both ways... This method has a bit of overhead deserializing the raw value in two times.
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;
}
}
I had a similar problem, but using a list with a lot of JSON itens (List<String>).
public class Errors {
private Integer status;
private List<String> jsons;
}
I managed the serialization using the #JsonRawValue annotation. But for deserialization I had to create a custom deserializer based on Roy's suggestion.
public class Errors {
private Integer status;
#JsonRawValue
#JsonDeserialize(using = JsonListPassThroughDeserialzier.class)
private List<String> jsons;
}
Below you can see my "List" deserializer.
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");
}
}

Categories

Resources