Skip to content

Commit cac064a

Browse files
jpicklykclaude
andcommitted
refactor: consolidate entity JSON serialization into shared serializers
Extract WorkItem, Note, and Dependency serialization into extension functions in EntityJsonSerializers.kt. Replace 17+ inline role.name.lowercase() / priority.name.lowercase() calls across 10 tool files with Role.toJsonString() / Priority.toJsonString() helpers. Removes duplicate private serializer functions from QueryItemsTool and QueryNotesTool, reducing ~40 lines of duplicated code. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 94800e8 commit cac064a

File tree

11 files changed

+151
-97
lines changed

11 files changed

+151
-97
lines changed
Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
package io.github.jpicklyk.mcptask.current.application.tools
2+
3+
import io.github.jpicklyk.mcptask.current.domain.model.Dependency
4+
import io.github.jpicklyk.mcptask.current.domain.model.Note
5+
import io.github.jpicklyk.mcptask.current.domain.model.Priority
6+
import io.github.jpicklyk.mcptask.current.domain.model.Role
7+
import io.github.jpicklyk.mcptask.current.domain.model.WorkItem
8+
import kotlinx.serialization.json.*
9+
10+
// ──────────────────────────────────────────────
11+
// Enum formatting
12+
// ──────────────────────────────────────────────
13+
14+
/** Canonical JSON representation of a [Role] value (lowercase enum name). */
15+
fun Role.toJsonString(): String = this.name.lowercase()
16+
17+
/** Canonical JSON representation of a [Priority] value (lowercase enum name). */
18+
fun Priority.toJsonString(): String = this.name.lowercase()
19+
20+
// ──────────────────────────────────────────────
21+
// WorkItem serializers
22+
// ──────────────────────────────────────────────
23+
24+
/**
25+
* Full JSON representation of a [WorkItem] with all fields.
26+
* Used for `get` operations and detailed single-item responses.
27+
*/
28+
fun WorkItem.toFullJson(): JsonObject = buildJsonObject {
29+
put("id", JsonPrimitive(id.toString()))
30+
parentId?.let { put("parentId", JsonPrimitive(it.toString())) }
31+
put("title", JsonPrimitive(title))
32+
description?.let { put("description", JsonPrimitive(it)) }
33+
put("summary", JsonPrimitive(summary))
34+
put("role", JsonPrimitive(role.toJsonString()))
35+
statusLabel?.let { put("statusLabel", JsonPrimitive(it)) }
36+
previousRole?.let { put("previousRole", JsonPrimitive(it.toJsonString())) }
37+
put("priority", JsonPrimitive(priority.toJsonString()))
38+
put("complexity", JsonPrimitive(complexity))
39+
put("depth", JsonPrimitive(depth))
40+
metadata?.let { put("metadata", JsonPrimitive(it)) }
41+
tags?.let { put("tags", JsonPrimitive(it)) }
42+
put("createdAt", JsonPrimitive(createdAt.toString()))
43+
put("modifiedAt", JsonPrimitive(modifiedAt.toString()))
44+
put("roleChangedAt", JsonPrimitive(roleChangedAt.toString()))
45+
}
46+
47+
/**
48+
* Minimal JSON representation of a [WorkItem] for list/search responses.
49+
* Includes only: id, parentId, title, role, priority, depth, tags.
50+
*/
51+
fun WorkItem.toMinimalJson(): JsonObject = buildJsonObject {
52+
put("id", JsonPrimitive(id.toString()))
53+
parentId?.let { put("parentId", JsonPrimitive(it.toString())) }
54+
put("title", JsonPrimitive(title))
55+
put("role", JsonPrimitive(role.toJsonString()))
56+
put("priority", JsonPrimitive(priority.toJsonString()))
57+
put("depth", JsonPrimitive(depth))
58+
tags?.let { put("tags", JsonPrimitive(it)) }
59+
}
60+
61+
/**
62+
* JSON object mapping each [Role] to its child count.
63+
* Used in overview responses for child-count-by-role summaries.
64+
*/
65+
fun roleCountToJson(counts: Map<Role, Int>): JsonObject = buildJsonObject {
66+
for (role in Role.entries) {
67+
put(role.toJsonString(), JsonPrimitive(counts[role] ?: 0))
68+
}
69+
}
70+
71+
// ──────────────────────────────────────────────
72+
// Note serializer
73+
// ──────────────────────────────────────────────
74+
75+
/**
76+
* JSON representation of a [Note] with optional body inclusion.
77+
* Used for both single-note and list responses.
78+
*
79+
* @param includeBody When false, omits the `body` field (metadata-only queries).
80+
*/
81+
fun Note.toJson(includeBody: Boolean = true): JsonObject = buildJsonObject {
82+
put("id", JsonPrimitive(id.toString()))
83+
put("itemId", JsonPrimitive(itemId.toString()))
84+
put("key", JsonPrimitive(key))
85+
put("role", JsonPrimitive(role))
86+
if (includeBody) put("body", JsonPrimitive(body))
87+
put("createdAt", JsonPrimitive(createdAt.toString()))
88+
put("modifiedAt", JsonPrimitive(modifiedAt.toString()))
89+
}
90+
91+
// ──────────────────────────────────────────────
92+
// Dependency serializer
93+
// ──────────────────────────────────────────────
94+
95+
/**
96+
* JSON representation of a [Dependency] with core fields.
97+
* Tools that need additional fields (effectiveUnblockRole, item info) extend inline.
98+
*/
99+
fun Dependency.toJson(): JsonObject = buildJsonObject {
100+
put("id", JsonPrimitive(id.toString()))
101+
put("fromItemId", JsonPrimitive(fromItemId.toString()))
102+
put("toItemId", JsonPrimitive(toItemId.toString()))
103+
put("type", JsonPrimitive(type.name))
104+
unblockAt?.let { put("unblockAt", JsonPrimitive(it)) }
105+
}

