Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Requesting ObjectNode, ArrayNode datastructure functionality for avaje jsonb #292

Open
ahrooran-r opened this issue Nov 29, 2024 · 3 comments
Labels
enhancement New feature or request

Comments

@ahrooran-r
Copy link

Jackson offers generic ObjectNode, ArrayNode functionality.

This is very useful to process unstructured json. Avaje Jsonb to my knowledge does not have this capability.

I have to use JDK Maps instead which is a lot difficult to process and parse since everything is a Map<String, Object>.

Please let me know if there is already a built in mechanism. Or a way for me to achieve same functionality as Jackson without including it.

@ahrooran-r ahrooran-r changed the title Requesting ObjectNode, ArrayNode datastructure functionality for avaje Requesting ObjectNode, ArrayNode datastructure functionality for avaje jsonb Nov 29, 2024
@SentryMan SentryMan added the enhancement New feature or request label Nov 29, 2024
@SentryMan
Copy link
Collaborator

We can potentially make a utility class to make extracting data out of Map<String, Object> easier

@SentryMan
Copy link
Collaborator

@ahrooran-r would something like this work for you?

public class JsonNode {
  private final Object value;

  public JsonNode(Object value) {
    this.value = value;
  }

  public Object value() {
    return value;
  }

  public boolean isString() {
    return value() instanceof String;
  }

  public boolean isNumber() {
    return value() instanceof Number;
  }

  public boolean isList() {
    return value() instanceof List;
  }

  public boolean isMap() {
    return value() instanceof Map;
  }

  public String asString() {
    return (String) value();
  }

  public Integer asInt() {
    return (Integer) value();
  }

  public Double asDouble() {
    return (Double) value();
  }

  public Long asLong() {
    return (Long) value();
  }

  public Boolean asBool() {
    return (Boolean) value();
  }

  @SuppressWarnings("unchecked")
  public List<JsonNode> asList() {
    var list = (List<Object>) value();

    return list.stream().map(JsonNode::new).collect(toList());
  }

  @SuppressWarnings("unchecked")
  public Map<String, JsonNode> asMap() {
    var map = (Map<String, Object>) value();

    return map.entrySet().stream().collect(toMap(Entry::getKey, e -> new JsonNode(e.getValue())));
  }
}

@rbygrave
Copy link
Contributor

In terms of Pattern Matching, I'm note sure the above is ideal. For Pattern matching we'd want the more specific types I think.

Note that Jackson has a mapper.valueToTree() method that will convert a "vanilla" type into a JsonNode type, so you might get enough of what you want by using that [not optimal but good enough?].

semi-structured & skipping content approach

Note that if we have a lot of json payload and we are only interested in a relatively small part of it, it can be a good approach to just model the parts that we want and let the parser skip the "unknown" properties. This will often be a lot better than parsing the whole lot of unstructured json and then dig in / traverse into the parts that we need [because skipping the unwanted content is cheaper].

So don't forget about this option of just mapping what we need and leveraging the "skip unknown" approach. If we have a LOT of json this should be our first approach imo.


Back on topic, for mapping unstructured json we ultimately use the adapter registered for Object.class which is at io.avaje.jsonb.core.BasicTypeAdapters.ObjectJsonAdapter

... and the interesting guts of that is:

static final class ObjectJsonAdapter implements JsonAdapter<Object> {
    private final Jsonb jsonb;
    private final JsonAdapter<List> listJsonAdapter;
    private final JsonAdapter<Map> mapAdapter;
    private final JsonAdapter<String> stringAdapter;
    private final JsonAdapter<Double> doubleAdapter;
    private final JsonAdapter<Boolean> booleanAdapter;

    ObjectJsonAdapter(Jsonb jsonb) {
      this.jsonb = jsonb;
      this.listJsonAdapter = jsonb.adapter(List.class);
      this.mapAdapter = jsonb.adapter(Map.class);
      this.stringAdapter = jsonb.adapter(String.class);
      this.doubleAdapter = jsonb.adapter(Double.class);
      this.booleanAdapter = jsonb.adapter(Boolean.class);
    }

    @Override
    public Object fromJson(JsonReader reader) {
      switch (reader.currentToken()) {
        case BEGIN_ARRAY:
          return this.listJsonAdapter.fromJson(reader);
        case BEGIN_OBJECT:
          return this.mapAdapter.fromJson(reader);
        case STRING:
          return this.stringAdapter.fromJson(reader);
        case NUMBER:
          var d = this.doubleAdapter.fromJson(reader);
          if (d % 1 == 0) {
            return d.longValue();
          }
          return d;
        case BOOLEAN:
          return this.booleanAdapter.fromJson(reader);
        case NULL:
          return null;
        default:
          throw new IllegalStateException("Expected a value but was " + reader.currentToken() + " at path " + reader.location());
      }
    }
...

A thought here is, could this JsonAdapter<Object> implementation be pluggable / swappable?

Could we swap out the current one to another that say uses the Jackson JsonNode types?

Could we have separate and optional module with avaje-json specific JsonNode style types [to represent Java Map, List, String, Double, Boolean, maybe not Null though?]. That means, have a JsonAdapter<Object> implementation that used those types instead of the standard java ones?

Hmmm.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
None yet
Development

No branches or pull requests

3 participants