Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/JSCalendar.Net/Converters/DurationJsonConverter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ private static Duration ParseDuration(string durationString)
};
}

private static int? ParseIntGroup(Group group)
private static int? ParseIntGroup(System.Text.RegularExpressions.Group group)
{
if (group.Success && int.TryParse(group.Value, out var value)) return value;

Expand Down
41 changes: 41 additions & 0 deletions src/JSCalendar.Net/Converters/JSCalendarObjectConverter.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
using System.Text.Json;
using System.Text.Json.Serialization;

namespace JSCalendar.Net.Converters;

/// <summary>
/// JSON converter for IJSCalendarObject interface to support polymorphic deserialization.
/// This converter reads the @type property to determine whether to deserialize an Event or Task.
/// </summary>
public class JSCalendarObjectConverter : JsonConverter<IJSCalendarObject>
{
public override IJSCalendarObject? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
// Read the JSON as a JsonDocument to inspect the @type property
using var jsonDoc = JsonDocument.ParseValue(ref reader);
var root = jsonDoc.RootElement;

// Check for @type property
if (!root.TryGetProperty("@type", out var typeProperty))
{
throw new JsonException("Missing @type property in JSCalendar object");
}

var typeName = typeProperty.GetString();

// Deserialize based on the @type value
var json = root.GetRawText();
return typeName switch
{
"Event" => JsonSerializer.Deserialize<Event>(json, options),
"Task" => JsonSerializer.Deserialize<Task>(json, options),
_ => throw new JsonException($"Unknown JSCalendar object type: {typeName}")
};
}

public override void Write(Utf8JsonWriter writer, IJSCalendarObject value, JsonSerializerOptions options)
{
// Serialize the concrete type
JsonSerializer.Serialize(writer, value, value.GetType(), options);
}
}
2 changes: 1 addition & 1 deletion src/JSCalendar.Net/Event.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ namespace JSCalendar.Net;
/// Represents a calendar event in JSCalendar format (RFC 8984).
/// This is the main type for calendar events.
/// </summary>
public sealed class Event
public sealed class Event : IJSCalendarObject
{
// Metadata Properties (Section 4.1)

Expand Down
120 changes: 120 additions & 0 deletions src/JSCalendar.Net/Group.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
using System.Text.Json.Serialization;

namespace JSCalendar.Net;

/// <summary>
/// Represents a collection of events and/or tasks in JSCalendar format (RFC 8984 Section 2.3).
/// A Group is used to organize related calendar objects together.
/// </summary>
public sealed class Group
{
// Metadata Properties (Section 4.1)

/// <summary>
/// Type identifier. MUST be "Group".
/// </summary>
[JsonPropertyName("@type")]
public string Type { get; init; } = "Group";

/// <summary>
/// Globally unique identifier for this group (Section 4.1.2).
/// REQUIRED property.
/// </summary>
[JsonPropertyName("uid")]
public required string Uid { get; init; }

/// <summary>
/// Producer identifier that created this group (Section 4.1.4).
/// </summary>
[JsonPropertyName("prodId")]
public string? ProdId { get; init; }

/// <summary>
/// Date and time this group was created (Section 4.1.5).
/// </summary>
[JsonPropertyName("created")]
public DateTimeOffset? Created { get; init; }

/// <summary>
/// Date and time this group was last updated (Section 4.1.6).
/// REQUIRED property.
/// </summary>
[JsonPropertyName("updated")]
public required DateTimeOffset Updated { get; init; }

// What and Where Properties (Section 4.2)

/// <summary>
/// Short summary or name of the group (Section 4.2.1).
/// Default: empty String
/// </summary>
[JsonPropertyName("title")]
public string Title { get; init; } = "";

/// <summary>
/// Detailed description of the group (Section 4.2.2).
/// Default: empty String
/// </summary>
[JsonPropertyName("description")]
public string Description { get; init; } = "";

/// <summary>
/// Content type of the description (Section 4.2.3).
/// Default: "text/plain"
/// </summary>
[JsonPropertyName("descriptionContentType")]
public string DescriptionContentType { get; init; } = "text/plain";

/// <summary>
/// Links to external resources associated with this group (Section 4.2.7).
/// </summary>
[JsonPropertyName("links")]
public Dictionary<string, Link>? Links { get; init; }

/// <summary>
/// Language tag for this group (Section 4.2.8).
/// </summary>
[JsonPropertyName("locale")]
public string? Locale { get; init; }

/// <summary>
/// Keywords or tags for this group (Section 4.2.9).
/// </summary>
[JsonPropertyName("keywords")]
public Dictionary<string, bool>? Keywords { get; init; }

/// <summary>
/// Categories for this group (Section 4.2.10).
/// </summary>
[JsonPropertyName("categories")]
public Dictionary<string, bool>? Categories { get; init; }

/// <summary>
/// Color to use when displaying this group (Section 4.2.11).
/// </summary>
[JsonPropertyName("color")]
public string? Color { get; init; }

// Time Zone Properties (Section 4.7)

/// <summary>
/// Time zone definitions referenced by entries in this group (Section 4.7.2).
/// </summary>
[JsonPropertyName("timeZones")]
public Dictionary<string, TimeZone>? TimeZones { get; init; }

// Group-specific Properties (Section 5.3)

/// <summary>
/// Collection of Event and/or Task objects in this group (Section 5.3.1).
/// REQUIRED property.
/// </summary>
[JsonPropertyName("entries")]
public required List<IJSCalendarObject> Entries { get; init; }

/// <summary>
/// Source URI from which updated versions of this group may be retrieved (Section 5.3.2).
/// </summary>
[JsonPropertyName("source")]
public string? Source { get; init; }
}
27 changes: 27 additions & 0 deletions src/JSCalendar.Net/IJSCalendarObject.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
using System.Text.Json.Serialization;
using JSCalendar.Net.Converters;

namespace JSCalendar.Net;

/// <summary>
/// Marker interface for JSCalendar objects that can be entries in a Group.
/// This interface is implemented by Event and Task objects.
/// </summary>
[JsonConverter(typeof(JSCalendarObjectConverter))]
public interface IJSCalendarObject
{
/// <summary>
/// Type identifier for this JSCalendar object.
/// </summary>
string Type { get; }

/// <summary>
/// Globally unique identifier for this object.
/// </summary>
string Uid { get; }

/// <summary>
/// Date and time this object was last updated.
/// </summary>
DateTimeOffset Updated { get; }
}
2 changes: 1 addition & 1 deletion src/JSCalendar.Net/Task.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ namespace JSCalendar.Net;
/// <summary>
/// Represents a task/todo item in JSCalendar format (RFC 8984 Section 2.2).
/// </summary>
public sealed class Task
public sealed class Task : IJSCalendarObject
{
// Metadata Properties (Section 4.1)

Expand Down
Loading
Loading