Fusion JSON support
Fusion JSON proposes a JsonMapper API which intends to support record models.
Here the supported features list:
-
Models must be records or
List<X>of a supported type, or aMap<String, X>of a supported type -
Supported primitives are
-
String -
String,BigDecimal(represented asstringin JSON but incoming data can be a number),{b,B}oolean,{d,D}ouble,int/Integer,{l,L}ong,OffsetDateTime,ZonedDateTime,LocalDate,LocalDateTime
-
-
Generic mapper is supported, it will bind
Objectas aMap<String, Object>values beingStringfor JSON strings,BigDecimalfor JSON numbers, anotherMap<String, Object>for JSON objects andList<Object>for JSON lists, -
A simple post processor prettifier (takes a JSON as input and formats it). It is used decorating the default
JsonMapper:io.yupiik.fusion.json.pretty.PrettyJsonMapper, -
The Fusion annotation processor will generate the JSON "codecs" from the code when a record is marked with
@JsonModel, the codec will be reflection free, -
You can customize the attribute names using
@JsonPropertyon the record members, -
You can map all unknown attributes in a
Map<String, Object>member marked with@JsonOthersannotation.
|
IMPORTANT
|
static model (annotations) are in |
Runtime Dependency
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>fusion-json</artifactId>
<version>${project.version}</version>
</dependency>
Example
To modelise the flow you just have to define a record marked with @JsonModel:
@JsonModel
public record MyModel(
@JsonProperty("boolean") boolean aBool,
BigDecimal bigDecimal,
int integer,
Integer nullableInt,
long lg,
double more,
String simplest,
LocalDate date,
LocalDateTime dateTime,
OffsetDateTime offset,
ZonedDateTime zoned,
Object generic,
AnotherModel nested,
List<Boolean> booleanList,
List<BigDecimal> bigDecimalList,
List<Integer> intList,
Collection<Long> longList,
List<Double> doubleList,
Set<String> stringList,
List<LocalDate> dateList,
List<LocalDateTime> dateTimeList,
List<OffsetDateTime> offsetList,
List<ZonedDateTime> zonedList,
List<Object> genericList,
List<AnotherModel> nestedList,
Map<String, String> mapStringString,
Map<String, Integer> mapStringInt,
Map<String, AnotherModel> mapNested) {
}
|
WARNING
|
|
Then read/write data using JsonMapper:
@Bean
public class MyService extends HttpServlet {
@Injection
JsonMapper mapper;
@Override
public void doGet(final HttpServletRequest req, final HttpServletResponse resp) throws IOException {
try (final var out = resp.getWriter()) {
mapper.write(createMyModelInstance(req), out);
}
}
}
Enums
Enumerations (de)serialization behavior can be customized by using some specific methods:
public enum MyEnum {
A, B;
public String toJsonString() { (1)
return this == A ? "first" : "second";
}
public static MyEnum fromJsonString(final String v) { (2)
return switch (v) {
case "first" -> MyEnum.A;
case "second" -> MyEnum.B;
default -> throw new IllegalArgumentException("Unsupported '" + v + "'");
};
}
}
-
toJsonStringis an instance method with no parameter used to replace.name()call during serialization, -
fromJsonStringis a static method with aStringparameter used to replace.valueOf(String)call during deserialization.
JSON-Pointer and JSON-Patch
JSON-Pointer ( https://datatracker.ietf.org/doc/html/rfc6901)
and JSON-Patch ( https://www.rfc-editor.org/rfc/rfc6902.html)
are available for generic types today (ie you type as Object the serialized/deserialized instances to use Map<String, Object> and List<Object> as JSON-Object/JSON-Array).
To use them, rely on GenericJsonPointer and GenericJsonPatch classes:
// is reusable
final var patch = new GenericJsonPatch(List.of(new JsonPatchOperation(add, "/baz", null, "qux")));
// "runtime"
final var object = Map.of("foo", "bar");
final var patched = patch.apply(object);
// patched={"foo":"bar","baz":"qux"}
Json Schema Validator
The Json Schema Validator can be used to validate a json content according to a json-schema ( https://json-schema.org/understanding-json-schema/reference).
@Bean
public class MyService {
@Injection
JsonMapper mapper;
public boolean validate(final MyModel myModel) throws IOException {
final var schema = mapper.fromString(Object.class,
"""
{
"$id": "https://spec.openapis.org/oas/3.1/schema/2022-10-07",
"$schema": "https://json-schema.org/draft/2020-12/schema",
"description": "The description of OpenAPI v3.1.x documents without schema validation, as defined by https://spec.openapis.org/oas/v3.1.0",
"type": "object"
...
}
"""); (1)
final JsonSchemaValidatorFactory factory = new JsonSchemaValidatorFactory(); (2)
final JsonSchemaValidator validator = factory.newInstance((Map<String, Object>) schema); (3)
final var result = validator.apply(myModel); (4)
validator.close();
return result.isSuccess(); (5)
}
}
-
use the
jsonMapperto load the json schema you want to use, - the factory instance can be reuse and is thread safe,
- create a validator from the json schema loaded,
- apply the validator to the input model object,
- result of the validation.
If the validation failed, the result object of the apply method will contain a list of the messages for each validation check:
final var result = validator.apply(myModel);
final var error = result.errors().iterator().next();
logger.info("Error on field '" + error.field() + "' " + error.message());
Pretty mapper
Fusion provide a json pretty mapper to print a json string.
import io.yupiik.fusion.framework.build.api.scanning.Injection;
import io.yupiik.fusion.json.JsonMapper;
import io.yupiik.fusion.json.pretty.PrettyJsonMapper;
@Bean
public class MyService {
@Injection
JsonMapper jsonMapper;
public void printJson() {
final JsonMapper prettyJsonMapper = new PrettyJsonMapper(jsonMapper); (1)
logger.info(jsonMapper.toString(MyClass));
}
}
-
use the injected
jsonMapperto create the instance of the PrettyJsonMapper.