How to get Objects based on Predicate from Thing

Hi, I am using the solid-client library to read messages in user inbox (e.g. https://.inrupt.net/inbox/). And to be honest I am quite unhappy now, as I still cannot get data I want, even after lot of time spent reading the docs, trying and debugging.

Resources I am using:

I have inbox, its RDF looks like:

@prefix : <#>.
@prefix inbox: <>.
@prefix ldp: <http://www.w3.org/ns/ldp#>.
@prefix terms: <http://purl.org/dc/terms/>.
@prefix XML: <http://www.w3.org/2001/XMLSchema#>.
@prefix st: <http://www.w3.org/ns/posix/stat#>.
@prefix pl: <http://www.w3.org/ns/iana/media-types/text/plain#>.
@prefix ld: <http://www.w3.org/ns/iana/media-types/application/ld+json#>.

inbox:
    a ldp:BasicContainer, ldp:Container;
    terms:modified "2021-01-24T20:12:16Z"^^XML:dateTime;
    ldp:contains
        <066fb2e0-5e6a-11eb-8a41-fb14022d87dc.txt>,
        <3535f720-55db-11eb-8c98-7b8ee26d0b89.jsonld>,
        ...
        <fdb380f0-3a01-11eb-8c98-7b8ee26d0b89.txt>;
    st:mtime 1611519136.625;
    st:size 4096.
<066fb2e0-5e6a-11eb-8a41-fb14022d87dc.txt>
    a pl:Resource, ldp:Resource;
    terms:modified "2021-01-24T17:31:43Z"^^XML:dateTime;
    st:mtime 1611509503.246;
    st:size 4.

   ...

<fdb380f0-3a01-11eb-8c98-7b8ee26d0b89.txt>
    a pl:Resource, ldp:Resource;
    terms:modified "2020-12-09T09:36:19Z"^^XML:dateTime;
    st:mtime 1607506579.071;
    st:size 4.

In TypeScript, with inbox URL I’d imagine I could do something like:

const inboxDataSet: SolidDataset = await getSolidDataset(inboxUrl, {fetch: this.session.fetch});
const inboxMessages: Thing[] = getThingAll(inboxDataSet, LDP.contains);

for (const inboxMessage of inboxMessages) {
   const: inboxMessageUrl = getUrl(inboxMessage);
   const messageWithMetadata = {
      url: inboxMessageUrl ,
      content: fetch(inboxMessageUrl), // need to fetch to get the content
      created: getDatetime(inboxMessage, DCTERMS.modified)
   }
  // use the messageWithMetadata further in my app
}

However, the method getThingAll(inboxDataSet, options) does not support (AFAIK not implemented yet) a way to do this. I cannot find another way to easily extract Objects from the SolidDataSet (subject) based on various Predicates.

To be clear, my question is: how do I get the message creation date from the inbox?
More generally, how to get any object by specifying predicate from existing subject/solidDataSet?

<https://<username>.inrupt.net/inbox/74cafc60-55df-11eb-8c98-7b8ee26d0b89.jsonld> <http://purl.org/dc/terms/modified> "2021-01-13T20:39:39Z"^^<http://www.w3.org/2001/XMLSchema#dateTime> .

Thank you :slight_smile:

2 Likes

Thanks for so explicitly mentioning your goal, that makes it easier to help :slight_smile:

Let me first answer the specific question: how do you get the dct:modified date from the inbox? And actually, you already have the answer in your code: getDatetime(inboxMessage, DCTERMS.modified). That will give you the DCTERMS.modified data for the specific inbox message in inboxMessage.

Now there are a couple of other things that are probably helpful to point out:

  • getThingAll does not take a second argument (you passed LDP.contains). It just gives you all the Things in inboxDataSet. Since it seems you’re somewhat familiar with RDF terminology already, a “Thing” is equivalent to a set of triples sharing the same subject.
  • getThingAll probably does what you want, but if you really do want just the Things that are referred to by LDP.contains, you’d do something like this:
const inboxMessageUrls = getContainedResourceUrlAll(inboxDataSet);
const inboxMessages = inboxMessageUrls.map(inboxMessageUrl => getThing(inboxDataset, inboxMessageUrl));
  • getUrl is intended to read a URL value from a Thing, similar to how getDatetime is intended to read a datetime value from a Thing. If you want the Thing’s URL, you’d use asUrl.
  • You call fetch, but fetch returns a Promise, so you’ll probably need to await it.

As for your last question:

More generally, how to get any object by specifying predicate from existing subject/solidDataSet?

In solid-client, you don’t get RDF objects from the SolidDataset directly. Instead, you first obtain the Things you are interested in, and then depending on the type of the data you are interested in, you call one of the thing/get functions with the relevant Thing and the predicate your are interested in.

And maybe to clarify this idea: your /inbox contains multiple messages, each represented by their own Thing. Thus, to get the date of a specific inbox message, you’ll first have to get the “Thing” representing the message you are interested in, and then you can read the date from that.

Let me know if you have further questions, or if something is unclear still :slight_smile:

1 Like

Thank you for the fast reply, that helps very much!

I could have been more specific on what I have already achieved and what not :slight_smile: Yes, I was able to get the date, but I struggled with extracting only messages from the inboxDataSet. I had this (note the comment):

const inboxDataSet: SolidDataset = await getSolidDataset(inboxUrl, {fetch: this.session.fetch});
const inboxMessages: Thing[] = getThingAll(inboxDataSet);

for (const inboxMessage of inboxMessages) {
   // now I had also the inbox itself in the inboxMessages array, not just messages
   const created: Date = getDatetime(inboxMessage, DCTERMS.modified);
   ...
}

Btw I was also trying to suggest that, as a developer with some understanding of RDF triples, I’d hope for a simpler way of getting the messages and that I struggled to do so :slight_smile:

E.g. why this:

you don’t get RDF objects from the SolidDataset directly

? It feels cumbersome, but I feel like there’s some catch/technical reason to that :slight_smile:


Anyway, with your help, I was able to achieve what I wanted with the following code (using getContainedResourceUrlAll did the trick):

const inboxDataSet = await getSolidDataset(inboxUrl, {fetch: this.session.fetch});
const inboxMessagesUrls: UrlString[] = getContainedResourceUrlAll(inboxDataSet);

let messagesForTable = new Array<InboxMessage>();

for (const inboxMessageUrl of inboxMessagesUrls) {
  const inboxMessage: Thing = getThing(inboxDataSet, inboxMessageUrl)
  const created: Date = getDatetime(inboxMessage, DCTERMS.modified);
  const messageFile: Blob = await getFile(inboxMessageUrl, {fetch: this.session.fetch});

  messageFile.text().then(text => {
    messagesForTable.push({
      url: inboxMessageUrl, content: text, type: messageFile.type, created: created
    })
  });
}

Thank you for your quick help!

2 Likes

It’s mainly the way it to make it easier for developers unfamiliar with RDF, since you’ll mostly be working with sets of data grouped by Subject. We’re still looking at making it easier for developers familiar with RDF to drop down to that level too, but haven’t aligned on the exact API for that yet, unfortunately.

But good to hear you figured it out!

2 Likes

Hello!

Thanks this thread has been very clear and helpful… I’m experiencing a similar issue to the OP. I could refactor my code to work with getContainedResourceUrlAll, but it will mean duplicating the storage of Things with their URLs so I’d prefer to have only one storage (of Things)

I wanted to open an issue requesting a method like Thing.getUrl() or overriding Thing.toString() to get the URL (I believe Apache Jena does this with Resource)… but I assume there’s a reason why the internal_url of my Thing has been hidden in the first place ?

In order to use asUrl I need the storage to use ThingPersisted which reading the docs seems to be only applicable if the resource was created locally via createThing?

For info on my use case, I’m storing a list of Things fetched from a turtle file on startup, I’m accessing certain fields such as VCARD.fn from these Things, but I also need the URI of the Thing, so that I can make a request to a Java backend which takes a URI of a resource, reads it, and uses its properties to find some interesting information about it. This needs to be done backend because the front-end should be able to “ask for content” in this way from multiple servers

In order to use asUrl I need the storage to use PersistedThing which reading the docs seems to be only applicable if the resource was created locally via createThing ?

To answer my own question, I found that I can access this by casting to ThingPersistsed:

const uri: string = asUrl(myThing as ThingPersistsed);
2 Likes

Yes, basically a Thing can have a full URL associated with it (which, if you’re using TypeScript, is typed as a ThingPersisted), or a local identifier that will be resolved to a full URL the moment you save it somewhere.

asUrl(myThing) works if your Thing has a full URL associated with it. If you are sure that it does (as in your case), you can indeed inform TypeScript about that by explicitly casting it to a ThingPersisted. Thanks for coming back and sharing your solution!

For those reading along or coming across this in the future: the above is really only relevant if you’re using TypeScript. Otherwise, the relevant info is that you’ll want to use asUrl to get the URL of a Thing.

However, if you plan on passing it a Thing that might only have a local identifier, it also takes a second argument: the URL of the Resource the Thing will be saved to. That would look something like asUrl(myThing, "https://example.com/baseUrl").

(That would, in fact, also have satisfied TypeScript, because it can now be sure that asUrl can actually make sense of the Thing it has been given.)

1 Like