JSR-353 How to add null values using javax.json.JsonObjectBuilder
Asked Answered
H

2

19

AS the javax.json docs suggest the way to create a JsonObject is using the provided builders like:

JsonBuilderFactory factory = Json.createBuilderFactory(config);
 JsonObject value = factory.createObjectBuilder()
     .add("firstName", "John")
     .add("lastName", "Smith")
     .add("age", 25)
     .add("address", factory.createObjectBuilder()
         .add("streetAddress", "21 2nd Street")
         .add("city", "New York")
         .add("state", "NY")
         .add("postalCode", "10021"))
     .add("phoneNumber", factory.createArrayBuilder()
         .add(factory.createObjectBuilder()
             .add("type", "home")
             .add("number", "212 555-1234"))
         .add(factory.createObjectBuilder()
             .add("type", "fax")
             .add("number", "646 555-4567")))
     .build();

This example adds values for the gives keys. In real life the values are probably derived from some (pojo) domain object like:

JsonBuilderFactory factory = Json.createBuilderFactory(config);
 JsonObject value = factory.createObjectBuilder()
     .add("firstname", customer.getFirstame())
     .add("lastname", customer.getLastame())
     .add("age", customer.getAge())
     ....

The JsonOBjectBuilder throw a NPE in case key or value is null. In case the customer has no registered age then above code will throw a NPE.

So basically for every field I add I have to check if the value is null or not and add the actual value and otherwise do not add the field or add JsonValue.NULL for the key. This causes a lot of (undesired) boilerplate...

In my case I ended up with custom JsonUtils class including various static methods like:

public static void add(JsonObjectBuilder builder, String name, Long value) {
    if (value == null) {
        builder.add(name,  JsonValue.NULL);
    }
    else {
        builder.add(name, value);
    }
}

public static void add(JsonObjectBuilder builder, String name, String value) {
    if (value == null) {
        builder.add(name,  JsonValue.NULL);
    }
    else {
        builder.add(name, value);
    }
}

and then calling:

builder = Json.createObjectBuilder();
JsonUtils.add(builder, "id", customer.getId());
JsonUtils.add(builder, "name", customer.getName());
JsonUtils.add(builder, "gender", customer.getGender());
builder.build()

But someway if feels not right. Why does the javax.json provide no easier way to add null values (without if else boilerplate) or am I missing something?

My main point of critism against the JSR-353 api is that despite it looks like a really nice fluent api (see top example from apidoc) but in reality it is not.

Hydrosome answered 12/3, 2014 at 21:11 Comment(1)
There are literally dozens of better choices than JSR-353; so maybe have a look at, say, Jackson jr (github.com/FasterXML/jackson-jr)? It has true fluent API ("composers"), unlike JSON-P that has more "sorta-kinda-almost" version.Vassar
L
14

It might be difficult to get your JsonObjectBuilder implementation from the factory but you can make it simpler.

Create a JsonObjectBuilder decorator class that checks for null:

public class NullAwareJsonObjectBuilder implements JsonObjectBuilder {
    // Use the Factory Pattern to create an instance.
    public static JsonObjectBuilder wrap(JsonObjectBuilder builder) {
      if (builder == null) {
        throw new IllegalArgumentException("Can't wrap nothing.");
      }
      return new NullAwareJsonObjectBuilder(builder);
    }

    // Decorated object per Decorator Pattern.
    private final JsonObjectBuilder builder;

    private NullAwareJsonObjectBuilder(JsonObjectBuilder builder) {
      this.builder = builder;
    }

    public JsonObjectBuilder add(String name, JsonValue value) {
      builder.add(name, (value == null) ? JsonValue.NULL : value);
    }

    // Implement all other JsonObjectBuilder methods.
    ..
}

And how you to use it:

JsonObjectBuilder builder = NullAwareJsonObjectBuilder.wrap(
        factory.createObjectBuilder());
builder.add("firstname", customer.getFirstame())
    .add(...
Labiche answered 13/3, 2014 at 8:38 Comment(4)
Interesting idea. I wonder if it's possible to even push this further and let the factory return a NullAwareJsonObjectBuilder itself?Hydrosome
I had a look to provide my own JsonProviderImpl using ServiceLoader but extending glassfish classes is not really an option. Almost all convienient fields and methods are private, so would end up copying everything.Hydrosome
Just implemented this solution... works great. A few minor points I learned along the way for the implemented methods. First, you need a return type (return this, in most cases; return builder.build() for the build() case). Secondly, the ternary operator with JsonValue.NULL in it doesn't work for other method signatures. I ended up with a plain conditional if (arg1==null) { builder.addNull(arg0); } else { builder.add(arg0,arg1); }Deanadeanda
I find it so mind-boggling that something like this isn't a part of the default implementation. Is there any good reason for this?Eternalize
H
2

Another possible solution is to use simple and lean helper to wrap a value into JsonValue object or return JsonValue.NULL if value is null.

Here is an example:

public final class SafeJson {
    private static final String KEY = "1";

    //private ctor with exception throwing

    public static JsonValue nvl(final String ref) {
        if (ref == null) {
            return JsonValue.NULL;
        }
        return Json.createObjectBuilder().add(KEY, ref).build().get(KEY);
    }

    public static JsonValue nvl(final Integer ref) {
        if (ref == null) {
            return JsonValue.NULL;
        }
        return Json.createObjectBuilder().add(KEY, ref).build().get(KEY);
    }

    public static JsonValue nvl(final java.sql.Date ref) {
        if (ref == null) {
            return JsonValue.NULL;
        }
        return Json.createObjectBuilder().add(KEY, ref.getTime()).build().get(KEY);
    }
}

Usage is simple:

Json.createObjectBuilder().add("id", this.someId)
    .add("zipCode", SafeJson.nvl(this.someNullableInt))
    .add("phone", SafeJson.nvl(this.someNullableString))
    .add("date", SafeJson.nvl(this.someNullableDate))
    .build(); 

Constructs like Json.createObjectBuilder().add(KEY, ref).build().get(KEY); looks a little bit weird but it is the only portable way to wrap value into JsonValue I've found so far.

In fact in JavaEE8 you can replace it with:

Json.createValue()

or underlying:

JsonProvider.provider().createValue()

call.

Hoarding answered 11/10, 2017 at 18:51 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.