I have json that contains multiple rows.
{
"rows": [{
"aId": 408,
"aFrstNm": "TIM",
},
{
"aId": 410,
"aFrstNm": "BOB",
},
{
"aId": 409,
"aFrstNm": "HENRY",
}]
}
and POJOs
public class User extends Employee {
#JsonProperty("tableName")
private Double aID;
#JsonProperty("aFrstNm")
private String aFrstNm;
getters and setters ommitted
}
public class Employee {
}
public class Employees {
private Collection<? extends Employee> rows;
getters and setters ommitted
}
What I am trying to do is to pass the User class to a custom deserializer to be able return employees as a collection of Users that extend Employee:
SimpleModule simpleModule = new SimpleModule("MyModule")
.addDeserializer(Employees.class, new JsonDeserializer<Employees>() {
#Override
public Employees deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
ObjectMapper mapper = (ObjectMapper) p.getCodec();
final Class<?> clazz = (Class<?>) ctxt.getAttribute("mappingClass");
final JsonNode jsonNode = (JsonNode) mapper.readTree(p).get("rows");
Employees employees = new Employees();
Collection<Product> rows = new ArrayList();
if (jsonNode.isArray()) for (final JsonNode objNode : jsonNode) {
boolean isValueNode = objNode.isValueNode();
String json = mapper.writeValueAsString(objNode);
Product product = (Product) new ObjectMapper().readValue(json, clazz);
rows.add(product);
System.out.println(jsonNode);
}
employees.setRows(rows);
return employees;
}
});
mapper.registerModule(simpleModule);
the variable clazz (final Class clazz) attribute should contain the class a parameter. I so far have been unable to find a mechanism that allows this. DeserializationContext seems to be promising but I have not been able to make this work. Any suggestions on using DeserializationContext or some other way to pass parameters?
Tnx.
Related
I am trying to make jackson deserialize missing properties that represent collections into empty collections instead of NULL. I've tried a few different things and this is the latest that isn't working.
If there is a way to make any potential solution globally configured for all POJO deserialization that would be great.
Given the following json, which is missing the property 'assets':
{
"name":"my-layer",
"code": "ly1",
"types":["type1", "type2"],
"private": false
}
Given the following POJO:
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonSetter;
import java.util.HashSet;
import java.util.Set;
import java.util.UUID;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Builder.Default;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
#Data
#Builder
#AllArgsConstructor
#NoArgsConstructor
#EqualsAndHashCode
public class CreateLayerRequest {
#NotBlank
#JsonProperty(required = true)
private String name;
#NotBlank
#JsonProperty(required = true)
private String code;
#Default
#NotNull
#JsonProperty(value = "private", required = true)
private Boolean privateLayer = Boolean.FALSE;
#Default
#JsonSetter(nulls = Nulls.AS_EMPTY)
#JsonProperty("types")
private Set<UUID> types = new HashSet<>();
#Default
#JsonSetter(nulls = Nulls.AS_EMPTY)
#JsonProperty("assets")
private Set<UUID> assets = new HashSet<>();
}
configured my fix to be global
#Configuration
public class MyAppConfiguration implements WebMvcConfigurer {
/**
* allows POJOs using json filtering to serialize all properties when not being filtered
*/
#Override
public void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
for (HttpMessageConverter<?> converter : converters) {
if (converter instanceof MappingJackson2HttpMessageConverter) {
((MappingJackson2HttpMessageConverter) converter)
.getObjectMapper()
// not related to this thread, this is for property filtering when serializing POJOs
.setFilterProvider(new SimpleFilterProvider().setFailOnUnknownId(false))
// these modules are related to to this thread
.registerModule(
new SimpleModule().addDeserializer(Set.class, new CollectionDeserializer())
)
.registerModule(
new SimpleModule().addDeserializer(List.class, new CollectionDeserializer())
)
.registerModule(
new SimpleModule().addDeserializer(Map.class, new CustomMapDeserializer())
);
}
}
}
#Slf4j
#NoArgsConstructor
#AllArgsConstructor
private static class CollectionDeserializer<T>
extends JsonDeserializer<Collection<T>>
implements ContextualDeserializer {
private JavaType type;
private final ObjectMapper mapper = new ObjectMapper();
#Override
public JsonDeserializer<?> createContextual(
DeserializationContext deserializationContext,
BeanProperty beanProperty) {
final JavaType javaType = deserializationContext.getContextualType() != null
? deserializationContext.getContextualType()
: beanProperty.getMember().getType();
return new CollectionDeserializer<>(javaType);
}
#Override
public Collection<T> deserialize(
JsonParser jsonParser,
DeserializationContext deserializationContext) throws IOException {
try {
if (Set.class.isAssignableFrom(type.getRawClass())) {
return deserializeJsonToCollection(jsonParser, new HashSet<>());
}
if (List.class.isAssignableFrom(type.getRawClass())) {
return deserializeJsonToCollection(jsonParser, new ArrayList<>());
}
} catch (IllegalArgumentException e) {
log.warn("unable to deserialize array property in request");
throw e;
}
final String message = "unable to deserialize collection";
log.warn(message);
throw new CollectionDeserializationException(message);
}
private Collection<T> deserializeJsonToCollection(
JsonParser jsonParser,
Collection<T> collection) throws IOException {
((ArrayNode) jsonParser
.getCodec()
.readTree(jsonParser))
.forEach(item -> {
try {
final T value = mapper.readValue(
String.format("\"%s\"", item.textValue()),
type.getContentType()
);
collection.add(value);
} catch (IOException e) {
final String message = String.format(
"unable to deserialize value [%s] to type [%]",
item.textValue(),
type.getContentType()
);
log.warn(message);
throw new CollectionDeserializationException(message);
}
});
return collection;
}
#Override
public Collection<T> getNullValue(DeserializationContext ctxt) {
if (Set.class.isAssignableFrom(type.getRawClass())) {
return new HashSet<>();
}
if (List.class.isAssignableFrom(type.getRawClass())) {
return new ArrayList<>();
}
return null;
}
}
#Slf4j
private static class CustomMapDeserializer<T, K> extends JsonDeserializer<Map<T, K>> {
private final ObjectMapper mapper = new ObjectMapper();
private final TypeReference<Map<T, K>> mapTypeReference = new TypeReference<>() {
};
#Override
public Map<T, K> deserialize(
JsonParser jsonParser,
DeserializationContext deserializationContext) {
try {
return mapper.readValue(jsonParser, mapTypeReference);
} catch (Exception e) {
final String message = "unable to deserialize map";
log.warn(message);
throw new MapDeserializationException(message, e);
}
}
#Override
public Map<T, K> getNullValue(DeserializationContext ctxt) {
return new HashMap<>();
}
}
I have JSON structured like:
{
"id" : "123",
"name" : [ {
"stuff" : [ {
"id" : "234",
"name" : "Bob"
}, {
"id" : "345",
"name" : "Sally"
} ]
} ]
}
That I want to map to the following data structure:
MyInterface1
#Value.Immutable
#JsonSerialize(as = ImmutableMyInterface1.class)
#JsonDeserialize(as = ImmutableMyInterface1.class)
public interface MyInterface1 {
String id();
#JsonDeserialize(using = MyInterface1Deserializer.class)
List<MyInterface2> name();
}
MyInterface2
#Value.Immutable
#JsonSerialize(as = ImmutableMyInterface2.class)
#JsonDeserialize(as = ImmutableMyInterface2.class)
public interface MyInterface2 {
#JsonDeserialize(using = StuffDeserializer.class)
Map<String, MyInterface3> stuff();
}
MyInterface3
#Value.Immutable
#JsonSerialize(as = ImmutableMyInterface3.class)
#JsonDeserialize(as = ImmutableMyInterface3.class)
public interface MyInterface3 {
String id();
String name();
}
I'm using an ObjectMapper with readValue(stringWithJson,MyInterface1.class) to map this JSON to MyInterface1, which should continue down the chain to MyInterface3. This setup was working when I was using a List in MyInterface2, i.e. List<MyInterface3> name();
However, I want this to be a map instead of a list, ideally with "id" from the inner JSON as the key. This would allow me to get values with the following syntax:
MyInterface1.get(0).MyInterface2.get("id1").name();
The problem is that when attempting to create a custom StuffDeserializer.class, I'm getting the error:
Can not deserialize instance of com.foo.ImmutableMyInterface2$Json out of START_ARRAY token
when trying to do:
public Map<String, MyInterface3> deserialize(JsonParser jsonParser, DeserializationContext ctxt)
throws IOException {
MyInterface2 foo = Unmarshaller.OBJECT_MAPPER.readValue(jsonParser, MyInterface2.class); // error here
...
I think this is because Jackson is expecting "stuff" to be a List 'cause of the JSON array. What's the best way to deserialize this JSON to a map that uses values from the inner JSON as a key?
I would create a custom JsonDeserializer to map id and name into a map:
public class StringHashMapValueDeserializer extends JsonDeserializer<HashMap<String, String>>{
#Override
public HashMap<String, String> deserialize(JsonParser parser, DeserializationContext ctxt)
throws IOException, JsonProcessingException {
HashMap<String, String> ret = new HashMap<String, String>();
ObjectCodec codec = parser.getCodec();
TreeNode node = codec.readTree(parser);
if (node.isArray()){
for (JsonNode n : (ArrayNode)node){
JsonNode id = n.get("id");
if (id != null){
JsonNode name = n.get("name");
ret.put(id.asText(), name.asText());
}
}
}
return ret;
}
}
And then I would create simple beans with annotating stuff property with the deserializer:
#Getter
#Setter
public class Name {
#JsonDeserialize(using = StringHashMapValueDeserializer.class)
Map<String, String> stuff;
#Override
public String toString() {
return "Name [stuff=" + stuff + "]";
}
}
Outer type:
#Getter
#Setter
public class OuterType {
String id;
List<Name> name;
#Override
public String toString() {
return "OuterType [id=" + id + ", stuff=" + name + "]";
}
}
Deserialization:
ObjectMapper mapper = new ObjectMapper();
OuterType response;
response = mapper.readValue(json, OuterType.class);
System.out.println(response);
System.out.println(response.getName().get(0).getStuff().get("234"));
console output:
OuterType [id=123, stuff=[Name [stuff={234=Bob, 345=Sally}]]]
Bob
Hope it helps.
I have the following Address and AddressList classes
public class Address {
private String street;
private String city;
private String state;
// ...
}
public class AddressList {
private List<Address> addresses;
// ...
}
and then a Person class
public class Person {
private String name;
#JsonDeserialize(contentUsing = ListDeserializer.class)
private Map<String, AddressList> addresses;
// ..
}
Then I have the Yaml file like this
---
name: 'abc'
addresses:
offices:
- street: 123 main st
city: san francisco
state: ca
- street: 234 post st
city: san francisco
state: ca
My List deserializer class is as follows:
public class ListDeserializer extends JsonDeserializer<AddressList> {
#Override
public AddressList deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException, JsonProcessingException {
return jsonParser.readValueAs(new TypeReference<Map<String, List<Address>>>() {
});
}
}
my parsing code is as follows:
ObjectMapper objectMapper = new ObjectMapper(new YAMLFactory());
return objectMapper.readValue(inputYamlFile, Person.class);
when I read the list of addresses: it prints as
{offices=null}
Can anyone help with resolving this?
If it was serialized as a list you have to deserialize it the same way. But you then have the list and and can iterate it yourself.
I found a way to resolve this issue using deserializing addresses map using Converter.
The modifications I made are
public class Person {
private String name;
#JsonDeserialize(converter = AddressListConverter.class)
private Map<String, AddressList> addresses;
// ..
}
I, then, wrote a new converter class for the AddressList.
public class AddressListConverter implements Converter<Map<String, List<LinkedHashMap>>, Map<String, AddressList>> {
#Override
public Map<String, AddressList> convert(Map<String, List<LinkedHashMap>> stringListMap) {
Map<String, AddressList> addressListMap = new HashMap<>();
ObjectMapper mapper = new ObjectMapper();
for (Map.Entry<String, List<LinkedHashMap>> entry : stringListMap.entrySet()) {
AddressList addressList = new AddressList();
for(LinkedHashMap map: entry.getValue()) {
Address address = mapper.convert(map, Address.class);
addressList.getAddresses().add(address);
}
addressListMap.put(entry.getKey(), addressList);
}
return addressListMap;
}
#Override
public JavaType getInputType(TypeFactory typeFactory) {
return typeFactory.constructMapType(Map.class, String.class, List.class);
}
#Override
public JavaType getOutputType(TypeFactory typeFactory) {
return typeFactory.constructMapType(Map.class, String.class, AddressList.class);
}
}
This should do the trick
I'm using only jersey and not jackson to create a REST api. I've two model objects,
public class Course {
private int id;
private String name;
private Teacher teacher;
}
public class Teacher {
private int id;
private String givenName;
private String familyName;
}
I'm creating a service and returning a List of Course objects,
public List<Course> getAll(){
return db.getCourseList();
}
The display is as expected,
[{"id":101,"name":"Introduction to Java","teacher":{"familyName":"Bar","givenName":"Foo","id":201}},{"id":102,"name":"Intermediate Java","teacher":{"familyName":"Prank","givenName":"Foo","id":202}}]
Now I want to customize my JSON object to display in the following format, with only the teacher ID.
[{"id":"100","name":"Introduction to Java","teacherId":"201"},{"id":"101","name":"Intermediate Java","teacherId":"201"}
So this is the view model that I designed.
#XmlRootElement
public class CourseTeacherIdView {
private int id;
private String name;
private int teacherId;
CourseTeacherIdView(){
}
public CourseTeacherIdView(int id, String name, int teacherId){
this.id = id;
this.name = name;
this.teacherId = teacherId;
}
}
And I use this method to return the List of view objects.
public List<CourseTeacherIdView> getAll(){
List<Course> list = db.getCourseList();
List<CourseTeacherIdView> viewList = new ArrayList<>();
for(Iterator<Course> itr = list.iterator(); itr.hasNext();){
Course c = (Course) itr.next();
viewList.add(new CourseTeacherIdView(c.getId(), c.getName(), c.getTeacher().getId()));
}
return viewList;
}
This is the result that I get.
[{},{},{}]
What am I doing wrong.
You can achieve that with Jackson and creating a custom serializer like the following:
public class CourseSerializer extends JsonSerializer<Course> {
#Override
public void serialize(Course value,
JsonGenerator gen,
SerializerProvider serializers) throws IOException {
gen.writeStartObject();
Field[] fields = value.getClass().getDeclaredFields();
for (Field field : fields) {
field.setAccessible(true);
try {
Object obj = field.get(value);
if (obj instanceof Teacher) {
Teacher teacher = (Teacher) obj;
gen.writeStringField("teacherId", String.valueOf(teacher.getId()));
} else {
gen.writeStringField(field.getName(), obj.toString());
}
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
gen.writeEndObject();
}
}
Test case:
public static void main(String[] args) throws Exception {
ObjectMapper mapper = new ObjectMapper();
SimpleModule module = new SimpleModule();
module.addSerializer(Course.class, new CourseSerializer());
mapper.registerModule(module);
Teacher teacher1 = new Teacher(123, "teacher1", "surename1");
Teacher teacher2 = new Teacher(234, "teacher2", "surename2");
Course course1 = new Course(1, "course1", teacher1);
Course course2 = new Course(2, "course2", teacher2);
List<Course> courses = Arrays.asList(new Course[]{course1, course2});
String serialized = mapper.writeValueAsString(courses);
}
Output:
[{
"id": "1",
"name": "course1",
"teacherId": "123"
}, {
"id": "2",
"name": "course2",
"teacherId": "234"
}]
If I understood you correctly you could either create new view model representation with only the id and map each object in the list to it, or use #jsonignore on not relevant fields (if jersey is using Jackson). Or even retrieve only the ids from the db. Depends on use case.
I have an array of objects of following class
public class Person
{
private Long id;
private String name;
//Rest of the getters and setters
}
public class Data
{
private ArrayList<Person> persons;
public Data()
{
persons = new ArrayList<Person>();
Person p1 = new Person(1L, "walter");
Person p2 = new Person(2L, "white");
persons.add(p1);
persons.add(p2);
}
}
Now if I create the object Data in my program and serialize this Data object using jackson, it will give me the following JSON.
{
"data": {
"persons": [
{
"id": 1,
"name": "walter"
},
{
"id": 2,
"name": "white"
}
]
}
}
is there any way to serialize this object into following numbered/indexed JSON?
{
"data": {
"persons": {
"1": {
"id": 1,
"name": "walter"
},
"2": {
"id": 2,
"name": "white"
}
}
}
}
I don't think that is something you can just tell Jackson to do.
The easiest solution I can think of is to turn the array you get into a HashMap<Integer, Person> and pass that into jackson.
If order matters (you want "1" to be before "2") then you can use a LinkedHashMap<Integer, Person>
Something like this:
HashMap<Integer, Person> pMap = new HashMap<>();
for(int i = 0; i < persons.size(); i++){
pMap.put(i, persons.get(i));
}
Edit:
Turns out you can do it, you just have to write the serializer yourself1, so you would do something like:
public class PersonSerializer extends JsonSerializer<Person> {
#Override
public void serialize(Person person, JsonGenerator jgen, SerializerProvider provider)
throws IOException, JsonProcessingException {
jgen.writeStartObject(String.valueOf(i));
jgen.writeNumberField("id", i);
jgen.writeStringField("name", person.getName());
jgen.writeEndObject();
}
}
You can tell jackson to use this serializer by simply adding an antotation to the Person class
#JsonSerialize(using = PersonSerializer.class)
public class Person
{
private Long id;
private String name;
//Rest of the getters and setters
}