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 asstring
in 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
Object
as aMap<String, Object>
values beingString
for JSON strings,BigDecimal
for 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
@JsonProperty
on the record members, -
You can map all unknown attributes in a
Map<String, Object>
member marked with@JsonOthers
annotation.
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 + "'");
};
}
}
-
toJsonString
is an instance method with no parameter used to replace.name()
call during serialization, -
fromJsonString
is a static method with aString
parameter 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 = jsonMapper.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
jsonMapper
to 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());