current/src/main/kotlin/io/github/jpicklyk/mcptask/current/application/tools/compound/CreateWorkTreeTool.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -314,7 +314,7 @@ Atomically create a hierarchical work tree: root item, child items, dependencies
314314
val rootJson = buildJsonObject {
315315
put("id", JsonPrimitive(rootResultItem.id.toString()))
316316
put("title", JsonPrimitive(rootResultItem.title))
317-
put("role", JsonPrimitive(rootResultItem.role.name.lowercase()))
317+
put("role", JsonPrimitive(rootResultItem.role.toJsonString()))
318318
put("depth", JsonPrimitive(rootResultItem.depth))
319319
rootResultItem.tags?.let { put("tags", JsonPrimitive(it)) }
320320
// Add expectedNotes from schema
@@ -342,7 +342,7 @@ Atomically create a hierarchical work tree: root item, child items, dependencies
342342
put("ref", JsonPrimitive(ref))
343343
put("id", JsonPrimitive(item.id.toString()))
344344
put("title", JsonPrimitive(item.title))
345-
put("role", JsonPrimitive(item.role.name.lowercase()))
345+
put("role", JsonPrimitive(item.role.toJsonString()))
346346
put("depth", JsonPrimitive(item.depth))
347347
item.tags?.let { put("tags", JsonPrimitive(it)) }
348348
// Add expectedNotes from schema

current/src/main/kotlin/io/github/jpicklyk/mcptask/current/application/tools/dependency/QueryDependenciesTool.kt

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -179,8 +179,8 @@ Returns dependencies with counts breakdown and optional graph traversal data.
179179
val item = fromResult.data
180180
put("fromItem", buildJsonObject {
181181
put("title", JsonPrimitive(item.title))
182-
put("role", JsonPrimitive(item.role.name.lowercase()))
183-
put("priority", JsonPrimitive(item.priority.name.lowercase()))
182+
put("role", JsonPrimitive(item.role.toJsonString()))
183+
put("priority", JsonPrimitive(item.priority.toJsonString()))
184184
})
185185
}
186186
is Result.Error -> { /* item may have been deleted; skip */ }
@@ -192,8 +192,8 @@ Returns dependencies with counts breakdown and optional graph traversal data.
192192
val item = toResult.data
193193
put("toItem", buildJsonObject {
194194
put("title", JsonPrimitive(item.title))
195-
put("role", JsonPrimitive(item.role.name.lowercase()))
196-
put("priority", JsonPrimitive(item.priority.name.lowercase()))
195+
put("role", JsonPrimitive(item.role.toJsonString()))
196+
put("priority", JsonPrimitive(item.priority.toJsonString()))
197197
})
198198
}
199199
is Result.Error -> { /* item may have been deleted; skip */ }

