Clojure, update nested content in map - clojure

I am confused about the required syntax for updating a value in a map, where said value is a vector of maps.
Given a map:
{:data-extracts [
{:name "some name"
:data "some data"
}]}
How can I update the value of :data, I know you can use assoc or conj to modify maps (well return new maps) but I'm unsure how this works when nested elements are present.
desired result:
{:data-extracts [
{:name "some name"
:data "new data"
}]}
Is there a way to do something like the following?
(update :data-extracts :data "new data")
How is this achieveable?
I tried the following:
(assoc opts :data-extracts [:name "Secret Escapes"
:data "new data"]))
But that doesn't work as I expected.

When using nested structures, you'll want to use *-in-functions (in this case assoc-in) and specify one key for each nesting level. In case of vectors, it's just index, in case of maps it's obvious:
(assoc-in [:data-extracts 0 :data] "new data")

Related

How can I update a map using the `update` function?

(def p {:name "James" :age 26})
I'm trying update method, like
(update p :name "David")
which does not work since the second argument has to be a function.
Try this:
(assoc p :name "David")
Please see this list of documentation, especially the Clojure CheatSheet! See also assoc-in and update-in as described under
Collections -> Maps
P.S. What you have there is a Clojure map value, which is different than an object in JavaScript or a JSON string.

How to transform object keys and values in Clojure

I'm using Datomic, although it doesn't particularly matter for this question. But it typically returns namespaced keys (and enum values are returned as namespaces keywords also). I want to translate the potentially nested structure to strip the namespaces from the keys and from values (and also string-ify the values of enums). I'm doing this because I'll return the result in a JSON REST API and the namespacing doesn't make much sense in that context. Here's a simple example structure:
{
:person/name "Kevin"
:person/age 99
:person/gender :gender/M
:person/address {
:address/state :state/NY
:address/city "New York"
:address/zip "99999"
}
}
And I'm hoping to translate to:
{
:name "Kevin"
:age 99
:gender "M"
:address {
:state "NY"
:city "New York"
:zip "99999"
}
}
One thing I know I can do is use (postwalk-replace {:person/name :name :person/age :age :person/gender :gender :person/address :address :address/city :city :address/state :state :address/zip :zip} the-entity) and that covers the keys, but not the values.
What other options do I have?
You can use clojure.walk/postwalk. Simple version doesn't differentiate between keywords as keys or values in a map, simply converts all keys to strings:
(def data {:person/name "Kevin"
:person/age 99
:person/gender :gender/M
:person/address {:address/state :state/NY
:address/city "New York"
:address/zip "99999"}})
(clojure.walk/postwalk
(fn [x]
(if (keyword? x)
(name x)
x))
data)
;; => => {"name" "Kevin", "age" 99, "gender" "M", "address" {"state" "NY", "city" "New York", "zip" "99999"}}
To implement exactly what you want you need to handle keys and values in a map separately:
(defn transform-keywords [m]
(into {}
(map (fn [[k v]]
(let [k (if (keyword? k) (keyword (name k)) k)
v (if (keyword? v) (name v) v)]
[k v]))
m)))
(clojure.walk/postwalk
(fn [x]
(if (map? x)
(transform-keywords x)
x))
data)
;; => => {:name "Kevin", :age 99, :gender "M", :address {:state "NY", :city "New York", :zip "99999"}}
As a side note: in my experience, the impedance mismatch between namespace-qualified and non-namespace-qualified keys at the boundary of your system can be an ongoing pain; what's more, having namespaced-qualified keys has significant advantages regarding code clarity (very good data traceability).
So I wouldn't give up namespace-qualified keys too readily. If EDN's syntax for namespacing (with dots and slashes) doesn't suit the consumers of your API, you may even want to use something more conventional like underscores (e.g :person_name instead of :person/name); a bit uglier, but it still gives you most of the benefits of namespace-qualified keys, you will not even need to transform the data structures, and Datomic won't mind.

validation of clojure schema

i have a problem in validation of clojure prismatic schema. Here is the code.
:Some_Var1 {:Some_Var2 s/Str
:Some_Var3 ( s/conditional
#(= "mytype1" (:type %)) s/Str
#(= "mytype2" (:type %)) s/Str
)}
I am trying to validate it using the code:
"Some_Var1": {
"Some_Var2": "string",
"Some_Var3": {"mytype1":{"type":"string"}}
}
but it is throwing me an error:
{
"errors": {
"Some_Var1": {
"Some_Var3": "(not (some-matching-condition? a-clojure.lang.PersistentArrayMap))"
}
}
}
It is a very basic code i am trying to validate. I am very new to clojure and still trying to learn basics of it.
Thanks,
Welcome to Clojure! It's a great language.
In Clojure, a keyword and a string are distinct types i.e. :type is not the same "type". For example:
user=> (:type {"type" "string"})
nil
(:type {:type "string"})
"string"
However, I think there is a deeper issue here: from looking at your data, it appears you want to encode the type information in the data itself and then check it based on that information. It might be possible, but it would be a pretty advanced usage of schema. Schema is typically used when the types are known ahead of type e.g. data like:
(require '[schema.core :as s])
(def data
{:first-name "Bob"
:address {:state "WA"
:city "Seattle"}})
(def my-schema
{:first-name s/Str
:address {:state s/Str
:city s/Str}})
(s/validate my-schema data)
I'd suggest that if you need validate based on encoded type information, it'd probably be easier to write a custom function for that.
Hope that helps!
Update:
An an example of how conditional works, here's a schema that will validate, but again, this is a non-idiomatic use of Schema:
(s/validate
{:some-var3
(s/conditional
;; % is the value: {"mytype1" {"type" "string"}}
;; so if we want to check the "type", we need to first
;; access the "mytype1" key, then the "type" key
#(= "string" (get-in % ["mytype1" "type"]))
;; if the above returns true, then the following schema will be used.
;; Here, I've just verified that
;; {"mytype1" {"type" "string"}}
;; is a map with key strings to any value, which isn't super useful
{s/Str s/Any}
)}
{:some-var3 {"mytype1" {"type" "string"}}})
I hope that helps.

Accessing value from defn

I need advice,
I try to make function :
(def user-map [new-name new-phone new-email]
{:name new-name
:phone new-phone
:email new-email})
With new-name, new-phone, new-email are user input. But when i try to compile it, it says too many arguments to def, after change def to defn, when i try to execute user-map in REPL i get something like
#<temp_atom$user_address zenedu.mm.dbase.temp_atom$user_address#714924b5
instead of actual map.
I need to get to the map, any advice?
It sounds like perhaps you are conceptually combining the value that will be returned from calling user-map as a function with some arguments and evaluating the symbol user-map on its own.
Evaluating
(user-map "me" "123456789" "me#here.com")
Which will return a map, by looking up the var user-map in the current namespace and calling the function stored in that var with these arguments. Where evaluating just
user-map
Will simply look up the var user-map in the current namespace and return the contents of that var, which in the case where you used defn, will be the function it's self. The REPL then prints the object I'd of that function.
In your use case, you need defn to define a builder (like a constructor in Java) for the object you want. The log
#<temp_atom$user_address zenedu.mm.dbase.temp_atom$user_address#714924b5
suggests that you are using another structure user-address somewhere in the application and it looks like there is confusion between a user-map object and this user-address.
Anyway, you may be interested to have a look at defrecord that provides a convenient way to build objects with a constructor (and potentially other functions related to this object), e.g.
(defrecord user [name phone email])
defrecord provides 2 constructors ->user and map->user:
(def me (->user "abb" "0102030405" "abb#mail.com"))
(def you (map->user {:email "das#mail.com" :phone "9090909090" :name "das"}))
And you can access the properties of a user through keywords exactly like a map:
user> (:name me)
"abb"
user> (:phone you)
"9090909090"
OK, you should use defn instead of def.
But what information really varies here? The number and order of the map keys, in this case [:name :phone :email].
A generic function that will build - from a key sequence - a function that will build the map from the value sequence is
(defn map-builder [keys]
(fn [& values] (zipmap keys values)))
You can then define
(def user-map (map-builder [:name :phone :email]))
... which works as required:
(user-map "me" "123456789" "me#here.com")
;{:email "me#here.com", :phone "123456789", :name "me"}
If performance is pressing, by all means use records instead of maps, as #AbbéRésina suggests.
Putting it simply...
The error you are receiving is due to essentially passing a vector as a second value to def. If you want to use def in this instance go with...
(def user-map-other
(fn [new-name new-phone new-email]
{:name new-name
:phone new-phone
:email new-email}))
Here we are using an anonymous function that accepts your three parameters. Here is a link to learn more about them => http://clojuredocs.org/clojure.core/fn
To gain access to the values contained in your function we can use get in this instance.
(get (user-map-other "Ben" "999" "bbb#mail.com") :phone) => "999"
(get (user-map-other "Ben" "999" "bbb#mail.com") :name) => "Ben"
(get (user-map-other "Ben" "999" "bbb#mail.com") :email) => "bbb#mail.com"
A more concise method would be to use defn as represented below.
(defn user-map [new-name new-phone new-email]
{:name new-name
:phone new-phone
:email new-email})
(get (user-map "Ben" "999" "bbb#mail.com") :phone) => "999"
(get (user-map "Ben" "999" "bbb#mail.com") :name) => "Ben"
(get (user-map "Ben" "999" "bbb#mail.com") :email) => "bbb#mail.com"

Array-map example in clojure

I am learning clojure and trying to implement a problem. I am storing maps in a vector. Each map contains an id. For example [{:id 1 :name "abc"} {:id 2 :name "xyz"}]. The map also contains some more fields.
I read somewhere that, instead of using a vector to store the maps, I could use an array-map and do away with my id and store it something like {1 {:name "abc"}, 2 {:name "xyz"}}.
I tried going through the clojure docs but didn't find a good example to achieve this. Can some please help me out and give me a good example?
You can use assoc to add values to a map. assoc takes 3 args. The first arg is the map that you want to add to, 2nd arg is a key, and the third is a value. The function returns the old map with the key-value pair added.
Example:
(assoc {} 1 {:name "abc"})
returns
{1 {:name "abc"}}
Your idea is to lift the :id entry of each record-map into an index, while removing it from the map. You end up with a map of :id-less records instead of a vector of full records.
The following function lifts the key fk out of the collection of maps ms:
(defn key-by [fk ms]
(into {} (map (fn [m] [(get m fk) (dissoc m fk)]) ms)))
For example,
(key-by :id [{:id 1 :name "abc"} {:id 2 :name "xyz"}])
;{1 {:name "abc"}, 2 {:name "xyz"}}
Note:
Every record should have an :id.
Your :ids had better be distinct, or you'll lose records.
Don't depend on array-map: it's an implementation detail. A
modified version might well be a hash-map.
If you need your map sorted by key, use a sorted-map.
If you need to keep your records in insertion order, think again.