Canonical Java Client (Json) Usage

Hi. I have a graph model that will for include nodes and edges - and also edge attributes (i.e. facets). Pre including facets, my solution was very simple and clean, along the lines of:

  final DgraphProto.Mutation mu =
      builder
          .setSetJson(ByteString.copyFromUtf8(new Gson().toJson(pojo)))
          .build();
    transaction.mutate(mu);
    transaction.commit();

The “pojo” being my root object, with fields, a list of other objects, etc. and the nodes and edges are all created as expected. So now I want to give the edge a property…

Q.1 Is there anyway I can leverage DgraphProto.Facet?
This type seems to just play work with DgraphProto.NQuad and cant work directly with the above JSON approach.

Q.2 So what I have working is adding the following to the above:

           ...
          .setSetNquads(ByteString.copyFromUtf8(nqs))
          ...

, where nqs is alone the lines of:

         _:a "the_big_rel" _:b (close=yes)

As the_big_rel was already defined in my pojo structure, now I seem to be duplicating. Is there a better way?

Q.3 Should me mindset be to build up the graph in a number of incremental transactions? My above approach is trying to build the nodes, edges and facets in a single go.
E.g. just create the root node. Then create the child nodes. Then create the rels and facts between the root and children.

I know this is a quiet general question, so appreciate any tips re how to build a graph favouring the JSON approach.

Thanks

Hi @damienburke,
Thanks for the well formed question.

I guess you could just use your pojo to add facets using json as mentioned here.
The way it would work with Gson is to use this annotation on the member of your pojo which represents facet: @SerializedName("predicate|facetName").

So, I guess that answers all your questions. Please let me know if you need more information.

1 Like

Thanks, Ill give that a go

1 Like

Hi. So I have facet on scalar predicate working beautifully. I am able to set/save and retrieve the value on a Java POJO. I’m not quiet there on setting facet on uid predicates though…

I’m essentially using the same approach for both. Where I am at, is that I think I’m setting the JSON correctly… I say this, as when I look at raw JSON response for my subsequent query (through ratel or debugging my Java code), I can see values for facet on uid predicates set.

class Order {
  int orderId;
  List<Item> items;
}

class Item {
  int itemId;

     /** Working */
    @SerializedName("itemName|facetOnScalarPredicate")
    String randomName;

     /** Not Working */
   @SerializedName("items|facetOnUidPredicate")
   String facetOnUidPredicate;
}

JSON:

"orderContainer": [
      {
        "orderId": 50,
        "uid": "0x96eb",
        "items": [
          {
            "itemId": 300,
            "itemName|facetOnScalarPredicate": "sup",
            "itemName": "first"
          },
          {
            "itemId": 301,
            "itemName|facetOnScalarPredicate": "yo",
            "itemName": "second"
          }
        ],
        "items|facetOnUidPredicate": {
          "0": "the rel really",
          "1": "the rel"
        }      }
    ]
  }

To restate a bit more;
itemName|facetOnScalarPredicate is deserialized and object field item.randomName set
item.facetOnUidPredicate is null

This is not completely surprising, and expect there could be a bit more Java code/json annotations to map this value back… Thanks

Hi @damienburke,
A facet is supposed to be property of an edge, not the property of a node. Seeing the Item POJO In your example here, it seems like facetOnUidPredicate is a property of the Item class and not a property of items edge inside an order. So, are you sure you want to have this as a facet on items edge? and if yes, are you sure you don’t want to move it a level up as part of an Order because the items edge is part of an Order?

Being said that, if you are just focussed on deserializing it in the manner that you have described, you can look into Custom Json Deserialization using Gson, and write a deserializer of your own to achieve something like that. Maybe following links will help you with that:

I guess, all you will have to do is write a custom deserializer for your Order class, and parse the json inside an order appropriately to form an Order object.

Please let me know if you need more help with that.

Thanks @abhimanyusinghgaur
I feel my example (and it is just an example, and the actual graph I want to ultimately design is v different domain) is somewhat confusing. What would be massively valuable to me at this point would be to see the Java POJOs (with their json annotations, etc.) that can support (i.e. read and write) the dgraph sample graph described here - https://dgraph.io/docs/query-language/#facets-edge-attributes - would look like. Is this something u could put together? I feel I would go down the same rabbit hole as I ended up with my order/line nodes.

So this graph will be:

Person - friend(s with) - Person
Person - rated - movie

==================================================================

The " Facets on scalar predicates", I think I have my head around, e.g.

 _:alice <mobile> "040123456" (since=2006-01-02T15:04:05) . 

, and would look like:

class Person {
 ...
  int mobile;
 @SerializedName("mobile|since")
  LocalDate mobSince;
   ...
}

, but the “Facets on UID predicates”, e.g.

   _:alice <friend> _:bob (close=true, relative=false) .

