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 3, for
instance, ships 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.