Awesome
Avaje-JsonB
Fast, reflection-free Json binding via apt source code generation. A light (~200kb + generated code) source code generation style alternative to Jackson's ObjectMapper or Gson. (code generation vs reflection)
- Annotate java classes with
@Json
(or use@Json.Import
for types we "don't own" or can't annotate) avaje-jsonb-generator
annotation processor generates Java source code to convert to/from json- No need to manually register generated adapters. (Uses ServiceLoader to auto-register)
- Constructors and accessors/getters/setters of any style "just work" (records, constructors, 'fluid setters')
- Jackson-like annotations:
@Json.Raw
,@Json.Property
,@Json.Ignore
,@Json.Alias
, etc. - Support Imports and Mixins (adding jsonb features to types we can't directly annotate).
- Supports Generic Types.
- Provides support for dynamic json views (similar in style to that presented by LinkedIn at java one in 2009
- One of the top three fastest Java JSON libraries
Built-in Type Adapters
Built-in support for reading and writing Java’s core data types:
- Primitives (int, float, char...) and their boxed counterparts (Integer, Float, Character...).
- BigInteger and BigDecimal
- java.time classes (Instant, LocalDate, LocalDateTime...)
- Arrays, Collections, Streams, Lists, Sets, and Maps
- Optionals (will unwrap and serialize the contained value)
- Strings
- Enums
- Other miscellaneous types (UUID, URL, URI)
Quick Start
Step 1 - Add dependencies
<dependency>
<groupId>io.avaje</groupId>
<artifactId>avaje-jsonb</artifactId>
<version>${avaje-jsonb-version}</version>
</dependency>
<!-- if using spring web, add the below to use jsonb for http messaging -->
<dependency>
<groupId>io.avaje</groupId>
<artifactId>avaje-jsonb-spring-starter</artifactId>
<version>${avaje-jsonb-version}</version>
</dependency>
And add avaje-jsonb-generator as an annotation processor.
<!-- Annotation processors -->
<dependency>
<groupId>io.avaje</groupId>
<artifactId>avaje-jsonb-generator</artifactId>
<version>${avaje-jsonb-version}</version>
<scope>provided</scope>
</dependency>
Step 2 - Add @Json
Add @Json
to the types we want to serialize.
The avaje-jsonb-generator
annotation processor will generate a JsonAdapter as java source code
for each type annotated with @Json
. These will be automatically registered with Jsonb
when it is started using a service loader mechanism.
@Json
public class Address {
private String street;
private String suburb;
private String city;
// object fields will automatically have adapters generated, no @Json required
// (though you can add @Json anyway to modify the generated adapter how you wish)
private OtherClass other;
//add getters/setters
}
This also works with records:
@Json
public record Address(String street, String suburb, String city) { }
For types we cannot annotate with @Json
we can place @Json.Import(TypeToimport.class)
on any class/package-info to generate the adapters.
Step 3 - Use
// build using defaults
Jsonb jsonb = Jsonb.builder().build();
JsonType<Customer> customerType = jsonb.type(Customer.class);
Customer customer = ...;
// serialize to json
String asJson = customerType.toJson(customer);
// deserialize from json
Customer customer = customerType.fromJson(asJson);
Step 4 - Use Json views
avaje-jsonb
supports dynamic json views. This allows us to specify which specific properties
to include when serializing to json.
For example:
Jsonb jsonb = Jsonb.builder().build();
JsonType<Customer> customerType = jsonb.type(Customer.class);
// only including the id and name
JsonView<Customer> idAndNameView = customerType.view("(id, name)");
String asJson = idAndNameView.toJson(customer);
JsonView<Customer> myView =
customerType.view("(id, name, billingAddress(*), contacts(lastName, email))");
// serialise to json the above specified properties only
String asJson = myView.toJson(customer);
Generated Code
Given the class:
@Json
public class Address {
private String street;
private City city;
private Suburb suburb;
//getters/setters ommited for brevity
}
The following code will be generated and used for serialization/deserialization.
@Generated
public final class AddressJsonAdapter implements JsonAdapter<Address>, ViewBuilderAware {
private final JsonAdapter<String> stringJsonAdapter;
private final JsonAdapter<City> cityJsonAdapter;
private final JsonAdapter<Suburb> suburbJsonAdapter;
private final PropertyNames names;
public AddressJsonAdapter(Jsonb jsonb) {
this.stringJsonAdapter = jsonb.adapter(String.class);
this.cityJsonAdapter = jsonb.adapter(City.class);
this.suburbJsonAdapter = jsonb.adapter(Suburb.class);
this.names = jsonb.properties("street", "city", "suburb");
}
@Override
public boolean isViewBuilderAware() {
return true;
}
@Override
public ViewBuilderAware viewBuild() {
return this;
}
@Override
public void build(ViewBuilder builder, String name, MethodHandle handle) {
builder.beginObject(name, handle);
builder.add("street", stringJsonAdapter, builder.method(Address.class, "getStreet", java.lang.String.class));
builder.add("city", cityJsonAdapter, builder.method(Address.class, "getCity", City.class));
builder.add("suburb", suburbJsonAdapter, builder.method(Address.class, "getSuburb", Suburb.class));
builder.endObject();
}
@Override
public void toJson(JsonWriter writer, Address address) {
writer.beginObject(names);
writer.names(names);
writer.name(0);
stringJsonAdapter.toJson(writer, address.getStreet());
writer.name(1);
cityJsonAdapter.toJson(writer, address.getCity());
writer.name(2);
suburbJsonAdapter.toJson(writer, address.getSuburb());
writer.endObject();
}
@Override
public Address fromJson(JsonReader reader) {
Address _$address = new Address();
// read json
reader.beginObject(names);
while (reader.hasNextField()) {
final String fieldName = reader.nextField();
switch (fieldName) {
case "street": {
_$address.setStreet(stringJsonAdapter.fromJson(reader)); break;
}
case "city": {
_$address.setCity(cityJsonAdapter.fromJson(reader)); break;
}
case "suburb": {
_$address.setSuburb(suburbJsonAdapter.fromJson(reader)); break;
}
default: {
reader.unmappedField(fieldName);
reader.skipValue();
}
}
}
reader.endObject();
return _$address;
}
}
Based on Moshi
avaje-jsonb
is based on Moshi with some changes as summarised below:
Changes from Moshi
- Generates Java source code (rather than Kotlin)
- uses custom parser inspired by dsl-json (with option of using jackson-core
JsonParser
andJsonGenerator
as parsers) - Has no fallback to reflection - jsonb is code generation or bust.`
- JsonReader - Make JsonReader an interface, default implementation using
Jsonb JsonReadAdapter
- JsonWriter - Make JsonWriter an interface, default implementation using
Jsonb JsonWriteAdapter
- JsonAdapter -> Make JsonAdapter an interface.
- Moshi -> Jsonb - Rename Moshi to Jsonb and make it an interface
- Moshi.Builder -> Jsonb.Builder - Basically the same but Jsonb.Builder as interface plus added Component and AdapterBuilder
- Add JsonType for a more friendly API to use rather than the underlying JsonAdapter
- Add Jsonb.Component interface - allows easy service loading of adapters
- Additionally, it generates a Jsonb.Component and uses service loading to auto-register all generated adapters. This means there is no need to register the generated adapters manually.
- Add fromObject() as a "covert from object" feature like Jackson ObjectMapper
- Add naming convention support
- Add
@Json.Import
to generate adapters for types that we can't put the annotation on (types we might not 'own') - Add Mixin feature similar to Jackson Mixins
- Add Types.listOf(), Types.setOf(), Types.mapOf() helper methods
- Provide an SPI with the view to target other json-p implementations JSONP/Yasson, GSON, etc
- Adds more common Java types with default built-in support - java.time types, java.util.UUID
- Adds support for json views