JavaScript (Node.js), 587 582 575 569 557 554 550550 549 bytes
tfw you didn't know that string.charCodeAt() == string.charCodeAt(0)
.
s=>s.replace(eval(`/[ㄱ-힣]|${M="(h[kol]?|n[jpl]?|ml?|[bi-puyOP])"}|([${S="rRseEfaqQtTdwWczxvg"}])(${M}((s[wg]|f[raqtxvg]|qt|[${S}])(?!${M}))?)?/g`,L="r,R,rt,s,sw,sg,e,E,f,fr,fa,fq,ft,fx,fv,fg,a,q,Q,qt,t,T,d,w,W,c,z,x,v,g,k,o,i,O,j,p,u,P,h,hk,ho,hl,y,n,nj,np,nl,n,m,ml,l".split`,`,l=L.filter(x=>!/[EQW]/.test(x)),I="indexOf"),(a,E,A,B,C,D)=>a<"~"?E?X(E):A&&C?F(43193+S[I](A)*588+L[I](C)*28+l[I](D)):X(A)+X(C)+X(D):(b=a.charCodeAt(0)-44032)<0?L[b+31439]||a:S[b/588|0]+L[30+b/28%21|0]+["",...l][b%28],F=String.fromCharCode,X=n=>n?F(L[I](n)+12593):"")
s=> // Main Function:
s.replace( // Replace all convertible strings:
eval(
`/ // Matching this regex:
[ㄱ-힣] // ($0) All Korean jamos and syllables
|${M="(h[kol]?|n[jpl]?|ml?|[bi-puyOP])"} // ($1) Isolated jungseong codes
|([${S="rRseEfaqQtTdwWczxvg"}]) // ($2) Choseong codes (also acts as lookup)
( // ($3) Jungseong and jongseong codes:
${M} // ($4) Jungseong codes
( // ($5) Jongseong codes:
( // ($6)
s[wg]|f[raqtxvg]|qt // Diagraphs unique to jongseongs
|[${S}] // Or jamos usable as choseongs
)
(?!${M}) // Not linked to the next jungseong
)? // Optional to match codes w/o jongseong
)? // Optional to match choseong-only codes
/g`, // Match all
L="(...LOOKUP TABLE...)".split`,`, // Lookup table of codes in jamo order
l=L.filter(x=>!/[EQW]/.test(x)), // Jongseong lookup - only first half is used
I="indexOf" // [String|Array].prototype.indexOf
),
(a,E,A,B,C,D)=> // Using this function:
a<"~"? // If the match is code (alphabets):
E? // If isolated jungseongs code:
X(E) // Return corresponding jamo
:A&&C? // Else if complete syllable code:
F(43193+S[I](A)*588+L[I](C)*28+l[I](D)) // Return the corresponding syllable
:X(A)+X(C)+X(D) // Else return corresponding jamos joined
:(b=a.charCodeAt(0)-44032)<0? // Else if not syllable:
L[b+31439]||a // Return code if jamo (if not, ignore)
:S[b/588|0]+L[30+b/28%21|0]+["",...l][b%28], // Else return code for the syllable
F=String.fromCharCode, // String.fromCharCode
X=n=> // Helper function to convert code to jamo
n? // If not undefined:
F(L[I](n)+12593) // Return the corresponding jamo
:"" // Else return empty string
)