30

I'm using Jackson to build a custom JSON object. Is the correct way of going about this?

It seems to work well (and the output is correct) but I may be missing the way I use JsonNodeFactory. Is the object meant to be passed around like I have done here?

JsonNodeFactory factory = JsonNodeFactory.instance;
ObjectNode dataTable = new ObjectNode(factory);
ArrayNode aaData = new ArrayNode(factory);

for (PkgLoad pkgLoad : pkgLoadList) {
    ObjectNode row = new ObjectNode(factory);
    row.put("ounces", pkgLoad.ounces);
    row.put("revolutions", pkgLoad.revolutions);
    aaData.add(row);
}

dataTable.put("aaData", aaData);
David Newcomb
  • 10,639
  • 3
  • 49
  • 62
Drew H
  • 4,699
  • 9
  • 57
  • 90

4 Answers4

52

This works, although intention is that it's factory that creates instances. But most commonly you just access all of it using ObjectMapper, like:

ObjectMapper mapper = new ObjectMapper();
ObjectNode dataTable = mapper.createObjectNode();
ArrayNode aa = dataTable.putArray("aaData");

The main reason for separate JsonNodeFactory is to allow you to create custom node types (usually sub-classes of standard instances); and then configure ObjectMapper to use different factory. For convenience, ArrayNode and ObjectNode do have reference to a factory instance, which is used with "putArray" and other methods that need to create new nodes.

StaxMan
  • 113,358
  • 34
  • 211
  • 239
5

If you do a lot of JsonNode building in code, you may be interesting in the following set of utilities. The benefit of using them is that they support a more natural chaining style that better shows the structure of the JSON under contruction.

Here is an example usage:

import static JsonNodeBuilders.array;
import static JsonNodeBuilders.object;

...

val request = object("x", "1").with("y", array(object("z", "2"))).end();

Which is equivalent to the following JSON:

{"x":"1", "y": [{"z": "2"}]}

Here are the classes:

import static lombok.AccessLevel.PRIVATE;

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.JsonNodeFactory;
import com.fasterxml.jackson.databind.node.ObjectNode;

import lombok.NoArgsConstructor;
import lombok.NonNull;
import lombok.RequiredArgsConstructor;
import lombok.val;

/**
 * Convenience {@link JsonNode} builder.
 */
@NoArgsConstructor(access = PRIVATE)
public final class JsonNodeBuilders {

  /**
   * Factory methods for an {@link ObjectNode} builder.
   */

  public static ObjectNodeBuilder object() {
    return object(JsonNodeFactory.instance);
  }

  public static ObjectNodeBuilder object(@NonNull String k1, boolean v1) {
    return object().with(k1, v1);
  }

  public static ObjectNodeBuilder object(@NonNull String k1, int v1) {
    return object().with(k1, v1);
  }

  public static ObjectNodeBuilder object(@NonNull String k1, float v1) {
    return object().with(k1, v1);
  }

  public static ObjectNodeBuilder object(@NonNull String k1, String v1) {
    return object().with(k1, v1);
  }

  public static ObjectNodeBuilder object(@NonNull String k1, String v1, @NonNull String k2, String v2) {
    return object(k1, v1).with(k2, v2);
  }

  public static ObjectNodeBuilder object(@NonNull String k1, String v1, @NonNull String k2, String v2,
      @NonNull String k3, String v3) {
    return object(k1, v1, k2, v2).with(k3, v3);
  }

  public static ObjectNodeBuilder object(@NonNull String k1, JsonNodeBuilder<?> builder) {
    return object().with(k1, builder);
  }

  public static ObjectNodeBuilder object(JsonNodeFactory factory) {
    return new ObjectNodeBuilder(factory);
  }

  /**
   * Factory methods for an {@link ArrayNode} builder.
   */

  public static ArrayNodeBuilder array() {
    return array(JsonNodeFactory.instance);
  }

