> ## Documentation Index
> Fetch the complete documentation index at: https://docs.westyx.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# Spring Boot SDK - JSON values

> The JsonValue tree, how the SDK picks a Jackson major, and migrating from JsonNode.

*New in v0.11.0.*

Config values arrive as `dev.westyx.nexus.json.JsonValue`, the SDK's own immutable JSON tree.

```java theme={null}
JsonValue tuning = nexus.getConfig("tuning").orElseThrow();

int retries      = tuning.get("retries").asInt(3);
String strategy  = tuning.get("strategy").asString("linear");
boolean jitter   = tuning.get("jitter").asBoolean(false);
JsonValue nested = tuning.get("limits").get("per_host");   // no null checks needed
String rawJson   = tuning.toJson();
```

## Why not Jackson's `JsonNode`

Because it would decide your Spring Boot version for you.

Jackson 2 lives in `com.fasterxml.jackson` and Jackson 3 lives in `tools.jackson`. They are different namespaces, deliberately, so the two can co-exist on a classpath during a migration. Spring Boot 3 ships the first and Spring Boot 4 ships the second.

A `JsonNode` on `getConfig` would therefore have pinned this SDK to one Jackson major, and through it to one Spring Boot major: the starter would have had to ship as two artifacts, and upgrading Spring Boot would have meant swapping SDK coordinates. Returning a type the SDK owns removes the coupling - **one artifact serves Spring Boot 3.5, 4.0 and 4.1.**

## Which Jackson does the SDK use

Whichever one your application already has. Both are `<optional>` dependencies, so the starter adds neither to your classpath.

| Your framework                                                | Jackson on the classpath | Adapter the SDK selects                      |
| ------------------------------------------------------------- | ------------------------ | -------------------------------------------- |
| Spring Boot 3.5 (`spring-boot-starter-json`)                  | Jackson 2                | `Jackson2Codec`                              |
| Spring Boot 4.x (`spring-boot-starter-jackson`)               | Jackson 3                | `Jackson3Codec`                              |
| Both present (Boot 4 plus the Jackson 2 compatibility module) | both                     | `Jackson3Codec` - the one your own beans use |

Selection happens once, when the client is built, and the SDK reports it on startup:

```
nexus: client ready (key_type=sk, kind=backend, wif=off, json=jackson3, ttl=PT1M)
```

<Note>
  With no Jackson on the classpath at all, client construction fails with a `NexusException` that names both sets of coordinates - not a `NoClassDefFoundError` from somewhere inside the SDK.
</Note>

## Reading values

`JsonValue` carries exactly what JSON has: strings, numbers, booleans, null, objects and arrays. Numbers are held as `BigDecimal`, so nothing is lost between the wire and whichever accessor you call.

| Method                                                                   | Behaviour                                                           |
| ------------------------------------------------------------------------ | ------------------------------------------------------------------- |
| `isString()` / `isNumber()` / `isBoolean()` / `isObject()` / `isArray()` | type checks                                                         |
| `isNull()`                                                               | JSON `null` - distinct from missing                                 |
| `isMissing()`                                                            | no such key or index                                                |
| `isPresent()`                                                            | present and not JSON `null`                                         |
| `asString()` / `asString(default)`                                       | string form of a scalar; numbers and booleans render as JSON        |
| `asInt(default)` / `asLong(default)` / `asDouble(default)`               | numeric form, default when not numeric                              |
| `numberValue()`                                                          | exact `BigDecimal`, or `null` when not a number                     |
| `get(String field)` / `get(int index)`                                   | child value, or `missing()` - **never `null`**                      |
| `size()` / `propertyNames()` / `properties()` / `elements()`             | structure                                                           |
| `toJson()`                                                               | JSON text                                                           |
| `toJavaObject()`                                                         | plain `Map` / `List` / `String` / `BigDecimal` / `Boolean` / `null` |

Absent lookups return `missing()` rather than `null`, so `value.get("a").get("b").asString("")` is safe without intermediate checks.

## Binding to your own types

`getConfigAs` goes through the application's object mapper, so your modules, naming strategy and date handling all apply:

```java theme={null}
record Endpoint(String host, int port) { }

Endpoint endpoint = nexus.getConfigAs("db.endpoint", Endpoint.class).orElseThrow();
```

In a Spring Boot application the auto-configuration wires this from your own `ObjectMapper` bean - on either Jackson major, through two nested `@ConditionalOnClass` configurations. Nothing to configure.

Outside Spring, wrap a mapper yourself:

```java theme={null}
// Jackson 3 (Spring Boot 4)
NexusConfig config = NexusConfig.builder()
        .baseUrl("https://<slug>.nexus.westyx.dev")
        .apiKey(System.getenv("NEXUS_API_KEY"))
        .jsonCodec(Jackson3Codec.using(myJsonMapper))
        .build();

// Jackson 2 (Spring Boot 3)
        .jsonCodec(Jackson2Codec.using(myObjectMapper))
```

Or hand the raw text to your own mapper: `myMapper.readValue(value.toJson(), MyType.class)`.

## Migrating from v0.10.0

| Before                                              | After                                                                    |
| --------------------------------------------------- | ------------------------------------------------------------------------ |
| `Optional<JsonNode> v = nexus.getConfig(k)`         | `Optional<JsonValue> v = nexus.getConfig(k)`                             |
| `Map<String, JsonNode> all = nexus.getAllConfigs()` | `Map<String, JsonValue> all = nexus.getAllConfigs()`                     |
| `node.asText()`                                     | `value.asString()`                                                       |
| `node.isTextual()`                                  | `value.isString()`                                                       |
| `node.path("x")`                                    | `value.get("x")`                                                         |
| `node.asInt()`                                      | `value.asInt(default)` - the default is required, there is no silent `0` |
| `NexusConfig.builder().objectMapper(m)`             | `.jsonCodec(Jackson3Codec.using(m))` or `Jackson2Codec.using(m)`         |

`getSecret`, `getFlag`, `evaluateAB` and every other method are unchanged.

## Next

* [API reference](/sdks/spring-boot/api-reference)
* [Configuration](/sdks/spring-boot/configuration)
* [Installation](/sdks/spring-boot/installation)
