因此,我有一个输入JSON,如下所示:
[{
"added": "2014-02-01T09:13:00Z",
"author": {
"id": "1",
"name": "George R R Martin",
"added_on": "2013-02-01T09:13:00Z"
},
"book": {
"id": "12",
"name": "Game of Thrones",
"genre": "Fantasy Fiction"
}
},
{
"added": "2015-02-01T09:13:00Z",
"author": {
"id": "2",
"name": "Patrick Rothfuss",
"added_on": "2012-09-13T011:40:00Z"
},
"book": {
"id": "15",
"name": "The Name of the Wind",
"genre": "Fantasy Fiction"
}
}, {
"added": "2016-02-01T09:13:00Z",
"author": {
"id": "2",
"name": "Patrick Rothfuss",
"added_on": "2012-09-13T011:40:00Z"
},
"book": {
"id": "17",
"name": "The Wise Man's Fear",
"genre": "Fantasy Fiction"
}
}]
我需要根据author.id对其进行分组。作者将拥有一个对象和他所创作的所有书籍的清单。 这是我期望的输出:
[
{
"author": "George R R Martin",
"added_on": "2013-02-01T09:13:00Z",
"books": [
{
"book_name": "Game of Thrones",
"added": "2014-02-01T09:13:00Z"
}
]
},
{
"author": "Patrick Rothfuss",
"added_on": "2012-09-13T011:40:00Z",
"books": [
{
"book_name": "The Name of the Wind",
"added": "2015-02-01T09:13:00Z"
}, {
"book_name": "The Wise Man's Fear",
"added": "2016-02-01T09:13:00Z"
}
]
}
]
我尝试通过普通的for循环进行操作-它可以工作。但是,只是为了学习更多有关Streams的知识,我想使用Streams进行尝试。
我尝试过:
Map<Author, List<Book>> collect = authorsList.stream()
.collect(Collectors.groupingBy(AuthorBookObj::getAuthor,
Collectors.mapping(AuthorBookObj::getBook, Collectors.toList())));
但是,没有得到我所需要的。相反,它创建了三个地图,而不是两个。
也尝试过:
Map<AuthorTuple, List<Book>> collect = authorsList.stream()
.collect(Collectors.groupingBy(authors -> new AuthorTuple(authors.getAuthor().getId(),
authors.getAuthor().getName(), authors.getAuthor().getAddedOn()),
Collectors.mapping(AuthorBookObj::getBook, Collectors.toList())));
它在列表中也给了我三个对象。我期望有两位作者和每位作者对应的书籍。
AuthBookObj:
public class AuthorBookObj
{
private String id;
private Author author;
private Book book;
private String added;
//getter, setter
}
public class Article
{
private String name;
private String id;
private String genre;
}
public class Author
{
private String name;
private String added_on;
private String id;
}
答案 0 :(得分:1)
问题不是您处理流的方式,而是对象的相等性。
正确的方法是使用以下代码:
Map<Author, List<Book>> collect = authorsList.stream()
.collect(Collectors.groupingBy(AuthorBookObj::getAuthor,
Collectors.mapping(AuthorBookObj::getBook, Collectors.toList())));
但是现在您正在比较Author对象,因为对象不同,您将获得三个条目。您需要在Author对象中添加一个哈希码和equals,以比较作者ID上的对象。
//code generated from intellij.
// Author.java
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Author author = (Author) o;
return getId() == author.getId();
}
@Override
public int hashCode() {
return Objects.hash(getId());
}
答案 1 :(得分:1)
如果您没有按需创建新的POJO类的限制,我将以这种方式进行
首先将输入的JSON解析为java对象
使用-Pattern '.z|f.'
和AuthorDetails
类的响应类
BookDetails
作者详细信息
class Response {
private String addedOn;
private AuthorDetails author;
private BookDetails book;
}
图书详细信息
class AuthorDetails {
private String id;
private String name;
private String addedOn;
}
然后我将输入json映射到class BookDetails {
private String id;
private String name;
private String gener;
}
List<Response>
然后将List<Response> list = Arrays.asList(new Response());
转换为所需的输出,我添加了几个POJO类
AuthorAndBooks
List<Response>
AuthorBooks
class AuthorAndBooks {
@JsonProperty("author")
private String author;
@JsonProperty("added_on")
private String addedOn;
@JsonProperty("books")
List<AuthorBooks> books;
}
现在按作者姓名进行分组
class AuthorBooks {
@JsonProperty("book_name")
private String name;
@JsonProperty("added")
private String added;
}
现在为每个作者添加书籍
Map<String, List<Response>> group = list.stream().
collect(Collectors.groupingBy(res->res.getAuthor().getName()));
答案 2 :(得分:1)
您必须覆盖equals
和hashCode
。如果您不这样做,您的课程将违反hashCode
的一般合同,这将阻止其在HashMap
和HashSet
等集合中正常运行。 Author类无法覆盖hashCode
,导致两个相等的实例具有不相等的哈希码,这违反了hashCode
协定。将此添加到您的Author
类中。
@Override
public int hashCode() {
return id.hashCode();
}
@Override
public boolean equals(Object obj) {
return obj instanceof Author && ((Author) obj).getId().equals(id);
}
有了这些,下面的代码片段应该可以正常工作。
Map<Author, List<Article>> booksByAuthor = authorsList.stream()
.collect(Collectors
.groupingBy(AuthorBookObj::getAuthor,
Collectors.mapping(AuthorBookObj::getBook, Collectors.toList())));
答案 3 :(得分:1)
首先要注意输入JSON中的"added"
字段。这是什么意思我猜它属于Book
对象。如果是这样,最好将此字段(如果可能)放在Book
对象内。然后,您需要将此json反序列化为Java对象。可以通过com.fasterxml.jackson.databind.ObjectMapper
完成,但是您可以为此使用任何json框架。
ObjectMapper mapper = new ObjectMapper();
AuthorBookObj[] objs = mapper.readValue(inputJson, AuthorBookObj[].class);
然后,您需要对这些对象进行分组,并且第一个解决方案非常适合:
Map<Author, List<Book>> collect = Arrays.stream(objs)
.collect(groupingBy(AuthorBookObj::getAuthor,
mapping(AuthorBookObj::getBook, toList())));
在上一个答案中如何提到它,您需要确保您的类中有equals/hashcode
个方法用作Map
(在这种情况下为Author
)中的键。现在的主要困惑是期望的json输出不代表Map
。它只是带有字段author
,added_on
,books
之类的一些自定义对象的列表。
因此,要实现此目标,您需要将Map<Author, List<Book>>
转换为自定义对象列表。例如:
public class PublicationInfo {
private String author;
private String added_on;
private List<BookBriefInfo> books;
...
}
public class BookBriefInfo {
private String book_name;
private String added;
...
}
List<PublicationInfo> infos = new ArrayList<>();
for (Map.Entry<Author, List<Book>> entry : collect.entrySet()) {
PublicationInfo info = new PublicationInfo();
info.setAuthor(entry.getKey().getName());
info.setAdded_on(entry.getKey().getAdded_on());
List<BookBriefInfo> bookInfos = new ArrayList<>();
for (Book book : entry.getValue()) {
bookInfos.add(new BookBriefInfo(book.getBook_name(), book.getAdded()))
}
info.setBooks(bookInfos);
}
最后可以序列化:
String jsonResult = mapper.writeValueAsString(infos);
顺便说一句,要对json输出进行格式化,只需对其进行配置:
mapper.configure(SerializationFeature.INDENT_OUTPUT, true);