Java 8 here. I am have 2 POJOs:
public class User {
private String id;
private String moniker;
// ... lots of other fields
// Getters & setters down here
}
public class UserDetail {
private String userId;
private String moniker;
// ... lots of other fields
// Getters & setters down here
}
I'm being given a List<User> and a Set<UserDetail>. If there are any UserDetails in that set whose userId fields match any of the User#id values in the user list, I need to update the respective User#moniker with the field of the same name in the UserDetail set.
I have been able to do this the "old" way (pre-Java 8 Stream/Collection APIs) like so:
final List<User> users = userService.fetchSomehow();
final Set<UserDetail> userDetails = userDetailService.fetchSomehow();
for (UserDetail userDetail : userDetails) {
for (User user : users) {
if (userDetail.getUserId().equals(user.getId())) {
user.setMoniker(userDetail.getMoniker());
}
}
}
How could I do this with the Java 8 APIs? That is, how could I loop through both collections, and for any elements with matching IDs, use the moniker value from the UserDetails to update the moniker value in the Users?
performance wise would be better if put userDetails in a map
Map<String, String> userDetailsMap = userDetails.stream()
.collect(Collectors.toMap(UserDetail::getUserId, UserDetail::getMoniker));
users.stream().filter(u -> userDetailsMap.containsKey(u.getId())).forEach(u -> {
u.setMoniker(userDetailsMap.get(u.getId()));
});
Since your userId is dependent with moniker parameter. So, I've implemented using both stream and advance for loop. You can have a glance. Your question is tricky and interesting as well.
Approach One
users.stream().forEach(System.out::println);
userdetails.stream().forEach(userdetail->{
for(User user : users) {
if(user.getId().equals(userdetail.getUserId())){
user.setMoniker(userdetail.getMoniker());
}
}
});
System.out.println("********************");
users.stream().forEach(System.out::println);
Approach Two
public static List<User> users = getUserList();
public static void main(String[] args) {
Set<UserDetail> userdetails = getUserDetails();
users.stream().forEach(System.out::println);
//Note: CompareUpdateList is name of my Class
userdetails.stream().forEach(CompareUpdateList::updateUser);
System.out.println("********************");
users.stream().forEach(System.out::println);
}
public static void updateUser(UserDetail userdetail) {
for(User user : users) {
if(user.getId().equals(userdetail.getUserId())){
user.setMoniker(userdetail.getMoniker());
}
}
}
Third Approach
public static List<User> users = getUserList();
public static void main(String[] args) {
Set<UserDetail> userdetails = getUserDetails();
users.stream().forEach(System.out::println);
//Note: CompareUpdateList is name of my Class
userdetails.stream().forEach(CompareUpdateList::updateUser);
System.out.println("********************");
users.stream().forEach(System.out::println);
}
public static void updateUser(UserDetail userdetail) {
users.stream().forEach(user->{
if(user.getId().equals(userdetail.getUserId())){
user.setMoniker(userdetail.getMoniker());
}
});
}
I have a JPA entity with a List of custom objects as one of its fields. Using a Jackson converter, I've managed to persist this list as a JSON array into a MySQL database, but Iam unable to insert into this list after its initial creation.
I can successfully retrieve the existing list, add a new object in memory(and test that it has been inserted), then save it via a Spring REST repository. However, it never seems to persist. Any ideas? Here is my code (this is a Spring Boot project FYI):
Candidate entity with a List inside
#Entity
#Table(name = "Candidates", schema = "Candidate")
public class Candidate extends ResourceSupport {
#Id
#Column(name = "CandidateID")
private Long candidateID;
// More fields
#Column(name = "Fields")
#Convert(converter = CollectionConverter.class)
private List<CandidateField> fields;
//Getters & setters
}
CandidateField class which makes up the List above. The CandidateField is simply a POJO that models the JSON stored in a single field in the Candidate table, it is not an independent entity.
public class CandidateField {
private Long fieldID;
private String name;
private boolean current;
public CandidateField () {
}
public CandidateField (Long fieldID, String name, boolean current) {
this.fieldID = fieldID;
this.name = name;
this.current = current;
}
//Getters & Setters
}
Converter
public class CollectionConverter implements AttributeConverter<List<CandidateField>, String> {
private ObjectMapper objectMapper = new ObjectMapper();
#Override
public String convertToDatabaseColumn(List<CandidateField> object) {
try {
return objectMapper.writeValueAsString(object);
} catch (JsonProcessingException e) {
e.printStackTrace();
return "";
}
}
#Override
public List<CandidateField> convertToEntityAttribute(String data) {
try {
return objectMapper.readValue(data, new TypeReference<List<CandidateField>>() {});
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
Code that persists to database
public void addField(Long fieldID, Long candidateID) {
Candidate candidate = repository.findOne(candidateID);
candidate.getFields().add(new CandidateField(fieldID, "", true));
repository.saveAndFlush(candidate);
}
Repository
#RepositoryRestResource
public interface CandidateRepository extends JpaRepository<Candidate,Long>{}
I can't seem to figure out why this won't persist. Any help will be very much appreciated. Cheers!
Consider defining the cascade type for your collection.
When you persist your Candidate objects the operation is not cascaded by default and thus you need to define it yourself unless you persist your CandidateField objects directly.
I want to use Objectify to query Google Cloud Datastore. What is an appropriate way to find a record based on a known key-value pair? The record is in the database, I verified this by Google's Datastore viewer.
Here is my method stub, which triggers the NotFoundException:
#ApiMethod(name="getUser")
public User getUser() throws NotFoundException {
String filterKey = "googleId";
String filterVal = "jochen.bauer#gmail.com";
User user = OfyService.ofy().load().type(User.class).filter(filterKey, filterVal).first().now();
if (user == null) {
throw new NotFoundException("User Record does not exist");
}
return user;
}
Here is the User class:
#Entity
public class User {
#Id
Long id;
private HealthVault healthVault;
private String googleId;
public User(String googleId){
this.googleId = googleId;
this.healthVault = new HealthVault();
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public HealthVault getHealthVault() {
return healthVault;
}
public void setHealthVault(HealthVault healthVault) {
this.healthVault = healthVault;
}
public String getGoogleId() {
return googleId;
}
public void setGoogleId(String googleId) {
this.googleId = googleId;
}
}
I think it fails because of transaction. You need to make a transctionless call like:
User user = OfyService.ofy().transactionless().load().type(User.class).filter(filterKey, filterVal).first().now();
More info about transactions on App Engine:
https://cloud.google.com/appengine/docs/java/datastore/transactions
https://github.com/objectify/objectify/wiki/Transactions
EDIT
Your object needs #Index annotation. It will add field to datastore index. Only properties that are in the index can be searchable. Filter method is one of them.
#Id
Long id;
#Index
private HealthVault healthVault;
#Index
private String googleId;
P.S. delete your object with googleId jochen.bauer#gmail.com and write it again to database after you updated your entity. And objectify will find it.
First add #Index in your fields model. I didn't see filterVal as an email in your model. Even so, to get the entity based in your filterVal assuming that is googleId is the field of your entity.
User user = OfyService.ofy().load().type(User.class).filter("googleId", filterVal).now();
And so if your filterKey is the id of your entity.
User user = OfyService.ofy().load().key(Key.create(User.class, filterKey)).now();
I have these two classes:
#JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class, property = "id",scope = Rol.class)
public class Rol extends MyEntity implements Serializable {
private Integer id;
private String rolName;
public Rol(Integer id, String rolName) {
this.id = id;
this.rolName = rolName;
}
...
}
#JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class, property = "id",scope = User.class)
public class User extends MyEntity implements Serializable {
private Integer id;
private String name;
private List<Rol> rolList;
public User(Integer id, String name, List<Rol> rolList) {
this.id = id;
this.name = name;
this.rolList = rolList;
}
...
}
and I try to serialize and deserialize the user object as following
Rol rol1 = new Rol(1, "MyRol");
Rol rol2 = new Rol(1, "MyRol");
List<Rol> rolList = new ArrayList();
rolList.add(rol1);
rolList.add(rol2);
user = new User(1, "MyUser", rolList);
ObjectMapper mapper = new ObjectMapper();
String jsonString = mapper.writeValueAsString(user);
User userJson = mappe.readValue(jsonString, User.class);
and the JsonMappingException: Already had POJO for id is produced. Why?
When I review the json result of the serialization I see that the result is
{"id": 1,"name": "MyName","rolList": [{"id": 1,"rolName": "MyRol"},{"id": 1,"rolName": "MyRol"}]}
when the result should be
{"id": 1,"name": "MyName","rolList": [{"id": 1,"rolName": "MyRol"},1]}
because rol1 and rol2 are different instances of the same POJO identifier with id 1.
How can I avoid the JsonMappingException? In my project I have some different instances of the same POJO. I can guarantee that if the id's are equal -> objects are equal.
Excuse me for my bad English.
For anyone returning to this question, it looks like there's option to do this with a custom ObjectIdResolver in Jackson. You can specify this on the #JsonIdentityInfo annotation, e.g. :
#JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class, property = "name",
resolver = CustomObjectIdResolver.class)
Then perhaps wrap the normal SimpleObjectIdResolver class to get going and customise bindItem().
In my case I wanted to avoid overlapping objectIds, so cleared down the references when I started a new Something:
public class CustomObjectIdResolver implements ObjectIdResolver {
private ObjectIdResolver objectIdResolver;
public CustomObjectIdResolver() {
clearReferences();
}
#Override
public void bindItem(IdKey id, Object pojo) {
// Time to drop the references?
if (pojo instanceof Something)
clearReferences();
objectIdResolver.bindItem(id, pojo);
}
#Override
public Object resolveId(IdKey id) {
return objectIdResolver.resolveId(id);
}
#Override
public boolean canUseFor(ObjectIdResolver resolverType) {
return resolverType.getClass() == getClass();
}
#Override
public ObjectIdResolver newForDeserialization(Object context) {
return new CustomObjectIdResolver();
}
private void clearReferences() {
objectIdResolver = new SimpleObjectIdResolver();
}
}
Jackson expects in this case different id for different class instances. There has been a previous discussion at github here. Overriding hashCode and equals will not help. Object references must match for equal id.
Options
Reuse Rol instances instead of making new ones with equal fields. As a bonus you will also save memory.
Modify the application logic so that it doesn't depend on #JsonIdentityInfo
I've been playing around with tweets in Eclipse for a while, which are presented as a json string.
To this end, I've created an object called Tweet (original, huh?) which takes certain information from the json string, and stores it in the Tweet object. Nothing fancy.
My Tweet class looks as follows:
public class Tweet implements TwitterMelding {
public Tweet() {
}
String created_at;
String id;
String text;
String user;
public void setUser(String user) {
this.user = user;
}
public void setText(String text) {
this.text = text;
}
public void setId(String id) {
this.id = id;
}
public void setCreated_at(String created_at) {
this.created_at = created_at;
}
}
Now, simple as it may look, there is one of those that doesn't work.
Specifically String user. What it's suppose to do, is store user ID of the person who posted the tweet.
The following is the tweet as obtained from Twitter in all it's horrible length:
{"created_at":"Sat Feb 08 15:37:37 +0000 2014","id":432176397474623489,"id_str":"432176397474623489","text":"Skal begynne \u00e5 selge vekter.. Eneste m\u00e5ten det konstant kommer penger i lommen","source":"\u003ca href=\"http:\/\/twitter.com\/download\/iphone\" rel=\"nofollow\"\u003eTwitter for iPhone\u003c\/a\u003e","truncated":false,"in_reply_to_status_id":null,"in_reply_to_status_id_str":null,"in_reply_to_user_id":null,"in_reply_to_user_id_str":null,"in_reply_to_screen_name":null,"user":{"id":366301747,"id_str":"366301747","name":"skinny-pete","screen_name":"JFarsund","location":"bj\u00f8rge","url":null,"description":"j\u00f8rgen er en tynn gutt med pack.. Men det teller vel ikke? Det gj\u00f8r vel ikke bio heller","protected":false,"followers_count":427,"friends_count":291,"listed_count":2,"created_at":"Thu Sep 01 23:03:49 +0000 2011","favourites_count":5103,"utc_offset":3600,"time_zone":"Copenhagen","geo_enabled":true,"verified":false,"statuses_count":8827,"lang":"no","contributors_enabled":false,"is_translator":false,"is_translation_enabled":false,"profile_background_color":"C0DEED","profile_background_image_url":"http:\/\/pbs.twimg.com\/profile_background_images\/378800000089578611\/6840970475350d63190eb05d3d7e47ec.png","profile_background_image_url_https":"https:\/\/pbs.twimg.com\/profile_background_images\/378800000089578611\/6840970475350d63190eb05d3d7e47ec.png","profile_background_tile":true,"profile_image_url":"http:\/\/pbs.twimg.com\/profile_images\/431961396528414720\/EwkxQBkW_normal.jpeg","profile_image_url_https":"https:\/\/pbs.twimg.com\/profile_images\/431961396528414720\/EwkxQBkW_normal.jpeg","profile_banner_url":"https:\/\/pbs.twimg.com\/profile_banners\/366301747\/1391822743","profile_link_color":"0084B4","profile_sidebar_border_color":"FFFFFF","profile_sidebar_fill_color":"DDEEF6","profile_text_color":"333333","profile_use_background_image":true,"default_profile":false,"default_profile_image":false,"following":null,"follow_request_sent":null,"notifications":null},"geo":{"type":"Point","coordinates":[60.33700829,5.24626808]},"coordinates":{"type":"Point","coordinates":[5.24626808,60.33700829]},"place":{"id":"2260fcb4a77f2bad","url":"https:\/\/api.twitter.com\/1.1\/geo\/id\/2260fcb4a77f2bad.json","place_type":"city","name":"Bergen","full_name":"Bergen, Hordaland","country_code":"NO","country":"Norge","contained_within":[],"bounding_box":{"type":"Polygon","coordinates":[[[5.1602697,60.1848543],[5.1602697,60.5335445],[5.6866852,60.5335445],[5.6866852,60.1848543]]]},"attributes":{}},"contributors":null,"retweet_count":0,"favorite_count":0,"entities":{"hashtags":[],"symbols":[],"urls":[],"user_mentions":[]},"favorited":false,"retweeted":false,"filter_level":"medium","lang":"no"}
It really is a long eye-sore.
I've added a few "..." to the next one, to make it slightly more readable, only showing the value I'm interested in:
{…,"user":{"id":366301747,"id_str":"366301747","name":"skinny-pete","screen_name":"JFarsund","location":"bj\u00f8rge","url":null,"description":"j\u00f8rgen er en tynn gutt med pack.. Men det teller vel ikke? Det gj\u00f8r vel ikke bio heller","protected":false,"followers_count":427,"friends_count":291,"listed_count":2,"created_at":"Thu Sep 01 23:03:49 +0000 2011","favourites_count":5103,"utc_offset":3600,"time_zone":"Copenhagen","geo_enabled":true,"verified":false,"statuses_count":8827,"lang":"no","contributors_enabled":false,"is_translator":false,"is_translation_enabled":false,"profile_background_color":"C0DEED","profile_background_image_url":"http:\/\/pbs.twimg.com\/profile_background_images\/378800000089578611\/6840970475350d63190eb05d3d7e47ec.png","profile_background_image_url_https":"https:\/\/pbs.twimg.com\/profile_background_images\/378800000089578611\/6840970475350d63190eb05d3d7e47ec.png","profile_background_tile":true,"profile_image_url":"http:\/\/pbs.twimg.com\/profile_images\/431961396528414720\/EwkxQBkW_normal.jpeg","profile_image_url_https":"https:\/\/pbs.twimg.com\/profile_images\/431961396528414720\/EwkxQBkW_normal.jpeg","profile_banner_url":"https:\/\/pbs.twimg.com\/profile_banners\/366301747\/1391822743","profile_link_color":"0084B4","profile_sidebar_border_color":"FFFFFF","profile_sidebar_fill_color":"DDEEF6","profile_text_color":"333333","profile_use_background_image":true,"default_profile":false,"default_profile_image":false,"following":null,"follow_request_sent":null,"notifications":null}, …}
Right, still with me?
As I mentioned above, what I want is the users ID, which I want assigned to the variable "user" in the Tweet object.
{…,"user":{"id":366301747,"id_str":"366301747",… }…}
All I want is to assign the number 366301747, to the variable "user" in my Tweet object.
But for the life of me, I cannot seem to.
To make sure the Tweet object gets the info it wants and not the info it doesn't, I am using a Jackson object:
ObjectMapper mapper = new ObjectMapper();
mapper.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
So my question.
How do I tell Tweet to take the 366301747 number from my json string and assign it to the variable "user"?
I'd prefer it doable with Jackson alone, and not having to import more JARs than necessary.
Please forgive the wall of text.
It can be pretty straightforward with Gson library.
Since, you've already done the hard work of creating the pojo, by looking at your json you can validate that User is a valid json object and not a String value.
Hence, let's modify your pojo's (Tweet) user attribute a little with:
User user;
public User getUser() {
return user;
}
public void setUser(User user) {
this.user = user;
}
Where User custom class is:
public class User {
private String id;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
}
Now just call the your Gson method to convert your object from the json (I stored the json to a file and read it through a BufferedReader):
public static void main(String[] args) throws FileNotFoundException {
Gson gson = new Gson();
BufferedReader br = new BufferedReader(new FileReader(
"json.txt"));
Tweet tweetObj = gson.fromJson(br, Tweet.class);
System.out.println(tweetObj.getUser().getId());
}
Output:
366301747
EDIT: Based on the comments, solution using jackson - 2 options
Keep newly created User class, the ObjectMapper code remains exactly the same and System.out.println(tweet.getUser().getId()) gets you the userid.
If User class is not to be used, change your Tweet to look like this:
Code:
public class Tweet {
String created_at;
String id;
String text;
Map<String, String> user;
public String getCreated_at() {
return created_at;
}
public void setCreated_at(String created_at) {
this.created_at = created_at;
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getText() {
return text;
}
public void setText(String text) {
this.text = text;
}
public Map<String, String> getUser() {
return user;
}
public void setUser(Map<String, String> user) {
this.user = user;
}
}
And print the userid in the calling method as:
ObjectMapper mapper = new ObjectMapper();
mapper.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
Tweet tweet = mapper.readValue(br, Tweet.class);
System.out.println(tweet.getUser().get("id"));
Gets you:
366301747
You can change the setUser method to take a Map and set user.id manually:
public void setUser(Map<String, Object> props) {
user = props.get("id").toString();
}