  public static ArrayNodeBuilder array(@NonNull boolean... values) {
    return array().with(values);
  }

  public static ArrayNodeBuilder array(@NonNull int... values) {
    return array().with(values);
  }

  public static ArrayNodeBuilder array(@NonNull String... values) {
    return array().with(values);
  }

  public static ArrayNodeBuilder array(@NonNull JsonNodeBuilder<?>... builders) {
    return array().with(builders);
  }

  public static ArrayNodeBuilder array(JsonNodeFactory factory) {
    return new ArrayNodeBuilder(factory);
  }

  public interface JsonNodeBuilder<T extends JsonNode> {

    /**
     * Construct and return the {@link JsonNode} instance.
     */
    T end();

  }

  @RequiredArgsConstructor
  private static abstract class AbstractNodeBuilder<T extends JsonNode> implements JsonNodeBuilder<T> {

    /**
     * The source of values.
     */
    @NonNull
    protected final JsonNodeFactory factory;

    /**
     * The value under construction.
     */
    @NonNull
    protected final T node;

    /**
     * Returns a valid JSON string, so long as {@code POJONode}s not used.
     */
    @Override
    public String toString() {
      return node.toString();
    }

  }

  public final static class ObjectNodeBuilder extends AbstractNodeBuilder<ObjectNode> {

    private ObjectNodeBuilder(JsonNodeFactory factory) {
      super(factory, factory.objectNode());
    }

    public ObjectNodeBuilder withNull(@NonNull String field) {
      return with(field, factory.nullNode());
    }

    public ObjectNodeBuilder with(@NonNull String field, int value) {
      return with(field, factory.numberNode(value));
    }

    public ObjectNodeBuilder with(@NonNull String field, float value) {
      return with(field, factory.numberNode(value));
    }

    public ObjectNodeBuilder with(@NonNull String field, boolean value) {
      return with(field, factory.booleanNode(value));
    }

    public ObjectNodeBuilder with(@NonNull String field, String value) {
      return with(field, factory.textNode(value));
    }

    public ObjectNodeBuilder with(@NonNull String field, JsonNode value) {
      node.set(field, value);
      return this;
    }

    public ObjectNodeBuilder with(@NonNull String field, @NonNull JsonNodeBuilder<?> builder) {
      return with(field, builder.end());
    }

    public ObjectNodeBuilder withPOJO(@NonNull String field, @NonNull Object pojo) {
      return with(field, factory.pojoNode(pojo));
    }

    @Override
    public ObjectNode end() {
      return node;
    }

  }

  public final static class ArrayNodeBuilder extends AbstractNodeBuilder<ArrayNode> {

    private ArrayNodeBuilder(JsonNodeFactory factory) {
      super(factory, factory.arrayNode());
    }

    public ArrayNodeBuilder with(boolean value) {
      node.add(value);
      return this;
    }

    public ArrayNodeBuilder with(@NonNull boolean... values) {
      for (val value : values)
        with(value);
      return this;
    }

    public ArrayNodeBuilder with(int value) {
      node.add(value);
      return this;
    }

    public ArrayNodeBuilder with(@NonNull int... values) {
      for (val value : values)
        with(value);
      return this;
    }

    public ArrayNodeBuilder with(float value) {
      node.add(value);
      return this;
    }

    public ArrayNodeBuilder with(String value) {
      node.add(value);
      return this;
    }

    public ArrayNodeBuilder with(@NonNull String... values) {
      for (val value : values)
        with(value);
      return this;
    }

    public ArrayNodeBuilder with(@NonNull Iterable<String> values) {
      for (val value : values)
        with(value);
      return this;
    }

    public ArrayNodeBuilder with(JsonNode value) {
      node.add(value);
      return this;
    }

    public ArrayNodeBuilder with(@NonNull JsonNode... values) {
      for (val value : values)
        with(value);
      return this;
    }

    public ArrayNodeBuilder with(JsonNodeBuilder<?> value) {
      return with(value.end());
    }

