Welcome! Please see the About page for a little more info on how this works.

0 votes
in Clojure by

Description

Hi everyone! I've got a Spring Boot config file I'm parsing. Values don't really matter:

server.port: 8000
spring.application.name: some application
spring.datasource.driver-class-name: org.whatever.Driver
spring.datasource.password: some_password
spring.datasource.platform: postgres
spring.datasource.url: jdbc:postgresql:does_not_matter
spring.datasource.username: some_username
spring.jpa.database: POSTGRESQL
some.service.endpoint: http://whatever
some.other.service.endpoint: http://something_else

I'm trying to extract / group different parts of that file, and I'm looking for what the suggested / idiomatic way of doing this is. I'm trying to follow the "data > functions > macros" idiom and the "better 100 functions for one data structure".

Specifically, I'm looking to parse / group the web endpoints, and then the database properties, in order to validate them using some business logic.

I've already parsed the file using java.util.Properties, so that's not of interest here. The file above is converted to a map.

Solution 1

Create a separate function for each "grouping" I want that parses what I want and returns a new map with just the data I want.

  • defn get-endpoints [props] returns

`

{
    "some.service.endpoint" "http://whatever",
    "some.other.service.endpoint" "http://something_else"
}

`

  • defn get-database-properties [props] etc.

Solution 2

Add additional keys to the original map to group what I want.

defn parse-groupings [props] returns

    {
        "server.port" "8000",
        "spring.application.name" "some application",
        "spring.datasource.driver-class-name" "org.whatever.Driver",
        ;; the rest of the original properties
       :groupings {
            :web-endpoints {
               ;; web endpoints go here
           },
           :database-props {
             ;; database properties get embedded here
           }

    }

Solution ???

There's a lot more solutions, of course, but I'm wondering what the community recommends.

Thank you!

1 Answer

0 votes
by

You can do a simple

(doto (Properties.)
  (.load (io/reader "props.properties")))

It will result in a map like

{...
  "server.port" "8080"
 ...}

And access the values as string (get props-map "server.port")

I prefer this approach because is easier to git grep about a value.

For a more "idiomatic" solution, you can do something like this:

(defn parse-props
  [in & {:keys [handlers default-handler]
         :or   {default-handler identity}}]
  (into {}
    (map (fn [[k v]]
           (let [[kw-name & ns-parts] (reverse (string/split k #"\."))
                 kw (keyword (string/join "." ns-parts)
                      kw-name)
                 f (get handlers kw default-handler)]
             [kw (f v)])))
    (doto (Properties.)
      (.load (io/reader in)))))
(parse-props "props.properties"
  :handlers {:server/port #(Long/parseLong %)})

It will result in a map like

{....
 :server/port 8080
 ...}
by
Thanks, @Enzzo, but I'm more curious about recommendations on grouping the map, like `get-endpoints` in the OP.
by
re-reading the question now, I see that it is more about a function that select data than something related to props. sorry for the missanswer.

About "get-endpoints", from code perspective, I would write something like (defn find-endpoints [config] (into {} (filter #(string/ends-with? % ".endpoint")) config)).
If required, we can assoc it back into config map.
I don't think that wroth to spend too much time thinking in the "design" of this function. It is a simple function that probably will be called in <10 places. It probably will not change over time.

A last comment from config perspective, I would create a explicit app.config.endpoint-props=..service1.endpoint,...service2.endpoint, to avoid naming conventions.
...