Refactoring Playlist API for Partial Updates

Problem

The original playlist settings API required full object updates, even for a single change such as toggling the "favorite" flag. This caused overfetching, unnecessary payload size, and UX issues because the frontend had to maintain and submit the full state. I refactored the endpoint to accept modular partial updates based on setting instructions, allowing targeted updates without impacting unrelated fields.

Original: Monolithic Approach
public Playlist MapToPlaylist(Playlist playlist) { // Updates ALL properties every time playlist.Name = Name; playlist.Shuffle = Shuffle; playlist.Delay = Delay; playlist.Repeat = Repeat; playlist.Favorite = Favorite; playlist.BackgroundSoundId = BackgroundSoundId; playlist.Archived = Archived; playlist.ModifiedDate = DateTime.UtcNow; return playlist; } }
Updated: Modular Approach
using SelfTalk.Data.Schema; using System.Text.Json; namespace SelfTalk.Models.Requests; public class UpdatePlaylistSettingsRequest { public record PlaylistSettingRequest(SettingTypeCode SettingTypeCode, object? SettingValue); public required List<PlaylistSettingRequest> Settings { get; set; } public Playlist MapToPlaylist(Playlist playlist) { SettingTypeCode typeCode = SettingTypeCode.NotSet; JsonElement? jsonElement = null; try { foreach (var setting in Settings) { typeCode = setting.SettingTypeCode; jsonElement = setting.SettingValue as JsonElement?; if (jsonElement is null) { UpdateNullableSetting(playlist, typeCode); } else { UpdateSetting(playlist, typeCode, jsonElement.Value); } } playlist.ModifiedDate = DateTime.UtcNow; return playlist; } catch (ArgumentException) { throw; } catch (Exception ex) { object? value = jsonElement is not null ? jsonElement.Value : null; var message = FormatSettingError(typeCode, value, $"Unhandled exception: {ex.Message}"); var detailedEx = new Exception(message, ex); throw detailedEx; } } private static void UpdateSetting(Playlist playlist, SettingTypeCode typeCode, JsonElement value) { switch (typeCode) { case SettingTypeCode.Name: playlist.Name = value.ToString(); break; case SettingTypeCode.Shuffle: playlist.Shuffle = value.GetBoolean(); break; case SettingTypeCode.Delay: int delay = value.GetInt32(); if (delay < 0) throw new ArgumentException(FormatSettingError(typeCode, value, $"{nameof(playlist.Delay)} cannot be negative")); playlist.Delay = delay; break; case SettingTypeCode.Repeat: int repeat = value.GetInt32(); if (repeat < 1) throw new ArgumentException(FormatSettingError(typeCode, value, $"{nameof(playlist.Repeat)} must be at least 1")); playlist.Repeat = repeat; break; case SettingTypeCode.Favorite: playlist.Favorite = value.GetBoolean(); break; case SettingTypeCode.Archived: playlist.Archived = value.GetBoolean(); break; case SettingTypeCode.BackgroundSoundId: playlist.BackgroundSoundId = value.GetGuid(); break; default: throw new ArgumentException(FormatSettingError(typeCode, value, $"{nameof(SettingTypeCode)} does not exist")); } } private static void UpdateNullableSetting(Playlist playlist, SettingTypeCode typeCode) { switch (typeCode) { case SettingTypeCode.BackgroundSoundId: playlist.BackgroundSoundId = null; break; default: throw new ArgumentException(FormatSettingError(typeCode, null, $"{nameof(SettingTypeCode)} does not exist or does not allow nulls")); } return; } private static string FormatSettingError(SettingTypeCode typeCode, object? value, string reason) { var valueDisplay = value is null ? "null" : value.ToString(); return $"Invalid setting — Type: {typeCode}, Value: {valueDisplay}. Reason: {reason}"; } } public enum SettingTypeCode { Name, Shuffle, Delay, Repeat, Favorite, Archived, BackgroundSoundId, NotSet }
Overview

Monolithic Request

Must send ALL 7 properties every time


Problems:
  • Overwrites unchanged values
  • Full payloads
  • Less flexible API
  • Client must track all state

Modular Request

Send only the settings you want to change


Benefits:
  • Selective updates only
  • Smaller payloads
  • More intuitive API
  • Better error handling
Request Examples

Original: Want to just toggle favorite?

{ "name": "My Playlist", // unchanged "shuffle": true, // unchanged "delay": 5000, // unchanged "repeat": 3, // unchanged "favorite": true, // ← only change "archived": false, // unchanged "backgroundSoundId": "abc-123" // unchanged }

Updated: Just toggle favorite

{ "settings": [ { "settingTypeCode": "Favorite", "settingValue": true // ← just this } ] }

Updated: Multiple selective changes

{ "settings": [ { "settingTypeCode": "Name", "settingValue": "New Playlist Name" }, { "settingTypeCode": "Delay", "settingValue": 8000 }, { "settingTypeCode": "BackgroundSoundId", "settingValue": null // ← can even set nulls selectively } ] }

Precision Updates

Only modify the exact properties you intend to change, leaving everything else untouched.

Reduced Payload

Smaller network requests by sending only necessary data instead of the entire object.

Better UX

Frontend doesn't need to maintain full state - can make targeted updates easily.

Performance

Less data transfer, more efficient database updates, faster response times.

Error Handling

Enhanced validation with specific error messages per setting type and value.

Extensibility

Easy to add new setting types without breaking existing API contracts.