Skip to content

Commit d7e1a09

Browse files
fix: correct ReleaseNotes deserialization for AutoUpdater (fixes #1039) (#1061)
* fix: correct ReleaseNotes deserialization for AutoUpdater (fixes #1039) - TypeScript normalize() now maps string releaseNotes to { note } objects and also handles arrays of strings (both cases were broken in PR #1041) - Added ReleaseNotesConverter (JsonConverter<ReleaseNoteInfo[]>) as defensive C# layer that handles all shapes: null, string, string[], object[] - Added [JsonConverter] attribute on UpdateInfo.ReleaseNotes - Added unit tests (no Electron required) covering all four input shapes * chore: sync generated autoUpdater.js and .map with updated TypeScript source * refactor: address Copilot PR review comments - Fix namespace: ElectronNET.Converter -> ElectronNET.API.Converter - Replace JsonDocument.ParseValue() with JsonSerializer.Deserialize<ReleaseNoteInfo>() for cleaner, allocation-free object array parsing - Fix Write(): empty ReleaseNoteInfo[] now serializes as [] instead of null - Use Array.Empty<ReleaseNoteInfo>() in UpdateInfo default initializer - Add tests: Serialize_WithEmptyReleaseNotes and Serialize_WithNullReleaseNotes
1 parent 30b7b1d commit d7e1a09

6 files changed

Lines changed: 223 additions & 5 deletions

File tree

src/ElectronNET.API/API/Entities/UpdateInfo.cs

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,8 @@
1-
namespace ElectronNET.API.Entities
1+
using System;
2+
using System.Text.Json.Serialization;
3+
using ElectronNET.API.Converter;
4+
5+
namespace ElectronNET.API.Entities
26
{
37
/// <summary>
48
///
@@ -24,7 +28,8 @@ public class UpdateInfo
2428
/// <summary>
2529
/// Gets or sets the release notes.
2630
/// </summary>
27-
public ReleaseNoteInfo[] ReleaseNotes { get; set; } = new ReleaseNoteInfo[0];
31+
[JsonConverter(typeof(ReleaseNotesConverter))]
32+
public ReleaseNoteInfo[] ReleaseNotes { get; set; } = Array.Empty<ReleaseNoteInfo>();
2833

2934
/// <summary>
3035
/// Gets or sets the release date.
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
using ElectronNET.API.Entities;
2+
using System;
3+
using System.Collections.Generic;
4+
using System.Text.Json;
5+
using System.Text.Json.Serialization;
6+
7+
namespace ElectronNET.API.Converter;
8+
9+
/// <summary>
10+
/// Handles the polymorphic shape of releaseNotes coming from electron-builder.
11+
/// Depending on the updater.fullChangelog setting, electron-builder sends:
12+
/// - null → when there are no notes
13+
/// - "some string" → plain string (FullChangelog = false, default)
14+
/// - ["note A", "note B"] → array of strings (after broken normalize in older TS)
15+
/// - [{ version, note }, ...] → array of objects (FullChangelog = true)
16+
/// All forms are normalised to ReleaseNoteInfo[] so the C# model stays clean.
17+
/// See: https://github.com/ElectronNET/Electron.NET/issues/1039
18+
/// </summary>
19+
public class ReleaseNotesConverter : JsonConverter<ReleaseNoteInfo[]>
20+
{
21+
// Ensure the converter is called even when the JSON token is null,
22+
// so we can return an empty array instead of null.
23+
public override bool HandleNull => true;
24+
25+
public override ReleaseNoteInfo[] Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
26+
{
27+
switch (reader.TokenType)
28+
{
29+
case JsonTokenType.Null:
30+
return Array.Empty<ReleaseNoteInfo>();
31+
32+
case JsonTokenType.String:
33+
// Plain string: "Some release notes"
34+
return new[] { new ReleaseNoteInfo { Note = reader.GetString() } };
35+
36+
case JsonTokenType.StartArray:
37+
var list = new List<ReleaseNoteInfo>();
38+
while (reader.Read() && reader.TokenType != JsonTokenType.EndArray)
39+
{
40+
if (reader.TokenType == JsonTokenType.String)
41+
{
42+
// Array of strings: ["Note A", "Note B"]
43+
list.Add(new ReleaseNoteInfo { Note = reader.GetString() });
44+
}
45+
else if (reader.TokenType == JsonTokenType.StartObject)
46+
{
47+
// Array of objects: [{ "version": "1.0", "note": "..." }]
48+
var entry = JsonSerializer.Deserialize<ReleaseNoteInfo>(ref reader, options) ?? new ReleaseNoteInfo();
49+
list.Add(entry);
50+
}
51+
else
52+
{
53+
reader.Skip();
54+
}
55+
}
56+
return list.ToArray();
57+
58+
default:
59+
throw new JsonException($"Unexpected token {reader.TokenType} when reading releaseNotes.");
60+
}
61+
}
62+
63+
public override void Write(Utf8JsonWriter writer, ReleaseNoteInfo[] value, JsonSerializerOptions options)
64+
{
65+
if (value is null)
66+
{
67+
writer.WriteNullValue();
68+
return;
69+
}
70+
71+
if (value.Length == 0)
72+
{
73+
writer.WriteStartArray();
74+
writer.WriteEndArray();
75+
return;
76+
}
77+
78+
writer.WriteStartArray();
79+
foreach (var item in value)
80+
{
81+
writer.WriteStartObject();
82+
if (item.Version is not null)
83+
{
84+
writer.WriteString("version", item.Version);
85+
}
86+
writer.WriteString("note", item.Note);
87+
writer.WriteEndObject();
88+
}
89+
writer.WriteEndArray();
90+
}
91+
}

src/ElectronNET.Host/api/autoUpdater.js

Lines changed: 4 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/ElectronNET.Host/api/autoUpdater.js.map

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/ElectronNET.Host/api/autoUpdater.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,11 @@ let electronSocket: Socket;
55

66
function normalize(updateInfo) {
77
if (typeof updateInfo?.releaseNotes === "string") {
8-
updateInfo.releaseNotes = [updateInfo.releaseNotes];
8+
updateInfo.releaseNotes = [{ note: updateInfo.releaseNotes }];
9+
} else if (Array.isArray(updateInfo?.releaseNotes)) {
10+
updateInfo.releaseNotes = updateInfo.releaseNotes.map((entry) =>
11+
typeof entry === "string" ? { note: entry } : entry,
12+
);
913
}
1014
}
1115

0 commit comments

Comments
 (0)