You should create specific Map type and provide it into deserialization process:
TypeFactory typeFactory = mapper.getTypeFactory();
MapType mapType = typeFactory.constructMapType(HashMap.class, String.class, Theme.class);
HashMap<String, Theme> map = mapper.readValue(json, mapType);
Answer from Michał Ziober on Stack OverflowYou should create specific Map type and provide it into deserialization process:
TypeFactory typeFactory = mapper.getTypeFactory();
MapType mapType = typeFactory.constructMapType(HashMap.class, String.class, Theme.class);
HashMap<String, Theme> map = mapper.readValue(json, mapType);
You can use TypeReference class which does the type casting for map with user defined types. More documentation at https://github.com/FasterXML/jackson-databind/
ObjectMapper mapper = new ObjectMapper();
Map<String,Theme> result =
mapper.readValue(src, new TypeReference<Map<String,Theme>>() {});
java - How to Deserialize a Map<String,Object> correctly in Jackson - Stack Overflow
java - Deserialize to a Map<String, Object> with correct type for each value - Stack Overflow
apex - Deserialize Very Specific JSON into Map <String, Object> - Salesforce Stack Exchange
java - Deserialize into Map<String, String> with Jackson - Stack Overflow
The solution that worked for me was using custom deserialization, @JsonDeserialize annotation & JsonDeserializer interface, in order to achieve the desired results.
Below is the solution:
public class Request {
private String method;
private String resource;
@JsonDeserialize(using = BodyDeserializer.class)
private final Map<String, Object> body;
private final Map<String, String> headers;
private final Map<String, String[]> parameters;
public Request() {
this.body = new HashMap();
this.headers = new HashMap();
this.parameters = new HashMap();
}
public String getMethod() {
return method;
}
public String getResource() {
return resource;
}
public Map<String, Object> getBody() {
return body;
}
public Map<String, String> getHeaders() {
return headers;
}
public Map<String, String[]> getParameters() {
return parameters;
}
public String getHeader(String name) {
return headers.get(name);
}
public Request setBody(Map<String, Object> body) {
this.body.putAll(body);
return this;
}
public Request setMethod(String name) {
this.method = name;
return this;
}
public Request setResource(String name) {
this.resource = name;
return this;
}
public Request setHeaders(Map<String, String> headers) {
this.headers.putAll(headers);
return this;
}
public Request setParameters(Map<String, String[]> parameters) {
this.parameters.putAll(parameters);
return this;
}
private static class BodyDeserializer extends JsonDeserializer<Map<String, Object>> {
@Override
public Map<String, Object> deserialize(JsonParser jp, DeserializationContext dc) throws IOException, JsonProcessingException {
JsonDeserializer<Object> deserializer = dc.findRootValueDeserializer(dc.constructType(Map.class));
Map<String, Object> map = (Map<String, Object>) deserializer.deserialize(jp, dc);
return map;
}
}
}
For the String-Problem, these sources might help:
Can not deserialize instance of java.lang.String out of START_OBJECT token
https://www.baeldung.com/jackson-map#1-mapltstring-stringgt-deserialization
Why this code can't work
Jackson is not much more powerful than you are.
If Jackson gets an object to serialize, it tries to serialize all of its values. And only its values (which is pretty good for the independence from classes). This is a json object:
{
"type":"apple",
"quantity":3,
"imageID":17
}
Now, what is the class of this object? It could be Fruit.class, Image.class or even RoundObject.class, json doesn't know and Jackson neither.
So how does json find out what the class is? By looking at the type of the object reference. In your case, it's Object. In Object.class, Jackson cannot find a constructor that requires the variables of the object that has been saved, so it crashes.
Solution
Trying to serialize objects is not a good idea. If you have very different classes you want to put in, e.g. Apple and Banana, make an interface or abstract class called Fruit that both of them implement. Now, use this annotation at the top of this class:
@JsonTypeInfo(
use = JsonTypeInfo.Id.NAME,
property = "type") // name of the variable to save the kind of object you put in. NO VARIABLES in all classes that extend from Fruit are allowed to have this name (or at least @JsonProperty).
@JsonSubTypes({
@JsonSubTypes.Type(value = Apple.class, name = "banana"),
@JsonSubTypes.Type(value = Banana.class, name = "apple"),
})
And using a Map<String, Fruit> should work.
You need to register a deserializer:
ObjectMapper mapper = new ObjectMapper();
SimpleModule module = new SimpleModule();
module.addDeserializer(Response.class, new ResponseDeserializer());
mapper.registerModule(module);
Response resp = mapper.readValue(myjson, Response.class);
Here is an example:
public class ResponseDeserializer extends StdDeserializer<Response> {
public ResponseDeserializer() {
this(null);
}
public ResponseDeserializer(Class<?> vc) {
super(vc);
}
@Override
public Response deserialize(JsonParser jp, DeserializationContext dc)
throws IOException, JsonProcessingException {
Response resp = new Response();
resp.parameters = new HashMap<>();
JsonNode node = jp.getCodec().readTree(jp);
ArrayNode parms = (ArrayNode)node.get("parameters");
for (JsonNode parm: parms) {
String key = parm.get("key").asText();
String value = parm.get("value").asText();
resp.parameters.put(key, value);
}
return resp;
}
}
Could you change your JSON to the following?
{
"parameters": {
"somekey": "somevalue",
"otherkey" : "othervalue"
}
}
If so, deserialization should work without any custom deserializer.
(Reference: https://blog.hackingcode.io/jackson-java-tutorial-deserialize-json-to-map/ )
First, addressing your problem. You are trying to de-serialize JSON to a class that doesn't have fields present in the JSON string, so it's failing to find those fields and returning the exception.
You could alternatively do the following:
ObjectMapper mapper = new ObjectMapper();
Map<String, Object> map = mapper.readValue(JSON_STRING, new TypeReference<Map<String, String>>(){});
box.setJsonMap(map);
where box would have a setter to set the json map
Or you could add all the fields to your Box POJO and use GSON to make this easier!
E.g. Json -> Object
Gson gson = new Gson();
Box box = gson.fromJson(JSON_STRING, Box.class);
E.g. Object -> Json
Gson gson = new Gson();
String str = gson.toJson(mBox); //mBox is some box object
GSON would populate all the fields within box to match the key, value pairs found in the JSON string. Ignore this if you don't want to do that but it is another alternative!
I think you need to add an annotated argument to your constructor. For example,
public Box(@JsonProperty("json") Map<String, Object> json) {
// TODO
}