I've got a bash script which outputs strings in the format Hostname IP MacAddr and is read by my script in written in C. I am trying to split these 3 up into an array, and make it so that I'm able to store them into a Json-c object to produce something that looks like {Clients: [{Hostname: Value, IP: Value, MacAddr: Value}]}.

Currently my program is able to read each string line by line and store it into an array (The array is initialised wrong just for testing purposes, I'm going to change that):

int get_list_of_connected_clients(json_object *input, json_object *output) {
    FILE *fp;
    char path[1035];
    int i = 0;
    char a[2][100];
    fp = popen("./Sample_Bash_Script_Test.sh", "r");
    if (fp == NULL) {
        printf("Failed To Run Script \n");
        exit(1);
    }
    while (fgets(path, sizeof(path) - 1, fp) != NULL) {
        stpcpy(a[i], path);
        i++;
    }
    pclose(fp);
}

Is anyone able to help me with this and steer me in the right direction? String manipulation in C is relatively new to me and I'm still trying to get my head round it!

Edit:

My function now looks like this:

int get_list_of_connected_clients(json_object* input, json_object* output){
    FILE *filepath;
    char output_line[1035];
    int index=0;
    char arr_clients[30][100];

    filepath = popen("./Sample_Bash_Script_Test.sh", "r");
    if (filepath == NULL){
        printf("Failed To Run Script \n");
        exit(1);
    }
    while (fgets(output_line, sizeof(output_line)-1, filepath) != NULL){
        stpcpy(arr_clients[index], output_line);
        index++;
    }
    pclose(filepath);

    /*Creating a json object*/
    json_object * jobj = json_object_new_object();

    /*Creating a json array*/
    json_object *jarray = json_object_new_array();

    json_object *jstring1[2][2];

    for (int y=0; y < 2; y++) {
        int x = 0;
        char *p = strtok(arr_clients[y], " ");
        char *array[2][3];

        while (p != NULL) {
            array[y][x++] = p;
            p = strtok(NULL, " ");
        }

        for (x = 0; x < 3; ++x) {
            jstring1[y][x] = json_object_new_string(array[y][x]);
            /*Adding the above created json strings to the array*/
            json_object_array_add(jarray,jstring1[y][x]);

        }
    }
    /*Form the json object*/
    json_object_object_add(jobj,"Clients", jarray);

    /*Now printing the json object*/
    printf ("%s",json_object_to_json_string(jobj));

    return 0;
    }

The output looks like this when I run it: { "Clients": [ "Hostname", "192.168.1.18", "XX:XX:XX:XX", "Hostname", "192.168.1.13", "XX:XX:XX:XX" ] }

Does anyone have any ideas what I'm doing wrong to stop it from breaking the list after every client? i.e

{
  "Clients" : [
    {
      "Hostname" : "example.com",
      "IP" : "127.0.0.1",
      "MacAddr" : "mactonight"
    },
    {
      "Hostname" : "foo.biz",
      "IP" : "0.0.0.0",
      "MacAddr" : "12:34:56:78"
    }
  ]
}

1 Answers

2
Schwern On Best Solutions

Rather than trying to build JSON as strings, use a library such as json-glib to build the JSON for you. This is more flexible and will handle all sorts of edge cases. It provides JsonBuilder to build up JSON structures.

We start by taking a file pointer, something else should open the file. Then we start a JsonBuilder and begin building the JSON structure declaring the { "Clients" object and starting the array.

JsonNode *bash_connected_clients_to_json(FILE *fp) {
    JsonBuilder *builder = json_builder_new();

    // { "Clients": [ ...
    json_builder_begin_object(builder);
    json_builder_set_member_name(builder, "Clients");
    json_builder_begin_array(builder);

Now we read each line and send it and the builder into a function to process the line and add it to the open array.

    char line[1024];
    while (fgets(line, sizeof(line), fp) != NULL) {
        bash_connected_clients_line_to_json(line, builder);
    }

Finally close the array and the object and return the JsonNode we've just built.

    // ... ] }
    json_builder_end_array(builder);
    json_builder_end_object(builder);

    return json_builder_get_root(builder);
}

Then the JsonNode can be printed.

int main() {
    JsonNode *json = bash_connected_clients_to_json(stdin);
    printf("%s", json_to_string(json, TRUE));
}

Processing each line starts by parsing it. This can be done in various was. sscanf works fine.

void bash_connected_clients_line_to_json( const char *line, JsonBuilder *builder ) {    
    char hostname[1024], ip[1024], macaddr[1024];
    if( sscanf(line, "%1023s %1023s %1023s", hostname, ip, macaddr) != 3 ) {
        fprintf(stderr, "Could not parse line: '%s'\n", line);
        return;
    }

Then we add a JSON object to the array we already have open, add each of our elements to the object, and close the object.

    // { "Hostname": "foo", "IP", "bar", "MacAddr", "baz" }
    json_builder_begin_object(builder);

    json_builder_set_member_name(builder, "Hostname");
    json_builder_add_string_value(builder, hostname);

    json_builder_set_member_name(builder, "IP");
    json_builder_add_string_value(builder, ip);

    json_builder_set_member_name(builder, "MacAddr");
    json_builder_add_string_value(builder, macaddr);

    json_builder_end_object(builder);
}

$ cat > test.txt
example.com 127.0.0.1 mactonight
foo.biz 0.0.0.0 12:34:56:78

$ ./test < test.txt
{
  "Clients" : [
    {
      "Hostname" : "example.com",
      "IP" : "127.0.0.1",
      "MacAddr" : "mactonight"
    },
    {
      "Hostname" : "foo.biz",
      "IP" : "0.0.0.0",
      "MacAddr" : "12:34:56:78"
    }
  ]
}

Or you can do it in a few lines of Ruby.

require 'json'

clients = []

STDIN.each do |line|
  fields = line.split(/\s+/)
  clients << {
    Hostname: fields[0],
    IP: fields[1],
    MacAddr: fields[2]
  }
end

connections = {}
connections[:Clients] = clients
puts connections.to_json

For json-c it's basically the same. The major difference is instead of bash_connected_clients_line_to_json adding a JSON object to a builder, it returns a JSON object.

json_object* bash_connected_clients_line_to_json( const char *line ) {    
    char hostname[1024], ip[1024], macaddr[1024];
    if( sscanf(line, "%1023s %1023s %1023s", hostname, ip, macaddr) != 3 ) {
        fprintf(stderr, "Could not parse line: '%s'\n", line);
        return NULL;
    }

    json_object *json = json_object_new_object();

    json_object_object_add(json, "Hostname", json_object_new_string(hostname));
    json_object_object_add(json, "IP", json_object_new_string(ip));
    json_object_object_add(json, "MacAddr", json_object_new_string(macaddr));

    return json;
}

This is then added to a JSON array of clients.

json_object *bash_connected_clients_to_json(FILE *fp) {
    json_object *clients = json_object_new_array();

    char line[1024];
    while (fgets(line, sizeof(line), fp) != NULL) {
        json_object_array_add(
            clients,
            bash_connected_clients_line_to_json(line)
        );
    }

    json_object *json = json_object_new_object();
    json_object_object_add(json, "Clients", clients);

    return json;
}