Deserialize JSON with Jackson without proper field - java

I've got this JSON : {"success":false}
I want to deserialize this into this POJO :
class Message {
private Map<String, String> dataset = new HashMap<String, String>();
#JsonProperty("success")
public boolean isSuccess() {
return Boolean.valueOf(dataset.get("success"));
}
#JsonProperty("success")
public void setSuccess(boolean success) {
dataset.put("success", String.valueOf(success));
}
}
Is it possible to deserialize this JSON into a class without field success?
So far, i've always got the "UnrecognizedPropertyException: Unrecognized field "success""
Thanks for your help!

You could implement a method and annotate it with #JsonAnySetter like this:
#JsonAnySetter
public void handleUnknownProperties(String key, Object value) {
// this will be invoked when property isn't known
}
another possibility would be turn this fail off like this:
objectMapper.configure(DeserializationConfig.Feature.FAIL_ON_UNKNOWN_PROPERTIES, false);
This would let you deserialize your JSON without failing when properties are not found.
Test
public static class Message {
private final Map<String, String> dataset = new HashMap<String, String>();
#Override
public String toString() {
return "Message [dataset=" + dataset + "]";
}
}
#Test
public void testJackson() throws JsonParseException, JsonMappingException, IOException {
String json = "{\"success\":false}";
ObjectMapper om = new ObjectMapper().configure(DeserializationConfig.Feature.FAIL_ON_UNKNOWN_PROPERTIES, false);
System.out.println(om.readValue(json, Message.class));
}

Note: I'm the EclipseLink JAXB (MOXy) lead and a member of the JAXB (JSR-222) expert group.
If you can't get it to work with Jackson, below is how you can support this use case with MOXy.
Message
No annotations are required on the Message class. By default property access is used. You can specify field access using #XmlAccessorType(XmlAccessType.FIELD), see: http://blog.bdoughan.com/2011/06/using-jaxbs-xmlaccessortype-to.html.
package forum11315389;
import java.util.*;
class Message {
private Map<String, String> dataset = new HashMap<String, String>();
public boolean isSuccess() {
return Boolean.valueOf(dataset.get("success"));
}
public void setSuccess(boolean success) {
dataset.put("success", String.valueOf(success));
}
}
jaxb.properties
To specify MOXy as your JAXB provider you need to include a file called jaxb.properties in the same package as your domain model with the following entry:
javax.xml.bind.context.factory=org.eclipse.persistence.jaxb.JAXBContextFactory
Demo
package forum11315389;
import java.io.StringReader;
import java.util.*;
import javax.xml.bind.*;
import javax.xml.transform.stream.StreamSource;
import org.eclipse.persistence.jaxb.JAXBContextProperties;
public class Demo {
public static void main(String[] args) throws Exception {
Map<String,Object> properties = new HashMap<String,Object>(1);
properties.put(JAXBContextProperties.MEDIA_TYPE, "application/json");
properties.put(JAXBContextProperties.JSON_INCLUDE_ROOT, false);
JAXBContext jc = JAXBContext.newInstance(new Class[] {Message.class}, properties);
StreamSource json = new StreamSource(new StringReader("{\"success\":false}"));
Unmarshaller unmarshaller = jc.createUnmarshaller();
Message message = unmarshaller.unmarshal(json, Message.class).getValue();
Marshaller marshaller = jc.createMarshaller();
marshaller.marshal(message, System.out);
}
}
Input/Output
{"success":false}

