learnxinyminutes-docs/hocon.md

578 lines
17 KiB
Markdown
Raw Normal View History

---
name: HOCON
filename: learnhocon.conf
contributors:
- [TehBrian, 'https://tehbrian.xyz']
---
Human-Optimized Configuration Object Notation, or HOCON, is a configuration and
data serialization format designed to be easily editable by humans.
It's a superset of JSON, meaning that any valid JSON is valid HOCON, but it
2024-08-19 22:06:05 +00:00
differs in being less opinionated. With its flexible yet determinable syntax,
resulting configuration files are often less noisy than with other formats.
2024-08-19 22:06:05 +00:00
Additionally, its support for comments makes it better-suited for user-facing
configuration than JSON.
```
2024-08-19 22:06:05 +00:00
// Anything after // or # is a comment. This is a comment.
# This is also a comment.
##################
### THE BASICS ###
##################
# Everything in HOCON is either a key, a value, or a separator.
# : and = are separators. They separate the key from the value.
key: value
another_key = another_value
# You can use either separator with or without whitespace on either side.
colon1:value
colon2: value
colon3 : value
equals1=value
equals2= value
equals3 = value
2024-08-19 22:06:05 +00:00
# As you'll see, HOCON has a very nonrestrictive syntax.
2024-08-19 22:06:05 +00:00
# HOCON isn't opinionated on how keys look.
THIS_IS_A_VALID_KEY: value
this-is-also-a-valid-key: value
2024-08-19 22:06:05 +00:00
keys can have spaces: value
or even numbers like 12345: value
"you can even quote keys if you'd like!": value
2024-08-19 22:06:05 +00:00
# Keys are case sensitive.
unique: value 1
UnIqUe: value 3
UNIQUE: value 2
# A key, followed by any separator, followed by a value, is called a field.
this_entire_line_is: a field
###################
### VALUE TYPES ###
###################
2024-08-19 22:06:05 +00:00
# A value can be of type: string, number, object, array, boolean, null.
# Simple values are values of any type except array and object.
## SIMPLE VALUES ##
quoted_string: "I like quoting my strings."
unquoted_string: I don't like quoting my strings.
# Special characters that cannot be used in unquoted strings are:
# $ " { } [ ] : = , + # ` ^ ? ! @ * &
2024-08-19 22:06:05 +00:00
# Unquoted strings do not support any kind of escaping.
# To use one of those special characters in a string, use a quoted string.
multiline_string: """This entire thing is a string!
One giant, multiline string.
You can put 'single' and "double" quotes without it being invalid."""
number: 123
negative: -123
fraction: 3.1415926536
2024-08-19 22:06:05 +00:00
scientific_notation: 1.2e6 // 1.2 * 10^6
boolean: true # or false
empty: null
## ARRAYS ##
# Arrays hold lists of values.
2024-08-19 22:06:05 +00:00
# Values in arrays can be separated with commas..
array: [ 1, 2, 3, 4, 5 ]
fibonacci: [1,1,2,3,5,8,13]
2024-08-19 22:06:05 +00:00
multiples_of_5: [5, 10, 15, 20,] # Notice the trailing comma. That's allowed.
# or newlines..
friends: [
"Brian"
"Sophie"
"Maya"
"Sabina"
]
# or both!
ingredients: [
"Egg",
"Sugar",
"Oil",
2024-08-19 22:06:05 +00:00
"Flour", # Trailing comma. That's allowed here too.
]
2024-08-19 22:06:05 +00:00
# Once again, HOCON has a very liberal syntax. Use whichever style you prefer.
no newline before or after bracket: ["This"
"is"
"an"
"array!"]
2024-08-19 22:06:05 +00:00
# Arrays can hold other arrays.
array in array: [ [1, 2, 3], ["a", "b", "c"] ]
array in array in array: [ [ [1, 2], [8, 9] ], [ ["a", "b" ], ["y", "z"] ] ]
## OBJECTS ##
# Objects hold fields.
2024-08-19 22:06:05 +00:00
# Just like arrays, fields in objects can be separated with commas..
object: { key: value, another_key: another_value }
server_connection: {ip: "127.0.0.1", port: 80}
2024-08-19 22:06:05 +00:00
first: {letter: a, number: 1,} # Trailing comma.
# or newlines..
power_grid: {
max_capacity: 15000
current_power: 1200
}
# or both!
food_colors: {
carrot: orange,
pear: green,
apple: red,
plum: purple,
2024-08-19 22:06:05 +00:00
banana: yellow, # Trailing comma. These pesky things show up everywhere!
}
2024-08-19 22:06:05 +00:00
# Arrays can hold objects.
coworkers: [
{
name: Jeff
age: 27
},
{
name: Henry
age: 35
},
{
name: Timmy
age: 12
}
]
# The field separator may be omitted if the key is followed by {
no_separator {
key: value
speed_of_light: very fast
ten: 10
2024-08-19 22:06:05 +00:00
# Objects can hold other objects.
another_object {
twenty: 20
speed_of_sound: also pretty fast
}
}
# In fact, the entirety of any HOCON document is an actually just an object.
# That object is called the root object. The only difference between it and any
# other object is that the curly brackets at the top and bottom of the document
# may be omitted.
# This means that HOCON documents can be formatted in the same way that
# regular objects can be formatted, including separating fields with commas
# rather than with newlines.
# Additionally, while the entirety of a HOCON document can be and is usually an
# object, it can also be an array. If it is an array, the opening and closing
# brackets at the top and bottom of the document must be explicitly written.
######################
### DUPLICATE KEYS ###
######################
is_happy: false
# If there is a duplicate key, the new value overrides the previous value.
is_happy: true
online_users: [Jacob, Mike]
# Same with arrays.
online_users: [Jacob, Mike, Henry]
# For objects, it's a bit different.
my_car: {
color: blue
speed: 9001
passengers: null
engine: {
running: true
temperature: 137
}
}
# If there is a duplicate key and both values are objects,
# then the objects are merged.
my_car: {
// These fields are added to the old, previous object.
nickname: "My Favorite Car"
type: 2-door sedan
// Since the value of this duplicate key is NOT an object,
// it simply overrides the previous value.
speed: 60
// Same with arrays. They override, not merge.
passengers: ["Nate", "Ty"]
// This object is recursively merged with the other object.
engine: {
// These two fields are added to the previous object.
type: gas
oil_level: 10
// This field overrides the previous value.
temperature: 179
}
}
# Object merging is done two at a time. That is to say, the first two objects
# merge into one, then that object merges with the next object, and so on.
# Because of this, if you set a field with an object value to a non-object value
# and then back to an object value, the new object will completely override any
# previous value.
2024-08-19 22:06:05 +00:00
// Null, a non-object value, overrides the object.
my_car: null
2024-08-19 22:06:05 +00:00
// Then, this object overrides null.
my_car: {
nickname: "My New Car"
type: 4-door minivan
color: gray
speed: 90
passengers: ["Ayden", "Liz"]
}
###########################
### VALUE CONCATENATION ###
###########################
## SIMPLE VALUE CONCATENATION ##
2024-08-19 22:06:05 +00:00
# Simple values (all value types except array and object) separated by
# whitespace are concatenated into a single string. The whitespace between
# values is preserved.
2024-08-19 22:06:05 +00:00
number_concat: 1 2 3 12.5 -3 2e5 // "1 2 3 12.5 -3 2e5"
boolean_concat: true false true // "true false true"
null_concat: null null null // "null null null"
mixed_concat: 1 true null // "1 true null"
# String value concatenation can appear anywhere that a quoted string can.
2024-08-19 22:06:05 +00:00
number_concat_in_array: [1 2, 3 4, 5 6] // ["1 2", "3 4", "5 6"]
# In fact, unquoted strings are actually just string value concatenations.
2024-08-19 22:06:05 +00:00
unquoted_string_concat: his name is jeff // "his name is jeff"
# Going further, even keys that are unquoted strings are actually just string
# value concatenations.
2024-08-19 22:06:05 +00:00
this is a key: value // the KEY is: "this is a key"
# The following field is identical to the field above.
"this is a key": value
2024-08-19 22:06:05 +00:00
# Quoted strings can also be concatenated.
# This will be useful later, when we cover substitutions.
quoted_string_concat: "her"" name" "is ""jenna" // "her name is jenna"
# Notice that the whitespace (or lack thereof) between values is preserved.
## ARRAY CONCATENATION ##
# Arrays separated by whitespace are merged into a single array.
2024-08-19 22:06:05 +00:00
array_concat: [1, 2, 3] [4, 5, 6] // [1, 2, 3, 4, 5, 6]
# Arrays cannot be concatenated with a non-array value.
2024-08-19 22:06:05 +00:00
//array_concat: true [false] // error!
//array_concat: 1 [2] // error!
## OBJECT CONCATENATION ##
# Objects separated by whitespace are merged into a single object.
# The merge functionality is identical to that of duplicate key object merging.
2024-08-19 22:06:05 +00:00
lamp: {on: true} {color: tan} // {on: true, color: tan}
# Similarly to arrays, objects cannot be concatenated with a non-object value.
2024-08-19 22:06:05 +00:00
//object_concat: true {on: false} // error!
//object_concat: 1 {number: 2} // error!
########################
### PATH EXPRESSIONS ###
########################
# Path expressions are used to write out a path through the object graph.
# Think of it as navigating through objects to a specific field.
# Each object to traverse through is called an element, and each element is
# separated with a period.
country: {
city: {
neighborhood: {
house: {
name: "My House"
address: 123 Example Dr.
}
}
}
}
2024-08-19 22:06:05 +00:00
# The path to the address could be written as:
# country.city.neighborhood.house.address
# Country, city, neighborhood, house, and address are all elements.
2024-08-19 22:06:05 +00:00
# Path expressions are used in two places: substitutions (which we'll get to
# in just a moment), and as keys. That's right: keys can be path expressions.
foo: {
bar: {
baz: {
number: 12
}
}
}
2024-08-19 22:06:05 +00:00
# Rather than tediously specifying each object, a path expression could be used.
# The following field represents the same object.
foo.bar.baz.number: 12
# Fields and objects specified with path expressions are merged in the same way
# that any object is usually merged.
foo.bar.baz.bool: true
// the object foo's value is: foo { bar { baz { number: 12, bool: true } } }
#####################
### SUBSTITUTIONS ###
#####################
# Substitutions refer to a specific value from some path expression.
2024-08-19 22:06:05 +00:00
# They're only allowed in values, not in keys or nested in other substitutions.
me: {
favorite_animal: parrots
favorite_food: cookies
}
2024-08-19 22:06:05 +00:00
# There are two syntaxes for substitutions:
# ${path_expression} and ${?path_expression}.
# The latter syntax will be covered in a moment.
my_fav_animal: ${me.favorite_animal}
my_fav_food: ${me.favorite_food}
# Substitutions are not parsed inside quoted strings. To get around this,
# either use an unquoted string or value concatenation.
animal_announcement: My favorite animal is ${my_fav_animal}
2024-08-19 22:06:05 +00:00
// "My favorite animal is parrots"
food_announcement: "My favorite food is "${my_fav_food}"!"
2024-08-19 22:06:05 +00:00
// "My favorite food is cookies!"
# Substitutions are parsed last in the document. Because of this, you can
# reference a key that hasn't been defined yet.
color_announcement: "My favorite color is" ${my_fav_color}"!"
2024-08-19 22:06:05 +00:00
// "My favorite color is blue!"
my_fav_color: blue
# Another effect of substitutions being parsed last is that substitutions will
2024-08-19 22:06:05 +00:00
# always use the latest, as in last, value assigned in the entire document.
color: green
2024-08-19 22:06:05 +00:00
their_favorite_color: ${color} // orange
color: orange
2024-08-19 22:06:05 +00:00
# This includes merged objects.
random_object: {
number: 12
}
2024-08-19 22:06:05 +00:00
the_number: ${random_object.number} // 15
random_object: {
number: 15
}
###############################
### UNDEFINED SUBSTITUTIONS ###
###############################
# A substitution using the ${path_expression} syntax with an undefined path
# expression, meaning a path expression that does not point to a defined value,
# is invalid and will therefore generate an error.
2024-08-19 22:06:05 +00:00
//${does.not.exist} // error!
# However, an undefined substitution using the ${?path_expression} syntax
# has different behavior depending on what it is the value of.
request: {
2024-08-19 22:06:05 +00:00
# If it is the value of a field, then the field won't be created.
response: ${?does.not.exist} // this field does not exist
type: HTTP
}
request: {
# Additionally, if it would have overridden a previous value, then the
# previous value remains unchanged.
type: ${?does.not.exist} // request.type is still HTTP
}
# If it is a value in an array, then it is simply not added.
values: [ 172, "Brian", ${?does.not.exist}, null, true, ]
2024-08-19 22:06:05 +00:00
// [ 172, "Brian", null, true ]
# If it is part of simple value concatenation, it acts as an empty string.
final_string: "String One"${?does.not.exist}"String Two"
2024-08-19 22:06:05 +00:00
// "String OneString Two"
# If it is part of array concatenation, it acts as an empty array.
final_array: [ 1, 2, 3 ] ${?does.not.exist} [ 7, 8, 9 ]
2024-08-19 22:06:05 +00:00
// [ 1, 2, 3, 7, 8, 9 ]
# If it is part of object concatenation, it acts as an empty object.
2024-08-19 22:06:05 +00:00
final_object: { a: 1 } ${?does.not.exist} { c: 3 }
// { a: 1, c: 3 }
######################################
### SELF-REFERENTIAL SUBSTITUTIONS ###
######################################
# Substitutions normally "look forward" and use the final value defined in the
# document. However, in cases when this would create a cycle, the substitution
# looks only backwards.
2024-08-19 22:06:05 +00:00
# A field that contains a substitution that points to itself or points to
# other fields that eventually point back to itself is called a
# self-referential field.
2024-08-19 22:06:05 +00:00
letters: "a b c" // "a b c"
letters: ${letters}" d" // "a b c d"
letters: ${letters}" e" // "a b c d e"
2024-08-19 22:06:05 +00:00
PATH: [/bin] // [/bin]
PATH: ${PATH} [/usr/bin] // [/bin, /usr/bin]
PATH: ${PATH} [/usr/local/bin] // [/bin, /usr/bin, /usr/local/bin]
2024-08-19 22:06:05 +00:00
x: "x" // "x"
y: ${x}"y" // "xy"
x: ${y}"z" // "xyz"
##########################
### += FIELD SEPARATOR ###
##########################
# In addition to : and =, there actually exists another separator: +=
2024-08-19 22:06:05 +00:00
# A field separated with += implies self-referential array concatenation.
# Essentially, it appends an element to a previously defined array.
a: [1]
b: [1]
2024-08-19 22:06:05 +00:00
# These two fields are equivalent.
a += 2 // [1, 2]
b: ${?b} [2] // [1, 2]
2024-08-19 22:06:05 +00:00
USERS: [/usr/luke] // [/usr/luke]
USERS += /usr/devon // [/usr/luke, /usr/devon]
USERS += /usr/michael // [/usr/luke, /usr/devon, /usr/michael]
# Since += only appends elements to a previously existing array, if the previous
# value was not an array, an error will be generated.
OTHER_USERS: /usr/luke
2024-08-19 22:06:05 +00:00
//OTHER_USERS += /usr/devon // error!
2024-08-19 22:06:05 +00:00
# The underlying substitution syntax used is ${?path}, not ${path}.
# Recall that, using the ${?} syntax, an undefined substitution in array
# concatenation acts as an empty array. Because of this, it is perfectly
# acceptable if the field that is being set is initially undefined.
2024-08-19 22:06:05 +00:00
//z: [] // not necessary
z += 3 // [3]
z += 4 // [3, 4]
2024-08-19 22:06:05 +00:00
NEW_USERS += /usr/sandra // [/usr/sandra]
NEW_USERS += /usr/kennedy // [/usr/sandra, /usr/kennedy]
NEW_USERS += /usr/robin // [/usr/sandra, /usr/kennedy, /usr/robin]
################
### INCLUDES ###
################
# Includes allow you to "import" one HOCON document into another.
# An include statement consists of the unquoted string "include" followed by
# whitespace and then a resource name, which is one of the following:
# - a single quoted string which is heuristically interpreted as a URL,
# filename, or a Java classpath resource.
# - url(), file(), or classpath(), with the parentheses surrounding a quoted
# string which is either a URL, filename, or classpath resource respectively.
# - required(), with the parentheses surrounding one of the above.
include "https://example.com/config.conf"
include "/foo/bar/config.conf"
include "config.conf"
include url("https://example.com/config.conf")
include file("/foo/bar/config.conf")
include classpath("config.conf")
# If the included file does not exist, it will be silently ignored and act as if
# it were an empty object. However, if it is wrapped around required(), then
# parsing will explicitly error if the file cannot be resolved.
2024-08-19 22:06:05 +00:00
//include required("doesnt_exist.conf") // error!
//include required(url("https://example.com/doesnt_exist.conf")) // error!
//include required(file("doesnt_exist.conf")) // error!
//include required(classpath("doesnt_exist.conf")) // error!
2024-08-19 22:06:05 +00:00
# The file specified by the include statement is called the included file.
# The file containing the include statement is called the including file.
# Including a file functions as if you directly replaced the include statement,
# wherever it may be, with the contents of the included file's root object.
# An included file must have an object as its root value and not an array.
# If the included file has an array as its root value, then it is invalid and
# an error will be generated.
# Pretend that the following is in a file called user_config.conf:
username: RandomUser1337
auto_login: true
color_theme: dark
screensaver: {
image: usr/images/screensaver.jpg
turn_on_after: 1m
}
2024-08-19 22:06:05 +00:00
# Then, we include that file.
include file("user_config.conf")
# We can now reference values from that file!
2024-08-19 22:06:05 +00:00
path_to_user_screensaver: ${screensaver.image} // "usr/images/screensaver.jpg"
greeting: "Welcome, "${username}"!" // "Welcome, RandomUser1337!"
# Duplicate keys override as they normally do.
2024-08-19 22:06:05 +00:00
status: "Auto Login: "${auto_login} // "Auto Login: true"
auto_login: false
2024-08-19 22:06:05 +00:00
status: "Auto Login: "${auto_login} // "Auto Login: false"
2024-08-19 22:06:05 +00:00
# Object merging is the same as usual.
screensaver: {
// This gets added to the screensaver object.
enable_during_day: false
// This overrides the previous value.
turn_on_after: 30s
}
# Include statements can appear in place of a field. Anywhere that a field
# could appear, an include statement could appear as well.
# Pretend that the following is in a file called server_settings.conf:
max_connections: 10
url: example.com
port: 80
admin_page: {
username: admin
password: pass12345
}
2024-08-19 22:06:05 +00:00
# Then, we include that file nested inside an object.
websites: {
my_epic_website: {
include file("server_settings.conf")
}
}
# Now, we can reference the contents of server_settings.conf as if they
# had been written directly into the object my_epic_website.
server_port: ${websites.my_epic_website.port}
the_password: "The password is: "${websites.my_epic_website.admin_page.password}
2024-08-19 22:06:05 +00:00
// "The password is: pass12345"
max_conn: "Max Connections: "${websites.my_epic_website.max_connections}
2024-08-19 22:06:05 +00:00
// "Max Connections: 10"
```
### More Resources
+ [Official HOCON Specification](https://github.com/lightbend/config/blob/master/HOCON.md)
2024-08-19 22:06:05 +00:00
+ [HOCON Playground](https://hocon-playground.tehbrian.dev)