    public ArrayNodeBuilder with(@NonNull JsonNodeBuilder<?>... builders) {
      for (val builder : builders)
        with(builder);
      return this;
    }

    @Override
    public ArrayNode end() {
      return node;
    }

  }

}

Note that the implementation uses Lombok, but you can easily desugar it to fill in the Java boilerplate.

btiernay
  • 7,873
  • 5
  • 42
  • 48
3
ObjectNode factory = JsonNodeFactory.instance.objectNode();

This works well. I think this is an easier way.

Cœur
  • 37,241
  • 25
  • 195
  • 267
3

Just a suggestion, it would be easier to directly deal with simple datatypes and serialize those to JSON and back using Jackson ObjectMapper, rather than deal with raw Jackson Treemodel

So in your example, you could create a structure of the following type:

class AaData{
    private List<ARow> rowList = new ArrayList<ARow>();
..

class ARow{
    String ounces;
    String revolutions;
..

Then the following will generate the appropriate json for you:

StringWriter sw = new StringWriter();
JsonFactory jf = new JsonFactory();
ObjectMapper m = new ObjectMapper();
m.writeValue(sw, aaData);
System.out.println(sw.toString());
Biju Kunjummen
  • 49,138
  • 14
  • 112
  • 125
  • That would be better and I have done that in some cases. The problem is, in this case I need to have control over every row because I have conditional statements on what to write. Sometimes ounces is grams. I have to write the row color to on each row. That is different for each row. – Drew H Jun 11 '11 at 15:30
  • 1
    @Drew H, I've seen you post about this project a couple of times. Overall, it looks to me like maybe it would be simpler if you made less or no presentation-based changes to your object model at the time of deserialization, and just simply inflated the object model from the JSON based on the data that is present in the JSON, without injecting additional data, and then later on, closer in the project to where the data is to be displayed, have code that is concerned with generating a view model (or altering the existing model) for presentation. – Programmer Bruce Jun 12 '11 at 03:33
  • 1
    (continuing...) My point is, if it's possible to keep the data binding between the JSON and the Java data structures simple, then keep it simple. It's fantastic when deserialization takes just two or three lines of code, which is entirely possible and very common when using APIs like Gson or Jackson. For me, it's a major reason to use these APIs. – Programmer Bruce Jun 12 '11 at 03:36
  • Hey man thanks for posting. I originally was doing exactly what you said. FlexJson was the only serializer that would serailize my object because of the circular references int he JPA entity models. I think maybe Jackson will do it but I'm not sure. I then got that into javascript and that's where I was doing the row coloring and display changes. I just felt this was bad separation of concerns. Row coloring is based on a few things. One of those is properties. This means when I pass aaData, iTotalRows, iTotalDisplayRows(for jQuery, datatables) I would have to pass the properties too. – Drew H Jun 12 '11 at 14:34
  • (continue). That means every time I person change the data table page(server side processing for datatables) I would have to pass those properties. Then I have to rely on a javascript call back to do the row coloring which I figured would be much slower than including the row color in my model and do the processing in Java. Maybe I'm simply over thinking it and should go back to the way I started(and the way you suggest..I believe). I really want to keep JSON serialization simple(that's what I'm aiming for) but the row coloring is going to have to be applied somewhere eventually. – Drew H Jun 12 '11 at 14:37
  • (continue). There are also certain things that need to be changed but the FlexJSON transformer is perfect for those. I'm not sure the Jackson alternative to that, at this point or if Jackson would support my model. I want to go with Jackson because it is much faster than the alternatives, but I like FlexJSON the most. – Drew H Jun 12 '11 at 14:44
  • For what it is worth, Jackson specifically focuses on reading/writing JSON and data binding; and two things that are out of scope are data validation and transformation. So if you are specifically looking for transformations, other libs may be way to go. – StaxMan Jun 13 '11 at 05:31