Skip to content

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 the V8 route, inside node_modules/sass/ for the Node route.

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.

var moduleResolver = WebjarV8ModuleResolver.builder()
    .jsonDeserializer(new Jackson2JsonDeserializer())
    .build();

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:

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.