1
votes

I'm working on a simple web service in Prolog and wanted to respond to my users with data formatted as JSON. A nice facility is reply_json_dict/1 which takes a dictionary and converts it in a HTTP response with well formatted JSON body.

My trouble is that building the response dictionary itself seems a little cumbersome. For example, when I return some data, I have data id but may/may not have data properties (possibly an unbound variable). At the moment I do the following:

OutDict0 = _{ id : DataId },
( nonvar(Props) -> OutDict1 = OutDict0.put(_{ attributes : Props }) ; OutDict1 = OutDict0 ),
reply_json_dict(OutDict1)

Which works fine, so output is { "id" : "III" } or { "id" : "III", "attributes" : "AAA" } depending whether or not Props is bound, but... I'm looking for an easier approach. Primarily because if I need to add more optional key/value pairs, I end up with multiple implications like:

OutDict0 = _{ id : DataId },
( nonvar(Props) -> OutDict1 = OutDict0.put(_{ attributes : Props }) ; OutDict1 = OutDict0 ),
( nonvar(Time) -> OutDict2 = OutDict1.put(_{ time : Time }) ; OutDict2 = OutDict1 ),
( nonvar(UserName) -> OutDict3 = OutDict2.put(_{ userName : UserName }) ; OutDict3 = OutDict2 ),
reply_json_dict(OutDict3)

And that seems just wrong. Is there a simpler way?

Cheers, Jacek

3

3 Answers

3
votes

Instead of messing with dictionaries, my recommendation in this case is to use a different predicate to emit JSON.

For example, consider json_write/2, which lets you emit JSON, also on current output as the HTTP libraries require.

Suppose your representation of data fields is the common Name(Value) notation that is used throughout the HTTP libraries for option processing:

Fields0 = [attributes(Props),time(Time),userName(UserName)],

Using the include/3, your whole example becomes:

main :-
        Fields0 = [id(DataId),attributes(Props),time(Time),userName(UserName)],
        include(ground, Fields0, Fields),
        json_write(current_output, json(Fields)).

You can try it out yourself, by plugging in suitable values for the individual elements that are singleton variables in the snippet above.

For example, we can (arbitrarily) use:

        Fields0 = [id(i9),attributes(_),time('12:00'),userName(_)],

yielding:

?- main.
{"id":"i9", "time":"12:00"}
true.

You only need to emit the suitable Content-Type header, and have the same output that reply_json_dict/1 would have given you.

2
votes

You can do it in one step if you use a list to represent all values that need to go into the dict.

?- Props = [a,b,c], get_time(Time),
   D0 = _{id:001},
   include(ground, [props:Props,time:Time,user:UserName], Fs),
   D = D0.put(Fs).
D0 = _17726{id:1},
Fs = [props:[a, b, c], time:1477557597.205908],
D = _17726{id:1, props:[a, b, c], time:1477557597.205908}.

This borrows the idea in mat's answer to use include(ground).

1
votes

Many thanks mat and Boris for suggestions! I ended up with a combination of your ideas:

dict_filter_vars(DictIn, DictOut) :-
    findall(Key=Value, (get_dict(Key, DictIn, Value), nonvar(Value)), Pairs),
    dict_create(DictOut, _, Pairs).

Which then I can use as simple as that:

DictWithVars = _{ id : DataId, attributes : Props, time : Time, userName : UserName },
dict_filter_vars(DictWithVars, DictOut),
reply_json_dict(DictOut)