// 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 # As you'll see, HOCON has a very nonrestrictive syntax. # HOCON isn't opinionated on how keys look. THIS_IS_A_VALID_KEY: value this-is-also-a-valid-key: value keys can have spaces: value or even numbers like 12345: value "you can even quote keys if you'd like!": value # 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 ### ################### # 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: # $ " { } [ ] : = , + # ` ^ ? ! @ * & # 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 scientific_notation: 1.2e6 // 1.2 * 10^6 boolean: true # or false empty: null ## ARRAYS ## # Arrays hold lists of values. # Values in arrays can be separated with commas.. array: [ 1, 2, 3, 4, 5 ] fibonacci: [1,1,2,3,5,8,13] 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", "Flour", # Trailing comma. That's allowed here too. ] # Once again, HOCON has a very liberal syntax. Use whichever style you prefer. no newline before or after bracket: ["This" "is" "an" "array!"] # 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. # 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} 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, banana: yellow, # Trailing comma. These pesky things show up everywhere! } # 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 # 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. // Null, a non-object value, overrides the object. my_car: null // 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 ## # Simple values (all value types except array and object) separated by # whitespace are concatenated into a single string. The whitespace between # values is preserved. 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. 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. 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. 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 # 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. array_concat: [1, 2, 3] [4, 5, 6] // [1, 2, 3, 4, 5, 6] # Arrays cannot be concatenated with a non-array value. //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. lamp: {on: true} {color: tan} // {on: true, color: tan} # Similarly to arrays, objects cannot be concatenated with a non-object value. //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. } } } } # The path to the address could be written as: # country.city.neighborhood.house.address # Country, city, neighborhood, house, and address are all elements. # 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 } } } # 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. # They're only allowed in values, not in keys or nested in other substitutions. me: { favorite_animal: parrots favorite_food: cookies } # 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} // "My favorite animal is parrots" food_announcement: "My favorite food is "${my_fav_food}"!" // "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}"!" // "My favorite color is blue!" my_fav_color: blue # Another effect of substitutions being parsed last is that substitutions will # always use the latest, as in last, value assigned in the entire document. color: green their_favorite_color: ${color} // orange color: orange # This includes merged objects. random_object: { number: 12 } 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. //${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: { # 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, ] // [ 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" // "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 ] // [ 1, 2, 3, 7, 8, 9 ] # If it is part of object concatenation, it acts as an empty object. 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. # 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. letters: "a b c" // "a b c" letters: ${letters}" d" // "a b c d" letters: ${letters}" e" // "a b c d e" PATH: [/bin] // [/bin] PATH: ${PATH} [/usr/bin] // [/bin, /usr/bin] PATH: ${PATH} [/usr/local/bin] // [/bin, /usr/bin, /usr/local/bin] x: "x" // "x" y: ${x}"y" // "xy" x: ${y}"z" // "xyz" ########################## ### += FIELD SEPARATOR ### ########################## # In addition to : and =, there actually exists another separator: += # A field separated with += implies self-referential array concatenation. # Essentially, it appends an element to a previously defined array. a: [1] b: [1] # These two fields are equivalent. a += 2 // [1, 2] b: ${?b} [2] // [1, 2] 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 //OTHER_USERS += /usr/devon // error! # 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. //z: [] // not necessary z += 3 // [3] z += 4 // [3, 4] 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. //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! # 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 } # Then, we include that file. include file("user_config.conf") # We can now reference values from that file! path_to_user_screensaver: ${screensaver.image} // "usr/images/screensaver.jpg" greeting: "Welcome, "${username}"!" // "Welcome, RandomUser1337!" # Duplicate keys override as they normally do. status: "Auto Login: "${auto_login} // "Auto Login: true" auto_login: false status: "Auto Login: "${auto_login} // "Auto Login: false" # 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 } # 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} // "The password is: pass12345" max_conn: "Max Connections: "${websites.my_epic_website.max_connections} // "Max Connections: 10"