import { JSON } from "../.."; import { COMMA, BRACE_LEFT, QUOTE, BRACE_RIGHT, COLON, } from "../../custom/chars"; import { isSpace, scanStringEnd } from "../../util"; import { scanValueEnd } from "../../util/scanValueEnd"; import { lastValueEnd, parseValue } from "./object"; function deserializeMapKey(start: usize, end: usize): T { // @ts-expect-error: exists const keyText = JSON.__deserialize(start - 2, end + 2); if (isString()) return changetype(keyText); return JSON.parse(keyText); } export function deserializeMap>( srcStart: usize, srcEnd: usize, dst: usize, ): T { const out = changetype>( dst || changetype(instantiate()), ); while (srcEnd > srcStart && isSpace(load(srcEnd - 2))) srcEnd -= 2; if (srcStart - srcEnd == 0) throw new Error("Input string had zero length or was all whitespace"); if (load(srcStart) != BRACE_LEFT) throw new Error( "Expected '{' at start of object at position " + (srcEnd - srcStart).toString(), ); if (load(srcEnd - 2) != BRACE_RIGHT) throw new Error( "Expected '}' at end of object at position " + (srcEnd - srcStart).toString(), ); deserializeMapBody(srcStart, srcEnd, changetype(out)); return changetype(out); } /** * Shared single-pass map-body parser used by both the top-level and struct-field * entry points. Dynamic `JSON.Value` values are parsed in one pass via * {@link parseValue}; typed values are bounds-scanned with {@link scanValueEnd} * because their generated deserializers take exact `(start, end)` bounds. */ export function deserializeMapBody>( srcStart: usize, srcEnd: usize, out: T, ): usize { let arbitraryValue = false; if (isManaged>() || isReference>()) { // @ts-ignore: instanceof on the (reference) value type arbitraryValue = changetype>>(0) instanceof JSON.Value; } if (srcStart >= srcEnd || load(srcStart) != BRACE_LEFT) throw new Error("Failed to parse JSON!"); srcStart += 2; while (srcStart < srcEnd && isSpace(load(srcStart))) srcStart += 2; if (srcStart >= srcEnd) throw new Error("Failed to parse JSON!"); if (load(srcStart) == BRACE_RIGHT) return srcStart + 2; while (srcStart < srcEnd) { while (srcStart < srcEnd && isSpace(load(srcStart))) srcStart += 2; if (load(srcStart) != QUOTE) break; const keyStart = srcStart + 2; const keyEnd = scanStringEnd(srcStart, srcEnd); if (keyEnd >= srcEnd) break; srcStart = keyEnd + 2; while (srcStart < srcEnd && isSpace(load(srcStart))) srcStart += 2; if (srcStart >= srcEnd || load(srcStart) != COLON) break; srcStart += 2; while (srcStart < srcEnd && isSpace(load(srcStart))) srcStart += 2; if (isReference>() && arbitraryValue) { const val = parseValue(srcStart, srcEnd); // @ts-ignore: type - valueof is JSON.Value in this branch changetype>(out).set( deserializeMapKey>(keyStart, keyEnd), changetype>(changetype(val)), ); srcStart = lastValueEnd(); } else { const valueEnd = scanValueEnd(srcStart, srcEnd); if (!valueEnd || valueEnd <= srcStart) break; // @ts-ignore: type changetype>(out).set( deserializeMapKey>(keyStart, keyEnd), JSON.__deserialize>(srcStart, valueEnd), ); srcStart = valueEnd; } while (srcStart < srcEnd && isSpace(load(srcStart))) srcStart += 2; if (srcStart >= srcEnd) break; const code = load(srcStart); if (code == COMMA) { srcStart += 2; while (srcStart < srcEnd && isSpace(load(srcStart))) srcStart += 2; continue; } if (code == BRACE_RIGHT) return srcStart + 2; break; } throw new Error("Failed to parse JSON!"); } export function deserializeMapField>( srcStart: usize, srcEnd: usize, dstObj: usize, dstOffset: usize = 0, ): usize { const fieldPtr = dstObj + dstOffset; let out = load(fieldPtr); if (!changetype(out)) { out = changetype(instantiate()); store(fieldPtr, out); } else { // Reusing an existing field map - clear it before repopulating. Fresh maps // (deserializeMap / deserializeMapArray) skip this. changetype>(out).clear(); } return deserializeMapBody(srcStart, srcEnd, out); }