Class A:
public class A{
private String Id;
private List<Product> products = new ArrayList<>();
}
public class Product{
private String prodId;
private String color;
private String size;
}
Example:
A = { "Id": 1 , products: [{"prodId":"Prod1", "color":"Red", "size":"L"},
{"prodId":"Prod2", "color":"Green", "size":"L"}] }
Class B:
public class B{
private String Id;
private List<ProductAvail> productAvail = new ArrayList<>();
}
public class ProductAvail{
private String prodId;
private String availability;
private String region;
}
Example:
B = { "Id": 1 , productAvail: [{"prodId":"Prod1", "availability":"Y", "region":"AA"},
{"prodId":"Prod2", "availability":"N", "region":"BB"}] }
I want to combine them into one like
public class C{
private String Id;
private List<Catalog> catalog = new ArrayList<>();
}
public class Catalog{
private String prodId;
private String color;
private String size;
private String availability;
private String region;
}
C = { "Id": 1 , catalog: [{"prodId":"Prod1", "color":"Red", "size":"L", "availability":"Y", "region":"AA"},
{"prodId":"Prod2", "color":"Green", "size":"L", "availability":"N", "region":"BB"}] }
What's the ideal way to do this in Java without using two for loops?
Given:
String aJson = "{ \"id\": 1 ,\"products\": [{\"prodId\":\"Prod1\", \"color\":\"Red\", \"size\":\"L\"}, {\"prodId\":\"Prod2\", \"color\":\"Green\", \"size\":\"L\"}] }";
String bJson = "{ \"id\": 1 ,\"productAvail\": [{\"prodId\":\"Prod1\", \"availability\":\"Y\", \"region\":\"AA\"}, {\"prodId\":\"Prod2\", \"availability\":\"N\", \"region\":\"BB\"}] }";
You can do:
ObjectMapper objectMapper = new ObjectMapper();
A a = objectMapper.readValue(aJson, A.class);
B b = objectMapper.readValue(bJson, B.class);
Map<String, ProductAvail> idToProductAvail = b.getProductAvail().stream()
.collect(Collectors.toMap(ProductAvail::getProdId, Function.identity()));
List<Catalog> catalogs = a.getProducts().stream()
.map(p -> {
ProductAvail pa = idToProductAvail.get(p.getProdId());
return new Catalog(pa.getProdId(), p.getColor(), p.getSize(),
pa.getAvailability(), pa.getRegion());
}).collect(Collectors.toList());
C c = new C(a.getId(), catalogs);
System.out.println(objectMapper.writeValueAsString(c));
Output:
{"id":"1","catalog":[{"prodId":"Prod1","color":"Red","size":"L","availability":"Y","region":"AA"},{"prodId":"Prod2","color":"Green","size":"L","availability":"N","region":"BB"}]}
Assuming you want to do this action in Java, after parsing your source data, you have two choices.
Guarantee that your source data is ordered by prodId for both lists, and that every Product has a corresponding ProductAvail. You can then simply assume that the index of the first has a valid entry at the same index in the second and traverse them both with a single loop.
Change your source data and class members such that you have Maps instead of Lists as output, using prodId as the key for each entry. You can then iterate the keys from the map of Products and use them to access both the Product and ProductAvail maps in a single loop.
-
If you can't make the guarantees from (1) or the changes from (2), you're stuck with your worst case of foreach product { foreach productavail { ... } } because you have to discover the correct ProductAvail for each Product.
You could take steps mitigate the impact by nulling discovered inner list members and skipping nulls, or by using parallelStream to throw the inner loop for each outer iteration at a different thread, but no action at this point avoids needing some riff on loop{loop{}} - as a best case.
Related
I have a service which is returning a List in JSON format.
Please find below code :
public List<SampleList> getValues() {
List<SampleList> sample = null;
sample= DAOFactory.sampleDAO.findByCriteria().add(Restrictions.isNull("endDate")).list();
return sample;
}
Class SampleList.java
public class SampleList {
private Integer categoryId;
private String categoryName;
//getter setter
}
Now my service is returning the JSON like below
{
categoryId : 1,
categoryName : "Test"
}
But I need anotherlist to be encapsulated here. Iw ant below output
{
categoryId : 1,
categoryName : "Test"
subCategory:
{
name: ""
}
}
For subCategory attribute I have another class similar to SampleList.java. I can get the sub categories corresponding to each category. Can anyone help me out to get expected response?
I dont want to touch my SampleList class.
You have to extend your class SampleList
Class SampleList.java
public class SampleList {
private Integer categoryId;
private String categoryName;
private SubCategory subCategory;
//getter setter
And before you return your list of course you have to set the correct subCategory in your SampleList item.
If you don't want to spoil your SampleList class of course you could add a layer of DTO objects and map between them or manipulate the response directly e.g. with ResponseBodyAdvice
Approach : 1
public class SampleList
{
private Integer categoryId;
private String categoryName;
// Getter and Setter
}
public class SampleList2
{
private String name;
// Getter and Setter
}
// Logic to get the JSON value without mapping two different classes
private void getJsonValue() throws JsonProcessingException, JSONException
{
SampleList sampleList = new SampleList();
sampleList.setCategoryId(1);
sampleList.setCategoryName("cat 1");
String sampleListJson = new ObjectMapper().writeValueAsString(sampleList);
SampleList2 sampleList2 = new SampleList2();
sampleList2.setName("Sub category");
String valueOfSample2 = new ObjectMapper().writeValueAsString(sampleList2);
JSONObject sampleListJsonObj = new JSONObject(sampleListJson); // for class SampleList
JSONObject sampleList2JsonObj = new JSONObject(valueOfSample2); // for class SampleList2
sampleListJsonObj.put("subCategory", sampleList2JsonObj);
System.out.println(sampleListJsonObj.toString());
}
Approach : 2
public class SampleList
{
private Integer categoryId;
private String categoryName;
private SampleList2 subCategory;
// Getter and Setter
}
public class SampleList2
{
private String name;
// Getter and Setter
}
// Logic to get with mapping two classes as mentioned above
private static void getJsonValue() throws JsonProcessingException
{
SampleList sampleList = new SampleList();
sampleList.setCategoryId(1);
sampleList.setCategoryName("cat 1");
SampleList2 sampleList2 = new SampleList2();
sampleList2.setName("Sub category");
sampleList.setSubCategory(sampleList2);
String jsonString = new ObjectMapper().writeValueAsString(sampleList);
System.out.println(jsonString.toString());
}
Please let me know if you have any questions on the same.
Thank you.
I want to map the Abc class to AbcDTO using "org.mapstruct.Mapping"
class Abc {
private List<Xyz> xyz = null;
private String uvw;
private String cde;
}
class AbcDTO{
private List<XyzDTO> xyz = null;
private String uvw;
private String cde;
}
class Xyz{
private String type;
private String value;
private String docId;
}
class XyzDTO{
private String type;
private String value;
private DocDTO document;
}
I tried to map the classes by using the annotation:
#Mappings({
#Mapping(source = "xyz.docId", target = "xyz.doc")
})
abcDTO abcToabcDTO(abc abc)
Can someone please help with how do i iterate through the nested beans and map the docId to doc?
If the names are same they map automatically but I want to map from docId to doc.
when you want map list you can define it :
#Mapping(source="docId", target="doc")
XyzDTO xyzToXyzDTO(XyZ xyz);
#Mapping(source="xyz", target="xyz") //useless if two lists got same name, but good for comprehention
AbcDTO abcToAbcDTA(Abc abc);
It should be as below (
you can give it a try):
#Mappings({
#Mapping(target="doc", source="abc.docId")
})
AbcDTO abcToabcDTO(Abc abc);```
Good morning all,
While writing my GUI I ran into a problem with my Arraylist.
In my Vereniging class there is a ArrayList containing Persoon objects, which are either Lid or Medewerker.
Now what I need is to filter all the Lid objects from that list and make a new Arraylist with Lid objects.
Does anyone know if this is possible?
I've tried running a enhanced for loop and adding the objects to a new Arraylist but it says the datatype doesnt meet (trying to add Persoon to a Lid list)
Classes;
Vereniging
public class Vereniging {
private String naam;
private ArrayList<Persoon> personen;
private ArrayList<Vliegtuig> vliegtuigen;
private ArrayList<Vlucht> vluchten;
private ArrayList<Hangaar> hangaars;
private DataHandler handler = new Database();
public Vereniging(String naam){
this.naam = naam;
personen = new ArrayList<>();
vliegtuigen = new ArrayList<>();
vluchten = new ArrayList<>();
hangaars = new ArrayList<>();
}
public ArrayList<Persoon> getPersonen() {
return personen;
}
Persoon
public abstract class Persoon implements Comparable<Persoon>{
private String voornaam;
private String tussenvoegsel;
private String achternaam;
private String woonplaats;
private String geslacht;
private String rekeningnr;
Persoon(String voornaam, String tussenvoegsel, String achternaam, String
woonplaats,
String geslacht, String rekeningnr){
this.voornaam = voornaam;
this.tussenvoegsel = tussenvoegsel;
this.achternaam = achternaam;
this.woonplaats = woonplaats;
this.geslacht = geslacht;
this.rekeningnr = rekeningnr;
}
Lid
public class Lid extends Persoon {
private String gebnaam;
private String wachtwoord;
private String rol;
public Lid(String voornaam, String tussenvoegsel, String achternaam, String woonplaats, String geslacht,
String rekeningnr, String gebnaam, String wachtwoord, String rol) {
super(voornaam, tussenvoegsel, achternaam, woonplaats, geslacht, rekeningnr);
this.gebnaam = gebnaam;
this.wachtwoord = wachtwoord;
this.rol = rol;
}
You can use the type comparison operator: instanceof. Which lets you compare if a variable is an instance of a class (or a subclass).
See the following example which iterates over the personen list and then uses the instanceof to determine if it in fact is an instance of Lid or not:
List<Lid> lids = new ArrayList<>();
for(Persoon persoon : personen){
if(persoon instanceof Lid){
// safe cast and add to lids
lids.add((Lid) persoon);
}
}
If you happen to use Java 8, you can make us of the new Stream API:
List<Lid> lids = personen.stream() // create a stream of persoonen
.filter(Lid.class::isInstance) // equivalent to using instanceof
.map(Lid.class::cast) // equivalent to casting
.collect(Collectors.toList()); // creating a list
As I can see you want to add object of parent into array list of child type.
So when your are iterating through list of persoon, check type of persoon by using instanceOf then type case that to Lid and add it to your new list.
Hope this will resolve your problem.
More Enhance solution can be as following.
personen.stream().filter(p->p instanceOf Lid).map(p->(Lid)p).collect(Collectors.toList());
by using stream API of Java 8 you can do it in a single line.
Thanks!
I have an objects class A:
public class A {
private Long id;
private String name;
private String mail;
private String moreData;
// ...
}
class B:
public class B {
private Long id;
private String name;
private String crc;
// ...
}
Can I use jackson to provide field mapping from object A to B copying correspond fields into target object.
I need from object
A {
Long id = 23L;
String name = "name";
String mail = "mail";
String moreData = "moreData";
// ...
}
get as target object
B {
Long id = 23L;
String name = "name";
String crc = mull;
// ...
}
after object mapping processing...
Is it possible implement solution using com.fasterxml.jackson in simple way?
Sure you can. Not withstanding a full understanding of why you want to do this, or that I think there might be more efficient ways than converting to JSON then back, but if you would like to use Jackson, here is what I would do:
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
B b = objectMapper.readValue(objectMapper.writeValueAsString(a), B.class);
Hope this helps. should do the job. The key will be to tell Jackson to not fail on unknown properties so it drops those you are not sure of.
My problem essentially comes down to this simplified example. I have data coming back from a DB which has some duplicate information in the rows.
In this example I have a list of TeamRow objects that come back from the DB. I can easily group these using Collectors.groupingBy:
public class TeamRow {
private int id;
private String name;
private String player;
public TeamRow(int id, String name, String player) {
this.id = id;
this.name = name;
this.player = player;
}
public int getId() {return id;}
public String getName() { return name; }
public String getPlayer() {return player;}
}
public class Team {
private int id;
private String name;
private List<String> players;
public Team(int id, String name, List<String> players) {
this.id = id;
this.name = name;
this.players = new ArrayList<String>(players);
}
}
List<TeamRow> dbTeams = new ArrayList<TeamRow>();
dbTeams.add(new TeamRow(1, "Team1", "Jonny"));
dbTeams.add(new TeamRow(1, "Team1", "Rob"));
dbTeams.add(new TeamRow(1, "Team1", "Carlos"));
dbTeams.add(new TeamRow(2, "Team2", "Shane"));
dbTeams.add(new TeamRow(2, "Team2", "Lucas"));
dbTeams.add(new TeamRow(3, "Team3", "Geraint"));
dbTeams.add(new TeamRow(3, "Team3", "Rocky"));
dbTeams.add(new TeamRow(3, "Team3", "Wayne"));
dbTeams.add(new TeamRow(3, "Team3", "Dwayne"));
dbTeams.add(new TeamRow(3, "Team3", "Lester"));
Map<Integer, List<TeamRow>> myMap = dbTeams.stream().collect(Collectors.groupingBy(TeamRow::getId));
However, what I'm actually trying to achieve is to convert the TeamRows to Teams. So that the id and name are only represented once and the players are stored in a List in the Team object. I can achieve this by adding a forEach over the map as shown below.
But I've been trying to figure out if there is a way I can achieve the same result by adding some sort of mapper or downstream collector. Would this even offer any benefit over adding a subsequent forEach ?? Eg:
List<Team> teams = dbTeams.stream().collect(Collectors.groupingBy(TeamRow::getId, ???), ???).???;
Conversion using forEach:
List<Team> teams = new ArrayList<>();
myMap.forEach((id, teamRows) -> {
if (teamRows.size() > 0) {
TeamRow tr = teamRows.get(0);
List<String> players = teamRows.stream().map(TeamRow::getPlayer).collect(Collectors.toList());
teams.add(new Team(id, tr.getName(), players));
}
});
Previously I said I would do it by creating an atomic transformer function like this:
Function<TeamRow, Team> getTeamRowTransformer() {
final Map<Integer, Team> map = new ConcurrentHashMap<Integer, Team>();
return (teamRow)->{
Team result = map.computeIfAbsent(teamRow.getId(), id->new Team(id, teamRow.getName(), Collections.emptyList()));
result.players.add(teamRow.getPlayer());
return result;
};
}
It handles the mapping and your stream code becomes one very legible step:
Set<Team> finalTeams = dbTeams.stream()
.map(getTeamRowTransformer())
.collect(Collectors.toSet());
HOWEVER, I realized, you can also do this:
List<Team> teams = dbTeams.stream()
.map(tr->new Team(tr.getId(), tr.getName(), Arrays.asList(tr.getPlayer())))
.collect(Collectors.collectingAndThen(
Collectors.groupingBy(t->t.id,
Collectors.reducing((Team a, Team b)->{
a.players.addAll(b.players);
return (Team)a;
})
), m->m.values().stream()
.filter(Optional::isPresent)
.map(Optional::get)
.collect(Collectors.toList())
)
);
This way you never have an accessible mutable collection until List<Team> teams is assigned.
You may use toMap collector with custom merge function. It's probably a good idea to add merge method to the Team class:
public class Team {
private final int id;
private final String name;
private final List<String> players;
public Team(int id, String name, List<String> players) {
this.id = id;
this.name = name;
this.players = new ArrayList<>(players);
}
// merges other team into this team, returning this team
public Team merge(Team other) {
assert id == other.id; // remove asserts if you don't like them
assert name.equals(other.name);
players.addAll(other.players);
return this;
}
}
Now you can solve your problem this way:
Collection<Team> teams = dbTeams.stream()
.map(tr -> new Team(tr.id, tr.name, Arrays.asList(tr.player)))
.collect(Collectors.toMap(t -> t.id, t -> t, Team::merge)).values();
You could try something like
List<Team> teamList = dbTeams.stream().collect(Collectors.collectingAndThen(Collectors.groupingBy(TeamRow::getId),
(m -> m.entrySet().stream().map(
e -> {
List<TeamRow> l = e.getValue();
return new Team(l.get(0).getId(), l.get(0).getName(), l.stream().map(TeamRow::getPlayer).collect(Collectors.toList()));
}
).collect(Collectors.toList()))));
Using collectingAndThen() you can use a function which maps the entries of the map to Teams. l.get(0) should not fail as there is always at least one entry in the list.
I am not sure if this is more concise, but at least it does not use foreach.