Is where I need help. Thanks!

Hey @damienburke,
Thanks for making it a bit more clear.

So, I have assembled some code for the example given in docs.
You can use Person2 and People2 while serializing the POJO to json.
For deserialization purpose, you can choose a pair from one of the following as per your need:

  • People1 and Person1
    OR
  • People and Person

You can refer the tests in attached code for more clarity on how to use them.

import com.google.gson.*;
import com.google.gson.annotations.SerializedName;
import org.junit.Test;

import java.lang.reflect.Type;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Map;

public class Playground {
    public static final String json = "{\n" +
            "    \"persons\": [\n" +
            "      {\n" +
            "        \"name\": \"Alice\",\n" +
            "        \"friend\": [\n" +
            "          {\n" +
            "            \"name\": \"Bob\",\n" +
            "            \"car|since\": \"2006-02-02T13:01:09Z\",\n" +
            "            \"car\": \"MA0134\"\n" +
            "          },\n" +
            "          {\n" +
            "            \"name\": \"Charlie\"\n" +
            "          },\n" +
            "          {\n" +
            "            \"name\": \"Dave\"\n" +
            "          }\n" +
            "        ],\n" +
            "        \"friend|close\": {\n" +
            "          \"0\": true,\n" +
            "          \"1\": false,\n" +
            "          \"2\": true\n" +
            "        },\n" +
            "        \"friend|relative\": {\n" +
            "          \"0\": false,\n" +
            "          \"1\": true,\n" +
            "          \"2\": true\n" +
            "        }\n" +
            "      }\n" +
            "    ]\n" +
            "  }";

    @Test
    public void TestPersonDeSerialization() {
        Gson gson = new Gson();
        People people = gson.fromJson(json, People.class);
        System.out.println(people);
        // People{persons=[Person{name='Alice', car='null', since='null', friend=[Person{name='Bob', car='MA0134', since='2006-02-02T13:01:09Z', friend=null, close=null, relative=null}, Person{name='Charlie', car='null', since='null', friend=null, close=null, relative=null}, Person{name='Dave', car='null', since='null', friend=null, close=null, relative=null}], close={0=true, 1=false, 2=true}, relative={0=false, 1=true, 2=true}}]}
    }

    @Test
    public void TestPerson1DeSerialization() {
        GsonBuilder gsonBuilder = new GsonBuilder();
        gsonBuilder.registerTypeAdapter(Person1.class, new Person1Deserializer());
        Gson gson = gsonBuilder.create();
        People1 people = gson.fromJson(json, People1.class);
        System.out.println(people);
        // People{persons=[Person{name='Alice', car='null', since='null', friend=[Person{name='Bob', car='MA0134', since='2006-02-02T13:01:09Z', friend=null, close=true, relative=false}, Person{name='Charlie', car='null', since='null', friend=null, close=false, relative=true}, Person{name='Dave', car='null', since='null', friend=null, close=true, relative=true}], close=null, relative=null}]}
    }

    @Test
    public void TestPerson2Serialization() {
        People2 people = new People2();
        people.persons = Collections.singletonList(
                new Person2("Alice", null, null, Arrays.asList(
                        new Person2("Bob", "MA0134", "2006-02-02T13:01:09Z", null, true, false),
                        new Person2("Charlie", false, true),
                        new Person2("Dave", true, true)
                ), null, null)
        );
        Gson gson = new Gson();
        System.out.println(gson.toJson(people));
        // {"persons":[{"name":"Alice","friend":[{"name":"Bob","car":"MA0134","car|since":"2006-02-02T13:01:09Z","friend|close":true,"friend|relative":false},{"name":"Charlie","friend|close":false,"friend|relative":true},{"name":"Dave","friend|close":true,"friend|relative":true}]}]}
    }
}

class People {
    List<Person> persons;

    @Override
    public String toString() {
        return "People{" +
                "persons=" + persons +
                '}';
    }
}

class Person {
    String name;
    String car;
    @SerializedName("car|since")
    String since;
    List<Person> friend;
    @SerializedName("friend|close")
    Map<String, Boolean> close;
    @SerializedName("friend|relative")
    Map<String, Boolean> relative;

    @Override
    public String toString() {
        return "Person{" +
                "name='" + name + '\'' +
                ", car='" + car + '\'' +
                ", since='" + since + '\'' +
                ", friend=" + friend +
                ", close=" + close +
                ", relative=" + relative +
                '}';
    }
}

class People1 {
    List<Person1> persons;

    @Override
    public String toString() {
        return "People{" +
                "persons=" + persons +
                '}';
    }
}

class Person1 {
    String name;
    String car;
    @SerializedName("car|since")
    String since;
    List<Person1> friend;
    Boolean close;
    Boolean relative;

