ObjDeepClone(): Deep clone an object.
ObjCompile() : Convert a user class object to a string packet.
ObjParse() : Convert a compiled packet back to object.
These can be used for string communication between scripts.
This is the part of ScriptSock project.
viewtopic.php?f=83&t=126320&p=559938#p559938
Need test more!
Update: Version: 2024.03.17
Update ObjDeepClone(), ObjCompile(), ObjParse():
- Improve checking of object types.
- Add skipping of Prototype object. (ObjCompile())
Current Limitation:
- Unable to check and skip static property[]{Get, Set}. (ObjCompile())
Update: Version: 2024.03.15
Update: Version: 2024.03.14
Update: Version: 2024.03.13.2
Update: Version: 2024.03.12.2
Test Class:
Code: Select all
Class ClassMain
{
A := 1
B := ClassB()
J :=
{
K: 8,
}
L := 9
M :=
{
N: [10, 10],
}
O(iVar)
{
return (iVar)
}
}
Class ClassB
{
C := 2
D := Map(
"E", 3,
"F", [4, {G: "5:5"}, 6]
)
H := ClassH()
}
Class ClassH
{
I := 7
}
ObjDeepClone() Example:
Code: Select all
oClassMain := ClassMain()
oTarget := ScriptVar.ObjDeepClone(oClassMain)
oClassMain.B.D["F"][2].G := "X:X"
MsgBox(oClassMain.B.D["F"][2].G " - " oTarget.B.D["F"][2].G) ; -> X:X - 5:5
ObjCompile() and ObjParse() Example:
Code: Select all
; Result packet:
; @ClassMain{A:1;B:@ClassB{C:2;D:@Map{E:3;F:@Array{1:4;2:@Object{G:5^DESCRIPTOR^5};3:6}};H:@ClassH{I:7}};J:@Object{K:8};L:9;M:@Object{N:@Array{1:10;2:10}}}
sSourcePacket := ScriptVar.ObjCompile(ClassMain())
A_Clipboard := "[S]: " sSourcePacket
Sleep(500)
oTarget := ScriptVar.ObjParse(sSourcePacket)
sTargetPacket := ScriptVar.ObjCompile(oTarget)
A_Clipboard := "[T]: " sTargetPacket
oTarget.O(10) ; -> 10
MsgBox((sSourcePacket = sTargetPacket) ? "Same (O)" : "Different (X)") ; -> Same (O)
ScriptVar Main
Code: Select all
; ----------------------------------------------------------------------------------------------------------------------------------
; ScriptVar
; Packet creator: Object deep clone, compile, and parse
;
; AutoHotkey V2 (2.0.11)
;
; Author: AstraVista
; Version: 2024.03.17
;
; Part of ScriptSock project:
; https://www.autohotkey.com/boards/viewtopic.php?f=83&t=126320&p=559938#p559938
; ----------------------------------------------------------------------------------------------------------------------------------
class ScriptVar
{
; Attribute are defined as a name in base, and a value in the class.
static Attribute :=
{
; TEST1
aAttribute: Map(),
}
; Property are defined as a name in the class, and a value in the class instance.
static Property :=
{
sTargetScript: "",
sSourceScript: "",
sCommand: "",
aData: Map(),
aMsg: Map(),
aFlag: Map(),
}
; Externals are defined only in the class.
static External :=
{
; TEST1
sExternal: "",
}
; Internals are defined both the class and the class instance. Therefore, it can accessed with "this." in both.
static Internal :=
{
Delim:
{
PACK: ";",
PACK_TRAN: "^PACKET^",
DESC: ":",
DESC_TRAN: "^DESCRIPTOR^",
KIND: "@",
KIND_TRAN: "^KIND^",
OBJS: "{",
OBJS_TRAN: "^OBJSTART^",
OBJE: "}",
OBJE_TRAN: "^OBJEND^",
}
}
static __New()
{
ScriptVar.DeleteProp("__New")
; OwnProps() returns not in defining order but randomly.
; Thus, creating order is random.
for vName, vValue in ScriptVar.OwnProps()
{
if (vName = "Attribute")
{
for vName, vValue in ScriptVar.%vName%.OwnProps()
{
ScriptVar.base.DefineProp(vName, {Value: vName})
}
for vName, vValue in ScriptVar.%vName%.OwnProps()
{
ScriptVar.DefineProp(vName, {Value: vValue})
}
}
if (vName = "Property")
{
for vName, vValue in ScriptVar.%vName%.OwnProps()
{
ScriptVar.DefineProp(vName, {Value: vName})
}
for vName, vValue in ScriptVar.%vName%.OwnProps()
{
ScriptVar.Prototype.DefineProp(vName, {Value: vValue})
}
}
if (vName = "External")
{
for vName, vValue in ScriptVar.%vName%.OwnProps()
{
ScriptVar.DefineProp(vName, {Value: vValue})
}
}
if (vName = "Internal")
{
for vName, vValue in ScriptVar.%vName%.OwnProps()
{
ScriptVar.base.Prototype.DefineProp(vName, {Value: vValue})
}
}
}
}
__New(sTargetScript := "", sCommand := "", aData := Map(), aMsg := Map(), aFlag := Map())
{
for vName, vValue in ScriptVar.Property.OwnProps()
{
this.%vName% := vValue
}
this.sTargetScript := sTargetScript
this.sSourceScript := A_ScriptFullPath
this.sCommand := sCommand
this.aData := aData.Clone()
this.aMsg := aMsg.Clone()
this.aFlag := aFlag.Clone()
}
Call(sTargetScript := "", sCommand := "", aData := Map(), aMsg := Map(), aFlag := Map())
{
this.__New(sTargetScript, sCommand, aData, aMsg, aFlag)
return(this)
}
; ==============================================================================================================================
; ObjDeepClone() Method
; ==============================================================================================================================
static ObjDeepClone(oSource)
{
bDebugA := true
bDebugB := false
; --------------------------------------------------------------------------------------------------------------------------
; Clone Object
; --------------------------------------------------------------------------------------------------------------------------
; Object must have Clone() method. But, use try {}.
if (bDebugB)
{
MsgBox("Type " Type(oSource) " is cloning.")
}
try
{
oTarget := oSource.Clone()
}
catch
{
if (bDebugA)
{
MsgBox("Type '" Type(oSource) "' is skipped in Clone() of ObjDeepClone().")
}
return (oSource)
}
; --------------------------------------------------------------------------------------------------------------------------
; Enumerate Properties
; --------------------------------------------------------------------------------------------------------------------------
if (Type(oTarget) = "Array")
{
for vPropName, vPropData in oTarget
{
if (IsObject(vPropData))
{
if (bDebugB)
{
MsgBox("Array Start: " vPropName " -> Data: " Type(vPropData))
}
oTarget[vPropName] := this.ObjDeepClone(vPropData)
if (bDebugB)
{
MsgBox("Array End: " vPropName " -> Data: " Type(oTarget[vPropName]))
}
}
}
}
else if (Type(oTarget) = "Map")
{
for vPropName, vPropData in oTarget
{
; If Map has an object in the left side, it has to be skipped.
; For, if the left side object is cloned, Map cannot search the object with the same name any more unfortunately.
if (IsObject(vPropName))
{
if (bDebugA)
{
MsgBox("[" Type(oTarget) " Property Left] Reference `"" Type(vPropName) "`" is skipped in the enumeration of ObjDeepClone().")
}
}
else
{
if (IsObject(vPropData))
{
if (bDebugB)
{
MsgBox("Map Start: " vPropName " -> Data: " Type(vPropData))
}
oTarget[vPropName] := this.ObjDeepClone(vPropData)
if (bDebugB)
{
MsgBox("Map End: " vPropName " -> Data: " Type(oTarget[vPropName]))
}
}
}
}
}
; Object must have OwnProps() method.
else if (oTarget is Object)
{
oEnumerator := 0
try
{
oEnumerator := oSource.OwnProps()
}
if (oEnumerator)
{
for vPropName, vPropData in oEnumerator
{
if (IsObject(vPropData))
{
if (bDebugB)
{
MsgBox("Object Start: " vPropName " -> Data: " Type(vPropData))
}
; Down to the next level.
oTarget.%vPropName% := this.ObjDeepClone(vPropData)
if (bDebugB)
{
MsgBox("Object End: " vPropName " -> Data: " Type(oTarget.%vPropName%))
}
}
}
}
}
if (bDebugB)
{
MsgBox("Type " Type(oTarget) " is returning.")
}
return (oTarget)
}
; ==============================================================================================================================
; ObjCompile() Method
; ==============================================================================================================================
static ObjCompile(oSource, &sPacket := -1)
{
bDebugA := true
; --------------------------------------------------------------------------------------------------------------------------
; Top Level
; --------------------------------------------------------------------------------------------------------------------------
if (sPacket = -1)
{
sPacket := ""
if (Type(oSource) = "Array")
{
sPacket .= this.Delim.KIND "Array"
}
else if (Type(oSource) = "Map")
{
sPacket .= this.Delim.KIND "Map"
}
else if (oSource is Object)
{
try
{
%Type(oSource)%()
}
catch
{
if (bDebugA)
{
MsgBox(" Type `"" Type(oSource) "`" is skipped in Call() of ObjCompile().")
}
return ("")
}
sPacket .= this.Delim.KIND Type(oSource)
}
; Down to the main level.
sPacket .= this.Delim.OBJS
this.ObjCompile(oSource, &sPacket)
sPacket .= this.Delim.OBJE
return (sPacket)
}
; --------------------------------------------------------------------------------------------------------------------------
; Main Level
; --------------------------------------------------------------------------------------------------------------------------
oEnumerator := 0
if (Type(oSource) = "Map" Or Type(oSource) = "Array")
{
oEnumerator := oSource
}
; Object must have OwnProps() method.
else if (oSource is Object)
{
oEnumerator := oSource.OwnProps()
}
if (oEnumerator)
{
for vPropName, vPropData in oEnumerator
{
if (IsObject(vPropName))
{
if (bDebugA)
{
MsgBox("[" Type(oSource) " Property Left] Reference `"" Type(vPropName) "`" is skipped in the enumeration of ObjCompile().")
}
continue
}
; All classes have prototype, which is enumerated.
if (vPropName = "Prototype")
{
if (bDebugA)
{
MsgBox("[" Type(oSource) " Property Left] `"" vPropName "`" is skipped in the enumeration of ObjCompile().")
}
continue
}
; Limitation:
; - static Property[]{Get, Set} is included in the enumerator.
; - But there is no way to check and skip them. So, the name and the value of current data is inserted.
sPacket .= vPropName
sPacket .= this.Delim.DESC
if (IsObject(vPropData))
{
try
{
%Type(vPropData)%()
}
catch
{
if (bDebugA)
{
MsgBox("[" Type(oSource) " Property " vPropName "] Type `"" Type(vPropData) "`" is skipped in Call() of ObjCompile().")
}
}
else
{
sPacket .= this.Delim.KIND Type(vPropData)
; Down to the next level.
sPacket .= this.Delim.OBJS
this.ObjCompile(vPropData, &sPacket)
sPacket .= this.Delim.OBJE
}
}
else
{
for vName, vValue in this.Delim.OwnProps()
{
if (!InStr(vName, this.Delim.TRAN))
{
vPropData := StrReplace(vPropData, vValue, this.Delim.%(vName this.Delim.TRAN)%)
}
}
sPacket .= vPropData
}
sPacket .= this.Delim.PACK
}
sPacket := RTrim(sPacket, this.Delim.PACK)
}
return
}
; ==============================================================================================================================
; ObjParse() Method
; ==============================================================================================================================
static ObjParse(sPacket, oTarget := -1)
{
bDebugA := true
bDebugB := false
bUseCallMethod := true
bUseMapDefault := true
if (bUseMapDefault)
{
sMapDefault := ""
}
; --------------------------------------------------------------------------------------------------------------------------
; Top Level
; --------------------------------------------------------------------------------------------------------------------------
if (oTarget = -1)
{
if (Type(sPacket) != "String")
{
return (0)
}
aPack := StrSplit(sPacket, this.Delim.PACK)
if (aPack.Length = 0)
{
return (0)
}
aDesc := StrSplit(aPack[1], this.Delim.DESC)
vPropData := aDesc[1]
if (RegExMatch(vPropData, this.Delim.KIND "(.*?)\" this.Delim.OBJS, &sKindName))
{
if (bUseCallMethod)
{
oTarget := %sKindName[1]%()
}
else
{
oTarget := {}
oTarget.base := %sKindName[1]%.Prototype
}
}
else
{
return (0)
}
if (bDebugB)
{
MsgBox("Top level start:`n`nPacket: " sPacket "`n`n" Type(oTarget) "`n`n-> " SubStr(sPacket, InStr(sPacket, this.Delim.OBJS) + 1))
}
; Down to the main level.
sPacket := SubStr(sPacket, InStr(sPacket, this.Delim.OBJS) + 1)
sPacket := this.ObjParse(sPacket, oTarget)
if (bDebugB)
{
MsgBox("Top level end:`n`n" sKindName[1] "`n`nPacket: " sPacket)
}
return (oTarget)
}
; --------------------------------------------------------------------------------------------------------------------------
; Main Level
; --------------------------------------------------------------------------------------------------------------------------
Loop
{
; ----------------------------------------------------------------------------------------------------------------------
; Parse Packet
; ----------------------------------------------------------------------------------------------------------------------
aPack := StrSplit(sPacket, this.Delim.PACK)
if (aPack.Length = 0)
{
if (bDebugB)
{
MsgBox("Packet: " sPacket "`n`nReturn: End of packet")
}
return (sPacket)
}
aDesc := StrSplit(aPack[1], this.Delim.DESC)
if (aDesc.Length <= 1)
{
if (aDesc.Length = 1)
{
if (InStr(aDesc[1], this.Delim.OBJE))
{
if (bDebugB)
{
MsgBox("Packet: " sPacket "`n`nReturn: " aDesc[1] "`n`n-> " LTrim(SubStr(sPacket, InStr(sPacket, this.Delim.OBJE) + 1), this.Delim.PACK))
}
; Up to the previous level.
sPacket := LTrim(SubStr(sPacket, InStr(sPacket, this.Delim.OBJE) + 1), this.Delim.PACK)
return (sPacket)
}
else
{
iPackStartPos := InStr(sPacket, this.Delim.PACK)
sPacket := (iPackStartPos) ? SubStr(sPacket, iPackStartPos + 1) : ""
}
}
else
{
sPacket := ""
}
}
vPropName := aDesc[1]
vPropData := aDesc[2]
if (bDebugB)
{
MsgBox("Seperate property:`n`nPacket: " sPacket "`n`n" vPropName "`n" vPropData)
}
; ----------------------------------------------------------------------------------------------------------------------
; Create Object
; ----------------------------------------------------------------------------------------------------------------------
if (InStr(vPropData, this.Delim.OBJS))
{
if (RegExMatch(vPropData, this.Delim.KIND "(.*?)\" this.Delim.OBJS, &sKindName))
{
oCreateObj := 0
try
{
oCreateObj := %sKindName[1]%()
}
if (!oCreateObj)
{
if (bDebugA)
{
MsgBox("[" Type(oTarget) " Property " vPropName "] Type `"" sKindName[1] "`" is skipped in Call() of ObjParse().")
}
}
else
{
; Make a shallow copy of Map's Default property to the created object in only first level.
; Because, below first level, all structures of object are newly created,
; and previously created data are all removed already.
; If avoiding this, must make objects from bottom to top. But this is almost impossible to make.
; Or do not create object if it already exist, but this makes object's stuctures corrupted.
if (IsSet(sMapDefault))
{
if (Type(oCreateObj) = "Map")
{
try
{
sMapDefault := oTarget[vPropName].Default
}
catch
{
try
{
sMapDefault := oTarget.%vPropName%.Default
}
}
oCreateObj.Default := sMapDefault
}
}
if (Type(oTarget) = "Array")
{
oTarget.InsertAt(vPropName, oCreateObj)
}
else if (Type(oTarget) = "Map")
{
oTarget[vPropName] := oCreateObj
}
else if (IsObject(oTarget))
{
oTarget.%vPropName% := oCreateObj
}
}
if (bDebugB)
{
MsgBox("Object begin:`n`nPacket: " sPacket "`n`n" sKindName[1] "`n`n-> " SubStr(sPacket, InStr(sPacket, this.Delim.OBJS) + 1))
}
; Down to the next level.
sPacket := SubStr(sPacket, InStr(sPacket, this.Delim.OBJS) + 1)
sPacket := this.ObjParse(sPacket, oCreateObj)
if (bDebugB)
{
MsgBox("Object end:`n`n" sKindName[1] "`n`nPacket: " sPacket)
}
}
}
; ----------------------------------------------------------------------------------------------------------------------
; Input Data
; ----------------------------------------------------------------------------------------------------------------------
else
{
iObjEndPos := 0
if (InStr(vPropData, this.Delim.OBJE))
{
iObjEndPos := InStr(sPacket, this.Delim.OBJE)
vPropData := StrReplace(vPropData, this.Delim.OBJE)
}
for vName, vValue in this.Delim.OwnProps()
{
if (!InStr(vName, this.Delim.TRAN))
{
vPropData := StrReplace(vPropData, this.Delim.%(vName this.Delim.TRAN)%, vValue)
}
}
if (Type(oTarget) = "Array")
{
if (oTarget.Has(vPropName))
{
oTarget.RemoveAt(vPropName)
}
oTarget.InsertAt(vPropName, vPropData)
}
else if (Type(oTarget) = "Map")
{
oTarget[vPropName] := vPropData
}
else if (IsObject(oTarget))
{
oTarget.%vPropName% := vPropData
}
if (iObjEndPos)
{
if (bDebugB)
{
MsgBox("Set value: `n`nPacket: " sPacket "`n`n" vPropName "`n" vPropData "`nReturn: " "true" "`n`n->" LTrim(SubStr(sPacket, iObjEndPos + 1), this.Delim.PACK))
}
; Up to the previous level.
sPacket := LTrim(SubStr(sPacket, iObjEndPos + 1), this.Delim.PACK)
return (sPacket)
}
else
{
if (bDebugB)
{
MsgBox("Set value: `n`nPacket: " sPacket "`n`n" vPropName "`n" vPropData "`nReturn: " "false" "`n`n->" SubStr(sPacket, InStr(sPacket, this.Delim.PACK) + 1))
}
iPackStartPos := InStr(sPacket, this.Delim.PACK)
sPacket := (iPackStartPos) ? SubStr(sPacket, iPackStartPos + 1) : ""
}
}
}
return (sPacket)
}
}