Motivação

Não havia algo concretizado durante a escrita deste artigo. A princípio apenas comecei a desenvolver uma ferramenta em C que utilizava JSON como arquivo de configuração. Foi divertido aprender a criar o parser, porém, mesmo decidido que vou usar outra linguagem para isso, decidi postar.

Objetivo

Fazer com que a ferramenta seja dinâmica ao ponto de usar um arquivo JSON para manipular os dados, em principal, adicionar e extrair valores através das chaves. Este artigo é produzido conforme avanço no “projeto” (por ora, projeto bem entre aspas mesmo).

Instalando dependência

Antes de qualquer coisa, certifique-se que o pacote “libjson-c-dev” (deb) ou “json-c-devel” (rpm) esteja instalado. Em alternativa, a instalação direto do código fonte é simples. Veja:

$ git clone https://github.com/json-c/json-c
$ mkdir json-c/json-c-build
$ cd !$
$ cmake ../../json-c

Observação: o truque “cd !$” vai depender do terminal em uso; por exemplo, não funciona no Fish Shell e no SH, mas, funciona no Bash.

Antes de fazer a instalação, recomendo que realize alguns testes (com valgrind):

$ make test
# somente execute se tiver valgrind instalado, claro.
$ make USE_VALGRIND=0 test

Se deu tudo certo até aqui, prossiga a instalação:

$ sudo make install

Construindo o JSON Parsing

Com a libjson-c instalada, vou seguir com a criação de um JSON parsing simples para facilitar a implementação dos próximos recursos.

#include <stdio.h>
#include <json-c/json.h>

int main(void) {
    struct json_object *pj; /* parsed json */
    struct json_object *bk; /* band key */
    struct json_object *sk; /* song key */

    pj = json_tokener_parse("{\"band\":\"Slayer\",\"song\":\"Piano Wire\"}");
    json_object_object_get_ex(pj, "band", &bk);
    json_object_object_get_ex(pj, "song", &sk);

    printf("%s - %s", json_object_get_string(bk), json_object_get_string(sk));

    return EXIT_SUCCESS;
}

Basta compilar, e não esquecer de mencionar a flag da biblioteca:

$ gcc -Wall main.c -o main -ljson-c
$ ./main
Slayer - Piano Wire

Maneiro! Apesar da simplicidade.

A função json_tokener_parse() retorna um objeto JSON válido, o que a torna bastante útil quando já existe os dados em JSON, pois também é possível criar do zero através da libjson-c usando a função json_object_new_object(), pelo nome já fica claro que ela cria um novo objeto. Neste caso, é possível acessar todo o JSON fazendo a conversão para string usando json_object_to_json_string(struct json *obj). Exemplo:

parsed_json = json_tokener(JSON_DATA);
printf("%s", json_object_to_json_string(parsed_json));

Considerando que o parsed_json é um tipo “struct json *obj”, nele fica fácil de trabalhar para manipular da maneira que quiser, inclusive adicionar outro objeto com

json_object_object_add(
    struct json_object *obj,
    const char *key,
    struct json_object *val)

Por exemplo:

struct json_object *mj = json_object_new_string("Michael Joseph Jackson");
parsed_json = json_tokener_parse(value);
json_object_object_add(parsed_json, "Pop", mj);
printf("%s\n", json_object_to_json_string(parsed_json));

Próxima implementação é usar esse parsing de uma forma mais flexível; por exemplo, imagine utilizar um arquivo JSON como configuração seja lá qual a forma desejada. Para isso, é necessário entender o conceito de estrutura de dados em C porque agora os dados serão acessados através de um objeto numa estrutura.

//---  config.h  ---
#ifndef __CONFIG_H__
#define __CONFIG_H__

typedef struct __attribute__((packed)) {
    const char *band;
    const char *song;
} config_t;

void config_init(config_t *key, char *value);

#endif
//--- /config.h  ---

A função json_object_get_string retorna um const char *, em vista disso, os membros da estrutura deve ser const char *. Caso contrário, na ausência do const, será necessário atribuir um casting char * em json_object_get_string. Inclusive, converter um ponteiro que aponta para qualquer tipo de dado para um ponteiro que aponta para um tipo de dado constante é naturalmente aceitável, mas o modo vice-versa não. Por isso que não é recomendado fazer algo do tipo:

(char *)json_object_get_string(key);

Vamos ao código:

// ---  config.c  ---
#include <stdio.h>
#include <config.h>
#include <json-c/json.h>

void config_init(config *key, char *value) {
    struct json_object *pj; /* parsed json */
    struct json_object *bk; /* band key */
    struct json_object *sk; /* song key */

    if (!value) {
        fputs("value not setted", stderr);
        exit(EXIT_FAILURE);
    }

    pj = json_tokener_parse(value);
    json_object_object_get_ex(pj, "band", &bk);
    json_object_object_get_ex(pj, "song", &sk);

    key->band = json_object_get_string(bk);
    key->song = json_object_get_string(sk);
}
// --- /config.c  ---
//---  main.c  ---
#include <stdio.h>
#include <json-c/json.h>
#include <config.h>

int main(void) {
    config_t conf;
    char *json = "{\"band\":\"Slayer\",\"song\":\"Piano Wire\"}";
    config_init(&conf, json);

    printf("%s - %s\n", conf.band, conf.song);

    return EXIT_SUCCESS;
}
//--- /main.c  ---

Com as mudanças realizadas, compile o código acima semelhante ao anterior, apenas adicione o -I. - note o “.” (ponto) logo depois de “I”, especificando para que o diretório atual seja considerado para a inclusão dos cabeçalhos usados os sinais menor-que (<) e maior-que (>) ao invés de aspas duplas.

$ gcc -Wall -I. main.c -o main -ljson-c
$ ./main
Slayer - Piano Wire

Considerações Finais

Como dito no início, este artigo foi escrito enquanto desenvolvia uma ferramenta que utilizava um arquivo JSON como configuração. Apesar de ter adquirido algumas experiências com a biblioteca libjson-c, horas depois consideirei utilizar outro método de configuração após entender que usar JSON como configuração é um improviso que acabou sendo romantizado e não comporta configurações complexas. Isto se dá ao fato de inúmeras limitações inerentes, como a chave com aspa dupla não faz sentido já que é uma CHAVE, não VALOR; o uso de comentário no JSON é outro paliativo, não é todo lugar que pode ser usado “//” nas chaves.

{
    "//": "Thrash Metal",
    "band": "Slayer",
    "//": "Last Album",
    "album": "Repentless"
}

Convenhamos também que isso não é algo legível e agradável para se trabalhar; o JSON também não suporta string em multi-linhas, por isso que é necessário o escape da Newline (“\n”) ou CRLF (“\r\n” - Carriage Return & Line Feed) dentre diversos outros problemas que pode ser enfrentado quando usado como arquivo de configuração.

Contudo, quis elucidar o quão fácil é manipular JSON em C começando com algo tão simples conforme demonstrado apenas com o “main.c” e evoluindo com a estrutura etc. Existem diversos projetos que suportam JSON como API ou como configuração, cabe você criar algo interessante agora. :-)