MCPcopy Index your code
hub / github.com/bohnman/squiggly

github.com/bohnman/squiggly @1.3.18

Chat with this repo
repository ↗ · DeepWiki ↗ · release 1.3.18 ↗ · + Follow
613 symbols 1,260 edges 100 files 56 documented · 9%
What it actually does AI analysis from the code graph — generated when you open this
loading…
README

Important Note

As of version 1.3.2, the preferred way to specify nested filters is to use square brackets intead of braces.

Preferred: assignee[firstName]

No longer Preferred but will still work: assignee{firstName}

The reason for this is that newer versions of Tomcat no longer allow braces to be specified on the url without being escaped. Square brackets are still permitted in the url and it is preferred to make the syntax url friendly.

Squiggly Filter For Jackson

Contents

What is it?

The Squiggly Filter is a Jackson JSON PropertyFilter, which selects properties of an object/list/map using a subset of the Facebook Graph API filtering syntax.

Probably the most common use of this library is to filter fields on the querystring like so:

?fields=id,reporter[firstName]

Integrating Squiggly into your webapp is covered in Custom Integration.

Requirements

Installation

Maven

<dependency>
    <groupId>com.github.bohnman</groupId>
    <artifactId>squiggly-filter-jackson</artifactId>
    <version>1.3.18</version>
</dependency>

General Usage

ObjectMapper objectMapper = Squiggly.init(new ObjectMapper(), "assignee{firstName}");
Issue object = new Issue();         // replace this with your object/collection/map here
System.out.println(SquigglyUtils.stringify(objectMapper, object));

Alternatively, if you need more control over configuring the ObjectMapper, you can do it this way:

String filterId = SquigglyPropertyFilter.FILTER_ID;
SquigglyPropertyFilter propertyFilter = new SquigglyPropertyFilter("assignee[firstName]");  // replace with your filter here
SimpleFilterProvider filterProvider = new SimpleFilterProvider().addFilter(filterId, propertyFilter);

ObjectMapper objectMapper = new ObjectMapper();
objectMapper.setFilterProvider(filterProvider);
objectMapper.addMixIn(Object.class, SquigglyPropertyFilterMixin.class);

Issue object = new Issue();         // replace this with your object/collection/map here
System.out.println(SquigglyUtils.stringify(objectMapper, object));

Also, you can generate a Plain Old Java Object (POJO) instead of a JSON String

ObjectMapper objectMapper = Squiggly.init(new ObjectMapper(), "assignee{firstName}");
System.out.println(SquigglyUtils.objectify(objectMapper, object, Object.class));

For applying filter on Collection of Objects and for returning Collection of POJOs instead of JSON String

List<User> users = Arrays.asList(
        new User("Peter", 12, "Dinklage"), 
        new User("Lena", 13, "Heady"));
String filter = "firstName,age";
ObjectMapper objectMapper = Squiggly.init(new ObjectMapper(), filter);  
List<User> filteredUsers = SquigglyUtils.listify(objectMapper, users, User.class);
// setify is also availble

Reference Object

For the filtering examples, let's use an the example object of type Issue

{
  "id": "ISSUE-1",
  "issueSummary": "Dragons Need Fed",
  "issueDetails": "I need my dragons fed pronto.",
  "reporter": {
    "firstName": "Daenerys",
    "lastName": "Targaryen"
  },
  "assignee": {
    "firstName": "Jorah",
    "lastName": "Mormont"
  },
  "actions": [
    {
      "id": null,
      "type": "COMMENT",
      "text": "I'm going to let Daario get this one.",
      "user": {
        "firstName": "Jorah",
        "lastName": "Mormont"
      }
    },
    {
      "id": null,
      "type": "CLOSE",
      "text": "All set.",
      "user": {
        "firstName": "Daario",
        "lastName": "Naharis"
      }
    }
  ],
  "properties": {
    "priority": "1",
    "email": "motherofdragons@got.com"
  }
}

Top-Level Filters

Select No Fields

String filter = "";
ObjectMapper mapper = Squiggly.init(mapper, filter);
System.out.println(SquigglyUtils.stringify(mapper, issue));
// prints {}

Select Single Field

