[Class] JSONFile - easily work with JSON files

Post your working scripts, libraries and tools
User avatar
RUNIE
Posts: 289
Joined: 03 May 2014, 14:50
GitHub: Run1e

[Class] JSONFile - easily work with JSON files

29 Jun 2017, 16:16

This is the class I use when I work with JSON files to keep it all tidy and nice.
It doesn't encode and decode to JSON, but it handles the other things like saving and managing the object.

Dependencies:
JSON loader/dumper by cocobelgica: https://github.com/cocobelgica/AutoHotkey-JSON
However the class can easily be modified to use another JSON dump/load lib.

To create a new JSON file wrapper and destroy it:

Code: Select all

MyJSON := new JSONFile(filepath)
MyJSON := ""
Methods:

Code: Select all

.Save(Prettify := false) - save object to file
.JSON(Prettify := false) - Get JSON text
.Fill(Object) - fill keys in from another object into the instance object

Instance variables:

Code: Select all

.File() - get file path
.Object() - get data object
Here's an example of it in use:

Code: Select all

#SingleInstance force
#NoEnv

jf := new JSONFile("test.json")

; jf now behaves (nearly) identical to a normal object when setting/getting keys

; you can set keys/value pairs
jf.key := "value"
msgbox % jf.JSON(true)

; you can also make subobjects like normally
jf.arr := ["sub", "array", 42]
msgbox % jf.JSON(true)

jf.obj := {mykey: "my value", subobj: {morekeys: "more values", keyseverywhere: "and even more values!"}}
msgbox % jf.JSON(true)

; you can also use object functions and methods like normally
msgbox % jf.HasKey("obj")
jf.obj.Delete("subobj")
msgbox % jf.JSON(true)
jf.Delete("obj")
msgbox % jf.JSON(true)

; you can also call several methods

; this method fills the object with keys from another object, in this case the default settings object from a project
; if a key already exists, it does nothing, it will only fill in keys that are missing
jf.Fill({ StartUp: true
	, Font: "Segoe UI Light"
	, Color: {Selection: 0x44C6F6, Tab: {Dark: 0x404040, Orange: 0xFE9A2E}} ; FE9A2E
	, GuiState: {ActiveTab: 1, GameListPos: 1, BindListPos: 1}
	, Plugins: ["text", "moretext"]
	, VibrancyScreens: [1]
	, VibrancyDefault: 50})

; to get the json call JSON(). you can call JSON(true) to get the prettified JSON
msgbox % jf.JSON(true)

; to save do Save()
jf.Save(true) ; save prettified JSON

; to for-loop over the shallow object, you have to get the actual data object and not the instance, so you gotta do
; this is practically the only difference from using a normal object
for Key, Value in jf.Object()
	msgbox % Key " => " (IsObject(Value) ? "*OBJECT*" : Value)

; similarly you can get the file name and file object via
msgbox % "File: " jf.File()
msgbox % "FileObj is object: " IsObject(jf.FileObj())

; to close the file object and clean up, simply delete the instance
jf := ""

ExitApp
And the actual class:

Code: Select all

/*
	Class JSONFile
	Written by Runar "RUNIE" Borge
	
	Dependencies:
	JSON loader/dumper by cocobelgica: https://github.com/cocobelgica/AutoHotkey-JSON
	However the class can easily be modified to use another JSON dump/load lib.
	
	To create a new JSON file wrapper:
	MyJSON := new JSONFile(filepath)
	
	And to destroy it:
	MyJSON := ""
	
	Methods:
		.Save(Prettify := false) - save object to file
		.JSON(Prettify := false) - Get JSON text
		.Fill(Object) - fill keys in from another object into the instance object
		
	Instance variables:
		.File() - get file path
		.Object() - get data object
*/

