You are viewing a plain text version of this content. The canonical link for it is here.
Posted to users@jena.apache.org by Zak Mc Kracken <za...@yahoo.it.INVALID> on 2018/08/24 12:08:38 UTC

Writing Embedding JSON-LD from Jena

Hi all,

Trying here in the mailing list too:

https://stackoverflow.com/questions/51966888/writing-embedding-json-ld-from-jena

In short, I want to get a JSON result from CONSTRUCT, which, in: person1 
foaf:knows person2, puts the person2 object inside person1, as value of 
'knows'. The current defaults set the person2's URI only and report the 
person2 object separately. JSON-LD and jsonld-java accept the @embed = 
@always option, but only the framing processing seem to recognise it, 
which makes things more complicated than I wish, since I don't need any 
framing, just always-embeded output.

Thanks in advance for any help,
Marco.


Re: Writing Embedding JSON-LD from Jena

Posted by Zak Mc Kracken <za...@yahoo.it.INVALID>.
Hi Francois, thank you for your insightful answer.

Meanwhile I had managed to do this:

         Model m = SparqlUtils.construct ( sparql, model );
         StringWriter sw = new StringWriter ();

         m.write ( sw, "JSON-LD" );

         @SuppressWarnings ( "unchecked" )
         Map<String, Object> js = (Map<String, Object>) 
JsonUtils.fromString ( sw.toString () );

         JsonLdOptions jsOpts = new JsonLdOptions ();
         jsOpts.setEmbed ( "@always" );

         Object jsldCtx = js.get ( "@context" );
         js = JsonLdProcessor.frame ( js, jsldCtx, jsOpts );
         // Compaction needs to be redone
         js = JsonLdProcessor.compact ( js, jsldCtx, jsOpts );

So, I get a json object from the Jena output and then I re-process it, 
repeating the same context that Jena returns initially. Don't know why 
the framing flattens things (eg., instead of "knows" I get 
"http://.../knows"), which forces me to redo the compact() operation 
too. Here I'm fine with re-parsing the JSON string coming from Jena, not 
a big overhead in my use case, though it might become a problem in 
general (would be great if I could get a JSON Map straight from Jena).

Marco.


