Ballerina "type 'json' does not support field access for assignment"

695 views Asked by At

I'm using the Ballerina language to create a RESTful web service for order management in an online retail store as instructed in here. In the resource function which handles HTTP PUT requests, there is a piece of code which will retrieve the relevant order from a json map object(currently using to store orders, hasn't connected a database yet.) and update some of its fields with the ones from the json object that was received with the HTTP PUT request.

I've declared the json map object, ordersMap.

map<json> ordersMap = {};

The resource function that handles PUT requests have a chunk of code that goes like this:

// Get the json obejct from the payload
var updatedOrder = <@untained> req.getJsonPayload();

// Get the existing order that is required by the request from the json map object.
json existingOrder = ordersMap[orderId];

// Updating the necessaries and saving it in the map object.
existingOrder.Order.Name = updatedOrder.Order.Name;
existingOrder.Order.Description = updatedOrder.Order.Description;
ordersMap[orderId] = existingOrder;

When I try to compile the program, the compiler throws following error.

invalid operation: type 'json' does not support field access for assignment

I've looked for solutions online and couldn't find anything. I'm using Ballerina 1.2.4

2

There are 2 answers

1
Dhananjaya On BEST ANSWER

This is because when you use json datatype the compiler cannot guarantee the fields you are accessing exists and (in case it does not exists it can't calculate the types to insert relevant entries).

When you want to operate on a piece of json data, and you know the shape of this json structure it's a good idea to create relevant types and operating on those and if you want a json back, then you can convert it back to json. Unfortunately there is no straightforward way to convert to (and back) from json in Ballerina 1.2.x, if you can switch to Ballerina Swanlake track you can use something like following.

import ballerina/io;

type Info record {
    User user;
};

type User record {
    string name;
    string bal_version;
};


public function main() returns error? {
    json j = { user: { name: "Hiran",  bal_version: "1.2.4"}};
    json k = { user: { name: "Hiran-New",  bal_version: "1.2.4"}};
    io:println(j);
    // Convert json to expected datatype.
    Info|error info = j.fromJsonWithType(Info);
    if (info is Info) {
        Info kInfo = check k.fromJsonWithType(Info);
        info.user.name = kInfo.user.name;

        // convert back to json
        json newJ = info.toJson();
        io:println(newJ);
    } else {
        // Expected json schema did not match.
        // Handle the error here.
    }
}

In ballerina 1.2.x

import ballerina/io;

public function main() returns error? {
    json j = { user: { name: "Hiran",  bal_version: "1.2.4"}};
    json k = { user: { name: "Hiran-New",  bal_version: "1.2.4"}};
    io:println(j);

    map<json> mj = <map<json>> j;
    map<json> user = <map<json>> mj.get("user");

    user["name"] = <string> k.user.name;
    // note, we update the original json value.
    io:println(j);
}

One big issue with the second approach is if the structure of incoming json value was different from what we expected, it will result in a panic due to typecast error.

0
ThisaruG On

If you're using Ballerina 1.2.4, I highly advise you to update to the newest Ballerina 1.2.x release (which is 1.2.8 currently, and sooner 1.2.9). They are backward compatible. So you shouldn't have any problem with updating.

Coming to your question, you can use the . operator to access the fields in a JSON. Look at the following example.

If you know the exact shape of the structure, you can use a defined record type. Then you can construct the record using the constructFrom() function. Otherwise, you can also access fields using the . operator.

In both, these cases make sure to handle the error accordingly. I have used checkpanic just to show the functionality, but you can handle it in a better way.

import ballerina/io;

type Address record {
    string no;
    string street;
    string city;
};

type Person record {
    string name;
    Address address;
};

public function main() {
    json j = getJson();
    Person p = checkpanic Person.constructFrom(j);
    io:print("Person Record: ");
    io:println(p);

    json address = checkpanic j.address;
    io:print("Address field from JSON: ");
    io:println(address);
}

function getJson() returns json {
    json j = {
        name: "Sherlock",
        address: {
            no: "221/B",
            street: "Baker Street",
            city: "London"
        }
    };
    return j;
}

This will print the following:

Person Record: name=Sherlock address=no=221/B street=Baker Street city=London
Address field from JSON: no=221/B street=Baker Street city=London

Tried this on Ballerina 1.2.8.

You can look into more examples here.