    @Override
    public String toString() {
        return "Person{" +
                "name='" + name + '\'' +
                ", car='" + car + '\'' +
                ", since='" + since + '\'' +
                ", friend=" + friend +
                ", close=" + close +
                ", relative=" + relative +
                '}';
    }
}

class Person1Deserializer implements JsonDeserializer<Person1> {
    private static final Gson gson = new Gson();

    @Override
    public Person1 deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
        JsonObject jsonObject = json.getAsJsonObject();
        JsonObject closeFriends = jsonObject.getAsJsonObject("friend|close");
        JsonObject relatives = jsonObject.getAsJsonObject("friend|relative");
        Person1 person1 = gson.fromJson(json, Person1.class);
        int i = 0;
        for (Person1 friend : person1.friend) {
            friend.close = closeFriends.get(String.valueOf(i)).getAsBoolean();
            friend.relative = relatives.get(String.valueOf(i)).getAsBoolean();
            i++;
        }
        return person1;
    }
}

class People2 {
    List<Person2> persons;

    @Override
    public String toString() {
        return "People{" +
                "persons=" + persons +
                '}';
    }
}

class Person2 {
    String name;
    String car;
    @SerializedName("car|since")
    String since;
    List<Person2> friend;
    @SerializedName("friend|close")
    Boolean close;
    @SerializedName("friend|relative")
    Boolean relative;

    public Person2(String name, Boolean close, Boolean relative) {
        this.name = name;
        this.close = close;
        this.relative = relative;
    }

    public Person2(String name, String car, String since, List<Person2> friend, Boolean close, Boolean relative) {
        this.name = name;
        this.car = car;
        this.since = since;
        this.friend = friend;
        this.close = close;
        this.relative = relative;
    }

    @Override
    public String toString() {
        return "Person{" +
                "name='" + name + '\'' +
                ", car='" + car + '\'' +
                ", since='" + since + '\'' +
                ", friend=" + friend +
                ", close=" + close +
                ", relative=" + relative +
                '}';
    }
}
1 Like

Thanks @abhimanyusinghgaur, that clears things up, and will spend some time playing around with this code and adapt to my use case.

It seems less that ideal to have one set of objects for serialization and another for the deserialization. Is this what people generally do when using facets / JSON and Dgraph? Thanks

Hi @damienburke,
I understand that it is not a good approach to have two different sets of objects for serialization and deserialization.

The reason I have written it like that is because of Gson.The thing is, one can’t guarantee the depth of graph, for example friends can again have friends, and so on. And, serialization would work fine with those annotations, but while deserializing, if I use the same POJO as the one which has all annotations, then I can’t use Gson inside the custom deserializer as it will use those annotations which I don’t want to.

So, I have written it with two different sets of POJOs.

But, if you were to use Jackson instead of Gson for json mapping, then you would have the ability to specify different names for serialization and deserialization for the same field, as mentioned in this stack overflow post:

Then, you can have only one POJO, with a custom deserializer using Jackson, while for serialization you could just use Jackson annotations on the same POJO.

I would really recommend you to switch to Jackson, if possible. It is a lot more customisable and offers a lot more features than Gson. Also IIRC, its speed benchmarks were better than Gson in a variety of conditions.

Thanks

1 Like

Thanks @abhimanyusinghgaur, I can use Jackson and Ill give that a go. Thanks for suggestion and SO link.

Again just wondering, if this is what people generally do? I’m kind of surprised that no-one has asked about this before and/or there is no documentation on this. Not that it is exceedingly difficult, but more over as the way the facets are modelled in general requires pause for thought (for me anyway, but am new to graphs…). What I’m wondering I guess also is if people just use RDF instead of JSON?

Hi @damienburke, thanks for participating in the community.

First of all, I would like to clarify that I am not JAVA dev. That’s why I didn’t get into the topic.

I noticed that you are having problems with the JSON format. This is probably due to the fact that we switched to a new format in the responses. I particularly think it makes life a little harder for Devs in general.

If this is your case, would you mind reading and commenting on this topic? Facets format in mutation requests and query responses - thanks.

Yeah, I think RDF, as it is today, made things easier than JSON.

In general, RDF is our base input format (side comment: It is worth mentioning that our RDF does not follow all W3C standards). It won’t change anytime in the future. So it is a safe harbor. The only issue is the JSON format see https://github.com/dgraph-io/dgraph/issues/4907. This happened because we supported Facets in List Type. This improvement ended up modifying the final JSON input format.

We would be very grateful to have your use case there in the post Facets format in mutation requests and query responses

Cheers.

1 Like

Thanks @MichelDiz for the additional info. I am going to stick with JSON approach for a day or 2 to see how I get on with it, and see what the (required custom) serializers / deserializers feel like. I will be sure to add any thoughts, etc. to that github issue in next day or so. Cheers