On 28/08/2018 00:41, François-Paul Servant wrote:
> Hi,
>
> quite some time I have not been using json-ld + jena (I wasn’t even aware of the “@embed”). Anyway, here are some observations. Hope they can help. (I may be wrong, but it seems that “@embed” is related to framings, so I only tried to use frames.)
>
> Using an example model:
>
> private Model exampleModel() {
>    Model m = ModelFactory.createDefaultModel();
>    String ns = "http://xmlns.com/foaf/0.1/";
>    Resource person = m.createResource(ns + "Person");
>    Resource s = m.createResource("http://a.com/person1");
>    m.add(s, m.createProperty(ns + "name"), "Jane Doe");
>    m.add(s, m.createProperty(ns + "title"), "Professor");
>    m.add(s, RDF.type, person);
>    Resource s2 = m.createResource("http://a.com/person2");
>    m.add(s2, m.createProperty(ns + "name"), "Gado Salamatou");
>    m.add(s2, m.createProperty(ns + "homePage"), "http://www.salamatou.com");
>    m.add(s2, m.createProperty(ns + "title"), "Dr");
>    m.add(s2, RDF.type, person);
>    
> 	Property knows = m.createProperty(ns + "knows");	
> 	m.add(s,knows,s2);
>
>    s = m.createResource();
>    m.add(s, RDF.type, m.createResource(ns + "Organization"));
>    return m;
> }
>
> following code:
>
> @Test public final void ftest1() {
> 	Model m = exampleModel();
> 	DatasetGraph g = DatasetFactory.wrap(m).asDatasetGraph();
> 	JsonLDWriteContext ctx = new JsonLDWriteContext();
>
> 	// no effect, it seems
> 	//	 JsonLdOptions opts = new JsonLdOptions ();
> 	//	
> 	//	 opts.setEmbed ( "@always" );
> 	//	 ctx.setOptions ( opts );
>
> 	Map<String, Object> frame = new HashMap<>();
> 	ctx.setFrame(frame);
>
> 	// only output Persons
> 	frame.put("@type", "http://xmlns.com/foaf/0.1/Person");
>
> 	write(g, RDFFormat.JSONLD_FRAME_PRETTY, ctx);
> }
>
> outputs the following:
>
> {
>    "@graph" : [ {
>      "@id" : "http://a.com/person1",
>      "@type" : "http://xmlns.com/foaf/0.1/Person",
>      "http://xmlns.com/foaf/0.1/knows" : {
>        "@id" : "http://a.com/person2",
>        "@type" : "http://xmlns.com/foaf/0.1/Person",
>        "http://xmlns.com/foaf/0.1/homePage" : "http://www.salamatou.com",
>        "http://xmlns.com/foaf/0.1/name" : "Gado Salamatou",
>        "http://xmlns.com/foaf/0.1/title" : "Dr"
>      },
>      "http://xmlns.com/foaf/0.1/name" : "Jane Doe",
>      "http://xmlns.com/foaf/0.1/title" : "Professor"
>    }, {
>      "@id" : "http://a.com/person2",
>      "@type" : "http://xmlns.com/foaf/0.1/Person",
>      "http://xmlns.com/foaf/0.1/homePage" : "http://www.salamatou.com",
>      "http://xmlns.com/foaf/0.1/name" : "Gado Salamatou",
>      "http://xmlns.com/foaf/0.1/title" : "Dr"
>    } ]
> }
>
> So,
> - we have “person2” embedded in “person1”, but it is repeated at the end anyway. I don’t know why, but I would say that this is related to JsonLD-Java, not jena.
> - there is no “@context” (which is automatically computed by jena when using a COMPACT or a FLATTEN output)
>
> Regarding the “@context”, trying (for instance)
> 	String atContextAsJson = "{\"name\":{\"@id\":\"http://xmlns.com/foaf/0.1/name\"},\"Person\": {\"@id\": \"http://xmlns.com/foaf/0.1/Person\"}}";
> 	ctx.setJsonLDContext(atContextAsJson);
> (the way to set the @context for compact of flatten) doesn’t work.
>
> The code of org.apache.jena.riot.writer.JsonLDWriter, doesn’t take the context into account when it’s about frames. It could be changed (around line 210) to
>          } else if (variant.isFrame()) {
>              Object frame = null;
>              if (jenaContext != null) {
>                  frame = jenaContext.get(JSONLD_FRAME);
>              }
>              if (frame == null) {
>                  throw new IllegalArgumentException("No frame object found in jena Context");
>              }
>
>              if (frame instanceof String) {
>                  frame = JsonUtils.fromString((String) frame);
>              }
>              
> 	    // THIS IS THE SUGGESTED CHANGE:
>              if (frame instanceof Map) {
>              	Map map = (Map) frame;
>              	if (map.get("@context") == null) {
>              		// no @context in frame: add one (from jena context, or create one -- should we create one ?)
>                  Object ctx = getJsonldContext(dataset, prefixMap, jenaContext);
>                  map.put("@context", ctx);
>              	}
>              }
>              
>              obj = JsonLdProcessor.frame(obj, frame, opts);
>
> and the previous would then work. But I don’t know whether it is a good idea.
>
> Another option is to pass the @context directly inside the frame, like this :
>
> @Test public final void ftest2() {
> 	Model m = exampleModel();
> 	DatasetGraph g = DatasetFactory.wrap(m).asDatasetGraph();
> 	JsonLDWriteContext ctx = new JsonLDWriteContext();
>
> 	// no effect, it seems
> 	//	 JsonLdOptions opts = new JsonLdOptions ();
> 	//	
> 	//	 opts.setEmbed ( "@always" );
> 	//	 ctx.setOptions ( opts );
>
> 	Map<String, Object> frame = new HashMap<>();
> 	ctx.setFrame(frame);
>
> 	// only output Persons
> 	frame.put("@type", "http://xmlns.com/foaf/0.1/Person");
>
> 	String atContextAsJson = "{\"name\":{\"@id\":\"http://xmlns.com/foaf/0.1/name\"},\"Person\": {\"@id\": \"http://xmlns.com/foaf/0.1/Person\"}}";
> 	try {
> 		frame.put("@context", JsonUtils.fromString(atContextAsJson));
> 	} catch (Exception e) { throw new RuntimeException(e); }
> 	
> 	write(g, RDFFormat.JSONLD_FRAME_PRETTY, ctx);
> }
>
> But I agree with you that it is annoying to pass the context, when we know that jena is able to compute it. But the method that does it in org.apache.jena.riot.writer.JsonLDWriter is not public. I would suggest to make it public:
>      public static Map<String, Object> createJsonldContext(Graph g, PrefixMap prefixMap, boolean addAllPrefixesToContext) {
>
> you then could write:
> @Test public final void ftest3() {
> 	Model m = exampleModel();
> 	DatasetGraph g = DatasetFactory.wrap(m).asDatasetGraph();
> 	JsonLDWriteContext ctx = new JsonLDWriteContext();
>
> 	// no effect, it seems
> 	//	 JsonLdOptions opts = new JsonLdOptions ();
> 	//	
> 	//	 opts.setEmbed ( "@always" );
> 	//	 ctx.setOptions ( opts );
>
> 	Map<String, Object> frame = new HashMap<>();
> 	ctx.setFrame(frame);
>
> 	// only output Persons
> 	frame.put("@type", "http://xmlns.com/foaf/0.1/Person");
>
>    PrefixMap pm = RiotLib.prefixMap(g);
>    boolean addAllPrefixesToContext = true;
>    Map<String, Object> map = JsonLDWriter.createJsonldContext(g.getDefaultGraph(), pm, addAllPrefixesToContext);
>    frame.put("@context", map);
>
> 	write(g, RDFFormat.JSONLD_FRAME_PRETTY, ctx);
> }
>
> to get following output:
>
> {
>    "@context" : {
>      "title" : "http://xmlns.com/foaf/0.1/title",
>      "homePage" : "http://xmlns.com/foaf/0.1/homePage",
>      "name" : "http://xmlns.com/foaf/0.1/name",
>      "knows" : {
>        "@id" : "http://xmlns.com/foaf/0.1/knows",
>        "@type" : "@id"
>      }
>    },
>    "@graph" : [ {
>      "@id" : "http://a.com/person1",
>      "@type" : "http://xmlns.com/foaf/0.1/Person",
>      "knows" : {
>        "@id" : "http://a.com/person2",
>        "@type" : "http://xmlns.com/foaf/0.1/Person",
>        "homePage" : "http://www.salamatou.com",
>        "name" : "Gado Salamatou",
>        "title" : "Dr"
>      },
>      "name" : "Jane Doe",
>      "title" : "Professor"
>    }, {
>      "@id" : "http://a.com/person2",
>      "@type" : "http://xmlns.com/foaf/0.1/Person",
>      "homePage" : "http://www.salamatou.com",
>      "name" : "Gado Salamatou",
>      "title" : "Dr"
>    } ]
> }
>
> fps
>
>> Le 24 août 2018 à 14:08, Zak Mc Kracken <za...@yahoo.it.INVALID> a écrit :
>>
>> Hi all,
>>
>> Trying here in the mailing list too:
>>
>> https://stackoverflow.com/questions/51966888/writing-embedding-json-ld-from-jena
>>
>> In short, I want to get a JSON result from CONSTRUCT, which, in: person1 foaf:knows person2, puts the person2 object inside person1, as value of 'knows'. The current defaults set the person2's URI only and report the person2 object separately. JSON-LD and jsonld-java accept the @embed = @always option, but only the framing processing seem to recognise it, which makes things more complicated than I wish, since I don't need any framing, just always-embeded output.
>>
>> Thanks in advance for any help,
>> Marco.
>>


Re: Writing Embedding JSON-LD from Jena

Posted by François-Paul Servant <fr...@gmail.com>.
Hi,

quite some time I have not been using json-ld + jena (I wasn’t even aware of the “@embed”). Anyway, here are some observations. Hope they can help. (I may be wrong, but it seems that “@embed” is related to framings, so I only tried to use frames.)

Using an example model:

private Model exampleModel() {
  Model m = ModelFactory.createDefaultModel();
  String ns = "http://xmlns.com/foaf/0.1/";
  Resource person = m.createResource(ns + "Person");
  Resource s = m.createResource("http://a.com/person1");
  m.add(s, m.createProperty(ns + "name"), "Jane Doe");
  m.add(s, m.createProperty(ns + "title"), "Professor");
  m.add(s, RDF.type, person);
  Resource s2 = m.createResource("http://a.com/person2");
  m.add(s2, m.createProperty(ns + "name"), "Gado Salamatou");
  m.add(s2, m.createProperty(ns + "homePage"), "http://www.salamatou.com");
  m.add(s2, m.createProperty(ns + "title"), "Dr");
  m.add(s2, RDF.type, person);
  
	Property knows = m.createProperty(ns + "knows");	
	m.add(s,knows,s2);

  s = m.createResource();
  m.add(s, RDF.type, m.createResource(ns + "Organization"));
  return m;
}

following code: 

@Test public final void ftest1() {
	Model m = exampleModel();
	DatasetGraph g = DatasetFactory.wrap(m).asDatasetGraph();
	JsonLDWriteContext ctx = new JsonLDWriteContext();

	// no effect, it seems
	//	 JsonLdOptions opts = new JsonLdOptions ();
	//	 
	//	 opts.setEmbed ( "@always" );
	//	 ctx.setOptions ( opts );

	Map<String, Object> frame = new HashMap<>();
	ctx.setFrame(frame);

	// only output Persons
	frame.put("@type", "http://xmlns.com/foaf/0.1/Person");

	write(g, RDFFormat.JSONLD_FRAME_PRETTY, ctx);
}

outputs the following:

{
  "@graph" : [ {
    "@id" : "http://a.com/person1",
    "@type" : "http://xmlns.com/foaf/0.1/Person",
    "http://xmlns.com/foaf/0.1/knows" : {
      "@id" : "http://a.com/person2",
      "@type" : "http://xmlns.com/foaf/0.1/Person",
      "http://xmlns.com/foaf/0.1/homePage" : "http://www.salamatou.com",
      "http://xmlns.com/foaf/0.1/name" : "Gado Salamatou",
      "http://xmlns.com/foaf/0.1/title" : "Dr"
    },
    "http://xmlns.com/foaf/0.1/name" : "Jane Doe",
    "http://xmlns.com/foaf/0.1/title" : "Professor"
  }, {
    "@id" : "http://a.com/person2",
    "@type" : "http://xmlns.com/foaf/0.1/Person",
    "http://xmlns.com/foaf/0.1/homePage" : "http://www.salamatou.com",
    "http://xmlns.com/foaf/0.1/name" : "Gado Salamatou",
    "http://xmlns.com/foaf/0.1/title" : "Dr"
  } ]
}

So,
- we have “person2” embedded in “person1”, but it is repeated at the end anyway. I don’t know why, but I would say that this is related to JsonLD-Java, not jena.
- there is no “@context” (which is automatically computed by jena when using a COMPACT or a FLATTEN output)

Regarding the “@context”, trying (for instance)
	String atContextAsJson = "{\"name\":{\"@id\":\"http://xmlns.com/foaf/0.1/name\"},\"Person\": {\"@id\": \"http://xmlns.com/foaf/0.1/Person\"}}";
	ctx.setJsonLDContext(atContextAsJson);
(the way to set the @context for compact of flatten) doesn’t work.

The code of org.apache.jena.riot.writer.JsonLDWriter, doesn’t take the context into account when it’s about frames. It could be changed (around line 210) to
        } else if (variant.isFrame()) {
            Object frame = null;
            if (jenaContext != null) {
                frame = jenaContext.get(JSONLD_FRAME);
            }
            if (frame == null) {
                throw new IllegalArgumentException("No frame object found in jena Context");
            }

            if (frame instanceof String) {
                frame = JsonUtils.fromString((String) frame);
            }
            
	    // THIS IS THE SUGGESTED CHANGE:
            if (frame instanceof Map) {
            	Map map = (Map) frame;
            	if (map.get("@context") == null) {
            		// no @context in frame: add one (from jena context, or create one -- should we create one ?)
                Object ctx = getJsonldContext(dataset, prefixMap, jenaContext);
                map.put("@context", ctx);
            	}
            }            
            
            obj = JsonLdProcessor.frame(obj, frame, opts);

and the previous would then work. But I don’t know whether it is a good idea.

Another option is to pass the @context directly inside the frame, like this :

@Test public final void ftest2() {
	Model m = exampleModel();
	DatasetGraph g = DatasetFactory.wrap(m).asDatasetGraph();
	JsonLDWriteContext ctx = new JsonLDWriteContext();

	// no effect, it seems
	//	 JsonLdOptions opts = new JsonLdOptions ();
	//	 
	//	 opts.setEmbed ( "@always" );
	//	 ctx.setOptions ( opts );

	Map<String, Object> frame = new HashMap<>();
	ctx.setFrame(frame);

	// only output Persons
	frame.put("@type", "http://xmlns.com/foaf/0.1/Person");

	String atContextAsJson = "{\"name\":{\"@id\":\"http://xmlns.com/foaf/0.1/name\"},\"Person\": {\"@id\": \"http://xmlns.com/foaf/0.1/Person\"}}";
	try {
		frame.put("@context", JsonUtils.fromString(atContextAsJson));
	} catch (Exception e) { throw new RuntimeException(e); }  
	
	write(g, RDFFormat.JSONLD_FRAME_PRETTY, ctx);
}

But I agree with you that it is annoying to pass the context, when we know that jena is able to compute it. But the method that does it in org.apache.jena.riot.writer.JsonLDWriter is not public. I would suggest to make it public: 
    public static Map<String, Object> createJsonldContext(Graph g, PrefixMap prefixMap, boolean addAllPrefixesToContext) {

you then could write:
@Test public final void ftest3() {
	Model m = exampleModel();
	DatasetGraph g = DatasetFactory.wrap(m).asDatasetGraph();
	JsonLDWriteContext ctx = new JsonLDWriteContext();

	// no effect, it seems
	//	 JsonLdOptions opts = new JsonLdOptions ();
	//	 
	//	 opts.setEmbed ( "@always" );
	//	 ctx.setOptions ( opts );

	Map<String, Object> frame = new HashMap<>();
	ctx.setFrame(frame);

	// only output Persons
	frame.put("@type", "http://xmlns.com/foaf/0.1/Person");

  PrefixMap pm = RiotLib.prefixMap(g);
  boolean addAllPrefixesToContext = true;
  Map<String, Object> map = JsonLDWriter.createJsonldContext(g.getDefaultGraph(), pm, addAllPrefixesToContext);
  frame.put("@context", map);

	write(g, RDFFormat.JSONLD_FRAME_PRETTY, ctx);
}

to get following output:

{
  "@context" : {
    "title" : "http://xmlns.com/foaf/0.1/title",
    "homePage" : "http://xmlns.com/foaf/0.1/homePage",
    "name" : "http://xmlns.com/foaf/0.1/name",
    "knows" : {
      "@id" : "http://xmlns.com/foaf/0.1/knows",
      "@type" : "@id"
    }
  },
  "@graph" : [ {
    "@id" : "http://a.com/person1",
    "@type" : "http://xmlns.com/foaf/0.1/Person",
    "knows" : {
      "@id" : "http://a.com/person2",
      "@type" : "http://xmlns.com/foaf/0.1/Person",
      "homePage" : "http://www.salamatou.com",
      "name" : "Gado Salamatou",
      "title" : "Dr"
    },
    "name" : "Jane Doe",
    "title" : "Professor"
  }, {
    "@id" : "http://a.com/person2",
    "@type" : "http://xmlns.com/foaf/0.1/Person",
    "homePage" : "http://www.salamatou.com",
    "name" : "Gado Salamatou",
    "title" : "Dr"
  } ]
}

fps

> Le 24 août 2018 à 14:08, Zak Mc Kracken <za...@yahoo.it.INVALID> a écrit :
> 
> Hi all,
> 
> Trying here in the mailing list too:
> 
> https://stackoverflow.com/questions/51966888/writing-embedding-json-ld-from-jena
> 
> In short, I want to get a JSON result from CONSTRUCT, which, in: person1 foaf:knows person2, puts the person2 object inside person1, as value of 'knows'. The current defaults set the person2's URI only and report the person2 object separately. JSON-LD and jsonld-java accept the @embed = @always option, but only the framing processing seem to recognise it, which makes things more complicated than I wish, since I don't need any framing, just always-embeded output.
> 
> Thanks in advance for any help,
> Marco.
>