I don't understand the question. Jackson will (de)serialize from/to the current version of the Message POJO you've defined in the original question just fine, without errors, and without any special configurations (other than the #JsonProperty annotations). The current Message POJO does not have a field named success, but it does define a property named success, which is why Jackson is happy to map the example JSON to it without any additional configurations. Are you wanting to remove the #JsonProperty annotations?
If that's the case, then you can do so, and Jackson will still (de)serialize from/to the Message POJO with the same example JSON without any other configurations necessary, because the isSuccess and setSuccess method signatures already adequately define that Message has a property named success, which matches the element name in the JSON.
The following examples demonstrate these points.
Example 1 with Message POJO exactly as defined in original question:
import java.util.HashMap;
import java.util.Map;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.databind.ObjectMapper;
public class JacksonFoo
{
public static void main(String[] args) throws Exception
{
// input: {"success":false}
String inputJson = "{\"success\":false}";
ObjectMapper mapper = new ObjectMapper();
Message message = mapper.readValue(inputJson, Message.class);
System.out.println(mapper.writeValueAsString(message));
// output: {"success":false}
}
}
class Message
{
private Map<String, String> dataset = new HashMap<String, String>();
#JsonProperty("success")
public boolean isSuccess()
{
return Boolean.valueOf(dataset.get("success"));
}
#JsonProperty("success")
public void setSuccess(boolean success)
{
dataset.put("success", String.valueOf(success));
}
}
Example 2 with Message POJO modified to remove #JsonProperty annotations.
import java.util.HashMap;
import java.util.Map;
import com.fasterxml.jackson.databind.ObjectMapper;
public class JacksonFoo
{
public static void main(String[] args) throws Exception
{
// input: {"success":false}
String inputJson = "{\"success\":false}";
ObjectMapper mapper = new ObjectMapper();
Message message = mapper.readValue(inputJson, Message.class);
System.out.println(mapper.writeValueAsString(message));
// output: {"success":false}
}
}
class Message
{
private Map<String, String> dataset = new HashMap<String, String>();
public boolean isSuccess()
{
return Boolean.valueOf(dataset.get("success"));
}
public void setSuccess(boolean success)
{
dataset.put("success", String.valueOf(success));
}
}
Example with MessageWrapper:
public class JacksonFoo
{
public static void main(String[] args) throws Exception
{
// input: {"success":false}
String inputJson = "{\"success\":true}";
ObjectMapper mapper = new ObjectMapper();
MessageWrapper wrappedMessage = mapper.readValue(inputJson, MessageWrapper.class);
System.out.println(mapper.writeValueAsString(wrappedMessage));
// output: {"success":true}
}
}
class MessageWrapper
{
#JsonUnwrapped
#JsonProperty // exposes non-public field for Jackson use
Message message;
}

Related

Dynamic Json To Java Pojo

I want to map the following json to a pojo in Java. In the snippet shown below, result is a Json object, whose value is another json object which is a map. I tried converting this to a Pojo, but it failed. The keys in the result map are dynamic, and I cannot guess them prior.
final_result :
{
"result":
{
"1597696140": 70.32,
"1597696141": 89.12,
"1597696150": 95.32,
}
}
The pojo that I created is :
#JsonIgnoreProperties(ignoreUnknown = true)
public class ResultData {
Map<Long, Double> resultMap;
public ResultData(Map<Long, Double> resultMap) {
this.resultMap = resultMap;
}
public ResultData() {
}
#Override
public String toString() {
return super.toString();
}
}
Upon trying to create the pojo using ObjectMapper :
ObjectMapper objectMapper = new ObjectMapper();
ResultData resultData = objectMapper.readValue(resultData.getJSONObject("result").toString(), ResultData.class);
What am I possible doing wrong here ?
Assume, your JSON payload looks like below:
{
"final_result": {
"result": {
"1597696140": 70.32,
"1597696141": 89.12,
"1597696150": 95.32
}
}
}
You can deserialise it to class:
#JsonRootName("final_result")
class ResultData {
private Map<Long, Double> result;
public Map<Long, Double> getResult() {
return result;
}
#Override
public String toString() {
return result.toString();
}
}
Like below:
import com.fasterxml.jackson.annotation.JsonRootName;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.io.File;
import java.io.IOException;
import java.util.Map;
public class Main {
public static void main(String[] args) throws IOException {
File jsonFile = new File("./src/main/resources/test.json");
ObjectMapper mapper = new ObjectMapper();
mapper.enable(DeserializationFeature.UNWRAP_ROOT_VALUE);
ResultData resultData = mapper.readValue(jsonFile, ResultData.class);
System.out.println(resultData);
}
}
Above code prints:
{1597696140=70.32, 1597696141=89.12, 1597696150=95.32}
Converting the JSONObject to Map and setting the map to the pojo field, solved the issue and didn't lead me to writing a custom deserializer.
Map<Long, Double> resultData = objectMapper.readValue(resultData.getJSONObject("result").toString(), Map.class);
FinalResultData finaResultData = new FinalResultData(resultData);

Map a JSON field (to a value) based on another field (which is a key) using Jackson

{
"key1": {
"parameter1": "String1",
"parameter2": "String2"
},
"key2": {
"parameter1": "String3",
"parameter2": "String4"
},
"key3": {
"parameter1": "String5",
"parameter2": "String6"
}
}
I have the above JSON (/Users/user1/Desktop/responseMap.json) which is basically a Map<String, MockResponse> where MockResponse is the below POJO:
public class MockResponse {
public String parameter1;
public String parameter2;
}
Now, I have another POJO - TestCase, and another JSON - testCase.json as below:
public class TestCase {
public String responseMapFileLocation;
public String mockResponseKey;
public MockResponse mockResponse;
}
testCase.json
{
"responseMapFileLocation": "/Users/user1/Desktop/responseMap.json",
"mockResponseKey": "key1",
"mockResponse": null
}
What I am able to do is first map testCase.json to TestCase using Jackson, then map responseMap.json to Map<String, MockResponse>, then in my code search for mockResponseKey in the map.
But what I want to do is when I map testCase.json to TestCase using Jackson, I want the value of variable mockResponse to set automatically based on the value of variable mockResponseKey using the first JSON map.
You need to write custom deserialiser for TestCase class. In custom deserialiser you can parse basic properties: responseMapFileLocation, mockResponseKey and load mockResponse from other file. To deserialiser MockResponse you can use new ObjectMapper instance. Below code shows how this concept could be implemented:
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.TreeNode;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonDeserializer;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.fasterxml.jackson.databind.type.MapType;
import java.io.File;
import java.io.IOException;
import java.util.Map;
public class JsonApp {
public static void main(String[] args) throws Exception {
File jsonFile = new File("./resource/test.json").getAbsoluteFile();
ObjectMapper mapper = new ObjectMapper();
System.out.println(mapper.readValue(jsonFile, TestCase.class));
}
}
class MockResponse {
public String parameter1;
public String parameter2;
}
#JsonDeserialize(using = TestCaseFromExternalFileDeserializer.class)
class TestCase {
public String responseMapFileLocation;
public String mockResponseKey;
public MockResponse mockResponse;
}
class TestCaseFromExternalFileDeserializer extends JsonDeserializer<TestCase> {
private final ObjectMapper mapper;
private final MapType mapType;
public TestCaseFromExternalFileDeserializer() {
mapper = new ObjectMapper();
mapType = mapper.getTypeFactory().constructMapType(Map.class, String.class, MockResponse.class);
}
#Override
public TestCase deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
TreeNode treeNode = p.readValueAsTree();
TestCase testCase = new TestCase();
testCase.responseMapFileLocation = ((JsonNode) treeNode.get("responseMapFileLocation")).asText();
testCase.mockResponseKey = ((JsonNode) treeNode.get("mockResponseKey")).asText();
parseMockResponse(testCase);
return testCase;
}
private void parseMockResponse(TestCase testCase) throws IOException {
Map<String, MockResponse> map = mapper.readValue(new File(testCase.responseMapFileLocation), mapType);
testCase.mockResponse = map.get(testCase.mockResponseKey);
}
}
You need to implement only toString method for each POJO class. Above code prints:
TestCase{responseMapFileLocation='./resource/responseMap.json', mockResponseKey='key1', mockResponse=MockResponse{parameter1='String1', parameter2='String2'}}
Both JSON files are in resource folder.
See also:
How use jackson ObjectMapper inside custom deserializer?
Jackson Streaming API - if you want to implement MockResponse deserialisation in faster way.
Tweaking the getter setter in your Test class and marking the field as private I was able to make it dynamic (Imports are from org.codehaus.jackson package)
class TestCase {
private String responseMapFileLocation;
private String mockResponseKey;
#JsonIgnore
private MockResponse mockResponse; //else value will be override in json value
public String getResponseMapFileLocation() {
return responseMapFileLocation;
}
public void setResponseMapFileLocation(String responseMapFileLocation) {
this.responseMapFileLocation = responseMapFileLocation;
}
public String getMockResponseKey() {
return mockResponseKey;
}
public void setMockResponseKey(String mockResponseKey1) throws IOException {
ObjectMapper mapper = new ObjectMapper();
Map<String, MockResponse> map = mapper.readValue(new File("C:\\Users\\Json1.json"), TypeFactory.mapType(HashMap.class, String.class, MockResponse.class));
this.mockResponse = map.get(mockResponseKey1);
this.mockResponseKey = mockResponseKey1;
}
public MockResponse getMockResponse() {
return mockResponse;
}
#Override
public String toString() {
return "TestCase [responseMapFileLocation=" + responseMapFileLocation + ", mockResponseKey=" + mockResponseKey
+ ", mockResponse=" + mockResponse + "]";
}
}
class MockResponse {
public String parameter1;
public String parameter2;
#Override
public String toString() {
return "MockResponse [parameter1=" + parameter1 + ", parameter2=" + parameter2 + "]";
}
}
and Running below code
public static void main(String[] args) throws IOException {
ObjectMapper mapper = new ObjectMapper();
TestCase testCase = mapper.readValue(new File("C:\\UsersJson2.json"), TestCase.class);
System.out.println(testCase);
}
output will be
TestCase [responseMapFileLocation=/Users/user1/Desktop/responseMap.json, mockResponseKey=key1, mockResponse=MockResponse [parameter1=String1, parameter2=String2]]
What you are asking for is not possible with just Jackson. Jackson is primarily a marshalling/unmarshalling tool, converting JSONs to Objects and vice versa. In other words, the value of the object must be known at the time of unmarshalling.
However you can unmarshal your json as a HashMap using the code:
new JSONObject(map);
search for the MockResponse-as-a-string using the mockResponseKey and then unmarshal that code into a new MockResponse.

