Menu

DateMorpher is never called because BeanMorpher incorrectly handles Date types

Help
2016-04-05
2016-05-06
  • Chris St.Onge

    Chris St.Onge - 2016-04-05

    Problem
    When converting a JSONObject with a string field representing a date such as "yyyy-mm-dd" to a Bean with a Date field using JSONObject.toBean(), the result is the current date being set on the Bean.

    Code

    JSONUtils.getMorpherRegistry().registerMorpher(new DateMorpher(new String[]{"yyyy-MM-dd"}));        
    TestBean bean = (TestBean)JSONObject.toBean(json, TestBean.class);
    

    The following code in MorpherRegistry is responsible for the problem behaviour:

    Morpher[] morphers = getMorphersFor( target );
          for( int i = 0; i < morphers.length; i++ ){
             Morpher morpher = morphers[i];
             if( morpher.supports( value.getClass() ) ){
                if( morpher instanceof ObjectMorpher ){
                   return ((ObjectMorpher) morpher).morph( value );
                }else{
                   try{
                      Method morphMethod = morpher.getClass()
                            .getDeclaredMethod( "morph", new Class[] { Object.class } );
                      return morphMethod.invoke( morpher, new Object[] { value } );
                   }
                   catch( Exception e ){
                      throw new MorphException( e );
                   }
                }
             }
          }
    

    Explanation
    What seems to be happening is a default BeanMorpher is in the list of morphers already and registering the DateMorpher adds it to the list as the second list object. The for loop starts at the beginning of the list and calls that morpher's supports() method to see if this morpher supports the target data type. For BeanMorpher, the supports() method only returns false for arrays so the supports() method will return true for Date. It then fails to morph the string representation of a Date into a Date and instantiates a new Date and returns it. This results in the current date being set on the Bean. There is no way that DateMorpher will be hit unless the target data type is an array, which it's not.

    Solution
    Without looking too deeply, the first impression is that since BeanMorpher seems to be the default last chance morpher, the for loop should start at the end of the list and loop backward. Alternatively, the registerMorpher() method could insert at the beginning of the morpher list instead of appending to the end.

    Morpher[] morphers = getMorphersFor( target );
          for( int i = morphers.length - 1; i >= 0; i++ ){
             Morpher morpher = morphers[i];
             if( morpher.supports( value.getClass() ) ){
                if( morpher instanceof ObjectMorpher ){
                   return ((ObjectMorpher) morpher).morph( value );
                }else{
                   try{
                      Method morphMethod = morpher.getClass()
                            .getDeclaredMethod( "morph", new Class[] { Object.class } );
                      return morphMethod.invoke( morpher, new Object[] { value } );
                   }
                   catch( Exception e ){
                      throw new MorphException( e );
                   }
                }
             }
          }
    

    Adding a second parameter of "true" to JSONUtils.getMorpherRegistry().registerMorpher() seems to force the DateMorpher to be used but I still think the list of morphers should be iterated over in reverse order using BeanMorpher as the default last chance morpher.

     

    Last edit: Chris St.Onge 2016-04-07
    • aalmiray

      aalmiray - 2016-05-06

      I'm afraid this issue can't be reproduced in the latest 3.0.0-SNAPSHOT codebase. See https://github.com/aalmiray/Json-lib/commit/df6981aab7858298b1243ffb59f780b4cdaaea2dPerhaps we squashed the bug when prepping up 3.0.0.

      Cheers,Andres
       -------------------------------------------
      Java Champion, Groovy Enthusiasthttp://jroller.com/aalmirayhttp://www.linkedin.com/in/aalmiray--What goes up, must come down. Ask any system administrator.There are 10 types of people in the world: Those who understand binary, and those who don't.To understand recursion, we must first understand recursion.

      On Tuesday, April 5, 2016 7:39 PM, Chris St.Onge <chrifister@users.sf.net> wrote:
      

      Problem
      When converting a JSONObject with a string field representing a date such as "yyyy-mm-dd" to a Bean with a Date field using JSONObject.toBean(), the result is the current date being set on the Bean.CodeJSONUtils.getMorpherRegistry().registerMorpher(new DateMorpher(new String[]{"yyyy-MM-dd"}));
      TestBean bean = (TestBean)JSONObject.toBean(json, TestBean.class);
      The following code in MorpherRegistry is responsible for the problem behaviour:Morpher[] morphers = getMorphersFor( target );
      for( int i = 0; i < morphers.length; i++ ){
      Morpher morpher = morphers[i];
      if( morpher.supports( value.getClass() ) ){
      if( morpher instanceof ObjectMorpher ){
      return ((ObjectMorpher) morpher).morph( value );
      }else{
      try{
      Method morphMethod = morpher.getClass()
      .getDeclaredMethod( "morph", new Class[] { Object.class } );
      return morphMethod.invoke( morpher, new Object[] { value } );
      }
      catch( Exception e ){
      throw new MorphException( e );
      }
      }
      }
      }
      Explanation
      What seems to be happening is a default BeanMorpher is in the list of morphers already and registering the DateMorpher adds it to the list as the second list object. The for loop starts at the beginning of the list and calls that morpher's supports() method to see if this morpher supports the target data type. For BeanMorpher, the supports() method only returns false for arrays so the supports() method will return true for Date. It then fails to morph the string representation of a Date into a Date and instantiates a new Date and returns it. This results in the current date being set on the Bean. There is no way that DateMorpher will be hit unless the target data type is an array, which it's not.Solution
      Without looking too deeply, the first impression is that since BeanMorpher seems to be the default last chance morpher, the for loop should start at the end of the list and loop backward. Alternatively, the registerMorpher() method could insert at the beginning of the morpher list instead of appending to the end.Morpher[] morphers = getMorphersFor( target );
      for( int i = morphers.length - 1; i >= 0; i++ ){
      Morpher morpher = morphers[i];
      if( morpher.supports( value.getClass() ) ){
      if( morpher instanceof ObjectMorpher ){
      return ((ObjectMorpher) morpher).morph( value );
      }else{
      try{
      Method morphMethod = morpher.getClass()
      .getDeclaredMethod( "morph", new Class[] { Object.class } );
      return morphMethod.invoke( morpher, new Object[] { value } );
      }
      catch( Exception e ){
      throw new MorphException( e );
      }
      }
      }
      }
      DateMorpher is never called because BeanMorpher incorrectly handles Date typesSent from sourceforge.net because you indicated interest in https://sourceforge.net/p/json-lib/discussion/587134/To unsubscribe from further messages, please visit https://sourceforge.net/auth/subscriptions/

       

Log in to post a comment.