-
Notifications
You must be signed in to change notification settings - Fork 40
Immutable DTO for hocon #106
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
I'm maintaining full backward compatibility with the old API. Should this be a separate library or should it be folded back into the core project? Ie, should config immutability be the default? |
You wanna re-implemented with all immutable data struct? |
The original HOCON API are all immutable, I'm just trying to see if we should stick to the original material, plus immutability does have its advantages. Programmatic HOCON building/materialization is also supported using a builder pattern, instead of using the fluent .With pattern, which I kind of hate, because it is so memory intensive. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Code documentation for builder pattern API, generic getter API, and API backward compatibility
.AddRange(value) | ||
.Build(); | ||
case HoconType.Literal: | ||
return new HoconImmutableLiteralBuilder() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sample for immutable Hocon literal builder pattern API, it acts/behaves exactly like a string builder.
.Merge(value.GetObject()) | ||
.Build(); | ||
case HoconType.Array: | ||
return new HoconImmutableArrayBuilder() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sample for immutable Hocon Array builder pattern API, it inherits from List<HoconImmutableElement>
, where HoconImmutableElement
is the base class for immutable object, array, and literal. Usage acts/behaves exactly like a List
switch (value.Type) | ||
{ | ||
case HoconType.Object: | ||
return new HoconImmutableObjectBuilder() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sample for immutable Hocon Object builder pattern API, it inherits from SortedDictionary<string, HoconImmutableElement>
, where HoconImmutableElement is the base class for immutable object, array, and literal. Usage acts/behaves exactly like a SortedDictionary
|
||
namespace Hocon.Immutable.Extensions | ||
{ | ||
public static class HoconImmutableExtensions |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Contains all of the static ToHoconImmutable
extension helper class. Converts the old Hocon classes into its immutable variant.
|
||
namespace Hocon.Immutable.Extensions | ||
{ | ||
public static class TypeConverterExtensions |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Contains static helper extension functions to cast HoconImmutableElement
class back into immutable object, array, or literal.
|
||
namespace Hocon.Immutable.Extensions | ||
{ | ||
public static class GenericGetterExtensions |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Generic value getter implementation. Converts an immutable Hocon literal string value to another primitive type. Usage: myHoconLiteral.Get<int>()
|
||
namespace Hocon.Immutable.Extensions | ||
{ | ||
public static class ValueGetterExtensions |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Contains static extension convenience helper functions to convert an immutable hocon literal into another primitive type. Sample use: myHoconLiteral.GetDecimal()
.
Also contains backward compatible getter helper functions that follows the old myHoconObject.GetInt("my.hocon.path")
API pattern.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Code documentation for the immutable API
root_2 : 1234 | ||
}"; | ||
var config = Parser.Parse(hocon).ToHoconImmutable(); | ||
Assert.True(config["root.bool"].GetBoolean()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sample use of the new API, it emulates how Microsoft.Extensions.Configuration
works, where you can supply a Hocon path into an array indexer to access children values. In this case, config
is an immutable Hocon object, and "root.bool"
is the path to the "bool"
property inside "root"
object.
The getter function GetBoolean()
acts directly on an ImmutableHoconLiteral
object instance. Object instance will be checked during runtime, invalid instance will throw an exception.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM
}"; | ||
var config = Parser.Parse(hocon).ToHoconImmutable(); | ||
Assert.True(config["root.bool"].GetBoolean()); | ||
Assert.True(config["root.bool"].Get<bool>()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Generic getter function version (syntactic sugar) of GetBoolean()
.
var config = Parser.Parse(hocon).ToHoconImmutable(); | ||
Assert.True(config["root.bool"].GetBoolean()); | ||
Assert.True(config["root.bool"].Get<bool>()); | ||
Assert.True(config["root.bool"]); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sample use of the auto-conversion getter. ImmutableHoconLiteral
contains implicit cast operator overrides to automatically convert the string value into other primitive types. In this case, it automatically converts the string value to bool
Have this on my to-do list this Friday |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I love this - there's a lot to it but it all looks very well tested and well done.
Question though - why ship it as a separate project? Why not make it part of the whole?
}; | ||
[Theory] | ||
[MemberData(nameof(ObjectArrayData))] | ||
public void CanIntelligentlyUseIndexerTypeToAccessMembers(string hocon) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Very cool - this is a slick addition to the API
root_2 : 1234 | ||
}"; | ||
var config = Parser.Parse(hocon).ToHoconImmutable(); | ||
Assert.True(config["root.bool"].GetBoolean()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM
That was what I asked earlier, should I fold this back into the main package or should I keep it separate into an opt-in package. The main reason I split it up is to make a proof of concept that it works without having to touch the main code; I was not planning on making these backward compatible, but it turns out that making it backward compatible was really trivial. |
Got it - let’s keep it in its own project for now and do a POC while integrating it into Akka.NET |
config["root.first-object.first-property"]
will treat config as an object value and traverse to the "root.first-object.first-property" path.config["root.my-array"][0]
will treat theconfig
instance as a Hocon object, traverse it to the"root.my-array"
value, and then the[0]
indexer will treat that value as an array. It will read array values directly, or if it is an object value, will try to convert it into an array using the "positively numeric keyed object to array convertion" rule. Accessing a literal value using any indexers will throw an exception.byte value = config["root.byte-value"]
, the value will be auto convert to byte and assigned to the variable. Auto convert will validate the conversion on the fly and throws an exception if the conversion is invalid.var myIntArray = config["root.int-array"].Get<int[]>()
will return an array of ints from "root.int-array" path, orvar myTimeSpan = config["root.time-span"].Get<TimeSpan>()
will return aTimeSpan
from "root.time-span" path.myConfig.Value.GetInt()
,myConfig.Value.GetInt("my-path")
etc.but they will only work on the Literal class instance, you can not pass a Hocon path into them anymore.The.Value
property is deprecated, it is included just to maintain backward compatibility with old code.