I've following flat list of objects:
public class FlatQuestion{
public int QuestionId;
public String QuestionName;
public int SectionId;
public String SectionName;
public int FieldId;
public String FieldName;
public int Input_Type_Id;
public String Input_Type_String;
}
I've been trying to convert it the list of questions. List<Question>
public class Question {
public int id;
public String name;
public List<Section> sections;
Question(String name, int id, List<Section> sections) {
this.name = name;
this.id = id;
this.sections = sections;
}
}
public class Section {
public int id;
public String name;
public List<Field> fields;
Section(int id, String name, List<Field> fields) {
this.name = name;
this.id = id;
this.fields = fields;
}
}
public class Field {
public int id;
public String lable;
public Input input_type;
Field(int id, String lable, Input input_type) {
this.id = id;
this.lable = lable;
this.input_type = input_type;
}
}
public class Input {
public int id;
public String type;
Input(int id, String type) {
this.id = id;
this.type = type;
}
}
The solution I've been working on is giving me multiple sections. How to get distinct values here:
final List<Question> results = data
.stream()
// extracting distinct questions, you have fetched
.map(FlatQuestion::QuestionId)
.distinct()
// now we have unique key for the data and can map it
.map(it ->
new Question(
it,
// extracting all sections, which were fetched in rows with references to templates.
data
.stream()
.filter(o -> o.QuestionId.equals(it))
.map(ing -> new Sections(ing.SectionId, ing.SectionName))
.collect(Collectors.toSet())
.collect(Collectors.toList()) ;
Adding some sample data:
FlatQuestion f1 = new FlatQuestion(1, "Student Survey", 1, "Basic info", 1, "Enter Name", 1, "Text");
FlatQuestion f2 = new FlatQuestion(1, "Student Survey", 1, "Basic info", 2, "Enter Address", 1, "Text");
FlatQuestion f3 = new FlatQuestion(1, "Student Survey", 2, "Class Info", 3, "Select No of subjects", 2, "Dropdown");
FlatQuestion f4 = new FlatQuestion(1, "Student Survey", 2, "Class info", 4, "Enter primary subject", 1, "Text");
FlatQuestion f5 = new FlatQuestion(2, "Patient Audit", 3, "Basic info", 5, "Enter Name", 1, "Text");
FlatQuestion f6 = new FlatQuestion(2, "Patient Audit", 3, "Basic info", 6, "Enter Address", 1, "Text");
FlatQuestion f7 = new FlatQuestion(2, "Patient Audit", 4, "Alcohol Consumption", 7, "How often you drink", 3, "Checkbox");
Let's take it step by step. Firstly, we'll need a method to map/create a Question
from a FlatQeustion
(assuming there are no duplicates):
private Question mapQuestion(FlatQuestion flatQuestion) {
return new Question(
flatQuestion.getQuestionId(),
flatQuestion.getQuestionName(),
List.of(new Section(
flatQuestion.getSectionId(),
flatQuestion.getSectionName(),
List.of(new Field(
flatQuestion.getFieldId(),
flatQuestion.getFieldName(),
new Input(
flatQuestion.getInput_Type_Id(),
flatQuestion.getInput_Type_String()
))
))
));
}
Then, for duplicates, let's start from the bottom. When we'll merge 2 Question objects, we'll need to merge their list of Sections, avoiding duplicates, so we need a method to merge the two List avoiding duplicates. it can be something like this:
private List<Section> mergeSections(List<Section> sections, List<Section> otherSections) {
// we'll group sections by section id and merge the duplicates with the merge function
Map<Integer, Section> allSectionsById = Stream.concat(sections.stream(), otherSections.stream())
.collect(Collectors.toMap(Section::getId, s -> s, this::mergeSectionsWithSameId));
// the map has no duplicates now, we can safely return the values as a list
return new ArrayList<>(allSectionsById.values());
}
As you can see, Collectors.toMap
allows us to specify a mapper for the key (in our case Section::getId
, a mapper for the value, and a mergeFunction). this merge function will be called when we have 2 elements with the same key. In our case we'll use these 2 Sections with the same key to create a merged one with all the fields:
private Section mergeSectionsWithSameId(Section first, Section second) {
if (first.getId() != second.getId()) {
throw new IllegalArgumentException("cannot merge questions with different ids");
}
List<Field> allFields = new ArrayList<>();
allFields.addAll(first.getFields());
allFields.addAll(second.getFields());
return new Section(first.getId(), first.getName(), allFields);
}
This means we can move one level up and use this functions to merge two Questions with the same id:
private Question mergeQuestionsWithSameId(Question first, Question second) {
if (first.getId() != second.getId()) {
throw new IllegalArgumentException("cannot merge questions with different ids");
}
return new Question(
first.getId(),
first.getName(),
mergeSections(first.getSections(), second.getSections())
);
}
Finally, let's apply the same technique for the Stream of Question objects:
Map<Integer, Question> questionById = Stream.of(f1, f2, f3, f4, f5, f6, f7)
.collect(Collectors.toMap( FlatQuestion::getQuestionId,
this::mapQuestion,
this::mergeQuestionsWithSameId )
);
As you can see, we are using the same Colllectors.toMap
and we group the elements by questionId
. Additionally, instead of keeping the elements as they are (previously q -> q
) now we map them from FlatQuestion to Question (with q -> mapQuestion(q)
or simply this::mapQuestion
).
Finally, we deal with the questions with the same id using (q1, a2) -> mergeQuestionsWithSameId(q1, q2)
or simply this::mergeQuestionsWithSameId
. You can access the resulting collection of unique questions with no duplicated fields using questionById.vales()