4

I have a Json file named name.json An example of such a Json file can be seen bellow

{
  "set": 5,
  "low": 0,
  "draw_set": "0.1 up to 0.3",
  "Wupet": "Hold",
  "": null
}

But it can be also the case that another time the Json file has another structure.

{
  "set": 5,
  "low": 0,
  "draw_set": "0.1 up to 0.3",
  "W_set": "Ramp 1.5 ∞C/min",
  "Wset": 0,
  "Wupet": "Hold",
  "": null
}

I want to convert the input of this Json file (the attributes and their types can be different in each file) to a C struct where the structs automatically detects which attributes (and their types) there are in the Json file. The attribute "" in "": null (can be given a random attribute name)

Then I want to automatically assign the values of Json value to the struct of object Book1.

Plan of my code

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

struct Books {
   float  set[50];
   int  low[50];
   char  draw_set[100];
   char  Wupet[100];
};

int main( ) {
    /* Read name.Json file */


   struct Books Book1;        /* Declare Book1 of type Book */
   /* Convert content Json to Struct Book1 with it's actual values*/

   return 0;
}

Edit: I don't want to ignore "Wset" and "W_set" keys if they exist in the Json file. The keys of the Json files are changing everything time (there are many unknown attributes that can occur in the future). The number of keys is not guarantee. The 'split over multiple lines' property is guaranteed? The null key name should be given a random name (multiple keys can occurs with like the null key name) The attribute types of the struct should be automatically detected and created. The Json file contains a single record (object)

I want to deal with these structures at runtime.

