json-mask selects fields from JSON-compatible Ruby objects while preserving the shape of the
response. It implements the field selector language used by Google's partial responses and the
JSON Mask project.
The library has no runtime dependencies.
Add the gem to your Gemfile:
gem "json-mask"Then run bundle install.
require "json_mask"
response = {
"id" => "abc123",
"name" => "Product demo",
"permissions" => [
{"id" => "owner", "role" => "owner", "email" => "owner@example.com"}
]
}
JsonMask.call(response, "id,permissions(id,role)")
# => {
# "id" => "abc123",
# "permissions" => [{"id" => "owner", "role" => "owner"}]
# }JsonMask.mask is an alias for JsonMask.call.
Compile selectors that will be reused:
mask = JsonMask.compile("id,name,permissions(role)")
mask.call(first_response)
mask.call(second_response)Compiled masks are immutable and safe to share between threads.
Passing nil, an empty string, or a whitespace-only string returns the original value unchanged.
This makes an optional HTTP fields parameter straightforward:
render json: JsonMask.call(payload, params[:fields])The syntax is loosely based on XPath:
| Selector | Meaning |
|---|---|
id,name |
Select multiple fields |
permissions/role |
Select a nested field |
permissions(id,role) |
Select multiple fields from an object or each object in an array |
permissions/* |
Select every field below permissions |
items/*/id |
Select id from every value below items |
Slash paths and parenthesized sub-selections traverse arrays transparently. Empty hashes remain in arrays, preserving their positions. Missing fields are omitted.
Backslash escapes structural characters in field names:
JsonMask.call({"a/b" => 1, "other" => 2}, 'a\/b')
# => {"a/b" => 1}
JsonMask.call({"*" => 1, "other" => 2}, '\\*')
# => {"*" => 1}The structural characters are ,, /, (, ), *, and \\. An asterisk is a wildcard only
when it is the entire, unescaped field name. Unescaped whitespace around field names and operators
is ignored; whitespace inside a field name is preserved.
String and symbol hash keys are supported, and the result preserves the key objects from the input. The input is never mutated.
Malformed selectors raise JsonMask::ParseError, which includes the original expression and the
zero-based character offset:
JsonMask.compile("files(id,,name)")
# raises JsonMask::ParseError: expected a field name at offset 9The parser applies conservative defaults suitable for accepting selectors from HTTP or MCP clients:
- Maximum selector length: 16,384 bytes
- Maximum nesting depth: 64
- Maximum field selectors: 1,000
The limits can be tightened for a specific boundary:
JsonMask.compile(fields, max_length: 1_024, max_depth: 16, max_selectors: 100)Exceeding a limit raises JsonMask::LimitError, a subclass of JsonMask::ParseError.
Validation is syntactic. Because the library has no response schema, a well-formed selector that names a field absent from the input simply omits that field; it cannot produce Google's schema-aware "Invalid field selection" error on its own.
The supported grammar follows the Google Drive fields parameter rules
and JSON Mask's documented grammar. This library intentionally validates malformed expressions
instead of attempting to recover from them.
The projector accepts JSON-compatible Hash and Array values. If a selected field contains a
scalar where the selector asks for nested fields, that field is omitted — except nil, which
passes through unchanged (matching the reference implementation), so a nullable field stays
distinguishable from an unselected one. A non-container root value with a non-empty selector
produces nil.
bundle install
bundle exec rake
bundle exec rake buildThe default Rake task runs the full test suite and RuboCop.
MIT. See LICENSE.txt.