current/src/main/kotlin/io/github/jpicklyk/mcptask/current/application/tools/items/CreateItemHandler.kt

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import io.github.jpicklyk.mcptask.current.application.service.ItemHierarchyValid
44
import io.github.jpicklyk.mcptask.current.application.tools.ResponseUtil
55
import io.github.jpicklyk.mcptask.current.application.tools.ToolExecutionContext
66
import io.github.jpicklyk.mcptask.current.application.tools.ToolValidationException
7+
import io.github.jpicklyk.mcptask.current.application.tools.toJsonString
78
import io.github.jpicklyk.mcptask.current.domain.model.Priority
89
import io.github.jpicklyk.mcptask.current.domain.model.Role
910
import io.github.jpicklyk.mcptask.current.domain.model.WorkItem
@@ -130,8 +131,8 @@ class CreateItemHandler(
130131
put("id", JsonPrimitive(result.data.id.toString()))
131132
put("title", JsonPrimitive(result.data.title))
132133
put("depth", JsonPrimitive(result.data.depth))
133-
put("role", JsonPrimitive(result.data.role.name.lowercase()))
134-
put("priority", JsonPrimitive(result.data.priority.name.lowercase()))
134+
put("role", JsonPrimitive(result.data.role.toJsonString()))
135+
put("priority", JsonPrimitive(result.data.priority.toJsonString()))
135136
put("requiresVerification", JsonPrimitive(result.data.requiresVerification))
136137
if (createdTags != null) {
137138
put("tags", JsonPrimitive(createdTags))

current/src/main/kotlin/io/github/jpicklyk/mcptask/current/application/tools/items/QueryItemsTool.kt

Lines changed: 10 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -305,7 +305,7 @@ Operations: get, search, overview
305305
}
306306
}
307307

308-
val itemJson = workItemToJson(item)
308+
val itemJson = item.toFullJson()
309309

310310
return if (includeAncestors) {
311311
val chains = context.workItemRepository().findAncestorChains(setOf(item.id))
@@ -409,13 +409,13 @@ Operations: get, search, overview
409409
put("items", JsonArray(items.map { item ->
410410
if (includeAncestors) {
411411
val ancestors = chains[item.id] ?: emptyList()
412-
val minimalJson = workItemToMinimalJson(item)
412+
val minimalJson = item.toMinimalJson()
413413
buildJsonObject {
414414
minimalJson.forEach { (k, v) -> put(k, v) }
415415
put("ancestors", buildAncestorsArray(ancestors))
416416
}
417417
} else {
418-
workItemToMinimalJson(item)
418+
item.toMinimalJson()
419419
}
420420
}))
421421
put("total", JsonPrimitive(totalCount))
@@ -475,9 +475,9 @@ Operations: get, search, overview
475475
}
476476

477477
val data = buildJsonObject {
478-
put("item", workItemToJson(item))
479-
put("childCounts", roleCounToJson(childCounts))
480-
put("children", JsonArray(children.map { workItemToMinimalJson(it) }))
478+
put("item", item.toFullJson())
479+
put("childCounts", roleCountToJson(childCounts))
480+
put("children", JsonArray(children.map { it.toMinimalJson() }))
481481
}
482482

483483
return successResponse(data)
@@ -506,9 +506,9 @@ Operations: get, search, overview
506506
buildJsonObject {
507507
put("id", JsonPrimitive(item.id.toString()))
508508
put("title", JsonPrimitive(item.title))
509-
put("role", JsonPrimitive(item.role.name.lowercase()))
510-
put("priority", JsonPrimitive(item.priority.name.lowercase()))
511-
put("childCounts", roleCounToJson(childCounts))
509+
put("role", JsonPrimitive(item.role.toJsonString()))
510+
put("priority", JsonPrimitive(item.priority.toJsonString()))
511+
put("childCounts", roleCountToJson(childCounts))
512512
if (includeChildren) {
513513
val children = when (val result = context.workItemRepository().findChildren(item.id)) {
514514
is Result.Success -> result.data
@@ -518,7 +518,7 @@ Operations: get, search, overview
518518
buildJsonObject {
519519
put("id", JsonPrimitive(child.id.toString()))
520520
put("title", JsonPrimitive(child.title))
521-
put("role", JsonPrimitive(child.role.name.lowercase()))
521+
put("role", JsonPrimitive(child.role.toJsonString()))
522522
put("depth", JsonPrimitive(child.depth))
523523
}
524524
}))
@@ -534,42 +534,4 @@ Operations: get, search, overview
534534
return successResponse(data)
535535
}
536536