Can A Data Field in JSON format be parsed? [duplicate]

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

Jackson map different attributes based on json in the same class

I have a class Response which has an attribute data.
A json file is mapped to this object. The data attribute can be of type TaskData or SubmitData on the json.
If the json has the object of type TaskData the object mapper must map to TaskData class or should map to `SubmitData' class.
In addition to the answer by Guillaume Polet, and if you can modify the JSON schema, this can also be done a little bit smoother using Jackson's Polymorphic (de)serialization via annotations:
#JsonTypeInfo(use=JsonTypeInfo.Id.Class, include=JsonTypeInfo.As.PROPERTY, property="#class")
class Data {}
class TaskData extends Data {}
class SubmitData extends Data {}
This will write out the full Java class name as an additional #class property. The json needs to include the #class property on input, however.
Instead of JsonTypeInfo.Id.Class it is also possible to perform explicit naming
#JsonTypeInfo(use=JsonTypeInfo.Id.Class, include=JsonTypeInfo.As.PROPERTY, property="#dataType")
#JsonSubTypes({
JsonSubTypes.Type(value=TaskData.class, name="task"),
JsonSubTypes.Type(value=SubmitData.class, name="submit")
})
class Data {}
#JsonTypeName("task")
class TaskData extends Data {}
#JsonTypeName("submit")
class SubmitData extends Data {}
This will yield an additional synthetic field #dataType, which will need to be present in the input.
If you cannot make the type explicit in the input JSON, you will need to stick to the manual approach.
You need to type your Response class as follows: public class Response<T>.
Then, when deserializing the input, provide a TypeReference to jackson to indicate the desired type.
See this example:
import java.io.IOException;
import java.io.StringReader;
import java.io.StringWriter;
import com.fasterxml.jackson.core.JsonGenerationException;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.ObjectMapper;
public class TestJacksonTyping {
public static void main(String[] args) throws JsonGenerationException, JsonMappingException, IOException {
ObjectMapper mapper = new ObjectMapper();
Response<TaskData> taskResponse = new Response<TaskData>();
TaskData taskData = new TaskData();
taskData.setTaskTitle("Some title");
taskResponse.setData(taskData);
Response<SubmitData> submitResponse = new Response<SubmitData>();
SubmitData submitData = new SubmitData();
submitData.setSubmitValue(256);
submitResponse.setData(submitData);
StringWriter sw = new StringWriter();
mapper.writeValue(sw, taskResponse);
String taskResponseJson = sw.toString();
mapper.writeValue(sw = new StringWriter(), submitResponse);
String submitResponseJson = sw.toString();
Response<TaskData> deserializedTaskResponse = mapper.reader(new TypeReference<Response<TaskData>>() {
}).readValue(new StringReader(taskResponseJson));
Response<SubmitData> deserializedSubmitResponse = mapper.reader(new TypeReference<Response<SubmitData>>() {
}).readValue(new StringReader(submitResponseJson));
System.out.println(deserializedTaskResponse.getData().getTaskTitle());
System.out.println(deserializedSubmitResponse.getData().getSubmitValue());
}
public static class Response<T> {
private T data;
public T getData() {
return data;
}
public void setData(T data) {
this.data = data;
}
}
public static class TaskData {
private String taskTitle;
public String getTaskTitle() {
return taskTitle;
}
public void setTaskTitle(String taskTitle) {
this.taskTitle = taskTitle;
}
}
public static class SubmitData {
private int submitValue;
public int getSubmitValue() {
return submitValue;
}
public void setSubmitValue(int submitValue) {
this.submitValue = submitValue;
}
}
}

Jackson - Modify an attribute at runtime without annotation

Let's say I have a bean:
public class Msg {
private int code;
private Object data;
... Getter/setters...
}
And I convert it into JSON or XML with this kind of test code:
public String convert() {
Msg msg = new Msg();
msg.setCode( 42 );
msg.setData( "Are you suggesting coconuts migrate?" );
ObjectMapper mapper = new ObjectMapper();
return mapper.writeValueAsString( msg );
}
The output will be somehow like that :
{"code":42,"data":"Are you suggesting coconuts migrate?"}
Now let's say I want to replace the 'data' attribute with some dynamic name:
public String convert(String name) {
Msg msg = new Msg();
msg.setCode( 42 );
msg.setData( "Are you suggesting coconuts migrate?" );
ObjectMapper mapper = new ObjectMapper();
// ...DO SOMETHING WITH MAPPER ...
return mapper.writeValueAsString( msg );
}
If I call the function convert( "toto") I woukld like to have this output:
{"code":42,"toto":"Are you suggesting coconuts migrate?"}
If I call the function convert( "groovy") I woukld like to have this output:
{"code":42,"groovy":"Are you suggesting coconuts migrate?"}
Of course I could do a String replace after JSON creation, but if you have an answer with a programmatic approach I'll take it.
Thanks
You can use PropertyNamingStrategy class to override class property. See simple implementation of this class:
class ReplaceNamingStrategy extends PropertyNamingStrategy {
private static final long serialVersionUID = 1L;
private Map<String, String> replaceMap;
public ReplaceNamingStrategy(Map<String, String> replaceMap) {
this.replaceMap = replaceMap;
}
#Override
public String nameForGetterMethod(MapperConfig<?> config, AnnotatedMethod method, String defaultName) {
if (replaceMap.containsKey(defaultName)) {
return replaceMap.get(defaultName);
}
return super.nameForGetterMethod(config, method, defaultName);
}
}
Example program could look like this:
import java.io.IOException;
import java.util.Collections;
import java.util.Map;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.PropertyNamingStrategy;
import com.fasterxml.jackson.databind.cfg.MapperConfig;
import com.fasterxml.jackson.databind.introspect.AnnotatedMethod;
public class JacksonProgram {
public static void main(String[] args) throws IOException {
Msg msg = new Msg();
msg.setCode(42);
msg.setData("Are you suggesting coconuts migrate?");
System.out.println(convert(msg, "test"));
System.out.println(convert(msg, "toto"));
System.out.println(convert(msg, "groovy"));
}
public static String convert(Msg msg, String name) throws IOException {
ObjectMapper mapper = new ObjectMapper();
mapper.setPropertyNamingStrategy(new ReplaceNamingStrategy(Collections.singletonMap("data", name)));
return mapper.writeValueAsString(msg);
}
}
Above program prints:
{"code":42,"test":"Are you suggesting coconuts migrate?"}
{"code":42,"toto":"Are you suggesting coconuts migrate?"}
{"code":42,"groovy":"Are you suggesting coconuts migrate?"}
One possibility would be to use so-called "any getter":
public class Msg {
public int code;
#JsonAnyGetter
public Map<String,Object> otherFields() {
Map<String,Object> extra = new HashMap<String,Object>();
extra.put("data", findDataObject()); // or whatever mechanism you want
extra.put("name", "Some Name");
return extra;
}
}
so that you can return arbitrary set of dynamic properties.
There is also matching "any getter" (#JsonAnyGetter) mechanism you can use to accept additional properties.

Categories