filter = "id";
ObjectMapper mapper = Squiggly.init(mapper, filter);
System.out.println(SquigglyUtils.stringify(mapper, issue));
// prints {"id":"ISSUE-1"}

Select Multiple Fields

filter = "id,issueSummary"
ObjectMapper mapper = Squiggly.init(mapper, filter);
System.out.println(SquigglyUtils.stringify(mapper, issue));
// prints {"id":"ISSUE-1", "issueSummary":"Dragons Need Fed"}

Select Fields Using Wildcards

filter = "issue*";
ObjectMapper mapper = Squiggly.init(mapper, filter);
System.out.println(SquigglyUtils.stringify(mapper, issue));
// prints {"issueSummary":"Dragons Need Fed", "issueDetails": "I need my dragons fed pronto."}

Select All Fields

filter = "**";
ObjectMapper mapper = Squiggly.init(mapper, filter);
System.out.println(SquigglyUtils.stringify(mapper, issue));
// prints the same json as our example object

Select All Fields of object, but only base fields of associated objects (more on this later)

filter = "*";
ObjectMapper mapper = Squiggly.init(mapper, filter);
System.out.println(SquigglyUtils.stringify(mapper, issue));
/* prints the following:
{
  "id": "ISSUE-1",
  "issueSummary": "Dragons Need Fed",
  "issueDetails": "I need my dragons fed pronto.",
  "reporter": {
    "firstName": "Daenerys",
    "lastName": "Targaryen"
  },
  "assignee": {
    "firstName": "Jorah",
    "lastName": "Mormont"
  },
  "actions": [
    {
      "id": null,
      "type": "COMMENT",
      "text": "I'm going to let Daario get this one.."
    },
    {
      "id": null,
      "type": "CLOSE",
      "text": "All set."
    }
  ],
  "properties": {
    "priority": "1",
    "email": "motherofdragons@got.com"
  }
}
*/

Nested Filters

Select Single Nested Field

String filter = "assignee[firstName]";
ObjectMapper mapper = Squiggly.init(mapper, filter);
System.out.println(SquigglyUtils.stringify(mapper, issue));
// prints {"assignee":{"firstName":"Jorah"}}

Select Multiple Nested Fields

String filter = "actions[text,type]";
ObjectMapper mapper = Squiggly.init(mapper, filter);
System.out.println(SquigglyUtils.stringify(mapper, issue));
// prints {"actions":[{"type":"COMMENT","text":"I'm going to let Daario get this one.."},{"type":"CLOSE","text":"All set."}]}
// NOTE: use can also use wildcards (e.g. actions{t*})

Select Same Field From Different Nested Objects

String filter = "(assignee,reporter)[firstName]";
ObjectMapper mapper = Squiggly.init(mapper, filter);
System.out.println(SquigglyUtils.stringify(mapper, issue));
// prints {"reporter":{"firstName":"Daenerys"},"assignee":{"firstName":"Jorah"}}

Select Deeply Nested Field

String filter = "actions[user[lastName]]";
ObjectMapper mapper = Squiggly.init(mapper, filter);
System.out.println(SquigglyUtils.stringify(mapper, issue));
// prints {"actions":[{"user":{"lastName":"Mormont"}},{"user":{"lastName":"Naharis"}}]}

Dot Syntax

As an alternative to using the braces syntax for nested filter, you can use the dot syntax

String filter = "assignee.firstName";
ObjectMapper mapper = Squiggly.init(mapper, filter);
System.out.println(SquigglyUtils.stringify(mapper, issue));
// prints {"assignee":{"firstName":"Jorah"}}

You can exclude fields using the dot syntax. Note that the exclusion applies to the last field.

String filter = "-assignee.firstName";
ObjectMapper mapper = Squiggly.init(mapper, filter);
System.out.println(SquigglyUtils.stringify(mapper, issue));
// prints {"assignee":{"lastName":"Mormont"}}

You can also combine the dot syntax with the nested syntax.

String filter = "actions.user[firstName]";
ObjectMapper mapper = Squiggly.init(mapper, filter);
System.out.println(SquigglyUtils.stringify(mapper, issue));
// prints {"actions":[{"user":{"firstName":"Jorah"}},{"user":{"firstName":"Daario"}}]}

