Skip to content

Instantly share code, notes, and snippets.

@peter
Last active March 23, 2016 08:59
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save peter/8126f22f0d27cf7b3239 to your computer and use it in GitHub Desktop.
Save peter/8126f22f0d27cf7b3239 to your computer and use it in GitHub Desktop.
Java vs Clojure - Dealing with JSON and Immutable Value Objects

Java

We are using the Builder pattern here. Notice how each field is duplicated about five times and how we need to implement equals and hashCode manually (left as an exercise for the reader):

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;

@JsonDeserialize(builder = User.Builder.class)
public class User {
    private int id;
    private String name;
    private Boolean active;

    public User(Builder builder) {
        this.id = builder.id;
        this.name = builder.name;
        this.active = builder.active;
    }

    public int getId() {
        return id;
    }

    public String getName() {
        return name;
    }

    public Boolean getActive() {
        return active;
    }

    public String toJson() {
        String json = null;
        try {
            json = new ObjectMapper().writeValueAsString(this);
        } catch (JsonProcessingException e) {
            System.out.println("Could not serialize object: " + e.getMessage());
        }
        return json;
    }

    // TODO: implement equals method

    // TODO: implement hashCode method

    public static final class Builder {
        private int id;
        private String name;
        private Boolean active;

        private Builder() {
        }

        public Builder(User copy) {
            this.id = copy.id;
            this.name = copy.name;
            this.active = copy.active;
        }

        public static Builder aUser() {
            return new Builder();
        }

        public Builder withId(int id) {
            this.id = id;
            return this;
        }

        public Builder withName(String name) {
            this.name = name;
            return this;
        }

        public Builder withActive(Boolean active) {
            this.active = active;
            return this;
        }

        public User build() {
            return new User(this);
        }
    }

    public static void main(String[] args) {
        User user1 = User.Builder.aUser()
                        .withId(1)
                        .withName("Joe")
                        .withActive(true)
                        .build();

        User user2 = new User.Builder(user1)
                        .withId(2)
                        .withName("Foo")
                        .build();

        System.out.println("user1: " + user1.toJson() + " user2: " + user2.toJson());
    }
}

Clojure

With Clojure we get value equality semantics and immutability out of the box. Records can be managed like maps.

(require '[cheshire.core :as json])
 
(defrecord User [id name active])

(def user1 (map->User {:id 1 :name "Joe" :active true}))
(def user2 (map->User (merge user1 {:id 2 :name "Foo"})))

(println "user1: " (json/generate-string user1) " user2: " (json/generate-string user2))

With run-time type checking using the prismatic/schema library and plain maps instead of records:

(require '[schema.core :as s])

(def User {
    :id s/Int
    :name s/Str
    (s/optional-key :bio) s/Str
    :active s/Bool
})

(defn new-user [user]
    (s/validate User user)
    user)

(def valid-user (new-user {:id 1 :name "Joe" :active true}))

(def invalid-user (new-user {:name "Joe"}))
; => :error {:id missing-required-key, :active missing-required-key}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment