Skip to content

avaje/avaje-jsonb

Repository files navigation

Build Maven Central javadoc License Discord

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 and JsonGenerator 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

Related works