JSON SPI
Every jsass setup needs a JSON deserializer, and the reason is not obvious: jsass reads
package.json files. Both module resolvers have to find a package's entry point before they can
hand its JavaScript to the engine, and that entry point is declared in package.json — inside the
sass WebJar for WebjarModuleResolver, inside node_modules/sass/ for NodeModulesResolver.
Rather than bundling a JSON parser and forcing a version on you, jsass declares a one-method SPI:
public interface JsonDeserializer {
Map<String, Object> deserialize(InputStream inputStream) throws IOException;
}
Pick an implementation
| Artifact | Class | Runs on |
|---|---|---|
jsass.jackson2 |
Jackson2JsonDeserializer |
Jackson 2.x (com.fasterxml.jackson) |
jsass.jackson3 |
Jackson3JsonDeserializer |
Jackson 3.x (tools.jackson) |
Take the one that matches the Jackson your application already has — Spring Boot 4 ships
Jackson 3, Spring Boot 3 still Jackson 2. If you have neither, jsass.jackson2 is the safe
default.
Discovery
You do not have to wire the deserializer into anything. Both artifacts register their
implementation as a ServiceLoader provider (provides io.bit3.jsass.json.JsonDeserializer in
their module descriptor, plus META-INF/services for the class path), and a resolver built
without jsonDeserializer(…) calls JsonDeserializer.discover():
discover() insists on exactly one provider visible to jsass's class loader. Zero providers or
more than one fail the resolver's build() with an IllegalStateException that names what to do —
so do not put jsass.jackson2 and jsass.jackson3 on the same runtime classpath and rely on
discovery. JsonDeserializer.discover(ClassLoader) searches a class loader of your choice.
Passing one explicitly
An explicit deserializer bypasses discovery. Use it when both Jackson modules are present, when the provider lives in a class loader jsass cannot see, or simply to be explicit:
import io.bit3.jsass.js.webjars.WebjarModuleResolver;
import io.bit3.jsass.json.jackson2.Jackson2JsonDeserializer;
var moduleResolver = WebjarModuleResolver.builder()
.jsonDeserializer(new Jackson2JsonDeserializer())
.build();
NodeModulesResolver.builder() takes the same jsonDeserializer(…). Both implementations are
stateless and thread-safe; one instance per application is plenty.
Bring your own
The SPI is small enough that any JSON library will do — Gson, JSON-B, Moshi, a hand-written
parser. Implement the interface and pass it to the resolver with jsonDeserializer(…) — or
register it as a ServiceLoader provider for io.bit3.jsass.json.JsonDeserializer to have it
discovered:
public class GsonJsonDeserializer implements JsonDeserializer {
private static final Type MAP_TYPE = new TypeToken<Map<String, Object>>() {}.getType();
private final Gson gson = new Gson();
@Override
public Map<String, Object> deserialize(InputStream inputStream) throws IOException {
try (var reader = new InputStreamReader(inputStream, StandardCharsets.UTF_8)) {
return gson.fromJson(reader, MAP_TYPE);
}
}
}
Only the keys a package.json uses to declare its entry point are ever read, so a plain nested
Map is all the SPI has to produce.