Reading and Writing JSON with CMake

While working on a package manager for CMake I came across the problem of finding a suitable format for my package descriptor files. First I thought about and tried using plain old CMake file that simply sets variables and can be included to access its contents. It was very quick and easy to implement. Hierarchical data structures however where hard to implement and a none standard format makes it harder to exchange data with a webservice/other programs. So I opted for using JSON as my import/export format. JSON is alot easier to parse than XML and is still ubiquitous in software developement so I cast aside XML (for now).

You can grab my implementation for reading and writing JSON with CMake at Github. Its part of my object oriented CMake library and uses my implementation for CMake maps and references.

Note: Because CMake only understands strings the deserializer only accepts string , array or object values. Also all keys and values have to be double quoted.

As always: feedback is most welcome if you feel like it you can tell me if you find it usefull, have ideas, or think that I have reinvented the wheel.

Here are some examples for serialization and deserialization:

# serialize empty value
json_serialize(res "")
assert(NOT res)


# serialze simple value
json_serialize(res "hello!")
assert(res)
assert("\"hello!\"" STREQUAL ${res})

# serialize a cmake list value
json_serialize(res "a;b;c")
assert(res)
assert(EQUALS "\"a\\\\;b\\\\;c\"" ${res})

#empty object
element(uut MAP)
element(END)
json_serialize(res ${uut})
assert("{}" STREQUAL ${res})

# empty list
element(uut LIST)
element(END)
json_serialize(res ${uut})
assert("[]" STREQUAL ${res})

# ref
ref_new(uut)
ref_set(${uut} "a b c")
json_serialize(res ${uut})
assert("\"a b c\"" STREQUAL ${res})

# list with one element
element(uut LIST)
value(1)
element(END)
json_serialize(res ${uut})
assert("[\"1\"]" STREQUAL ${res})

# list with multiple elements element
element(uut LIST)
value(1)
value(2)
element(END)
json_serialize(res ${uut})
assert("[\"1\",\"2\"]" STREQUAL ${res})

# object with single value
element(uut MAP)
value(KEY k1 val1)
element(END)
json_serialize(res ${uut})
assert("{\"k1\":\"val1\"}" STREQUAL ${res})

# object with multiple value
element(uut MAP)
value(KEY k1 val1)
value(KEY k2 val2)
element(END)
json_serialize(res ${uut})
assert("{\"k1\":\"val1\",\"k2\":\"val2\"}" STREQUAL ${res})

# list with single map
element(uut LIST)
element()
value(KEY k1 1)
element(END)
element(END)
json_serialize(res ${uut})
assert("[{\"k1\":\"1\"}]" STREQUAL ${res})


# deserialize a empty value
json_deserialize(res "")
assert(NOT res)

# deserialize a empty object
json_deserialize(res "{}")
assert(res)
ref_isvalid( ${res}  is_ref)
assert(is_ref MESSAGE "expected res to be a ref")

# desirialize a empty array
json_deserialize(res "[]")
assert(res)
ref_isvalid( ${res} is_ref)
assert(is_ref MESSAGE "expected res to be a ref")

# deserialize a simple value
json_deserialize(res "\"teststring\"")
assert(${res} STREQUAL "teststring")

# deserialize a array with one value
json_deserialize(res "[\"1\"]")
map_navigate(val "res[0]")
assert(${val} STREQUAL "1")

#deserialize a array with multiple values
json_deserialize(res "[\"1\", \"2\"]")
map_navigate(val "res[0]")
assert(${val} STREQUAL "1")
map_navigate(val "res[1]")
assert(${val} STREQUAL "2")

# deserialize a simple object with one value
json_deserialize(res "{ \"key\" : \"value\"}")
map_navigate(val "res.key")
assert(${val} STREQUAL "value")

# deserialize a simple object with multiple values
json_deserialize(res "{ \"key\" : \"value\", \"key2\" : \"val2\"}")
map_navigate(val "res.key")
assert(${val} STREQUAL "value")
map_navigate(val "res.key2")
assert(${val} STREQUAL "val2")


# deserialize a simple nested structure
json_deserialize(res "{ \"key\" : {\"key3\":\"myvalue\" }, \"key2\" : \"val2\"}")
map_navigate(val "res.key2")
assert(${val} STREQUAL "val2")
map_navigate(val "res.key.key3")
assert(${val} STREQUAL "myvalue")

# deserialize a nested structure containing both arrays and objects
json_deserialize(res "{ \"key\" : [\"1\", \"2\"], \"key2\" : \"val2\"}")
map_navigate(val "res.key2")
assert(${val} STREQUAL "val2")
map_navigate(val "res.key[0]")
assert(${val} STREQUAL "1")
map_navigate(val "res.key[1]")
assert(${val} STREQUAL "2")

# deserialize a 'deeply' nested structure and get a value
json_deserialize(res "{ \"key\" : [{\"k1\":\"v1\"}, \"2\"], \"key2\" : \"val2\"}")
map_navigate(val "res.key[0].k1")
assert(${val} STREQUAL "v1")

3 comments for “Reading and Writing JSON with CMake

  1. Renato
    2015/01/11 at 15:11

    Hi,

    I wondering if you could guide me along your example. I downloaded the code and created my CMakeLists.txt with the following lines:

    ——————————START-FILE——————————

    cmake_minimum_required(VERSION 2.8)

    include(“${CMAKE_CURRENT_LIST_DIR}/../oo-cmake.cmake”)

    json_deserialize(res “{ \”key\” : {\”key3\”:\”myvalue\” }, \”key2\” : \”val2\”}”)

    map_navigate(val “res.key2”) assert(${val} STREQUAL “val2”)

    ——————————END-FILE——————————-

    I got the following error:

    —————————–START-ERROR——————————

    CMake Error at /home/oo-make/workspace/oo-cmake/cmake

    /core/eval_truth.cmake:3 (if):

    if given arguments:

    "(" "STREQUAL" "val2" ")"

    Unknown arguments specified

    Call Stack (most recent call first):

    /home/oo-make/workspace/oo-cmake/cmake/core/assert.cmake:141 (eval_truth)

    CMakeLists.txt:5 (assert)

    — Configuring incomplete, errors occurred! —————————–END-ERROR——————————

    In specific, I am interested in the part of parsing JSON strings in a CMake environment.

    Kind regards,

    Renato.

    • 2015/01/11 at 15:27

      Hi

      I would suggest the following:

      `

      json_deserialize(“{\”a\”:{\”b\”:123}}”) ans(res) # see documentation for https://github.com/toeb/oo-cmake#return

      # now you can e.g. print the resulting map: to the console

      json_print(${res})

      # or access it using nav or map_get (I would discourage using map_navigate directly as it will probably change soon)

      nav(val = res.a.b) assert(${val} EQUAL 123)

      # if you are only interested in converting from json to cmake look at the following unit test file:

      # however this does not support escape sequences currently (I could add that bu haven’t had to and it is slow)

      #

      json_string_to_cmake(“\”inputstring\””) ans(res) assert(${res} STREQUAL “inputstring”)

      `

      sorry the formatting is not working correctly.

      cheers tobi

      • Parvathi
        2016/07/13 at 07:55

        Hi Tobi,

        I just came accoss an issue while parsing a json file and reading the configuration. map_navigate: indexation ‘[]’ is not supported for map_navigate(val “res.config[0].test_no”)

        but , assertf(“{res.config[1].test_no}” STREQUAL “1”) this works for me. Why is it so?

Leave a Reply

Your email address will not be published. Required fields are marked *