I have the following JSON object that is parsed through a HTTP request.
{
"firstName": "User",
"lastName": "Test",
"emailId": "[email protected]",
"formsAndQuestions": {
"Form1": {
"Question1": {
"value": "NEVER",
"isBoolean": false
},
"Question2": {
"value": "YES"
}
},
"Form2": {
"Question1": {
"value": "OTHER"
}
}
}
}
The form object can be dynamic. Therefore I'm deserializing this in my Java class with the following variable.
Map<String, Map<String, Map<String, String>>> forms;
Then I'm going through the complex loop below to iterate these and read the answer values.
for (Map.Entry<String, Map<String, Map<String, String>>> entry : input.formsAndQuestions().get().entrySet()) {
logger.log("Form: " + entry.getKey());
for (Map.Entry<String, Map<String, String>> entry1 : entry.getValue().entrySet()) {
logger.log("Question: " + entry1.getKey());
for (Map.Entry<String, String> entry2 : entry1.getValue().entrySet()) {
logger.log("key: " + entry2.getKey());
logger.log("value: " + entry2.getValue());
}
}
}
Is there a better way to do this? Loop seems pretty complex. I don't want to create a Java object class for deserializing the json as the "formsAndQuestions" object can have multiple forms added/removed with multiple questions/answers per form added or removed.
Would greatly appreciate any feedback on this approach.
Even so forms and questions vary, the structure is the same. You can easily model this in Java
using @JsonAnySetter
annotation to set properly random forms. Take a look on below example:
import com.fasterxml.jackson.annotation.JsonAnySetter;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.ToString;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;
public class FormsAndQuestionsApp {
public static void main(String[] args) throws IOException {
File jsonFile = new File("./resource/test.json").getAbsoluteFile();
ObjectMapper mapper = new ObjectMapper();
Result result = mapper.readValue(jsonFile, Result.class);
result.getFormsAndQuestions().getForms().forEach(System.out::println);
}
}
@Data
@ToString
class Result {
private String firstName;
private String lastName;
private String emailId;
private FormsAndQuestions formsAndQuestions;
}
@Data
@ToString
class FormsAndQuestions {
private List<Form> forms = new ArrayList<>();
@JsonAnySetter
public void anySetter(String formName, Map<String, Question> questions) {
// update questions with keys (question's name)
questions.forEach((k, v) -> v.setQuestion(k));
forms.add(new Form(formName, questions.values()));
}
}
@Data
@ToString
@AllArgsConstructor
class Form {
private String name;
private Collection<Question> questions;
}
@Data
@ToString
class Question {
private String question;
private String value;
private Boolean isBoolean;
}
Above code prints:
Form(name=Form1, questions=[Question(question=Question1, value=NEVER, isBoolean=false), Question(question=Question2, value=YES, isBoolean=null)])
Form(name=Form2, questions=[Question(question=Question1, value=OTHER, isBoolean=null)])
The key point is public void anySetter(String formName, Map<String, Question> questions)
method. Using @JsonAnySetter
we convert JSON Object
to a list, because for every unknown key-value
pair this method will be invoked. Also, we convert inner object to proper Question
POJO
. value
field is set by Jackson
we need to set question
property only. Right now, it should be much easier to traverse this list and use later in some business logic.