Class JSONFile {
	static Instances := []
	
	__New(File) {
		FileExist := FileExist(File)
		JSONFile.Instances[this] := {File: File, Object: {}}
		ObjRelease(&this)
		FileObj := FileOpen(File, "rw")
		if !IsObject(FileObj)
			throw Exception("Can't access file for JSONFile instance: " File, -1)
		if FileExist {
			try
				JSONFile.Instances[this].Object := JSON.Load(FileObj.Read())
			catch e {
				this.__Delete()
				throw e
			} if (JSONFile.Instances[this].Object = "")
				JSONFile.Instances[this].Object := {}
		} else
			JSONFile.Instances[this].IsNew := true
		return this
	}
	
	__Delete() {
		if JSONFile.Instances.HasKey(this) {
			ObjAddRef(&this)
			JSONFile.Instances.Delete(this)
		}
	}
	
	__Call(Func, Param*) {
		; return instance value (File, Object, FileObj, IsNew)
		if JSONFile.Instances[this].HasKey(Func)
			return JSONFile.Instances[this][Func]
		
		; return formatted json
		if (Func = "JSON")
			return StrReplace(JSON.Dump(this.Object(),, Param.1 ? A_Tab : ""), "`n", "`r`n")
		
		; save the json file
		if (Func = "Save") {
			try
				New := this.JSON(Param.1)
			catch e
				return false
			FileObj := FileOpen(this.File(), "w")
			FileObj.Length := 0
			FileObj.Write(New)
			FileObj.__Handle
			return true
		}
		
		; fill from specified array into the JSON array
		if (Func = "Fill") {
			if !IsObject(Param.2)
				Param.2 := []
			for Key, Val in Param.1 {
				if (A_Index > 1)
					Param.2.Pop()
				HasKey := Param.2.MaxIndex()
						? this.Object()[Param.2*].HasKey(Key) 
						: this.Object().HasKey(Key)
				Param.2.Push(Key)
				if IsObject(Val) && HasKey
					this.Fill(Val, Param.2), Param.2.Pop()
				else if !HasKey
					this.Object()[Param.2*] := Val
			} return
		}
		
		return Obj%Func%(this.Object(), Param*)
	}
	
	__Set(Key, Val) {
		return this.Object()[Key] := Val
	}
	
	__Get(Key) {
		return this.Object()[Key]
	}
}
User avatar
Maestr0
Posts: 81
Joined: 05 Dec 2013, 17:43

Re: [Class] JSONFile - easily work with JSON files

17 Aug 2019, 11:23

Very cool! Are you going to port it to v2?
User avatar
RUNIE
Posts: 289
Joined: 03 May 2014, 14:50
GitHub: Run1e

Re: [Class] JSONFile - easily work with JSON files

17 Aug 2019, 20:53

Maestr0 wrote:
17 Aug 2019, 11:23
Very cool! Are you going to port it to v2?
Nope. Feel free to post a v2 version here if you decide to do it though.
User avatar
Maestr0
Posts: 81
Joined: 05 Dec 2013, 17:43

Re: [Class] JSONFile - easily work with JSON files

18 Aug 2019, 04:50

RUNIE wrote:
17 Aug 2019, 20:53
Maestr0 wrote:
17 Aug 2019, 11:23
Very cool! Are you going to port it to v2?
Nope. Feel free to post a v2 version here if you decide to do it though.
When v2 is done changing things, I plan to. Right now it's still a bit.... too alpha.
freakkk
Posts: 17
Joined: 21 Sep 2014, 20:14

Re: [Class] JSONFile - easily work with JSON files

29 Dec 2019, 14:23

Well, somehow I never saw this until now. I ended up creating a class that is simpler than yours, but differs enough that I thought I would post it on here, in case you want to add any ideas to your library.
  • File (or folder file is in) doesn't need to already exist when instance is created
  • You can set file to save when exiting script (or when instance is deleted)
Methods:

Code: Select all

JsonFile::Str( [space, replacer ] )   - Convert object to JSON string
JsonFile::Write( [space, replacer ] ) - Writes JSON string to file
JsonFile::Save( [space, replacer ] )  - Writes to file, but only if changed
Example Use:

