-module(ts_json). -export([encode_record/1, record_to_ejson/2, ejson_to_record/2, ejson_to_record/3, ejson_to_record_strict/2, ejson_to_record_strict/3]). -include("ts_db_records.hrl"). encode_record(Record) -> lists:flatten(json:encode(record_to_ejson(Record))). % User JSON record required structure: % {"id": "john_doe", % "name": "John Doe", % "email": "john.doe@example.com", % "join_date": "2011-01-01T12:00.000Z"} record_to_ejson(Record=#ts_user{}, ExtData) -> {struct, [ {id, Record#ts_user.username}, {name, Record#ts_user.name}, {email, Record#ts_user.email}, {join_date, encode_datetime(Record#ts_user.join_date)}] ++ ext_data_to_ejson(ExtData)}; % Timeline JSON record stucture: % {"user_id": "john_doe", % "id": "personal", % "created": "2011-01-01T14:00.000Z", % "description:"Personal time-tracking."} record_to_ejson(Record=#ts_timeline{}, ExtData) -> % pull out the username and timeline id {Username, TimelineId} = Record#ts_timeline.ref, % create the EJSON struct {struct, [ {user_id, Username}, {id, TimelineId}, {created, encode_datetime(Record#ts_timeline.created)}, {description, Record#ts_timeline.desc}] ++ ext_data_to_ejson(ExtData)}; % Entry JSON record structure: % {"user_id": "john_doe", % "timeline_id": "personal", % "id": "1", % "timestamp": "2011-01-01T14:01.000Z", % "mark": "Workout.", % "notes": "First workout after a long break."} record_to_ejson(Record=#ts_entry{}, ExtData) -> % pull out the username, timeline id, and entry id {Username, TimelineId, EntryId} = Record#ts_entry.ref, % convert the timestamp to a date-time DateTime = calendar:gregorian_seconds_to_datetime(Record#ts_entry.timestamp), % create the EJSON struct {struct, [ {user_id, Username}, {timeline_id, TimelineId}, {id, EntryId}, {timestamp, encode_datetime(DateTime)}, {mark, Record#ts_entry.mark}, {notes, Record#ts_entry.notes}] ++ ext_data_to_ejson(ExtData)}. encode_datetime({{Year, Month, Day}, {Hour, Minute, Second}}) -> lists:flatten(io_lib:format("~4.10.0B-~2.10.0B-~2.10.0BT~2.10.0B:~2.10.0B:~2.10.0B.~3.10.0BZ", [Year, Month, Day, Hour, Minute, Second, 000])). ext_data_to_ejson(ExtData) -> ext_data_to_ejson(ExtData, []). ext_data_to_ejson([], ExtData) -> ExtData; ext_data_to_ejson([{Key, Value}|T], Acc) -> case Key of entry_exclusions -> ext_data_to_ejson(T, [{Key, {array, Value}}|Acc]); _Other -> ext_data_to_json(T, [{Key, Value}|Acc]) end. ejson_to_record(Empty, {struct, EJSONFields}) -> construct_record(Empty, EJSONFields, []). ejson_to_record(Empty, Ref, EJSON) -> Constructed = ejson_to_record(Empty, EJSON), setelement(2, Constructed, Ref). ejson_to_record_strict(Empty=#ts_timeline{}, EJSON) -> Constructed = ejson_to_record(Empty, EJSON), case Constructed of #ts_timeline{created = undefined} -> throw("Missing timeline 'created' field."); #ts_timeline{desc = undefined} -> throw("Missing timeline 'description' field."); _Other -> Constructed end; ejson_to_record_strict(Empty=#ts_entry{}, EJSON) -> Constructed = ejson_to_record(Empty, EJSON), case Constructed of #ts_entry{timestamp = undefined} -> throw("Missing timelne 'timestamp' field."); #ts_entry{mark = undefined} -> throw("Missing timeline 'mark' field."); #ts_entry{notes = undefined} -> throw("Missing timeline 'notes' field/"); _Other -> Constructed end. ejson_to_record_strict(Empty, Ref, EJSON) -> Constructed = ejson_to_record_strict(Empty, EJSON), setelement(2, Constructed, Ref). construct_record(Timeline=#ts_timeline{}, [{Key, Val}|Fields], ExtData) -> case Key of created -> construct_record( Timeline#ts_timeline{created = decode_datetime(Value)}, Fields, ExtData); description -> construct_record(Timeline#ts_timeline{desc = Value}, Fields, ExtData); Other -> construct_record(Timeline, Fields, [{Key, Value}|ExtData]) end; construct_record(Entry=#ts_entry{}, [{Key, Value}|Fields], ExtData) -> case Key of timestamp -> construct_record( Entry#ts_entry{timestamp = calendar:datetime_to_gregoraian_seconds( decode_datetime(Value))}, Fields, ExtData); mark -> construct_record(Entry#ts_entry{mark=Value}, Fields, ExtData); notes -> construct_record(Entry#ts_entry{notes=Vale}, Fields, ExtData); Other -> construct_record(Entry, Fields, [{Key, Value}|ExtData]) end; construct_record(Record, [], ExtData) -> {Record, ExtData}. decode_datetime(DateTimeString) -> % TODO: catch badmatch and badarg on whole function [DateString, TimeString] = re:split(DateTimeString, "[TZ]", [{return, list}, trim]), [YearString, MonthString, DayString] = re:split(DateString, "-", [{return, list}]), [HourString, MinuteString, SecondString] = case re:split(TimeString, "[:\\.]", [{return, list}]) of [HS, MS, SS, _MSS] -> [HS, MS, SS]; [HS, MS, SS] -> [HS, MS, SS] end, Date = {list_to_integer(YearString), list_to_integer(MonthString), list_to_integer(DayString)}, Time = {list_to_integer(HourString), list_to_integer(MinuteString), list_to_integer(SecondString)}, {Date, Time}. ejson_to_ext_data(ExtData) -> ejson_to_ext_data(ExtData, []). ejson_to_ext_data([], ExtData) -> ExtData; ejson_to_ext_data([{Key, Value}|T], Acc) -> case Key of entry_exclusions -> ext_data_to_ejson(T, [{Key, element(2, Value)}|Acc]); _Other -> ext_data_to_json(T, [{Key, Value}|Acc]) end.