-
Notifications
You must be signed in to change notification settings - Fork 10
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
Pick and implement an approach to JSON-LD support #2
Comments
For reference - there are branches with various partially-complete attempts to approach this from different angles. The main branch has another incomplete implementation. |
There are more details in the roadmap files of each branch. |
I have been looking into json-ld, the libraries that exist, as well as the goals for this project. On the json-ld page(https://json-ld.org/) I was using to understand it better, it really only had two libraries listed that were available for use. json-ld(dot)net seems to be mainly for turning one kind of json-ld document into another kind, and does not serialize/deserialize from json to c# classes and vice versa. dotnetrdf is very similar to json-ld(dot)net, but does more than just that. But it still does not serialize and de-serialize from c# classes to json and vice versa. Given the desire to minimize external dependencies, I think the best bet is to use System.Text.Json. JsonNode and/or JsonDocument are other good options to consider later, if System.Text.Json is insufficient for the project's needs. What have been the biggest hurdles to choosing an approach? I could be missing some key information. |
@Nbjohnston86 Sorry for the very late response! I read your comment weeks ago but couldn't quite work out how to explain the problem. I'll try now: The main challenge I've encountered is that many real-world uses of ActivityPub are basically "add Y additional context to X basic object type(s)". For example, mastodon-compatible microblogging software usually implements quote posts by adding an additional context to the Even simple uses can end up with a situation where one JSON-LD object needs to deserialize into a type like |
One idea, which is really not that great tbh, is to deserialize to a synthetic type that maps contexts to the appropriate subclass in a one-to-one fashion. This would be the responsibility of library users, but its not too much effort. Something like this: using ActivityPubSharp.Types;
using Some.ThirdParty.Lib;
namespace Some.ActivityPub.Application;
/// <summary>
/// Note object supported by this application
/// </summary>
public class SupportedNote {
// This is provided by ActivityPubSharp and has the typical ActivityStreams Note properties.
public ASNote Note { get; set; }
// This is unique to Some.ActivityPub.Application and provides fields for a custom signature.
public SignedObject Signature { get; set; }
// This is provided by Some.ThirdParty.Lib and provided extra properties for quote posts.
// Null if the context was not included in the source object.
public QuotePost? Quote { get; set; }
} The deserializer could read an attribute such as |
@warriordog I've also been having a lot of trouble with this. I'm probably going to have to take a crack at it myself, unless a miracle happens in the next couple of weeks or so and you or someone else figures out a good deserialization strategy for this bonkers format. In case it helps your thinking, my plan is to run the messages through json-ld.net and use that to frame them into a consistent shape that |
@jenniferplusplus that's the best approach I've heard so far, but it seems kind of inefficient and wasteful. I'm not sure there's any way around it, though. I did have one other idea, which works kind of like this:
Now for the part that makes this somewhat kinda workable:
This might seem clunky, but the DX might be kind of OK compared to the other options. You could use it like this: #region This can be in a NuGet package or wherever
public class Quote : IExtension<ASObject>
{
public const string QuoteExtensionUri = "https://example.com/some.uri";
/// <summary>
/// This object, which quotes the target of <see cref="QuoteId">.
/// </summary>
[JsonIgnore]
public ASObject Object { get; internal set; }
/// <summary>
/// URI / ID of the object that this object quotes
/// </summary>
public required ASLink QuoteId { get; set; }
}
public static class QuoteExtension
{
public static Quote? AsQuote(this ASObject obj)
{
IsQuote(obj, out var quote);
return quote;
}
public static bool IsQuote(this ASObject obj, [NotNullWhen(true)] out Quote? quote)
{
// Happy path - try to get from cache
if (obj.LoadedExtensions.TryGetValue(Quote.QuoteExtensionUri, out quote))
return true;
// Not there; we need to create it
quote = obj.OriginalJson.Deserialize<Quote>(obj.OriginalJsonOptions);
if (quote == null)
return false; // Deserialization failed
// Set object - can't be done automatically.
// ugly, but oh well
quote.Object = obj;
// Cache it for performance
obj.LoadedExtensions[Quote.QuoteExtensionUri] = quote;
return true;
}
}
#endregion
// This would be in application code somewhere
ASObject somePost = GetPostFromWherever();
DoSomethingWithPost(somePost);
DoAnotherThing(somePost);
// The fun part
if (somePost.IsQuote(out var quote))
{
DoSomethingWithAQuote(quote);
GetTheOriginalPost(quote.QuoteId);
}
MoreStuffWithObject(somePost); There is a significant downside, which is that unknown extensions wont be preserved round-trip. I have no idea if that's actually important but its something to be aware of. Another downside is the broken encapsulation (exposing |
I made a start at my framing plan and then gave it up because it's impossible to do in a way that's performant enough for this use case. And also writing those documents is harder than writing custom converters, somehow. So instead I'm trying a somewhat polymorphic deserialization strategy using custom converters. I don't yet have a good solution for extensions though. And I'm unexpectedly tripping over the Here, if you'd like to have a look. |
oh, and a fact I recently learned that might make you feel better: |
Your implementation looks pretty good so far! I'm also using a polymorphic converter, although I'm using reflection to figure out the AS type -> .NET type mappings at runtime. I really wish I'd learned about
Thanks for that tip about JsonSerializerOptions! That does make it a bit less bad. |
The current JSON implementation is reasonably workable, and its likely the best we can do until System.Text.JSON is overhauled to expose internal serializers (see #21). That leaves two remaining aspects that are now covered elsewhere:
That means we can finally close this issue! 🥳 |
Full JSON-LD is probably not practical to support, but these features are critical for ActivityPub support:
Additionally, these would be nice to have:
The text was updated successfully, but these errors were encountered: