-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)}] ++ lists:map(fun ext_data_to_ejson/1, 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}] ++ lists:map(fun ext_data_to_ejson/1, 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}] ++ lists:map(fun ext_data_to_ejson/1, 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({Key, Value}) -> case Key of entry_exclusions -> {Key, {array, Value}}; _Other -> {Key, Value} 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 -> ExtDataProp = ejson_to_ext_data({Key, Value}), construct_record(Timeline, Fields, [ExtDataProp|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 -> ExtDataProp = ejson_to_ext_data({Key, Value}), construct_record(Entry, Fields, [ExtDataProp|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({Key, Value}) -> case Key of entry_exclusions -> {array, ExclList} = Value, {Key, ExclList}; _Other -> {Key, Value} end.