537-
// ──────────────────────────────────────────────
538-
// JSON serialization helpers
539-
// ──────────────────────────────────────────────
540-
541-
private fun workItemToJson(item: WorkItem): JsonObject = buildJsonObject {
542-
put("id", JsonPrimitive(item.id.toString()))
543-
item.parentId?.let { put("parentId", JsonPrimitive(it.toString())) }
544-
put("title", JsonPrimitive(item.title))
545-
item.description?.let { put("description", JsonPrimitive(it)) }
546-
put("summary", JsonPrimitive(item.summary))
547-
put("role", JsonPrimitive(item.role.name.lowercase()))
548-
item.statusLabel?.let { put("statusLabel", JsonPrimitive(it)) }
549-
item.previousRole?.let { put("previousRole", JsonPrimitive(it.name.lowercase())) }
550-
put("priority", JsonPrimitive(item.priority.name.lowercase()))
551-
put("complexity", JsonPrimitive(item.complexity))
552-
put("depth", JsonPrimitive(item.depth))
553-
item.metadata?.let { put("metadata", JsonPrimitive(it)) }
554-
item.tags?.let { put("tags", JsonPrimitive(it)) }
555-
put("createdAt", JsonPrimitive(item.createdAt.toString()))
556-
put("modifiedAt", JsonPrimitive(item.modifiedAt.toString()))
557-
put("roleChangedAt", JsonPrimitive(item.roleChangedAt.toString()))
558-
}
559-
560-
private fun workItemToMinimalJson(item: WorkItem): JsonObject = buildJsonObject {
561-
put("id", JsonPrimitive(item.id.toString()))
562-
item.parentId?.let { put("parentId", JsonPrimitive(it.toString())) }
563-
put("title", JsonPrimitive(item.title))
564-
put("role", JsonPrimitive(item.role.name.lowercase()))
565-
put("priority", JsonPrimitive(item.priority.name.lowercase()))
566-
put("depth", JsonPrimitive(item.depth))
567-
item.tags?.let { put("tags", JsonPrimitive(it)) }
568-
}
569-
570-
private fun roleCounToJson(counts: Map<Role, Int>): JsonObject = buildJsonObject {
571-
for (role in Role.entries) {
572-
put(role.name.lowercase(), JsonPrimitive(counts[role] ?: 0))
573-
}
574-
}
575537
}

current/src/main/kotlin/io/github/jpicklyk/mcptask/current/application/tools/notes/QueryNotesTool.kt

Lines changed: 2 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
package io.github.jpicklyk.mcptask.current.application.tools.notes
22

33
import io.github.jpicklyk.mcptask.current.application.tools.*
4-
import io.github.jpicklyk.mcptask.current.domain.model.Note
54
import io.github.jpicklyk.mcptask.current.domain.repository.Result
65
import io.modelcontextprotocol.kotlin.sdk.types.ToolAnnotations
76
import io.modelcontextprotocol.kotlin.sdk.types.ToolSchema
@@ -130,7 +129,7 @@ Read-only query operations for Notes (get, list).
130129

131130
return when (val result = noteRepo.getById(id)) {
132131
is Result.Success -> {
133-
successResponse(noteToJson(result.data))
132+
successResponse(result.data.toJson())
134133
}
135134
is Result.Error -> {
136135
errorResponse(
@@ -156,7 +155,7 @@ Read-only query operations for Notes (get, list).
156155
is Result.Success -> {
157156
val notes = result.data
158157
val data = buildJsonObject {
159-
put("notes", JsonArray(notes.map { noteToJson(it, includeBody) }))
158+
put("notes", JsonArray(notes.map { it.toJson(includeBody) }))
160159
put("total", JsonPrimitive(notes.size))
161160
}
162161
successResponse(data)
@@ -171,17 +170,4 @@ Read-only query operations for Notes (get, list).
171170
}
172171
}
173172

174-
// ──────────────────────────────────────────────
175-
// JSON serialization helper
176-
// ──────────────────────────────────────────────
177-
178-
private fun noteToJson(note: Note, includeBody: Boolean = true): JsonObject = buildJsonObject {
179-
put("id", JsonPrimitive(note.id.toString()))
180-
put("itemId", JsonPrimitive(note.itemId.toString()))
181-
put("key", JsonPrimitive(note.key))
182-
put("role", JsonPrimitive(note.role))
183-
if (includeBody) put("body", JsonPrimitive(note.body))
184-
put("createdAt", JsonPrimitive(note.createdAt.toString()))
185-
put("modifiedAt", JsonPrimitive(note.modifiedAt.toString()))
186-
}
187173
}

0 commit comments

Comments
 (0)