This is probably one of those questions where the title says it all.
I am quite fascinated by the ObjectMapper's readValue(file, class) method, found within the Jackson library which reads a JSON string from a file and assigns it to an object.
I'm curious if this is possible to do by simply getting JSON from a string and applying it to an object.
Some sort of alternative readValue() method, which takes a String, instead of a file, and assigns it to an object?
For instance, while the default readValue(file, class) method looks like this:
ObjectMapper mapper = new ObjectMapper();
Student student = mapper.readValue("C:\\student.json", Student.class);
I was wondering if there was some method in Jackson, which allowed the following:
ObjectMapper mapper = new ObjectMapper();
Student student = mapper.readValue("{\"id\":100,\"firstName\":\"Adam\"}", Student.class);
The second example takes a string and an object of a class while the first one takes a file and an object of a class.
I just want to cut out the middle man, in this case, the file.
Is this doable or does no such method exist within the constraints of Jackson?
Try this,
You can't create a new string like your doing.
String string = "{\"id\":100,\"firstName\":\"Adam\"}";
Student student = mapper.readValue(string, Student.class);
And instead of handling errors in every mapper.readValue(json,Class) you can write a helper class which has another Generic method.
and use
String jsonString = "{\"id\":100,\"firstName\":\"Adam\"}";
Student student = JSONUtils.convertToObject(jsonString,Student.class);
I'm returning nulland fancying printing trace & checking null later on. You can handle error cases on your own way.
public class JSONUtils {
public static String convertToJSON(Object object)
{
ObjectWriter ow = new ObjectMapper().writer().withDefaultPrettyPrinter();
String json;
try {
json = ow.writeValueAsString(object);
} catch (JsonProcessingException e) {
e.printStackTrace();
return convertToJSON(e);
}
return json;
}
public static <T> T convertToObject(Class<T> clazz,String jsonString)
{
try {
ObjectMapper mapper = new ObjectMapper();
return (T)mapper.readValue(jsonString, clazz);
}catch(Exception e)
{
e.printStackTrace();
return null;
}
}
}
Related
I have a generic class like this:
public class Pojo<T> {
#JsonProperty("value")
public T[] values;
};
The T can either hold a String, a LocalDateTime or an Integer. The differentiation between String and Integer seems to work fine, most likely because those types are represented differently in the serialized JSON file. However, when I have a datetime in my JSON object (see example), it is parsed as a string anyway.
The actual type in use for the value field is determined by the input. While I do have that knowledge in the following minimal example, this is not true for all uses of this code in my program. I can only rely on the pattern of the value of field value.
public class Main {
ObjectMapper mapper = new ObjectMapper();
mapper.registerModule(new JavaTimeModule());
mapper.configure(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY, true);
String json = "{\"value\": \"2022-02-22T12:00:00\"}";
Pojo<?> deserialized = mapper.readValue(json, Pojo.class);
assert deserialized.value[0] instanceof LocalDateTime;
}
I haven't had any success tinkering with JsonTypeInfo yet. Is there a way to parse the value field as an array of LocalDateTime objects if all values for this field match the pattern yyyy-MM-dd'T'hh:mm:ss?
Here is your problem - Pojo<?> deserialized = mapper.readValue(json, Pojo.class).
ObjectMapper does not know what type to parse T to. To tell it the type you need to use either readValue overload with TypeReference, or the overload with JavaType. I find JavaType easier to read, so here is an example with it:
public class Test {
public static void main(String[] args) throws Exception {
ObjectMapper mapper = new ObjectMapper();
mapper.registerModule(new JavaTimeModule());
mapper.configure(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY, true);
String json = "{\"value\": \"2022-02-22T12:00:00\"}";
JavaType javaType = TypeFactory.defaultInstance().constructParametricType(Pojo.class, LocalDateTime.class);
Pojo<LocalDateTime> deserialized = mapper.readValue(json, javaType);
System.out.println(deserialized.values[0].toLocalDate());
System.out.println(deserialized.values[0].toLocalTime());
}
}
When constructing the parametric JavaType the first argument is the class itself, next are concrete generic types.
Edit: Having in mind the new info, the best i can come up with is custom deserializer, which resolves the type at runtime. Something like this:
public class UnknownTypeDeserializer<T> extends StdDeserializer<Pojo<T>> {
private final DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss");
public UnknownTypeDeserializer() {
super((Class<?>) null);
}
#Override
public Pojo<T> deserialize(JsonParser parser, DeserializationContext ctxt) throws IOException, JacksonException {
JsonNode node = parser.getCodec().readTree(parser);
String value = node.get("value").asText();
Pojo<T> pojo = new Pojo<>();
T[] arr;
try {
arr = (T[]) new LocalDateTime[]{LocalDateTime.parse(value, this.formatter)};
} catch (Exception exc) {
try {
arr = (T[]) new Integer[]{Integer.parseInt(value)};
} catch (NumberFormatException numberFormatException) {
arr = (T[]) new String[]{value};
}
}
pojo.values = arr;
return pojo;
}
}
The idea is try to parse to LocalDateTime, if not possible, try to parse to int, if not possible again leave as String. That's quite the ugly way to do it, but i am trying just to illustrate the idea. Instead of catching exceptions, it might be better to examine the format, using regex for example, and according to which regex matches, parse to correct type. I tested it with string and int, and it actually works.
public class Test {
public static void main(String[] args) throws Exception {
ObjectMapper mapper = new ObjectMapper();
mapper.registerModule(new JavaTimeModule());
SimpleModule simpleModule = new SimpleModule();
simpleModule.addDeserializer(Pojo.class, new UnknownTypeDeserializer<>());
mapper.registerModule(simpleModule);
String json1 = "{\"value\": \"2022-02-22T12:00:00\"}";
Pojo<?> deserialized1 = mapper.readValue(json1, Pojo.class);
System.out.println("is date - " + (deserialized1.values[0] instanceof LocalDateTime));
String json2 = "{\"value\": \"bla bla bla\"}";
Pojo<?> deserialized2 = mapper.readValue(json2, Pojo.class);
System.out.println("is string - " + (deserialized2.values[0] instanceof String));
String json3 = "{\"value\": 41}";
Pojo<?> deserialized3 = mapper.readValue(json3, Pojo.class);
System.out.println("is int - " + (deserialized3.values[0] instanceof Integer));
}
}
I have a class (Jackson annotations/getters/setters/etc are omitted):
public class Sample {
public String name;
public Integer value;
}
I have an instance, e.g.:
Sample sample = new Sample("one", null),
and i have a json string:
{"name" = "two", "value" = 3}
And i update the object with json:
ObjectMapper mapper = new ObjectMapper();
mapper.readerForUpdating(sample).readValue(json);
After updating my object looks like this:
[Sample: name = "two", value = 3]
But i need do not overwrite not null fields, as the name is, so my object after updating would looks like this:
[Sample: name = "one", value = 3]
Unfortunally, i can't edit my class and Jackson annotations, so i need to change somehow a config of my mapper. Is threre a way to do it?
The idea behind the readerForUpdating method is not to create a new instance of the object,just to replace the values of the passed object into the object for update.
I had the same problem ,wanted to replace ONLY the values that are not null,but to do that I needed to isolate the ObjectMapper and configure it to not transfer null values ,which combined with the readerForUpdating method does what we want:
public static void updateModels(Object original,Object data) {
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES,false);
try {
objectMapper.readerForUpdating(original).readValue(objectMapper.writeValueAsBytes(data));
} catch (IOException e) {
e.printStackTrace();
}
}
I have some nested classes in Java, simplified here. Getters and setters exist.
Example
public class Planet {
#JsonProperty("name")
private String name;
#JsonProperty("moons")
private List<Moon> moons;
}
public class Moon {
#JsonProperty("moonname")
private String name;
#JsonProperty("craters")
private int craters;
}
I want to be able to deserialize the records on mongo (following this same structure) to java objects on the rest controller, specifically the HTTP GET request.
#RestController
#RequestMapping("/planets")
public class PlanetController {
#Autowired
private PlanetService planetService;
#RequestMapping("/")
public List<Planet> getAllPlanets() {
//Need to deserialize here
return planetService.getAll();
}
#RequestMapping("/{name}")
public Planet getItemsWithName(#PathVariable("name") String name) {
//deserialize here
return planetService.getEntryWithName(name.toLowerCase());
}
PlanetService.getAll() is expecting return type of List. getEntryWithName() is expecting return type of Planet.
How can I loop the results in the getAll() so I can deserialize them before they are returned?
Using Jackson's object mapper, I can do the serialization of a Java object to a JSON object.
ObjectMapper mapper = new ObjectMapper();
try {
mapper.writeValue(new File("target/mars.json"), mars);
} catch (IOException e) {
e.printStackTrace();
}
I can probably use readValue for the opposite process but I don't know how to loop the results.
I will appreciate the help. Let me know if something is not clear.
public List<Planet> getAllPlanets() {
List<Planet> planets = planetService.getAll();
String jsonString = new ObjectMapper().writeValueAsString(planets);
return planets;
}
I have this method:
private String serializeToJson(T item) {
String json;
ObjectWriter ow = new ObjectMapper().writer().withDefaultPrettyPrinter();
try {
json = ow.writeValueAsString(item);
} catch (IOException e) {
e.printStackTrace();
json = "";
}
return json;
}
with this item:
and yet json equals:
{
"saveDate" : "12:29:29 29-Mar-02015"
}
why is failureDict not serialize?
and this is the item:
public class FailedResponses {
HashMap<String, Set<String>> failuresDict;
public String saveDate;
public FailedResponses() {
failuresDict = new HashMap<>();
}
Jackson will work magic on public fields or public getters and setters. I'd recommend that you make the fields of your object private for better encapsulation, and add the public getters/setters to allow jackson to de/serialize it.
Personally I like to use the jackson annotations to make it explicit what the object is being used for, and so that you have full control over the naming of the fields that jackson creates, without having to create non-idiomatic getter/setter or variable names
I have a class like this:
public class DeserializedHeader
int typeToClassId;
Object obj
I know what type of object obj is based on the typeToClassId, which is unfortunately only known at runtime.
I want to parse obj out based on typeToClassId - what's the best approach here? Annotations seem like they're out, and something based on ObjectMapper seems right, but I'm having trouble figuring out what the best approach is likely to be.
Something along the lines of
Class clazz = lookUpClassBasedOnId(typeToClassId)
objectMapper.readValue(obj, clazz)
Obviously, this doesn't work since obj is already deserialized... but could I do this in 2 steps somehow, perhaps with convertValue?
This is really complex and painful problem. I do not know any sophisticated and elegant solution, but I can share with you my idea which I developed. I have created example program which help me to show you how you can solve your problem. At the beginning I have created two simple POJO classes:
class Product {
private String name;
// getters/setters/toString
}
and
class Entity {
private long id;
// getters/setters/toString
}
Example input JSON for those classes could look like this. For Product class:
{
"typeToClassId" : 33,
"obj" : {
"name" : "Computer"
}
}
and for Entity class:
{
"typeToClassId" : 45,
"obj" : {
"id" : 10
}
}
The main functionality which we want to use is "partial serializing/deserializing". To do this we will enable FAIL_ON_UNKNOWN_PROPERTIES feature on ObjectMapper. Now we have to create two classes which define typeToClassId and obj properties.
class HeaderType {
private int typeToClassId;
public int getTypeToClassId() {
return typeToClassId;
}
public void setTypeToClassId(int typeToClassId) {
this.typeToClassId = typeToClassId;
}
#Override
public String toString() {
return "HeaderType [typeToClassId=" + typeToClassId + "]";
}
}
class HeaderObject<T> {
private T obj;
public T getObj() {
return obj;
}
public void setObj(T obj) {
this.obj = obj;
}
#Override
public String toString() {
return "HeaderObject [obj=" + obj + "]";
}
}
And, finally source code which can parse JSON:
// Simple binding
Map<Integer, Class<?>> classResolverMap = new HashMap<Integer, Class<?>>();
classResolverMap.put(33, Product.class);
classResolverMap.put(45, Entity.class);
ObjectMapper mapper = new ObjectMapper();
mapper.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
String json = "{...}";
// Parse type
HeaderType headerType = mapper.readValue(json, HeaderType.class);
// Retrieve class by integer value
Class<?> clazz = classResolverMap.get(headerType.getTypeToClassId());
// Create dynamic type
JavaType type = mapper.getTypeFactory().constructParametricType(HeaderObject.class, clazz);
// Parse object
HeaderObject<?> headerObject = (HeaderObject<?>) mapper.readValue(json, type);
// Get the object
Object result = headerObject.getObj();
System.out.println(result);
Helpful links:
How To Convert Java Map To / From JSON (Jackson).
java jackson parse object containing a generic type object.