domingo, 7 de junho de 2015

parsing json date elements with angular-js automatically

problem: parsing a json string to a javascript object convert date to string and not to a Date object

solution: introspect the object after the transformation replacing string objects with date objects.

How to do it?

The first thing is to define your app protocol and how date will be formated as a string object.  Be consistent in all functions of your app.

A good choice is accomplish ISO 8601 standard. This assumes that dates is represented always in this format YYYY-MM-DD, To express Times as string objects use this mask HH:mm:ss and Timestamp you can set the mask for YYYY-MM-DDTHH:mm:ss.sssZ. Real time application will requires more precision, anyway, this is not the focus here.

@client side


The app at client side must guarantee that every http response will call the funcion paseDate below:

 var ISO8601_DATE_TIME_FORMAT = /^(\d{4}|\+\d{6})(?:-(\d{2})(?:-(\d{2})(?:T(\d{2}):(\d{2}):(\d{2})\.(\d{1,})(Z|([\-+])(\d{2}):(\d{2}))?)?)?)?$/;  
 function parseDate(obj) {  
      if (!obj || obj == 'undefined') return;  
      for (var prop in obj) {  
           var t_name = typeof(obj[prop]);  
           if (t_name == "object") {  
                parseDate(obj[prop]);  
           } else if (t_name == "string") {  
                var search;  
                var valor = obj[prop];
                if (search = valor.match(ISO8601_DATE_TIME_FORMAT)) {  
                     try {  
                          obj[prop] = new Date(Date.parse(valor));  
                     } catch (e) {};  
                }  
           }  
      }       
 }  
 function parseJSON(s) {  
      var o = JSON.parse(jsonString);  
      parseDate(o);  
 }  

Another good choice is include moment.js in your project

Example:


      var obj = parseJSON(s)  

Where 's' is a string from a http response in json format,
like the one below

           {"id":"dL93if5p47Mad3_36mXzYf","idTarefa":"dOPxTsNKkmO9AsfTaEvrLG","idEmpresa":"2sR0qM_t4ROaDL1Zj7Kvns","idUsuario":"6br-0sd1AyrbEmo4Hh39-J","nomeUsuario":"RiCARDO A. HARARI","dataCadastro":"2015-04-20T05:26:09.009"}}

obj.dataCadastro will be replaced by a Date object and then you can put it into app scope and associate with an input type  "date" html5 component.

INPUT TYPE="date" class="form-control" ng-model="obj.dataCadastro"

With angular-js each response can be automatically parsed

Sample config that will trigger paseDate

 myAppReferenceObject.config(["$httpProvider", function ($httpProvider) {  
      $httpProvider.defaults.transformResponse.push(function(responseData){  
           parseDate(responseData);  
           return responseData;  
      });  
 }]);  


myAppReferenceObject is your app, the angular.module,
All http data response will be automatically transformed to a date.


 $http.post(myURL)  
      .success(function(data) {  
           console.log(data.dataCadastro); // here dataCadastro is already a date object  
      ...  


@ server side

In the java server side you have to implement the jsonification process
I recommend gson lib to jsonification strings from/to a plain value object (aka pojo)
https://code.google.com/p/google-gson/

For GSON just define a serializer and a deserialize class as follows:


 package com.technique.engine.data.nosql;  
 import java.lang.reflect.Type;  
 import java.text.SimpleDateFormat;  
 import java.util.Date;  
 import com.google.gson.JsonDeserializationContext;  
 import com.google.gson.JsonDeserializer;  
 import com.google.gson.JsonElement;  
 import com.google.gson.JsonParseException;  

 public class DateDeserealizer implements JsonDeserializer<Date> {  
      /**  
       * returns a date object of the json element  
       * json element can start with mask: yyyy-MM-dd or dd/MM/yyyy   
       * and finish with HH:mm:ss or HH:mm:ss.sss or HH:mm:ss.sssz  
       * valid json formats sample: 01/12/2014 ; 2014-12-01 ; 01/12/2014T23:02:01; 01/12/2014T23:02:01.987; 01/12/2014T23:02:01.987Z; 2014-12-01T23:02:01.987   
       */  
      @SuppressWarnings("unused")  
      @Override  
      public Date deserialize(JsonElement json, Type arg1, JsonDeserializationContext arg2) throws JsonParseException {  
           if (json == null) return null;  
           String s = json.getAsString();  
           try {  
                if (s.indexOf('T') > -1) {  
                     int i = s.indexOf('Z');  
                     // date with / separator will will assume this mask dd/MM/yyyy - and date with - separator will be expected yyyy-MM-dd  
                     boolean ddmmyyyy = s.indexOf('/') > -1;  
                     if (i > -1) s = s.substring(0, i);  
                     i = s.length();  
                     SimpleDateFormat sdf = null;  
                     if (i == 10) {  
                          sdf = new SimpleDateFormat(ddmmyyyy ? "dd/MM/yyyy" : "yyyy-MM-dd");  
                     } else if (i == 17) {  
                          sdf = new SimpleDateFormat(ddmmyyyy ? "dd/MM/yyyy'T'HH:mm:ss" : "yyyy-MM-dd'T'HH:mm:ss");  
                     } else if (i == 21) {  
                          sdf = new SimpleDateFormat(ddmmyyyy ? "dd/MM/yyyy'T'HH:mm:ss.sss" : "yyyy-MM-dd'T'HH:mm:ss.sss");  
                     } else if (i == 8) {  
                          sdf = new SimpleDateFormat(ddmmyyyy ? "dd/MM/yy" : "yy-MM-dd");  
                     }  
                     if (sdf != null) return sdf.parse(s);  
                }  
           } catch (Exception e) {  
           }  
           try {  
                return new Date(json.getAsLong());  
           } catch (Exception e2) {  
                throw new JsonParseException("invalid date");  
           }  
      }  
 }  





 package com.technique.engine.data.nosql;  
 import java.lang.reflect.Type;  
 import java.text.SimpleDateFormat;  
 import java.util.Date;  
 import com.google.gson.JsonElement;  
 import com.google.gson.JsonPrimitive;  
 import com.google.gson.JsonSerializationContext;  
 import com.google.gson.JsonSerializer;  
 public class DateSerializer implements JsonSerializer<Date> {  
      @SuppressWarnings("unused")  
      @Override  
      public JsonElement serialize(Date src, Type arg1, JsonSerializationContext arg2) {  
           return src == null ? null : new JsonPrimitive(getJsonDate(src));  
      }  
      protected static String getJsonDate(Date date) {  
           SimpleDateFormat sdf1 = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.sss'Z'");  
           return sdf1.format(date);
      }  
 }  


Use the method below to get the Gson object:

      private static Gson getGson() {  
           DateSerializer ser = new DateSerializer();  
           DateDeserealizer deser = new DateDeserealizer();  
           return new GsonBuilder()  
             .registerTypeAdapter(Date.class, ser)  
             .registerTypeAdapter(Date.class, deser)  
             .excludeFieldsWithModifiers(Modifier.STATIC, Modifier.VOLATILE)  
             .create();  
      }  



Sample:

      String fromHTTPRequest = "{\"maleFemale\":\"1\",\"name\":\"John\",\"email\":\"john@a.com\"}";  
      UsuarioVO u = getGson().fromJson(fromHTTPRequest, UsuarioVO.class)  
      System.out.println(u.name); // print 'John'  


UsuarioVO is a pojo with at least these 3 public atributes:

public int maleFemale; //0-undefined, 1-male, 2-female
public String name;
public String email;