Reading data from JSON file using DAML Script

Hello!

This is my first time posting here. I am new to DAML. I am trying to find how to extract data from JSON file to the ledger using DAML script. I am currently working on putting records of students into the ledger, using smart contract. Any advise/guidance is really appreciated.

The daml script command takes a --input-file flag that must be a JSON value with a structure that matches the type of the first (and only) argument of the script you’re running.

Here is the documentation of daml script for full details.

Does that help?

2 Likes

Thanks very much for your reply. I am still a bit lost on how the data structure that we defined in JSON can directly be applied to the elements within our script. For example, given the following JSON structure:
{
“album”: “The White Stripes”,
“year”: 1999,
“US_peak_chart_post”: “-”
},

How can we extract the data directly to the DAML variables. Any suggestions?

You can write a daml script that returns the data type you’re expecting. Then if you use the --output-file argument of the daml script command, it’ll produce a json file in the format that the daml code is expecting.

And you can use the daml repl, like this:

daml repl /path/to/darfile.dar

daml> import AlbumModule
daml> :json Album with album = "The White Stripes", year = 1999, usPeakChartPost = "-"

You can experiment with the correspondence between Daml data types and their JSON equivalent using the :json directive at the REPL.

For example (look to the following for additional details):

$ daml repl .daml/dist/t-0.0.1.dar --import t
daml> let a = [Album with title = "first title", year = 12, us_peak_chart_post = ""]
daml> :json a
[{"title":"first title","year":12,"us_peak_chart_post":""}]
daml>

Here is a full example of how things fit together, to make things a bit more concrete. Let’s start with daml.yaml (generated by daml new t using the Daml SDK version 2.8.1):

# for config file options, refer to
# https://docs.daml.com/tools/assistant.html#project-config-file-daml-yaml

sdk-version: 2.8.1
name: t
source: daml
init-script: Main:setup
version: 0.0.1
dependencies:
  - daml-prim
  - daml-stdlib
  - daml-script

Next, we have daml/Main.daml (also initially generated by the daml new t command, but heavily modified):

module Main where

import Daml.Script (script, Script)
import DA.List (head)

data Album = Album with
  title : Text
  year: Int
  us_peak_chart_post : Text

setup : [Album] -> Script ()
setup albums = script do
  debug $ "There were " <> show (length albums) <> "albums."
  debug $ "The first one was titled: " <> show (title $ head albums) <> "."

Finally, we have an input file albums.json:

[{"title": "The White Stripes",
  "year": 1999,
  "us_peak_chart_post": "-"}]

Now, with that, we can run daml build to create the dar file:

$ daml build
Running single package build of t as no multi-package.yaml was found.

2024-02-15 18:29:41.10 [INFO]  [build]
Compiling t to a DAR.

2024-02-15 18:29:41.83 [INFO]  [build]
Created .daml/dist/t-0.0.1.dar
$

and, once the dar is created, we can run the above daml repl command (now that .daml/dist/t-0.0.1.dar exists), or we can run the script against our input JSON file. However, we first need a running ledger, so, in another terminal but form the same folder:

$ daml sandbox
Starting Canton sandbox.
Listening at port 6865
Canton sandbox is ready.

and, while that’s left running:

$ daml script --ledger-host localhost --ledger-port 6865 --dar .daml/dist/t-0.0.1.dar --script-name Main:setup --input-file albums.json
Slf4jLogger started
[DA.Internal.Prelude:557]: \"There were 1albums.\"
[DA.Internal.Prelude:557]: \"The first one was titled: \\\"The White Stripes\\\".\"
Running CoordinatedShutdown with reason [ActorSystemTerminateReason]
$

You’ll probably want to be doing other things with the data than print it as debug logs, but hopefully this shows you how to tie the pieces together.

3 Likes

Thank you very much for the example given. It is clear now.

Just another related question. How do include the data that we extracted into a template, and submit it as a transaction using createCmd?

You need to create a data declaration that matches the structure of your JSON file (or format your JSON file to match one of your data declarations). Once you have that declaration, including said data in a template is just adding an attribute of that type to that template.

Here is a more complete example. We use the same daml.yaml as before (daml new t), but our daml/Main.daml is now:

module Main where

import Daml.Script

data Thing = Thing with a: Int, b: Text
  deriving (Show, Eq)

template Asset
  with
    owner : Party
    payload: Thing
  where
    signatory owner

setup : Script ()
setup = script do
  alice <- allocatePartyWithHint "Alice" (PartyIdHint "Alice")
  aliceId <- validateUserId "alice"
  createUser (User aliceId (Some alice)) [CanActAs alice]
  debug $ show alice


