-
Notifications
You must be signed in to change notification settings - Fork 102
Boon Filtering for Java beans, JSON and Maps
Boon Home | Boon Source | If you are new to boon, you might want to start here. Simple opinionated Java for the novice to expert level Java Programmer. Low Ceremony. High Productivity. A real boon to Java to developers!
Many languages have support for querying objects and filtering objects easily. Java does in JDK 8, but Boon adds it as well, and it can compliment features of JDK 8 as well as work in Java 7 land. Boon adds filtering to to Java. You can filter JSON, Java, and Maps. You can also use indexed queries which are a lot faster than linear search queries that come with JDK 8.
This tutorial builds on the tutorial for path expressions and sorting.
Let's create some sample objects to run some filters on.
List of Java Bean instances to filter
static List<Department> departmentsList = list(
new Department("Engineering").add(
new Employee(1, 100, "Rick", "Hightower", "555-555-1000"),
new Employee(2, 200, "John", "Smith", "555-555-1215", "555-555-1214", "555-555-1213"),
new Employee(3, 300, "Drew", "Donaldson", "555-555-1216"),
new Employee(4, 400, "Nick", "LaySacky", "555-555-1217")
),
new Department("HR").add(
new Employee(5, 100, "Dianna", "Hightower", "555-555-1218"),
new Employee(6, 200, "Derek", "Smith", "555-555-1219"),
new Employee(7, 300, "Tonya", "Donaldson", "555-555-1220"),
new Employee(8, 400, "Sue", "LaySacky", "555-555-9999")
), new Department("Manufacturing").add(),
new Department("Sales").add(),
new Department("Marketing").add()
);
The above configures many departments each with a few employees.
But since this is Boon and Boon treats JSON like it is part of Java, there is support for lists and maps and JSON.
The listing below creates lists of maps that we can run our filters on as well. Same code works with maps and Java objects.
List of Java Maps to filter that contain similar department and employee data
static List<?> departmentObjects = list(
map("name", "Engineering",
"employees", list(
map("id", 1, "salary", 100, "firstName", "Rick", "lastName", "Hightower",
"contactInfo", map("phoneNumbers",
list("555-555-0000")
)
),
map("id", 2, "salary", 200, "firstName", "John", "lastName", "Smith",
"contactInfo", map("phoneNumbers", list("555-555-1215",
"555-555-1214", "555-555-1213"))),
map("id", 3, "salary", 300, "firstName", "Drew", "lastName", "Donaldson",
"contactInfo", map("phoneNumbers", list("555-555-1216"))),
map("id", 4, "salary", 400, "firstName", "Nick", "lastName", "LaySacky",
"contactInfo", map("phoneNumbers", list("555-555-1217")))
)
),
map("name", "HR",
"employees", list(
map("id", 5, "salary", 100, "departmentName", "HR",
"firstName", "Dianna", "lastName", "Hightower",
"contactInfo",
map("phoneNumbers", list("555-555-1218"))),
map("id", 6, "salary", 200, "departmentName", "HR",
"firstName", "Derek", "lastName", "Smith",
"contactInfo",
map("phoneNumbers", list("555-555-1219"))),
map("id", 7, "salary", 300, "departmentName", "HR",
"firstName", "Tonya", "lastName", "Donaldson",
"contactInfo", map("phoneNumbers", list("555-555-1220"))),
map("id", 8, "salary", 400, "departmentName", "HR",
"firstName", "Sue", "lastName", "LaySacky",
"contactInfo", map("phoneNumbers", list("555-555-9999")))
)
),
map("name", "Manufacturing", "employees", Collections.EMPTY_LIST),
map("name", "Sales", "employees", Collections.EMPTY_LIST),
map("name", "Marketing", "employees", Collections.EMPTY_LIST)
);
Without future ado, let's start filtering some object. Follow the comments in the listing below.
Lists to work with
List<Department> departments;
List<Department> departmentsWithEmployeeNamedRick;
List<Employee> employees;
/** Copy list of departments. */
departments = Lists.deepCopy(departmentsList);
/** Get all employees in every department. */
employees = (List<Employee>) atIndex(departments, "employees");
The above jus uses some of Boon helper features to make copies of list and slice them and dice them. Essentially we created a list by extracting all of the employees in each department in the list, and it took one like of code. :) That is boon baby!
Now lets start filtering the list. In Boon a filter is basically like a SQL query.
Search for departments who have employees with the first name Rick
/* -----------------
Search for departments that have an employee with the
first name Rick.
*/
departmentsWithEmployeeNamedRick = filter(departments,
contains("employees.firstName", "Rick"));
Think about what we just did. We just searched all of the departments to see if any of them have an employee named "Rick", and we did it with one line of code.
Now I do a little validate so I can turn this example into a test. :)
Validation that it works...Search for departments who have employees with the first name Rick
/* Verify. */
Int.equalsOrDie(1, departmentsWithEmployeeNamedRick.size());
Str.equalsOrDie("Engineering",
departmentsWithEmployeeNamedRick.get(0).getName());
Now lets search for all employees who are in the HR department and this just verify that the search works.
Grab All employees in HR
/* Grab all employees in HR. */
List<Employee> results = filter(employees, eq("department.name", "HR"));
/* Verify. */
Int.equalsOrDie(4, results.size());
Str.equalsOrDie("HR", results.get(0).getDepartment().getName());
Notice Boon has a DSL like construct for doing queries so eq("department.name", "HR") equates to the employees who are in the department named HR.
You can nest criteria in and methods and or methods. The filter method takes many criteria objects so here we are searching for employees who are in HR and who have a salary greater than 30.
Find All employees who work in HR and make more than 301
/** Grab employees in HR with a salary greater than 301 */
results = filter(employees, eq("department.name", "HR"), gt("salary", 301));
/* Verify. */
Int.equalsOrDie(1, results.size());
Str.equalsOrDie("HR", results.get(0).getDepartment().getName());
Str.equalsOrDie("Sue", results.get(0).getFirstName());
Thus far we have worked with Boon with Java instances in a List, but you can also query java.util.Maps in a List as follows:
Now work with Maps
/** Now work with maps */
List<Map<String, Object>> employeeMaps =
(List<Map<String, Object>>) atIndex(departmentObjects, "employees");
/** Grab employees in HR with a salary greater than 301 */
List<Map<String, Object>> resultObjects = filter(employeeMaps,
eq("departmentName", "HR"), gt("salary", 301));
/* Verify. */
Int.equalsOrDie(1, resultObjects.size());
Str.equalsOrDie("HR", (String) resultObjects.get(0).get("departmentName"));
Str.equalsOrDie("Sue", (String) resultObjects.get(0).get("firstName"));
The above is the same code but against list of maps instead of list of Java objects.
The filtering also works with JSON. This is Boon remember and Boon always has a way to work with JSON.
Now work with JSON
/** Now with JSON. */
String json = toJson(departmentObjects);
puts(json);
List<?> array = (List<?>) fromJson(json);
employeeMaps =
(List<Map<String, Object>>) atIndex(array, "employees");
resultObjects = filter(employeeMaps,
eq("departmentName", "HR"), gt("salary", 301));
/* Verify. */
Int.equalsOrDie(1, resultObjects.size());
Str.equalsOrDie("HR", (String) resultObjects.get(0).get("departmentName"));
Str.equalsOrDie("Sue", (String) resultObjects.get(0).get("firstName"));
The criteria can have complex path expressions. We have just scratched the service of what Boon can do. Boon can slice, dice, transform, and search Java objects 50 ways to Sunday.
To read more about Boon sorting and searching capabilities please read.
In addition to linear searching as you saw above, Boon has the ability to run indexed searches as well as ETL transforms for Java and JSON objects in memory.
Much of Boon's sorting, index searching, ETL, and so on came out of Boon's Data Repo, which provides the indexed search capabilities. You can churn through millions of objects in memory in mere moments with indexed collections.
- Boon Data Repo step by step
- Boon Path Expressions which get used by sorting, filtering and indexed collection searches
Boon's DataRepo allows you to treat Java collections more like a database. DataRepo is not an in memory database, and cannot substitute arranging your objects into data structures optimized for your application.
If you want to spend your time providing customer value and building your objects and classes and using the Collections API for your data structures, then Boon DataRepo is meant for you. This does not preclude breaking out the Knuth books and coming up with an optimized data structure. It just helps keep the mundane things easy and so you can spend your time making the hard things possible.
This project came out of a need. I was working on a project that planned to store large collection of domain objects in memory for speed, and somebody asked an all to important question that I overlooked. How are we going to query this data. My answer was we will use the Collections API and the Streaming API. Then I tried to do this...
I tired using the JDK 8 stream API on a large data set, and it was slow. It was a linear search/filter. This is by design, but for what I was doing, it did not work. I needed indexes to support arbitrary queries.
Boon's data repo augments the streaming API and provides a indexed collection search and many other utilities around searching, filtering and transforming Java class instances, Maps, and JSON.
Boon's DataRepo does not endeavor to replace the JDK 8 stream API, and in fact it works well with it. By design, DataRepo works with standard collection libraries.
Boon's data repo makes doing index based queries on collections a lot easier.
It provides a simplified API for doing so.
You can use a wrapper class to wrap a collection into a indexed collection.
Let's say you have a method that creates 200,000 employee objects like this:
List<Employee> employees = TestHelper.createMetricTonOfEmployees(200_000);
So now we have 200,000 employees. Let's search them...
First wrap Employees in a searchable query:
employees = query(employees);
Now search:
List<Employee> results = query(employees, eq("firstName", firstName));
So what is the main difference between the above and the stream API?
employees.stream().filter(emp -> emp.getFirstName().equals(firstName)
About a factor of 20,000% faster to use Boon DataRepo!
There is an API that looks just like your built-in collections. There is also an API that looks more like a DAO object or a Repo Object.
A simple query with the Repo/DAO object looks like this:
List<Employee> employees = repo.query(eq("firstName", "Diana"));
A more involved query would look like this:
List<Employee> employees = repo.query(
and(eq("firstName", "Diana"), eq("lastName", "Smith"), eq("ssn", "21785999")));
Or this:
List<Employee> employees = repo.query(
and(startsWith("firstName", "Bob"), eq("lastName", "Smith"), lte("salary", 200_000),
gte("salary", 190_000)));
Or this:
List<Employee> employees = repo.query(
and(startsWith("firstName", "Bob"), eq("lastName", "Smith"), between("salary", 190_000, 200_000)));
Or if you want to use JDK 8 stream API, this works with it not against it:
int sum = repo.query(eq("lastName", "Smith")).stream().filter(emp -> emp.getSalary()>50_000)
.mapToInt(b -> b.getSalary())
.sum();
The above would be much faster if the number of employees was quite large. It would narrow down the employees whose name started with Smith and had a salary above 50,000. Let's say you had 100,000 employees and only 50 named Smith so now you narrow to 50 quickly by using the TreeMap which effectively pulls 50 employees out of 100_000, then we do the filter over just 50 instead of the whole 100,000.
To learn more about the Boon Data Repo go here:
/*
* Copyright 2013-2014 Richard M. Hightower
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* __________ _____ __ .__
* \______ \ ____ ____ ____ /\ / \ _____ | | _|__| ____ ____
* | | _// _ \ / _ \ / \ \/ / \ / \\__ \ | |/ / |/ \ / ___\
* | | ( <_> | <_> ) | \ /\ / Y \/ __ \| <| | | \/ /_/ >
* |______ /\____/ \____/|___| / \/ \____|__ (____ /__|_ \__|___| /\___ /
* \/ \/ \/ \/ \/ \//_____/
* ____. ___________ _____ ______________.___.
* | |____ ___ _______ \_ _____/ / _ \ / _____/\__ | |
* | \__ \\ \/ /\__ \ | __)_ / /_\ \ \_____ \ / | |
* /\__| |/ __ \\ / / __ \_ | \/ | \/ \ \____ |
* \________(____ /\_/ (____ / /_______ /\____|__ /_______ / / ______|
* \/ \/ \/ \/ \/ \/
*/
package com.examples;
import org.boon.primitive.Int;
import org.boon.Lists;
import org.boon.Str;
import org.boon.criteria.ObjectFilter;
import org.junit.Test;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import static org.boon.Boon.fromJson;
import static org.boon.Boon.puts;
import static org.boon.Boon.toJson;
import static org.boon.Lists.copy;
import static org.boon.Lists.lazyAdd;
import static org.boon.Lists.list;
import static org.boon.Maps.map;
import static org.boon.core.reflection.BeanUtils.atIndex;
import static org.boon.criteria.ObjectFilter.*;
public class FilteringObjects {
public static void main(String... args) {
List<Department> departments;
List<Department> departmentsWithEmployeeNamedRick;
List<Employee> employees;
/** Copy list of departments. */
departments = Lists.deepCopy(departmentsList);
/** Get all employees in every department. */
employees = (List<Employee>) atIndex(departments, "employees");
/* -----------------
Search for departments that have an employee with the
first name Rick.
*/
departmentsWithEmployeeNamedRick = filter(departments,
contains("employees.firstName", "Rick"));
/* Verify. */
Int.equalsOrDie(1, departmentsWithEmployeeNamedRick.size());
Str.equalsOrDie("Engineering",
departmentsWithEmployeeNamedRick.get(0).getName());
/* Grab all employees in HR. */
List<Employee> results = filter(employees, eq("department.name", "HR"));
/* Verify. */
Int.equalsOrDie(4, results.size());
Str.equalsOrDie("HR", results.get(0).getDepartment().getName());
/** Grab employees in HR with a salary greater than 301 */
results = filter(employees, eq("department.name", "HR"), gt("salary", 301));
/* Verify. */
Int.equalsOrDie(1, results.size());
Str.equalsOrDie("HR", results.get(0).getDepartment().getName());
Str.equalsOrDie("Sue", results.get(0).getFirstName());
/** Now work with maps */
List<Map<String, Object>> employeeMaps =
(List<Map<String, Object>>) atIndex(departmentObjects, "employees");
/** Grab employees in HR with a salary greater than 301 */
List<Map<String, Object>> resultObjects = filter(employeeMaps,
eq("departmentName", "HR"), gt("salary", 301));
/* Verify. */
Int.equalsOrDie(1, resultObjects.size());
Str.equalsOrDie("HR", (String) resultObjects.get(0).get("departmentName"));
Str.equalsOrDie("Sue", (String) resultObjects.get(0).get("firstName"));
/** Now with JSON. */
String json = toJson(departmentObjects);
puts(json);
List<?> array = (List<?>) fromJson(json);
employeeMaps =
(List<Map<String, Object>>) atIndex(array, "employees");
resultObjects = filter(employeeMaps,
eq("departmentName", "HR"), gt("salary", 301));
/* Verify. */
Int.equalsOrDie(1, resultObjects.size());
Str.equalsOrDie("HR", (String) resultObjects.get(0).get("departmentName"));
Str.equalsOrDie("Sue", (String) resultObjects.get(0).get("firstName"));
}
@Test
public void test() {
FilteringObjects.main();
}
public static class ContactInfo {
String address;
List<String> phoneNumbers;
}
public static class Employee implements Comparable<Employee> {
int id;
int salary;
String firstName;
String lastName;
ContactInfo contactInfo = new ContactInfo();
Department department;
public Employee() {
}
public Employee(int id, int salary, String firstName, String lastName,
String... phoneNumbers) {
this.id = id;
this.salary = salary;
this.firstName = firstName;
this.lastName = lastName;
for (String phone : phoneNumbers) {
contactInfo.phoneNumbers = lazyAdd(contactInfo.phoneNumbers, phone);
}
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public int getSalary() {
return salary;
}
public void setSalary(int salary) {
this.salary = salary;
}
public String getFirstName() {
return firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
public String getLastName() {
return lastName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
public Department getDepartment() {
return department;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Employee employee = (Employee) o;
if (id != employee.id) return false;
if (salary != employee.salary) return false;
if (firstName != null ? !firstName.equals(employee.firstName) : employee.firstName != null) return false;
if (lastName != null ? !lastName.equals(employee.lastName) : employee.lastName != null) return false;
return true;
}
@Override
public int hashCode() {
int result = id;
result = 31 * result + salary;
result = 31 * result + (firstName != null ? firstName.hashCode() : 0);
result = 31 * result + (lastName != null ? lastName.hashCode() : 0);
return result;
}
@Override
public String toString() {
return "Employee{" +
"id=" + id +
", salary=" + salary +
", department=" + (department == null ? "NONE" : department.getName()) +
", phone number=" + atIndex(this, "contactInfo.phoneNumbers[0]") +
", firstName='" + firstName + '\'' +
", lastName='" + lastName + '\'' +
"}";
}
@Override
public int compareTo(Employee otherEmployee) {
return this.firstName.compareTo(otherEmployee.firstName);
}
public void setDepartment(Department department) {
this.department = department;
}
}
public static class Department {
private String name;
private List<Employee> employees;
public Department() {
}
public Department(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Department add(Employee... employees) {
for (Employee employee : employees) {
employee.setDepartment(this);
}
this.employees = lazyAdd(this.employees, employees);
return this;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Department that = (Department) o;
if (employees != null ? !employees.equals(that.employees) : that.employees != null) return false;
if (name != null ? !name.equals(that.name) : that.name != null) return false;
return true;
}
@Override
public int hashCode() {
int result = name != null ? name.hashCode() : 0;
result = 31 * result + (employees != null ? employees.hashCode() : 0);
return result;
}
@Override
public String toString() {
return "Department{" +
"name='" + name + '\'' +
", employees=" + atIndex(employees, "id") +
'}';
}
}
static List<Department> departmentsList = list(
new Department("Engineering").add(
new Employee(1, 100, "Rick", "Hightower", "555-555-1000"),
new Employee(2, 200, "John", "Smith", "555-555-1215", "555-555-1214", "555-555-1213"),
new Employee(3, 300, "Drew", "Donaldson", "555-555-1216"),
new Employee(4, 400, "Nick", "LaySacky", "555-555-1217")
),
new Department("HR").add(
new Employee(5, 100, "Dianna", "Hightower", "555-555-1218"),
new Employee(6, 200, "Derek", "Smith", "555-555-1219"),
new Employee(7, 300, "Tonya", "Donaldson", "555-555-1220"),
new Employee(8, 400, "Sue", "LaySacky", "555-555-9999")
), new Department("Manufacturing").add(),
new Department("Sales").add(),
new Department("Marketing").add()
);
static List<?> departmentObjects = list(
map("name", "Engineering",
"employees", list(
map("id", 1, "salary", 100, "firstName", "Rick", "lastName", "Hightower",
"contactInfo", map("phoneNumbers",
list("555-555-0000")
)
),
map("id", 2, "salary", 200, "firstName", "John", "lastName", "Smith",
"contactInfo", map("phoneNumbers", list("555-555-1215",
"555-555-1214", "555-555-1213"))),
map("id", 3, "salary", 300, "firstName", "Drew", "lastName", "Donaldson",
"contactInfo", map("phoneNumbers", list("555-555-1216"))),
map("id", 4, "salary", 400, "firstName", "Nick", "lastName", "LaySacky",
"contactInfo", map("phoneNumbers", list("555-555-1217")))
)
),
map("name", "HR",
"employees", list(
map("id", 5, "salary", 100, "departmentName", "HR",
"firstName", "Dianna", "lastName", "Hightower",
"contactInfo",
map("phoneNumbers", list("555-555-1218"))),
map("id", 6, "salary", 200, "departmentName", "HR",
"firstName", "Derek", "lastName", "Smith",
"contactInfo",
map("phoneNumbers", list("555-555-1219"))),
map("id", 7, "salary", 300, "departmentName", "HR",
"firstName", "Tonya", "lastName", "Donaldson",
"contactInfo", map("phoneNumbers", list("555-555-1220"))),
map("id", 8, "salary", 400, "departmentName", "HR",
"firstName", "Sue", "lastName", "LaySacky",
"contactInfo", map("phoneNumbers", list("555-555-9999")))
)
),
map("name", "Manufacturing", "employees", Collections.EMPTY_LIST),
map("name", "Sales", "employees", Collections.EMPTY_LIST),
map("name", "Marketing", "employees", Collections.EMPTY_LIST)
);
static boolean ok;
}
Thoughts? Write me at richard high tower AT g mail dot c-o-m (Rick Hightower).
If you are new to boon start here:
- Java Boon Byte Buffer Builder
- Java Boon Slice Notation
- Java Boon Slice's work with TreeSets
- Java Boon Description
- More...
- Boon Home
- Boon Source
- Introducing Boon October 2013
- Java Slice Notation
- What if Java collections were easy to search and sort?
- Boon HTTP utils
- Boon Java JSON parser Benchmarks or hell yeah JSON parsing is damn fast!
- Boon JSON parser is really damn fast! Part II
- Boon JSON parser Round III now just not fast as but much faster than other Java JSON parsers
- Boon World's fastest Java JSON parser Round IV from fast to blazing to rocket fuel aka Braggers going to brag
- Boon gets adopted by JSON Path as the default Java JSON parser
- Boon graphics showing just how fast Boon JSON parsing is - about 50% to 200% faster than the graphs shown here now so wicked fast became wickeder - just got sick of making graphics
- 10 minute guide to Boon JSON parsing after I added @JsonIgnore, @JsonProperty, @JsonView, @Exposes, etc.
- Hightower speaks to the master of Java JSON parsing, the king of speed The COW TOWN CODER!
- Boon provides easy Java objects from lists, from maps and from JSON.
Easily read in files into lines or a giant string with one method call. Works with files, URLs, class-path, etc. Boon IO support will surprise you how easy it is. Boon has Slice notation for dealing with Strings, Lists, primitive arrays, Tree Maps, etc. If you are from Groovy land, Ruby land, Python land, or whatever land, and you have to use Java then Boon might give you some relief from API bloat. If you are like me, and you like to use Java, then Boon is for you too. Boon lets Java be Java, but adds the missing productive APIs from Python, Ruby, and Groovy. Boon may not be Ruby or Groovy, but its a real Boon to Java development.
Core Boon will never have any dependencies. It will always be able to run as a single jar. This is not just NIH, but it is partly. My view of what Java needs is more inline with what Python, Ruby and Groovy provide. Boon is an addition on top of the JVM to make up the difference between the harder to use APIs that come with Java and the types of utilities that are built into Ruby, Python, PHP, Groovy etc. Boon is a Java centric view of those libs. The vision of Boon and the current implementation is really far apart.
===
Contact Info
blog|[twitter](https://twitter.com/RickHigh|[infoq]http://www.infoq.com/author/Rick-Hightower|[stackoverflow](http://stackoverflow.com/users/2876739/rickhigh)|[java lobby](http://java.dzone.com/users/rhightower)|Other | richard high tower AT g mail dot c-o-m (Rick Hightower)|work|cloud|nosql
YourKit supports Boon open source project with its full-featured Java Profiler. YourKit, LLC is the creator of innovative and intelligent tools for profiling Java and .NET applications. Take a look at YourKit's leading software products: YourKit Java Profiler and YourKit .Net profiler.