Move SSD remote truth payload reads to typed Kotlin

ober

c7e82626e1de6b44faea870e59076915f4e5bf9d

diff --git a/templates/ssd-review.ss b/templates/ssd-review.ss
index 5fc5911..2a7613e 100644
--- a/templates/ssd-review.ss
+++ b/templates/ssd-review.ss
@@ -3844,7 +3844,11 @@
     (typed-kotlin-file "com/sfb/ssdreview/RemoteTruthSelect.kt"
       (kotlin-imports (org json JSONArray) (org json JSONObject))
       (typed-library (com sfb ssdreview)
-        (export bestRemoteTruthForSession remoteTruthTimestamp)
+        (export bestRemoteTruthForSession remoteTruthTimestamp
+                remoteTruthPayloadTruth remoteTruthPayloadMatchModified
+                remoteTruthPayloadCount remoteTruthPayloadTruths
+                remoteTruthEntriesCount remoteTruthEntryAt
+                remoteTruthEntryTruth remoteTruthEntryModified)
         (type JSONArray)
         (type JSONObject)
         (type Int)
@@ -3854,6 +3858,21 @@
             (if (< generated (int 0))
               (json-object-opt-int-default truth "created_at" (int 0))
               generated)))
+        (def (remoteTruthPayloadTruth (payload : JSONObject)) : (Nullable JSONObject)
+          (json-object-opt-json-object payload "truth"))
+        (def (remoteTruthPayloadMatchModified (payload : JSONObject)) : Int
+          (let ((match (json-object-opt-json-object payload "match")))
+            (if (nullable-null? match)
+                (int 0)
+                (json-object-opt-int-default (nullable-get match) "modified" (int 0)))))
+        (def (remoteTruthPayloadCount (payload : JSONObject)) : Int32
+          (json-object-opt-int32-default payload "count" (int32 1)))
+        (def (remoteTruthPayloadTruths (payload : JSONObject)) : JSONArray
+          (truthJsonArrayOrEmpty (json-object-opt-json-array payload "truths")))
+        (def (remoteTruthEntriesCount (entries : JSONArray)) : Int32
+          (json-array-length entries))
+        (def (remoteTruthEntryAt (entries : JSONArray) (index : Int32)) : JSONObject
+          (json-array-get-json-object entries index))
         (def (betterRemoteTruth? (candidate : JSONObject) (current : JSONObject)) : Bool
           (let ((candidateCount (truthGroupCount candidate))
                 (currentCount (truthGroupCount current)))
@@ -3870,6 +3889,8 @@
               current)))
         (def (remoteTruthEntryTruth (entry : JSONObject)) : (Nullable JSONObject)
           (json-object-opt-json-object entry "truth"))
+        (def (remoteTruthEntryModified (entry : JSONObject)) : Int
+          (json-object-opt-int-default entry "modified" (int 0)))
         (def (bestRemoteTruthForSession (session : SsdSession)
                                         (entries : JSONArray)) : (Nullable JSONObject)
           (for/fold ((best (nullable-none JSONObject)))
@@ -6840,9 +6861,9 @@
        "            }"
        "            val response = readResponseText(connection)"
        "            val payload = JSONObject(response)"
-       "            val compactTruth = payload.optJSONObject(\"truth\")"
+       "            val compactTruth = remoteTruthPayloadTruth(payload)"
        "            if (compactTruth != null) {"
-       "                val modified = payload.optJSONObject(\"match\")?.optLong(\"modified\", 0L) ?: 0L"
+       "                val modified = remoteTruthPayloadMatchModified(payload)"
        "                var imported = 0"
        "                if (writeRemoteTruth(compactTruth, modified)) imported += 1"
        "                if (sameShipTruth(session, compactTruth)) {"
@@ -6852,15 +6873,15 @@
        "                    }"
        "                }"
        "                if (imported > 0) rebuildLearnedExamples()"
-       "                return payload.optInt(\"count\", 1)"
+       "                return remoteTruthPayloadCount(payload)"
        "            }"
-       "            val entries = payload.optJSONArray(\"truths\") ?: JSONArray()"
-       "            if (entries.length() > MAX_ZIP_ENTRIES) throw IllegalStateException(\"Too many remote truth entries\")"
+       "            val entries = remoteTruthPayloadTruths(payload)"
+       "            if (remoteTruthEntriesCount(entries) > MAX_ZIP_ENTRIES) throw IllegalStateException(\"Too many remote truth entries\")"
        "            var imported = 0"
-       "            for (i in 0 until entries.length()) {"
-       "                val entry = entries.getJSONObject(i)"
-       "                val truth = entry.optJSONObject(\"truth\") ?: continue"
-       "                val modified = entry.optLong(\"modified\", 0L)"
+       "            for (i in 0 until remoteTruthEntriesCount(entries)) {"
+       "                val entry = remoteTruthEntryAt(entries, i)"
+       "                val truth = remoteTruthEntryTruth(entry) ?: continue"
+       "                val modified = remoteTruthEntryModified(entry)"
        "                if (writeRemoteTruth(truth, modified)) imported += 1"
        "            }"
        "            val bestForSession = bestRemoteTruthForSession(session, entries)"
@@ -6871,7 +6892,7 @@
        "                }"
        "            }"
        "            if (imported > 0) rebuildLearnedExamples()"
-       "                entries.length()"
+       "                remoteTruthEntriesCount(entries)"
        "            } finally {"
        "                connection.disconnect()"
        "            }"