add_thing: Thing -> Script ()
add_thing t = script do
  -- Get the alice party back - this only works against the sandbox
  parties <- listKnownParties
  let [alice] = filter (\p -> p.displayName == Some "Alice") parties

  thing_id <- submit alice.party do
    createCmd Asset with
      owner = alice.party
      payload = t
  pure ()

list_things : Script [Thing]
list_things = script do
  -- Get the alice party back - this only works against the sandbox
  parties <- listKnownParties
  let [alice] = filter (\p -> p.displayName == Some "Alice") parties
  
  things <- query @Asset alice.party
  pure $ map (\(_cid, asset) -> asset.payload) things

Note that for a real ledger you’ll need to deal with authentication.

We start the ledger in a separate terminal:

$ daml sandbox
Starting Canton sandbox.
Listening at port 6865
Canton sandbox is ready.

We then need to:

  • Build the DAR file.
  • Upload the DAR file to the ledger. This was not necessary in the previous case, but now we need t because the DAR file contains the definition of the template we want to create contracts for. In a real situation you’ll probably want to have a separate Daml project with just the templates, and have the script project depend on that.
  • Run the setup script to create the alice party.
  • Run the add_thing script any number of times.
  • Run the list_things script.

Here is a sample such interaction:. This uses the special file >(cat) to display the results of the list_things script directly rather than saving it to a file.

$ daml build
Running single package build of t as no multi-package.yaml was found.

2024-02-22 15:31:49.52 [INFO]  [build]
Compiling t to a DAR.

2024-02-22 15:31:50.39 [INFO]  [build]
Created .daml/dist/t-0.0.1.dar
$ daml ledger upload-dar --host localhost --port 6865 .daml/dist/t-0.0.1.dar
Uploading .daml/dist/t-0.0.1.dar to localhost:6865
DAR upload succeeded.
$ daml script --ledger-host localhost --ledger-port 6865 --dar .daml/dist/t-0.0.1.dar --script-name Main:setup
Slf4jLogger started
[DA.Internal.Prelude:557]: \"'Alice::122043af73ecba74362ba1a4295506b1e20ee424ac2e1a3563530cb122cb647f9b8c'\"
Running CoordinatedShutdown with reason [ActorSystemTerminateReason]
$ cat t1.json
{"a": 4, "b": "hello"}
$ cat t2.json
{"a": 8, "b": "bye"}
$ daml script --ledger-host localhost --ledger-port 6865 --dar .daml/dist/t-0.0.1.dar --script-name Main:add_thing --input-file t1.json
Slf4jLogger started
Running CoordinatedShutdown with reason [ActorSystemTerminateReason]
$ daml script --ledger-host localhost --ledger-port 6865 --dar .daml/dist/t-0.0.1.dar --script-name Main:add_thing --input-file t1.json
Slf4jLogger started
Running CoordinatedShutdown with reason [ActorSystemTerminateReason]
$ daml script --ledger-host localhost --ledger-port 6865 --dar .daml/dist/t-0.0.1.dar --script-name Main:add_thing --input-file t2.json
Slf4jLogger started
Running CoordinatedShutdown with reason [ActorSystemTerminateReason]
$ daml script --ledger-host localhost --ledger-port 6865 --dar .daml/dist/t-0.0.1.dar --script-name Main:add_thing --input-file t1.json
Slf4jLogger started
Running CoordinatedShutdown with reason [ActorSystemTerminateReason]
$ daml script --ledger-host localhost --ledger-port 6865 --dar .daml/dist/t-0.0.1.dar --script-name Main:list_things --output-file >(cat)
Slf4jLogger started
[{
  "a": 4,
  "b": "hello"
}, {
  "a": 4,
  "b": "hello"
}, {
  "a": 8,
  "b": "bye"
}, {
  "a": 4,
  "b": "hello"
}]
Running CoordinatedShutdown with reason [ActorSystemTerminateReason]
$ daml script --ledger-host localhost --ledger-port 6865 --dar .daml/dist/t-0.0.1.dar --script-name Main:add_thing --input-file t1.json
Slf4jLogger started
Running CoordinatedShutdown with reason [ActorSystemTerminateReason]
$ daml script --ledger-host localhost --ledger-port 6865 --dar .daml/dist/t-0.0.1.dar --script-name Main:list_things --output-file >(cat)
Slf4jLogger started
[{
  "a": 4,
  "b": "hello"
}, {
  "a": 4,
  "b": "hello"
}, {
  "a": 8,
  "b": "bye"
}, {
  "a": 4,
  "b": "hello"
}, {
  "a": 4,
  "b": "hello"
}]
Running CoordinatedShutdown with reason [ActorSystemTerminateReason]
$
1 Like