About Monkey 2 › Forums › Monkey 2 Development › Something like this json config manager useful to anyone as a module?
This topic contains 5 replies, has 3 voices, and was last updated by
skn3 2 years, 5 months ago.
-
AuthorPosts
-
November 6, 2016 at 2:39 am #4798
This is just a little class I wrote for my app. It lets you define a a config structure at the start of your app and then then lets you Get or Set various value types. The class deals with loading, merging and saving with/from/to a json file. It also lets you add save hooks that get triggered when saving.
Let me know if this is of any use to you, and I will investigate how to make my first module in monkey2!
An example:
Monkey12345678910111213141516171819202122232425262728293031'exampleGlobal Config:= New ConfigHandlerFunction Main:Void()'hard code the configuration propertiesLocal group1 := Config.Define("group1")group1.Define("x", 55)group1.Define("y", 66)Local group2 := Config.Define("group2")Local item1 := group2.Define("item1")item1.Define("name", "apple")item1.Define("rect",New Recti(0,0,64,64))Local item2 := group2.Define("item2")item2.Define("name", "tree")item2.Define("rect",New Recti(0,0,64,128))'load (or create default config)Config.Load("path.json")'add save handler to update same values before savingConfig.Saving += Lambda()Config.Set("group2.item2.name","changed :D")End'get some valuesPrint Config.Get("group2.item1.rect.width",0)'trigger saveConfig.Save()EndThe class:
Monkey123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672#Import "<std>"'sub classesClass Setting Implements IContainer<Setting> AbstractProtectedField id:StringPublic'propertiesProperty Length:Int() VirtualReturn 0End'type propertiesProperty IsGroup:Bool() VirtualReturn FalseEndProperty IsBool:Bool() VirtualReturn FalseEndProperty IsInt:Bool() VirtualReturn FalseEndProperty IsFloat:Bool() VirtualReturn FalseEndProperty IsString:Bool() VirtualReturn FalseEnd'value propertiesProperty BoolValue:Bool() VirtualReturn falseSetter:Void(value:Bool) VirtualEndProperty IntValue:Int() VirtualReturn 0Setter:Void(value:Int) VirtualEndProperty FloatValue:Float() VirtualReturn 0.0Setter:Void(value:Float) VirtualEndProperty StringValue:String() VirtualReturn ""Setter:Void(value:String) VirtualEnd'operatorsOperator[]:Setting(id:String) VirtualReturn nullEnd'define apisMethod Define:Setting(id:String) VirtualReturn NullEndMethod Define:Setting(id:String, value:Bool) VirtualReturn NullEndMethod Define:Setting(id:String, value:Int) VirtualReturn NullEndMethod Define:Setting(id:String, value:Float) VirtualReturn NullEndMethod Define:Setting(id:String, value:String) VirtualReturn NullEndMethod Define:Setting(id:String, value:Recti) VirtualReturn NullEndMethod Define:Setting(id:String, value:Rectf) VirtualReturn NullEnd'apiMethod ToString:String() VirtualReturn ""EndMethod BuildJson:Void(parentJson:JsonObject) VirtualEndMethod Clear:Void() VirtualEndEndClass SettingGroup Extends SettingProtectedField depth:IntField children := New StringMap<Setting>()Public'constructorMethod New(id:String="")Self.id = idEnd'propertiesProperty Length:Int() OverrideReturn children.Count()EndProperty IsGroup:Bool() OverrideReturn TrueEnd'container/iterationMethod All:StringMap<Setting>.ValueIterator()Return children.Values.All()End'operatorsOperator[]:Setting(id:String) OverrideReturn children[id]End'apiMethod Clear:Void() Overridechildren.Clear()EndMethod BuildJson:Void(parentJson:JsonObject) Override'create json object for this groupLocal thisJson := New JsonObjectparentJson.SetValue(id, Cast<JsonValue>(thisJson))'recurse into childrenFor Local child := Eachin children.Valueschild.BuildJson(thisJson)NextEnd'building apiMethod Define:Setting(id:String) Override'dfine a group'check to see if child group existsLocal group:= Cast<SettingGroup>(children[id])If group = Null'nope, need to create new onegroup = New SettingGroup(id)children[id] = groupEndif'set depthgroup.depth = depth + 1'chainReturn Cast<Setting>(group)EndMethod Define:Setting(id:String, value:Bool) OverrideLocal setting:= children[id]If setting = Null Or setting.IsBool = Falsechildren[id] = New SettingBool(id, value)Elsesetting.BoolValue = valueEndif'chainReturn settingEndMethod Define:Setting(id:String, value:Int) OverrideLocal setting:= children[id]If setting = Null Or setting.IsInt = Falsechildren[id] = New SettingInt(id, value)Elsesetting.IntValue = valueEndif'chainReturn settingEndMethod Define:Setting(id:String, value:Float) OverrideLocal setting:= children[id]If setting = Null Or setting.IsFloat = Falsechildren[id] = Cast<Setting>(New SettingFloat(id, value))Elsesetting.FloatValue = valueEndif'chainReturn settingEndMethod Define:Setting(id:String, value:String) OverrideLocal setting:= children[id]If setting = Null Or setting.IsString = Falsechildren[id] = Cast<Setting>(New SettingString(id, value))Elsesetting.StringValue = valueEndif'chainReturn settingEndMethod Define:Setting(id:String, value:Recti) Override'make sure rect group existsLocal group:= children[id]If group = Null Or group.IsGroup = False'create groupgroup = New SettingGroup(id)children[id] = group'define the propertiesgroup.Define("x",value.X)group.Define("y",value.Y)group.Define("width",value.Width)group.Define("height",value.Height)Else'update valuesgroup.Clear()group["x"].IntValue = value.Xgroup["y"].IntValue = value.Ygroup["width"].IntValue = value.Widthgroup["height"].IntValue = value.HeightEndif'chainReturn groupEndMethod Define:Setting(id:String, value:Rectf) Override'make sure rect group existsLocal group:= children[id]If group = Null Or group.IsGroup = False'create groupgroup = New SettingGroup(id)children[id] = group'define the propertiesgroup.Define("x",value.X)group.Define("y",value.Y)group.Define("width",value.Width)group.Define("height",value.Height)Else'update valuesgroup.Clear()group["x"].FloatValue = value.Xgroup["y"].FloatValue = value.Ygroup["width"].FloatValue = value.Widthgroup["height"].FloatValue = value.HeightEndif'chainReturn groupEndEndClass SettingValue<T> Extends Setting AbstractProtectedConst NULL_VALUE:TField value:TPublicMethod New()EndProperty BoolValue:Bool() OverrideReturn Cast<Bool>(value)Setter:Void(value:Bool) OverrideIf valueSelf.value = Cast<T>("1")ElseSelf.value = Cast<T>("0")EndifEndProperty IntValue:Int() OverrideReturn Cast<Int>(value)Setter:Void(value:Int) OverrideSelf.value = Cast<T>(value)EndProperty FloatValue:Float() OverrideReturn Cast<Float>(value)Setter:Void(value:Float) OverrideSelf.value = Cast<T>(value)EndProperty StringValue:String() OverrideReturn ToString()Setter:Void(value:String) OverrideSelf.value = Cast<T>(value)End'apiMethod Clear:Void() Overridevalue = NULL_VALUEEndEndClass SettingBool Extends SettingValue<Bool> FinalMethod New(id:String, value:Bool)Self.id = idSelf.value = valueEndProperty IsBool:Bool() OverrideReturn TrueEndMethod BuildJson:Void(parentJson:JsonObject) OverrideparentJson.SetBool(id, value)EndMethod ToString:String() OverrideIf valueReturn "1"ElseReturn "0"EndifEndEndClass SettingInt Extends SettingValue<Int> FinalMethod New(id:String, value:Int)Self.id = idSelf.value = valueEndProperty IsInt:Bool() OverrideReturn TrueEndMethod BuildJson:Void(parentJson:JsonObject) OverrideparentJson.SetNumber(id, value)EndMethod ToString:String() OverrideReturn Cast<String>(value)EndEndClass SettingFloat Extends SettingValue<Float> FinalMethod New(id:String, value:Float)Self.id = idSelf.value = valueEndProperty IsFloat:Bool() OverrideReturn TrueEndMethod BuildJson:Void(parentJson:JsonObject) OverrideparentJson.SetNumber(id, value)EndMethod ToString:String() OverrideReturn Cast<String>(value)EndEndClass SettingString Extends SettingValue<String> FinalMethod New(id:String, value:String)Self.id = idSelf.value = valueEndProperty Length:Int() OverrideReturn value.LengthEndProperty IsString:Bool() OverrideReturn TrueEndMethod BuildJson:Void(parentJson:JsonObject) OverrideparentJson.SetString(id, value)EndMethod ToString:String() OverrideReturn valueEndEndClass ConfigHandler Extends SettingGroupProtectedField path:StringField lock := 0Field dirty := FalseField lookup:= New StringMap<Setting>PublicField Saving:Void()'sub classClass Undefined Extends ThrowableEnd'constructorMethod New()Super.New()End'propertiesProperty Path:String()Return pathEnd'constructorProtectedMethod BuildLookup:Void(prefix:String, setting:Setting)'lets build the idprefix += setting.id'save to lookuplookup[prefix] = setting'recurse?If setting.IsGroupprefix += "."Local group := Cast<SettingGroup>(setting)For Local child := Eachin groupBuildLookup(prefix, child)NextEndifEndPublic'apiMethod Lock:Void()lock += 1EndMethod Unlock:Void()If lock = 1lock = 0Save()Elseif lock > 1lock -= 1EndifEndProtectedFunction LoadRecurse:Void(thisKey:String, thisJson:JsonValue, thisSetting:Setting)'only continue if setting/json are valid otherwise it does nothingIf thisSetting And thisJsonIf thisSetting.IsGroup'dealing with group so we can only load json array or objectIf thisJson.IsObject'yupso we now recurse the json
For Local childNode := Eachin thisJson.ToObject()LoadRecurse(childNode.Key, childNode.Value, thisSetting[childNode.Key])NextElseIf thisJson.IsArray'yupso we now recurse the json
Local jsonArray := Cast<JsonArray>(thisJson)For Local childIndex := 0 Until jsonArray.LengthLoadRecurse(childIndex, jsonArray[childIndex], thisSetting[Cast<String>(childIndex)])NextElse'nope!thisSetting.Clear()EndifElse'dealing with value so we can only load json valuesIf thisJson.IsBool Or thisJson.IsNumber Or thisJson.IsString'convert json value type to config value typeIf thisSetting.IsBoolthisSetting.BoolValue = thisJson.ToBool()Elseif thisSetting.IsIntthisSetting.IntValue = Cast<Int>(thisJson.ToNumber())Elseif thisSetting.IsFloatthisSetting.FloatValue = Cast<Float>(thisJson.ToNumber())Elseif thisSetting.IsStringthisSetting.StringValue = thisJson.ToString()Else'invalid (but this wouldnt happen)!thisSetting.Clear()EndifElse'invalid json, so just count this as empty!thisSetting.Clear()EndifEndifEndifEndPublicMethod Load:Void(path:String)Self.path = path'build lookup maplookup.Clear()For Local setting := Eachin children.ValuesBuildLookup("", setting)Next'now load the existing json and merge it over the topIf GetFileType(path) = FileType.FileLocal json := JsonObject.Load(path)'iterate over all children in root json objectIf jsonFor Local node := Eachin jsonLoadRecurse(node.Key, json[node.Key], children[node.Key])NextEndifEndifEndMethod Save:Void()If lock > 0'laterdirty = TrueElse'nowdirty = False'fire save callbacks first (this lets stuff update the config before saving)Saving()'create json root object and let settings build into itLocal json := New JsonObjectFor Local setting := Eachin children.Valuessetting.BuildJson(json)Next'save json string to disk!SaveString(json.ToJson(), path)EndifEndMethod ToString:String() OverrideLocal json := New JsonObjectFor Local setting := Eachin children.Valuessetting.BuildJson(json)NextReturn json.ToJson()End'get value apiMethod Get:Bool(path:String, defaultValue:Bool)Local setting := lookup[path]If setting = NullReturn defaultValueElseReturn setting.BoolValueEndifEndMethod Get:Int(path:String, defaultValue:Int)Local setting := lookup[path]If setting = NullReturn defaultValueElseReturn setting.IntValueEndifEndMethod Get:Float(path:String, defaultValue:Float)Local setting := lookup[path]If setting = NullReturn defaultValueElseReturn setting.FloatValueEndifEndMethod Get:String(path:String, defaultValue:String)Local setting := lookup[path]If setting = NullReturn defaultValueElseReturn setting.StringValueEndifEndMethod Get:Recti(path:String, defaultValue:Recti)Local setting := lookup[path]If setting = Null Or setting.IsGroup = False Or setting["x"] = Null Or setting["y"] = Null Or setting["width"] = Null Or setting["height"] = NullReturn defaultValueElseReturn New Recti(setting["x"].IntValue,setting["y"].IntValue,setting["width"].IntValue,setting["height"].IntValue)EndifEndMethod Get:Rectf(path:String, defaultValue:Rectf)Local setting := lookup[path]If setting = Null Or setting.IsGroup = False Or setting["x"] = Null Or setting["y"] = Null Or setting["width"] = Null Or setting["height"] = NullReturn defaultValueElseReturn New Recti(setting["x"].FloatValue,setting["y"].FloatValue,setting["width"].FloatValue,setting["height"].FloatValue)EndifEnd'set value apiMethod Set:Void(path:String, value:Bool)Local setting := lookup[path]If setting = NullThrow New UndefinedElsesetting.BoolValue = valueEndifEndMethod Set:Void(path:String, value:Int)Local setting := lookup[path]If setting = NullThrow New UndefinedElsesetting.IntValue = valueEndifEndMethod Set:Void(path:String, value:Float)Local setting := lookup[path]If setting = NullThrow New UndefinedElsesetting.FloatValue = valueEndifEndMethod Set:Void(path:String, value:String)Local setting := lookup[path]If setting = NullThrow New UndefinedElsesetting.StringValue = valueEndifEndMethod Set:Void(path:String, value:Recti)Local setting := lookup[path]If setting = Null Or setting.IsGroup = False Or setting["x"] = Null Or setting["y"] = Null Or setting["width"] = Null Or setting["height"] = NullThrow New UndefinedElsesetting["x"].IntValue = value.Xsetting["y"].IntValue = value.Ysetting["width"].IntValue = value.Widthsetting["height"].IntValue = value.HeightEndifEndMethod Set:Void(path:String, value:Rectf)Local setting := lookup[path]If setting = Null Or setting.IsGroup = False Or setting["x"] = Null Or setting["y"] = Null Or setting["width"] = Null Or setting["height"] = NullThrow New UndefinedElsesetting["x"].FloatValue = value.Xsetting["y"].FloatValue = value.Ysetting["width"].FloatValue = value.Widthsetting["height"].FloatValue = value.HeightEndifEndEndNovember 6, 2016 at 10:32 am #4805This helper looked useful.
About code – what about using Variant type to store values instead of few vars int/bool/string/float/etc? It will reduce similar code parts, I think.
November 6, 2016 at 1:39 pm #4810I was tempted but didn’t know if it was safe to use the Reflection stuff yet. Also what overhead does using monkey2 reflection bring? I already noticed someone mentioning that their file size had increased dramatically.
November 6, 2016 at 2:33 pm #4812Hm. If we look into
Monkey1modules/monkey/types.monkey2we’ll see that base class Object also contains reflection data – TypeInfo.
Therefore it’s not a problem (no overhead?) to use Variant. Or even ‘Object’ type to boxing.
Also I heard that reflection now working only if we import <reflection> module.
November 6, 2016 at 5:54 pm #4820The current state of reflection is that, yes, it does add some overhead to an app so it’s now optional – you need to use #Import “<reflection>” to activate it. This is currentlyall or nothing. Eventually, reflection will be selectable on a module by module basis. I’ve reduced reflection overhead a bit and will be reducing it further in future, but reflection will never be ‘free’.
Without reflection, you can still use Variants and Typeinfo should work for built-ins types. However, all objects will always return ‘Class Object’ for typeinfo. It should be possible to add ‘minimal’ type info for all classes eventually too, even with reflection off, so you can at least inspect class names.
November 6, 2016 at 6:44 pm #4821Ok that’s good to know!
-
AuthorPosts
You must be logged in to reply to this topic.