One limitation is that you cannot use the | syntax with the dot syntax

String filter = "(actions.user,assignee)[firstName]";
ObjectMapper mapper = Squiggly.init(mapper, filter);
System.out.println(SquigglyUtils.stringify(mapper, issue));
// throws exception

Regex Filters

In addition to using wildcards, you can also use regular expressions.

Here's an example:

String filter = "~iss[a-z]e.*~";
ObjectMapper mapper = Squiggly.init(mapper, filter);
System.out.println(SquigglyUtils.stringify(mapper, issue));
// prints {"issueSummary":"Dragons Need Fed","issueDetails":"I need my dragons fed pronto."}

Notice the tildes mark the begin and of the regex pattern.

You can also specifiy a case insensitive match.

String filter = "~iss[a-z]esumm.*~i";
ObjectMapper mapper = Squiggly.init(mapper, filter);
System.out.println(SquigglyUtils.stringify(mapper, issue));
// prints {"issueSummary":"Dragons Need Fed"}

Why use tildes and not forward slashes for regular expressions? Tildes are query string friendly and forward slashes are not.

However, you may use forward slashes if you like.

String filter = "/iss[a-z]esumm.*/i";
ObjectMapper mapper = Squiggly.init(mapper, filter);
System.out.println(SquigglyUtils.stringify(mapper, issue));
// prints {"issueSummary":"Dragons Need Fed"}

Other Filters

Selecting from Maps

Selecting from maps is the same as selecting from objects. Instead of selecting from fields, you are selecting from keys. The main downside of selecting from maps that their matches are unable to be cached.

Map<String, Object> map = new HashMap<>();
map.put("foo", "bar");
map.put("bear", "baz");
String filter = "foo";
ObjectMapper mapper = Squiggly.init(mapper, filter);
System.out.println(SquigglyUtils.stringify(mapper, map));
// prints {"foo":"bar"}

Selecting from Collections (Lists/Arrays/Etc).

Selecting from collection just assumes the top-level objects are the elements in the collection, not the collection itself.

List<User> list = Arrays.asList(
    new User("Peter", "Dinklage"),
    new User("Lena", "Heady")
);

String filter = "firstName";
ObjectMapper mapper = Squiggly.init(mapper, filter);
System.out.println(SquigglyUtils.stringify(mapper, list));
// prints [{"firstName":"Peter"}, {"firstName":"Lena"}]

Resolving Conflicts

When a filter includes two criteria that match the same field, the one that is more specific wins.

For example, if the filter is "**,reporter[firstName]", then all fields will be excluded. However, the reporter field will only include the firstName field.

Specificity is determined using the following logic:

  • an exact name is the most specific
  • a ** is the least specific
  • a * is the second to least specific
  • otherwise, the number of non-wildcard characters is counted, the higher the number, the more specific
  • if two filters have the same specificity, the latter one is chosen

Excluding Fields

In order to exclude fields, you need to prefix the field name with a minus sign (-).

Let's look at an example

String filter = "-reporter";
ObjectMapper mapper = Squiggly.init(mapper, filter);
System.out.println(SquigglyUtils.stringify(mapper, issue));
// prints everything except the reporter field

Here's an example excluding a nested field:

String filter = "**,reporter[-firstName]";
ObjectMapper mapper = Squiggly.init(mapper, filter);
System.out.println(SquigglyUtils.stringify(mapper, issue));
// prints everything, the reporter object will only have the firstName excluded

NOTE: Excluded fields can't have nested filters

String filter = "**,-reporter[firstName]";
ObjectMapper mapper = Squiggly.init(mapper, filter);
System.out.println(SquigglyUtils.stringify(mapper, issue));
// throws an exception

Property Views

In addition to selecting fields by name, you can assign a name to a group of fields. This is called a property view.

Reference Objects

Let's use these reference objects for the examples.

@Target(FIELD)
@Retention(RUNTIME)
@Documented
@PropertyView({"super"})
public @interface SuperView {
}

class Address {
    String line1 = "55 Hollywood Blvd.";
    String line2 = "";
    String city = "Hollywood";
    String state = "CA";

    @SuperView
    double lat;

    @SuperView
    double lon;
}

class User {
    String firstName = "Peter";
    String lastName = "Dinklage";