jennifer ruurs
  • 302
  • 1
  • 4
  • 12
  • 3
    OK; that's a good start. Now, what are you going to do next? (We will help you resolve problems that you identify; we won't simply do your homework or write the code for you.) . Do you plan to ignore the `"Wset"` and `"W_set"` keys? Is the order of the keys guaranteed? Is the number of keys guaranteed? Is the 'split over multiple lines' property guaranteed? What needs to be done with the field with the null key name? – Jonathan Leffler Sep 11 '19 at 17:05
  • 2
    You can either create your own function to read the json file (which is a bit complex) or use any of the existing C libraries to read the file. I would recommend going for the second option. – alrevuelta Sep 11 '19 at 17:08
  • 1
    You can find a list of about 18 C libraries for parsing JSON at the bottom of the home page of [https://json.org/](https://json.org/) – Jonathan Leffler Sep 11 '19 at 17:10
  • Does the JSON file contain only a single record (object), or can it contain many similar records? Why are you treating `"set"` as an array of 50 `float`? Is the `float` a typo for `char`, or do you expect `"set": [ 1.2, 2.1, 3.2, 2.3 ],` as the entry in the JSON, or what? Similarly with `low`. – Jonathan Leffler Sep 11 '19 at 17:12
  • @JonathanLeffler Of course. I don't want to ignore "Wset" and "W_set" keys if they exist in the Json file. The keys of the Json files are changing everything time (there are many unknown attributes that can occur in the future). The number of keys is not guarantee. The 'split over multiple lines' property is guaranteed? The null key name should be given a random name (multiple keys can occurs with like the null key name) The attribute types of the struct should be automatically detected and created. The Json file contains a single record (object) – jennifer ruurs Sep 11 '19 at 17:15
  • 2
    If the structure of the JSON can vary, you need a more dynamic C structure, you can't just hard-code specific member names. The JSON libraries mentioned above probably have something you can use instead of defining your own struct. – Barmar Sep 11 '19 at 17:18
  • 1
    You structure has no way of storing keys like `"Wset"`, so it isn't adequate to represent what can be in the data file. We can't tell that such keys are crucial to you; your data structure hints that they're not important. This, though, is why you need to use a library rather than roll your own (unless you've got a lot of time on your hands). I'm rolling my own library; I've been at it, on and (mostly) off, for a year or two. It's entertaining. – Jonathan Leffler Sep 11 '19 at 17:18
  • 1
    What I mean by the 'split over multiple line' property is that your sample data shows the JSON object with each key and value on its own line. JSON doesn't require that; it would be happy with a single line of data for the whole file, with no spaces outside of strings. If you have a 1 GiB file with zero or one newlines in it, JSON won't care (it's probably valid JSON), but the parsing techniques to deal with that are probably different from those you can employ if you know that the data for each element of the object will be on a separate line. – Jonathan Leffler Sep 11 '19 at 17:30
  • @JonathanLeffler The JSON object has each key and value on its own line indeed. The Json file is automatically generated. – jennifer ruurs Sep 11 '19 at 17:39
  • Are you looking to deal with these structures at runtime, or parse a series of JSON files to produce static C structs, generating the C at compile time? It sounds like the former, and if that is the case, I'd recommend a library such as [jansson](https://jansson.readthedocs.io/en/latest/tutorial.html) – Attie Sep 11 '19 at 17:46
  • I don't understand your example - why would you want 50 elements in the `float set` field per item? – Attie Sep 11 '19 at 17:47
  • @Attie I want to deal with these structures at runtime indeed – jennifer ruurs Sep 11 '19 at 17:48
  • @Attie Ideally i want to set 50 elements in the float set field per item to be as small as possible (but if that is not possible i can be also set as large as required) – jennifer ruurs Sep 11 '19 at 17:50

1 Answers1

7

The attribute types of the struct should be automatically detected and created.

You can't do it without some wacky macros. The fields of a structs in C are fixed at compile time.

Automatically populating your struct would require being able to figure out the fields and types of your struct: reflection. C does not have reflection. You could write up some clever macros, but it's probably not worth it for something this simple.

Automatically adding fields to your struct based on the JSON is also impossible without some wacky macros.

Instead, we'll parse the JSON into a flexible data structure and you can do whatever you like with that. If you want to put them all into a struct, that struct would have to contain all possible fields. There are techniques to write extend structs so you can have a basic Book and then additional extended structs for different types of books.

JSON-Glib provides both. It parses the JSON into JsonNodes which can be JsonArrays or JsonObjects. Then you can do whatever you want with them.


Before we do that, the struct needs some work.

struct Books {
    gint64      set;
    gint64      low;
    const char  *draw_set;
    const char  *wupet;
};

set is not a float, but an integer.

We're using the GLib types to match what the GLib library returns and to avoid overflow with large numbers.

Fixed sized buffers should be avoided when reading input, they can easily overflow. Instead JSON-Glib can allocate the correct amount of memory. We'll duplicate that and store a pointer to it.


Here's an example of parsing your JSON file using JSON-Glib and manually populating your struct.

#include <stdlib.h>
#include <stdio.h>
#include <glib-object.h>
#include <json-glib/json-glib.h>

struct Books {
    gint64      set;
    gint64      low;
    char        *draw_set;
    char        *wupet;
};

int main(int argc, char** argv) {
    GError *error = NULL;

    if (argc < 2) {
        printf("Usage: test <filename.json>\n");
        return EXIT_FAILURE;
    }

    // Parse the JSON from the file
    JsonParser *parser = json_parser_new();
    json_parser_load_from_file(parser, argv[1], &error);
    if(error) {
        printf("Unable to parse `%s': %s\n", argv[1], error->message);
        g_error_free(error);
        g_object_unref(parser);
        return EXIT_FAILURE;
    }

    // Get the root
    JsonNode *root = json_parser_get_root(parser);

    // Turn the root into a JSON object
    JsonObject *stuff = json_node_get_object(root);

    // Get each object member and assign it to the struct.
    struct Books book = {
        .set = json_object_get_int_member(stuff, "set"),
        .low = json_object_get_int_member(stuff, "low"),
        // Duplicate the strings to avoid pointing to memory inside the parser.
        .draw_set = g_strdup(json_object_get_string_member(stuff, "draw_set")),
        .wupet = g_strdup(json_object_get_string_member(stuff, "Wupet"))
    };

    printf(
        "set = %ld, low = %ld, draw_set = '%s', wupet = '%s'\n",
        book.set, book.low, book.draw_set, book.wupet
    );

    // We're finished working with the parser. Deallocate the
    // parser and all the memory it has allocated, including
    // the nodes.
    g_object_unref(parser);

    return EXIT_SUCCESS;
}
Schwern
  • 153,029
  • 25
  • 195
  • 336
  • 3
    @ryyker: https://stackoverflow.com/questions/47202557/what-is-a-designated-initializer-in-c – Ilmari Karonen Sep 11 '19 at 19:32
  • @Schwern I receive the error: passing argument 1 of ‘json_object_unref’ from incompatible pointer type [-Wincompatible-pointer-types] json_object_unref(root); In file included from /usr/include/json-glib-1.0/json-glib/json-glib.h:29:0, from test.c:4: /usr/include/json-glib-1.0/json-glib/json-types.h:314:23: note: expected ‘JsonObject * {aka struct _JsonObject *}’ but argument is of type ‘JsonNode * {aka struct _JsonNode *}’ void json_object_unref (JsonObject *object); ld:demo.json: file format not recognized; treating as linker script – jennifer ruurs Sep 20 '19 at 11:51
  • 1
    @jenniferruurs I goofed twice. The first goof is `root` is a `JsonNode` so it should have been `json_node_unref`, don't know why `json_object_unref` worked but it doesn't anymore. Might be a newer version. The second goof is deallocating the nodes is unnecessary, deallocating the parser also deallocates all the nodes its made. I've updated the answer. – Schwern Sep 20 '19 at 20:54
  • @Schwern Thank you when I enter 'gcc -O2 $(pkg-config --cflags glib-2.0 json-glib-1.0) -o test test.c $(pkg-config --libs glib-2.0 json-glib-1.0) demo.json' /usr/bin/ld:demo.json: file format not recognized; treating as linker script /usr/bin/ld:demo.json:1: syntax error collect2: error: ld returned 1 exit status And another question: How is it possible to hardcode the location of the file instead of telling the compiler where the file is located? – jennifer ruurs Sep 21 '19 at 06:16
  • @Schwern the actual error that I receive is: Segmentation fault (core dumped) – jennifer ruurs Sep 21 '19 at 16:22
  • 1
    @jenniferruurs It looks like you're trying to compile the JSON file. Instead, compile the program and give it the JSON file. `gcc -O2 $(pkg-config --cflags glib-2.0 json-glib-1.0) -o test test.c $(pkg-config --libs glib-2.0 json-glib-1.0)` and then `./test demo.json`. [Passing in arguments is basic C](https://www.tutorialspoint.com/cprogramming/c_command_line_arguments.htm). Unless you're doing this to learn C, I would suggest using an easier language like Ruby or Python for your JSON handling. – Schwern Sep 21 '19 at 17:19
  • @Schwern Indeed did tried `gcc -O2 $(pkg-config --cflags glib-2.0 json-glib-1.0) -o test test.c $(pkg-config --libs glib-2.0 json-glib-1.0)` and then `./test demo.json`, the error that I then received is "Segmentation fault (core dumped)" _(Segmentation fault is a specific kind of error caused by accessing memory that “does not belong to you)_ – jennifer ruurs Sep 21 '19 at 19:39
  • @jenniferruurs I tracked it down, you can do it too by adding `puts` statements in the code to narrow down where it's segfaulting. Or use a tool like `valgrind`. I could only get it to happen when compiling your way. The problem appears to be `if( error )` because `error` is declared but not initialized so `if( error )` is trying to read some garbage. The code has been updated. PS You'll want to compile with some warnings on. `-Wall -Wshadow -Wwrite-strings -Wextra -Wconversion -std=c11 -pedantic` for example. – Schwern Sep 21 '19 at 20:33
  • @Schwern The error that I receive now is `implicit declaration of function ‘strdup’; did you mean ‘strcmp’? [-Wimplicit-function-declaration] .draw_set = strdup(json_object_get_string_member(stuff, "draw_set")), strcmp initialization makes pointer from integer without a cast [-Wint-conversion] (near initialization for ‘book.draw_set’) initialization makes pointer from integer without a cast [-Wint-conversion] .wupet = strdup(json_object_get_string_member(stuff, "Wupet")) ^~~~~~ (near initialization for ‘book.wupet’)` – jennifer ruurs Sep 22 '19 at 05:57
  • @Schwern how can I prevent it to read gabage and what is going wrong, because I don't want the code to end up in the if statement at all (meaning that I can not read the json file at all) (Thank you by the way for providing me tips on how to detect errors by the way I am new in C) – jennifer ruurs Sep 22 '19 at 06:02
  • 1
    @jenniferruurs [`strdup` is a POSIX function](https://pubs.opengroup.org/onlinepubs/9699919799/functions/strdup.html), not strictly in the standard, but very common. [It's easy enough to write your own](https://stackoverflow.com/questions/252782/strdup-what-does-it-do-in-c#252802). Since we're using GLib anyway we can use their [`g_strdup`](https://developer.gnome.org/glib/stable/glib-String-Utility-Functions.html#g-strdup) instead. As for the error handling, you don't have a choice. If you're given invalid JSON which cannot be parsed that has to be handled somehow. And you're welcome. – Schwern Sep 22 '19 at 19:50