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
}
Related
I got the following JSON that im trying to deserialize:
{
"items": [
{
"id": 29000012,
"name": "Crystal League I",
"iconUrls": {
"small": "https://api-assets.clashofclans.com/leagues/72/kSfTyNNVSvogX3dMvpFUTt72VW74w6vEsEFuuOV4osQ.png",
"tiny": "https://api-assets.clashofclans.com/leagues/36/kSfTyNNVSvogX3dMvpFUTt72VW74w6vEsEFuuOV4osQ.png",
"medium": "https://api-assets.clashofclans.com/leagues/288/kSfTyNNVSvogX3dMvpFUTt72VW74w6vEsEFuuOV4osQ.png"
}
},
{
"id": 29000015,
"name": "Master League I",
"iconUrls": {
"small": "https://api-assets.clashofclans.com/leagues/72/olUfFb1wscIH8hqECAdWbdB6jPm9R8zzEyHIzyBgRXc.png",
"tiny": "https://api-assets.clashofclans.com/leagues/36/olUfFb1wscIH8hqECAdWbdB6jPm9R8zzEyHIzyBgRXc.png",
"medium": "https://api-assets.clashofclans.com/leagues/288/olUfFb1wscIH8hqECAdWbdB6jPm9R8zzEyHIzyBgRXc.png"
}
}
],
"paging": {
"cursors": {}
}}
Im trying to deserialize it with the following DTO:
#JsonRootName("items")
#JsonIgnoreProperties(value={ "paging" })
public class League {
private Long id;
private String name;
private IconUrls iconUrls;
public League() {
}
}
class IconUrls {
private String small;
private String tiny;
private String medium;
public IconUrls() {
}
}
But im getting the following error:
com.fasterxml.jackson.databind.exc.MismatchedInputException: Root name ('items') does not match expected ('List') for type `java.util.List<gg.stats.wrapper.entities.League>
I have also set: DeserializationFeature.UNWRAP_ROOT_VALUE
This is the call of the method from my Client:
List<League> getLeagueList();
The problem might be the "paging" key.
Any workaround for that?
I actually found a solution by myself:
#JsonIgnoreProperties(value={ "paging" }, allowGetters=true)
public class ResponseWrapper<T> {
private List<T> items;
#JsonProperty("items")
public List<T> getResponseContent() {
return this.items;
}
#JsonProperty("items")
public void setResponseContent(List<T> items) {
this.items = items;
}
}
I have an API built in Java Spring that return (using JacksonJaxbJsonProvider 2.5.5) a JSON object from this class:
public class FieldValues {
private String code;
private Object value;
public String getCode() {
return code;
}
public void setCode(String code) {
this.code = code;
}
public Object getValue() {
return value;
}
public void setValue(Object value) {
this.value = value;
}
}
In the main object I've
#JsonRootName(value = "WorkRequest")
#XmlRootElement(name = "WorkRequest")
#JsonIgnoreProperties(ignoreUnknown = true)
public class WorkRequestDTOResponse {
private List<FieldValues> fieldValues;
public List<FieldValues> getFieldValues() {
return fieldValues;
}
public void setFieldValues(List<FieldValues> fieldValues) {
this.fieldValues = fieldValues;
}
}
But the output of the fieldValues object is this:
"fieldValues": [
{
"code": "anomaly",
"value": {
"#xsi.type": "ns3:boolean",
"$": "true"
}
},{
"code": "internal_note",
"value": {
"#xsi.type": "ns3:string",
"$": "Test text example"
}
}
]
instead what I need is this:
"fieldValues": [
{
"code": "anomaly",
"value": true
},{
"code": "internal_note",
"value": "Test text example"
}
]
This is my JSON Provider:
public class ErmesJSONProvider extends JacksonJaxbJsonProvider {
public ErmesJSONProvider() {
ObjectMapper mapper = new ObjectMapper();
mapper.configure(DeserializationFeature.UNWRAP_ROOT_VALUE, true);
mapper.configure(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY, true);
mapper.configure(SerializationFeature.WRAP_ROOT_VALUE, true);
mapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, true);
mapper.configure(SerializationFeature.WRITE_SINGLE_ELEM_ARRAYS_UNWRAPPED, false);
_mapperConfig.setMapper(mapper);
_mapperConfig.getConfiguredMapper().setAnnotationIntrospector(new JacksonAnnotationIntrospector());
}
}
Trying to use a String instead an object:
public class FieldValues {
private String code;
private String value;
But if I set this value as String fieldValues.setValue("true"), the JSON output is "value": true instead "value": "true"
Likewise if I set this value as String but with an Integer fieldValues.setValue("1"), the JSON output is "value": 1 instead "value": "1"
If I print the return object using ObjectMapper I've the right JSON:
String payload = new ObjectMapper().writeValueAsString(requestResult)
but if I return a Response like this:
return Response.status(Response.Status.CREATED).entity(new GenericEntity<RequestResult>(requestResult){}).build()
it return the wrong JSON.
I can't understand why 😥
Someone can help me? Thanks.
So this is the Json I am trying to convert to a Java bean
I am using jackson to bind JSON to my data objects
{
"legend": {
"id": "379ec5d8c",
"name": "Stabil Koos",
"payers": [{
"id": "ab1651df-b835-495558-a2a5-2e6d42f7a41e",
"ranking": 1,
"buyer": {
"id": "67a6359838-0fda-43a6-9e2b-51a7d399b6a1",
"nationality": "en",
"stats": {
"gameeCount": 16581,
"count": 99098
}
}
},
{
"id": "379ecw555d8c",
"ranking": 2,
"buyer": {
"id": "2b451d0eda-ab0c-4345660-be3f-6ba3bebf1f81",
"nationality": "no",
"stats": {
"gamerCount": 1182,
"count": 7113
}
}
}
]
}
}
My beans look like this ;
public class League implements Serializable {
private String id;
private String name;
#JsonUnwrapped
private List<Payer> payers;
// getters and setters
Payers bean :
public class Payers implements Serializable {
private String id;
private long ranking;
private Buyer buyer;
// getters and setters
I am using Rest Template and postForObject in Junit
#Before
public void beforeTest() {
restTemplate = new RestTemplate();
headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
entity = new HttpEntity(REQUEST, headers);
}
And my final code to retrieve the object is :
#Test
public void retrieveData() {
League league = restTemplate.postForObject(ENDPOINT_URL, entity, League.class);
System.out.println(legend);
}
The JSON you show is for an object that has a league property which is a League object and not a league object itself. You need an additional response class:
class LeagueResponse {
private League league;
League getLeague() { return league; }
}
and:
LeagueResponse leagueResponse = restTemplate.postForObject(ENDPOINT_URL, entity, LeagueResponse.class);
League league = leagueResponse.getLeague();
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've a #OneToMany relation from School to teachers.
If I load the teachers on frontend I get a list with teachers; but some of them are instead of objects ids....
#OneToMany
#JsonManagedReference(value = "teachers")
#JsonIgnore // only admins
private Set<Teacher> teachers = new HashSet<>();
It's #JsonIgnore, because I made a extra call in my controller to prevent to long loading times.
Next I don't delete the entries in the db - I just disable them; here is the getter with the filter:
#JsonIgnore // only admin
public Set<Teacher> getTeachers() {
return teachers.stream().filter(DbModel::isEnabled).collect(Collectors.toSet());
}
Finally the access in the controller:
#RequestMapping(value = "/{schoolId}/teacher", method = RequestMethod.GET)
public Set<Teacher> readTeacher(#PathVariable long schoolId, #RequestParam("teacher") long adminId) {
School school = schoolRepository.findOne(schoolId);
Teacher admin = teacherRepository.findOne(adminId);
if (school == null) {
throw new NotFoundException(School.class, schoolId);
}
if (admin == null) {
throw new NotFoundException(Teacher.class, adminId);
}
if (!admin.isAdmin(school)) {
throw new NoAccessRightException();
}
return school.getTeachers();
}
How to get always only objects?
Update: Example output:
[
{
"#id": 1,
"id": 1,
// contend
},
{
"#id": 5,
"id": 2,
// contend
},
2,
{
"#id": 6,
"id": 8,
// contend
},
4,
3
]
another question: can I make a projection on only one call?
To ignore try this.
#JsonAutoDetect(getterVisibility = JsonAutoDetect.Visibility.NONE, fieldVisibility = JsonAutoDetect.Visibility.ANY)
public class MyClass {
#JsonIgnore // only admins
private Set<Teacher> teachers = new HashSet<>();
}
So Jackson will include all properties, except those marked with #JsonIgnore.
To add only the ID of the children, you need a custom serializer.
#JsonAutoDetect(getterVisibility = JsonAutoDetect.Visibility.NONE, fieldVisibility = JsonAutoDetect.Visibility.ANY)
public class School {
#JsonSerialize(using = TeachersSerializer .class)
private Set<Teacher> teachers = new HashSet<>();
}
The serializer:
public class TeachersSerializer extends JsonSerializer<Set<Teacher>> {
#Override
public void serialize(Set<Teacher> values, JsonGenerator jgen, SerializerProvider provider) throws IOException {
jgen.writeStartArray();
for (Teacher value : values) {
jgen.writeStartObject();
jgen.writeNumberField("id", value.getId());
jgen.writeEndObject();
}
jgen.writeEndArray();
}
}
If you want to return only in this controller, create a Teachers class.
#JsonAutoDetect(getterVisibility = JsonAutoDetect.Visibility.NONE, fieldVisibility = JsonAutoDetect.Visibility.ANY)
public class Teachers implements Serializable {
#JsonSerialize(using = TeachersSerializer.class)
private Set<Teacher> teachers = new HashSet<>();
public Teachers(Set<Teacher> teachers){
this.teachers = teachers;
}
}
In your controller:
public class Controller {
public Teachers readTeacher(...){
....
return new Teachers(school.getTeachers());
}
}