    @PropertyView("secret")
    String phone = "555-555-1212";

    @SuperView
    Address address;

}

The Base View

If nothing is annotated on a field, it is assumed to belong to the "base" view. There is @BaseView convenience annotation, but it's not needed. See Changing Defaults to alter this behavior.

In the case of User, fields firstName and lastName

Extension points exported contracts — how you extend this code

SquigglyMetricsSource (Interface)
Represents a source that can supply metrics. Implementations should be thread safe. [4 implementers]
src/main/java/com/github/bohnman/squiggly/metric/source/SquigglyMetricsSource.java
HotelService (Interface)
(no doc) [2 implementers]
examples/spring-data-jpa-hibernate/src/main/java/sample/data/jpa/service/HotelService.java
PersonRepository (Interface)
(no doc)
examples/spring-data-rest/src/main/java/com/github/bohnman/examples/springdatarest/PersonRepository.java
SquigglyName (Interface)
(no doc) [10 implementers]
src/main/java/com/github/bohnman/squiggly/name/SquigglyName.java
CityService (Interface)
(no doc) [2 implementers]
examples/spring-data-jpa-hibernate/src/main/java/sample/data/jpa/service/CityService.java
ConcertRepository (Interface)
(no doc)
examples/spring-data-rest/src/main/java/com/github/bohnman/examples/springdatarest/ConcertRepository.java
SquigglyContext (Interface)
A squiggly context provides parsing and filtering information to the {@link com.github.bohnman.squiggly.filter.SquigglyP [2 …
src/main/java/com/github/bohnman/squiggly/context/SquigglyContext.java
ReviewsSummary (Interface)
(no doc) [2 implementers]
examples/spring-data-jpa-hibernate/src/main/java/sample/data/jpa/service/ReviewsSummary.java

Core symbols most depended-on inside this repo

put
called by 48
src/main/java/com/github/bohnman/squiggly/web/RequestSquigglyContextProvider.java
get
called by 24
src/main/java/com/github/bohnman/squiggly/name/AnyDeepName.java
getName
called by 16
src/main/java/com/github/bohnman/squiggly/name/SquigglyName.java
equals
called by 15
src/main/java/com/github/bohnman/squiggly/filter/SquigglyPropertyFilter.java
collectify
called by 8
src/main/java/com/github/bohnman/squiggly/util/SquigglyUtils.java
toString
called by 7
examples/spring-data-jpa-hibernate/src/main/java/sample/data/jpa/domain/City.java
init
called by 6
src/main/java/com/github/bohnman/squiggly/Squiggly.java
createName
called by 6
src/main/java/com/github/bohnman/squiggly/parser/SquigglyParser.java

Shape

Method 513
Class 84
Interface 14
Enum 2

Languages

Java100%

Modules by API surface

src/test/java/com/github/bohnman/squiggly/filter/SquigglyPropertyFilterTest.java54 symbols
src/main/java/com/github/bohnman/squiggly/filter/SquigglyPropertyFilter.java28 symbols
src/main/java/com/github/bohnman/squiggly/parser/SquigglyParser.java18 symbols
examples/standalone/src/main/java/com/github/bohnman/squiggly/examples/standalone/model/Issue.java17 symbols
examples/spring-boot/src/main/java/com/github/bohnman/squiggly/examples/springboot/model/Issue.java17 symbols
examples/servlet/src/main/java/com/github/bohnman/squiggly/examples/servlet/model/Issue.java17 symbols
examples/dropwizard/src/main/java/com/github/bohnman/squiggly/examples/dropwizard/model/Issue.java17 symbols
src/main/java/com/github/bohnman/squiggly/config/SquigglyConfig.java16 symbols
src/main/java/com/github/bohnman/squiggly/web/RequestSquigglyContextProvider.java15 symbols
src/test/java/com/github/bohnman/squiggly/model/Issue.java13 symbols
examples/spring-data-jpa-hibernate/src/main/java/sample/data/jpa/domain/Review.java13 symbols
src/main/java/com/github/bohnman/squiggly/bean/BeanInfoIntrospector.java12 symbols

For agents

$ claude mcp add squiggly \
  -- python -m otcore.mcp_server <graph>

⬇ download graph artifact