Home

Awesome

Build Maven Central javadoc License Discord

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)

Built-in Type Adapters

Built-in support for reading and writing Java’s core data types:

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

Related works