Mapper

org.apache.johnzon:johnzon-mapper:2.2.0

Contents

johnzon-mapper maps Java objects to JSON and back, on top of whichever JSON Processing implementation you choose:

final MySuperObject object = createObject();

try (Mapper mapper = new MapperBuilder().build()) {
    mapper.writeObject(object, outputStream);

    final MySuperObject otherObject = mapper.readObject(inputStream, MySuperObject.class);
}

The mapper uses a direct Java-to-JSON representation. This bean:

public class MyModel {
  private int id;
  private String name;

  // getters/setters
}

maps to:

{
  "id": 1234,
  "name": "Johnzon doc"
}

Customization happens either directly on the MapperBuilder or through annotations.

Annotations

@JohnzonIgnore

Ignores a field, optionally only up to some version when the mapper has one:

public class MyModel {
  @JohnzonIgnore
  private String name;

  // getters/setters
}

To serialize name from version 3 on but ignore it for versions 1 and 2:

public class MyModel {
  @JohnzonIgnore(minVersion = 3)
  private String name;

  // getters/setters
}

@JohnzonConverter

Converters handle advanced mapping between Java and JSON. The types are:

  • Converter: maps between Java and JSON based on the string representation
  • Adapter: a converter not limited to String
  • ObjectConverter.Reader: converts JSON to Java at a low level
  • ObjectConverter.Writer: converts Java to JSON at a low level
  • ObjectConverter.Codec: a Reader and Writer in one

The most common case is a custom date format, which a plain Converter covers:

public class LocalDateConverter implements Converter<LocalDate> {
    @Override
    public String toString(final LocalDate instance) {
        return instance.toString();
    }

    @Override
    public LocalDate fromString(final String text) {
        return LocalDate.parse(text);
    }
}

When you need to modify the structure of the JSON itself (wrapping a value, for instance), use a Reader/Writer or a Codec. Register converters globally on the MapperBuilder, or decorate the field:

public class MyModel {
  @JohnzonConverter(LocalDateConverter.class)
  private LocalDate date;

  // getters/setters
}

Mapping java.lang.Class

ClassConverter is not registered by default: reading a java.lang.Class from a document lets the document pick which class is loaded, so it must be registered explicitly with the classes it is allowed to load. Both allow-lists (exact names and name prefixes) default to empty, i.e. any incoming class name is rejected:

new MapperBuilder()
    .addConverter(Class.class, new ClassConverter(
        List.of("com.company.handler.Default"),   // allowed class names
        List.of("com.company.handler.")))         // allowed class name prefixes
    .build();

@JohnzonProperty

When the JSON name is not Java-friendly (_foo, foo-bar, or even 200), @JohnzonProperty sets the name to use:

public class MyModel {
  @JohnzonProperty("__date")
  private LocalDate date;

  // getters/setters
}

@JohnzonAny

When you don’t fully know your model but want to handle all keys, @JohnzonAny captures and serializes the rest:

public class AnyMe {
    private String name; // regular serialization for the known 'name' field

    /* A TreeMap stores the unknown fields for the @JohnzonAny methods here,
       but any storage works. @JohnzonIgnore keeps it from being exposed as
       an actual 'unknownFields' property in JSON. */
    @JohnzonIgnore
    private Map<String, Object> unknownFields = new TreeMap<String, Object>();

    public String getName() {
        return name;
    }

    public void setName(final String name) {
        this.name = name;
    }

    @JohnzonAny
    public Map<String, Object> getAny() {
        return unknownFields;
    }

    @JohnzonAny
    public void handle(final String key, final Object val) {
        this.unknownFields.put(key, val);
    }
}

Access modes

MapperBuilder ships several access modes, selected with setAccessModeName():

  • field: uses fields and ignores getters/setters
  • method: uses getters/setters (a getter without a setter serializes but never reads)
  • strict-method: the POJO-convention based default. Like method, but collection getters are not used to write
  • both: field and method accessors merged

You can also implement your own access mode.