JSON Gson Serializer and Deserializer

Alternative option to work with JSON in Java is to use Google Gson open source library.

To use IC4J Candid Gson library add dependencies to Gradle or Maven project.

implementation 'org.ic4j:ic4j-candid-gson:0.6.19.5'

Use GsonSerializer and GsonDeserializer to serialize and deserialize Java Gson JSON object to and from the Candid payload of type RECORD.

A fully functional example using GsonSerializer and GsonDeserializer can be found here.

This example uses Motoko to call the canister main.mo. The canister uses 2 complex types, LoanApplication and LoanOffer.

main.mo
// Loan Application
public type LoanApplication = {
    id: Nat;
    firstname: Text;
    lastname: Text;
    zipcode: Text;
    ssn: Text;
    amount: Float;
    term: Nat16;
    created: Int;
 };

// Loan Offer
public type LoanOffer = {
    providerid: Principal;
    providername: Text;
    applicationid: Nat;
    apr: Float;
    created: Int;
};

The canister has 2 methods: apply and getOffers.

main.mo
public shared (msg) func apply(input : LoanApplication) : async LoanOffer { 
       counter += 1;
        Debug.print("Loan Application for user " #Principal.toText(msg.caller));
        
        let offer  : LoanOffer = {
            providerid = Principal.fromActor(this);
            providername = "Loan Provider";
            applicationid = counter;
            apr = 3.14;
            created = Time.now();
        };

        var userOffers :  ?Offers<LoanOffer> = offers.get(msg.caller);

        switch userOffers {
            case (null) { var userOffer : Offers<LoanOffer> = Buffer.Buffer(0); userOffer.add(offer);  offers.put(msg.caller, userOffer)};
            case (?userOffer) { userOffer.add(offer); };
        };
        return offer;
};

public query (msg) func getOffers() : async [LoanOffer] {
        var userOffers :  ?Offers<LoanOffer> = offers.get(msg.caller);

        switch userOffers {
            case (null) { return [] };
            case (?userOffer) { return userOffer.toArray() };
        };
};

The example uses the file with JSON LoanApplication payload as an input.

LoanApplication.json
{
"id" : 0,
"firstname" : "John",
"lastname" : "Doe",
"zipcode" : "99999",
"ssn" : "111-11-1111",
"amount" : 2000.00,
"term" : 24,
"created" : 0
}

To be able to properly map JSON names and values to Candid name types declare the IDLType structure as follows:

Main.java
Map<Label,IDLType> applicationRecord = new TreeMap<Label,IDLType>();
applicationRecord.put(Label.createNamedLabel("id"), IDLType.createType(Type.NAT));
applicationRecord.put(Label.createNamedLabel("firstname"), IDLType.createType(Type.TEXT));
applicationRecord.put(Label.createNamedLabel("lastname"), IDLType.createType(Type.TEXT));
applicationRecord.put(Label.createNamedLabel("zipcode"), IDLType.createType(Type.TEXT));
applicationRecord.put(Label.createNamedLabel("ssn"), IDLType.createType(Type.TEXT));		
applicationRecord.put(Label.createNamedLabel("amount"), IDLType.createType(Type.FLOAT64));
applicationRecord.put(Label.createNamedLabel("term"), IDLType.createType(Type.NAT16));
applicationRecord.put(Label.createNamedLabel("created"), IDLType.createType(Type.INT));
		
IDLType idlType =  IDLType.createType(Type.RECORD, applicationRecord);
		
Map<Label,IDLType> offerRecord = new TreeMap<Label,IDLType>();
offerRecord.put(Label.createNamedLabel("providerid"), IDLType.createType(Type.PRINCIPAL));
offerRecord.put(Label.createNamedLabel("providername"), IDLType.createType(Type.TEXT));
offerRecord.put(Label.createNamedLabel("applicationid"), IDLType.createType(Type.NAT));	
offerRecord.put(Label.createNamedLabel("apr"), IDLType.createType(Type.FLOAT64));		
offerRecord.put(Label.createNamedLabel("created"), IDLType.createType(Type.INT));
		
IDLType resultIdlType =  IDLType.createType(Type.RECORD, offerRecord);	

Next, create IDLValue using the GsonSerializer create method.

The Serializer input is the variable type JsonElement.

Main.java
JsonElement jsonValue = readNode(LOAN_APPLICATION_FILE)		
IDLValue idlValue = IDLValue.create(jsonValue, GsonSerializer.create(idlType));
		
List<IDLValue> idlArgs = new ArrayList<IDLValue>();		
idlArgs.add(idlValue);
byte[] buf = IDLArgs.create(idlArgs).toBytes();

Use UpdateBuilder, QueryBuilder or Raw Methods to call the Canister and deserialize output to JsonElement.

Main.java
CompletableFuture<byte[]> response = UpdateBuilder.create(agent,Principal.fromString(icCanister), "apply").arg(buf).callAndWait(Waiter.create(60, 5));
		
byte[] output = response.get();
JsonElement jsonResult = IDLArgs.fromBytes(output).getArgs().get(0)
		.getValue(GsonDeserializer.create(resultIdlType), JsonElement.class);

Last updated