Code: Select all

  ;--- Example 1:  Basic usuage demo
  jf := new JsonFile("test.json") ;- Create instance.
                                  
  jf.key1 := 11                   ;- Add/change keys just like any other object
  jf.key2 := {key3:33}
  
  ;** JsonFile.Str( [space, replacer ] )
  MsgBox % jf.Str()   ;- Str() method converts to json string
  
  ;** JsonFile.Write( [space, replacer ] )
  If jf.Write()          ;- Write() converts to json, but also writes to file
    MsgBox File saved.
  
  ; jf.key4 := A_Now    ; add another key for testing
  
  ;** JsonFile.Save( [space, replacer ] )
  If jf.Save()   ;- Does same as write(), but only writes to file if changes were made
    MsgBox json file successfully saved
  Else  
    MsgBox No changes were made, so file wasn't changed


  ;--- Example 2:  Saving JsonFile on exit

  ;- If param 2 is a number, json will be saved to file on exit, 
  ;   and the number is the 'space' parameter for json.Dump() method.  
  settings := new JsonFile(A_ScriptName ".json", 0)

  If !FileExist(A_ScriptName ".json"){
    settings.k1 := 1
    settings.k2 := 2
    ; ..etc..
    ; settings.Save(1)  ;- Force to save right now,
  }

  MsgBox % "Settings:`n" settings.Str(1)

; ..or since number was specified when creating instance, json will save now
ExitApp
Class:

Code: Select all

class JsonFile {
; class JsonFile extends JSON {
  __New(filePath,saveOnExit:=""){
    If !(JSON.Load.__Class = "JSON.LOAD")
      Throw Exception("`t" A_ThisFunc "()  - The JsonFile class requires that you also include the JSON.ahk library (by Coco) in your script.`n`nhttps://www.autohotkey.com/boards/viewtopic.php?t=627`nhttps://github.com/cocobelgica/AutoHotkey-JSON/blob/master/JSON.ahk" , -1)
    ObjSetBase(this.base,JSON)
    this.filePath := ComObjCreate("Scripting.FileSystemObject").GetAbsolutePathName(filePath)
    (saveOnExit ~= "^[0-9]+$")  ?  ( this.saveOnExit:=saveOnExit, OnExit(ObjBindMethod(this,"__Delete")) )
    returnObj := {}
    try returnObj := this.Load( this.OrgfileStr:=FileOpen(this.filePath,"r").Read() )
    ObjSetBase(returnObj,this)
    return returnObj
  }
  __Delete(){
    (this.saveOnExit ~= "^[0-9]+$")  ?  this.Save(this.saveOnExit)
  }
  Str(space:="",replacer:=""){
    return this.Dump(this,replacer,space)
  }
  Write(space:="",replacer:=""){
    filePath := this.base.filePath
    SplitPath, filePath,,fileDir
    If !InStr( FileExist(fileDir), "D")  ;- Create dir if it doesn't exist
      FileCreateDir, % fileDir
    try FileOpen(filePath, "w`n","UTF-8-RAW").Write( this.base.OrgfileStr:=this.Str(space,replacer) ).Close()
    catch error
      return false, errorLevel := error
    return true, errorLevel := false
  }
  Save(space:="",replacer:=""){
    If !(this.Str(space,replacer) = this.base.OrgfileStr)
      return this.Write(space,replacer)  ;, m("saved")
  }
}
burque505
Posts: 1124
Joined: 22 Jan 2017, 19:37

Re: [Class] JSONFile - easily work with JSON files

29 Dec 2019, 16:06

@freakkk, works like a charm, thanks for sharing this.
Regards,
burque505
freakkk
Posts: 17
Joined: 21 Sep 2014, 20:14

Re: [Class] JSONFile - easily work with JSON files

29 Dec 2019, 16:09

burque505 wrote:
29 Dec 2019, 16:06
thanks for sharing this.
Thanks for taking a look at it! :)
User avatar
RUNIE
Posts: 289
Joined: 03 May 2014, 14:50
GitHub: Run1e

Re: [Class] JSONFile - easily work with JSON files

31 Dec 2019, 02:38

@freakkk The save on exit feature is nice. Some people might prefer to have explicit control when their data is saved, but it's really application-specific. Cool class overall!

Return to “Scripts and Functions”

Who is online

Users browsing this forum: elModo7, Thoughtfu1Tux and 63 guests