복붙노트

[SPRING] JSON을 사용하여 중첩 된 객체를 Spring MVC 컨트롤러에 게시

SPRING

JSON을 사용하여 중첩 된 객체를 Spring MVC 컨트롤러에 게시

내가 그렇게 정의 된 POST 처리기와 컨트롤러가 :

@RequestMapping(value="/ajax/saveVendor.do", method = RequestMethod.POST)
public @ResponseBody AjaxResponse saveVendor( @Valid UIVendor vendor,
                                              BindingResult result,
                                              Locale currentLocale )

UIVendor 객체는 JSON 형식으로 볼 때 다음과 같습니다.

var vendor = 
{
  vendorId: 123,
  vendorName: "ABC Company",
  emails : [
             { emailAddress: "abc123@abc.com", flags: 2 },
             { emailAddress: "xyz@abc.com", flags: 3 }
           ]
}

UIVendor 빈에는 적절한 Setter와 getter (getEmails / setEmails)가있는 ArrayList 유형의 "Emails"라는 필드가 있습니다. NotificationEmail 객체에는 적절한 공용 설정자 / getter도 있습니다.

다음 코드를 사용하여 개체를 게시하려고하면 :

$.post("ajax/saveVendor.do", $.param(vendor), saveEntityCallback, "json" );

로그에서이 오류가 발생합니다.

Invalid property 'emails[0][emailAddress]' of bean class [beans.UIVendor]: Property referenced in indexed property path 'emails[0][emailAddress]' is neither an array nor a List nor a Map; returned value was [abc123@abc.com]

어떻게 이처럼 중첩 된 객체를 Spring 컨트롤러에 올바르게 게시하고 적절한 객체 구조로 올바르게 역 직렬화 할 수 있습니까?

최신 정보 Bohzo의 요청에 따라 UIVendor 클래스의 내용이 있습니다. 이 클래스는 웹 서비스 생성 빈 클래스를 래핑하여 VendorAttributes를 개별 필드로 노출합니다.

package com.mycompany.beans;

import java.util.*;
import org.apache.commons.lang.*;
import com.mycompany.domain.Vendor;
import com.mycompany.domain.VendorAttributes;
import org.apache.commons.logging.*;
import org.codehaus.jackson.annotate.JsonIgnore;

public class UIVendor
{
  private final Log logger = LogFactory.getLog( this.getClass() );
  private Vendor vendor;
  private boolean ftpFlag;
  private String ftpHost;
  private String ftpPath;
  private String ftpUser;
  private String ftpPassword; 
  private List<UINotificationEmail> emails = null;

  public UIVendor() { this( new Vendor() ); }
  public UIVendor( Vendor vendor )
  {
    this.vendor = vendor;
    loadVendorAttributes();
  }

  private void loadVendorAttributes()
  {
    this.ftpFlag = false;
    this.ftpHost = this.ftpPassword = this.ftpPath = this.ftpUser = "";
    this.emails = null;

    for ( VendorAttributes a : this.vendor.getVendorAttributes() )
    {
      String key = a.getVendorFakey();
      String value = a.getVendorFaValue();
      int flags = a.getFlags();

      if ( StringUtils.isBlank(key) || StringUtils.isBlank(value) ) continue;

      if ( key.equals( "ftpFlag" ) )
      {
        this.ftpFlag = BooleanUtils.toBoolean( value );
      }
      else if ( key.equals( "ftpHost" ) )
      {
        this.ftpHost = value;
      }
      else if ( key.equals("ftpPath") )
      {
        this.ftpPath = value;
      }
      else if ( key.equals("ftpUser") )
      {
        this.ftpUser = value;
      }
      else if ( key.equals("ftpPassword") )
      {
        this.ftpPassword = value;
      }
      else if ( key.equals("email") )
      {
        UINotificationEmail email = new UINotificationEmail(value, flags);
        this.getEmails().add( email );
      }
    }
  }

  private void saveVendorAttributes()
  {
    int id = this.vendor.getVendorId();
    List<VendorAttributes> attrs = this.vendor.getVendorAttributes();
    attrs.clear();

    if ( this.ftpFlag )
    {      
      VendorAttributes flag = new VendorAttributes();
      flag.setVendorId( id );
      flag.setStatus( "A" );
      flag.setVendorFakey( "ftpFlag" );
      flag.setVendorFaValue( BooleanUtils.toStringTrueFalse( this.ftpFlag ) );
      attrs.add( flag );

      if ( StringUtils.isNotBlank( this.ftpHost ) )
      {
        VendorAttributes host = new VendorAttributes();
        host.setVendorId( id );
        host.setStatus( "A" );
        host.setVendorFakey( "ftpHost" );
        host.setVendorFaValue( this.ftpHost );
        attrs.add( host );

        if ( StringUtils.isNotBlank( this.ftpPath ) )
        {
          VendorAttributes path = new VendorAttributes();
          path.setVendorId( id );
          path.setStatus( "A" );
          path.setVendorFakey( "ftpPath" );
          path.setVendorFaValue( this.ftpPath );
          attrs.add( path );
        }

        if ( StringUtils.isNotBlank( this.ftpUser ) )
        {
          VendorAttributes user = new VendorAttributes();
          user.setVendorId( id );
          user.setStatus( "A" );
          user.setVendorFakey( "ftpUser" );
          user.setVendorFaValue( this.ftpUser );
          attrs.add( user );
        }

        if ( StringUtils.isNotBlank( this.ftpPassword ) )
        {
          VendorAttributes password = new VendorAttributes();
          password.setVendorId( id );
          password.setStatus( "A" );
          password.setVendorFakey( "ftpPassword" );
          password.setVendorFaValue( this.ftpPassword ); 
          attrs.add( password );
        }
      }      
    }

    for ( UINotificationEmail e : this.getEmails() )
    {
      logger.debug("Adding email " + e );
      VendorAttributes email = new VendorAttributes();
      email.setStatus( "A" );
      email.setVendorFakey( "email" );
      email.setVendorFaValue( e.getEmailAddress() );
      email.setFlags( e.getFlags() );
      email.setVendorId( id );
      attrs.add( email );
    }
  }

  @JsonIgnore
  public Vendor getVendor()
  {
    saveVendorAttributes();
    return this.vendor;
  }

  public int getVendorId()
  {
    return this.vendor.getVendorId();
  }
  public void setVendorId( int vendorId )
  {
    this.vendor.setVendorId( vendorId );
  }

  public String getVendorType()
  {
    return this.vendor.getVendorType();
  }
  public void setVendorType( String vendorType )
  {
    this.vendor.setVendorType( vendorType );
  }

  public String getVendorName()
  {
    return this.vendor.getVendorName();
  }
  public void setVendorName( String vendorName )
  {
    this.vendor.setVendorName( vendorName );
  }

  public String getStatus()
  {
    return this.vendor.getStatus();
  }
  public void setStatus( String status )
  {
    this.vendor.setStatus( status );
  }

  public boolean isFtpFlag()
  {
    return this.ftpFlag;
  }
  public void setFtpFlag( boolean ftpFlag )
  {
    this.ftpFlag = ftpFlag;
  }

  public String getFtpHost()
  {
    return this.ftpHost;
  }
  public void setFtpHost( String ftpHost )
  {
    this.ftpHost = ftpHost;
  }

  public String getFtpPath()
  {
    return this.ftpPath;
  }
  public void setFtpPath( String ftpPath )
  {
    this.ftpPath = ftpPath;
  }

  public String getFtpUser()
  {
    return this.ftpUser;
  }
  public void setFtpUser( String ftpUser )
  {
    this.ftpUser = ftpUser;
  }

  public String getFtpPassword()
  {
    return this.ftpPassword;
  }
  public void setFtpPassword( String ftpPassword )
  {
    this.ftpPassword = ftpPassword;
  }

  public List<UINotificationEmail> getEmails()
  {
    if ( this.emails == null )
    {
      this.emails = new ArrayList<UINotificationEmail>();
    }
    return emails;
  }

  public void setEmails(List<UINotificationEmail> emails)
  {
    this.emails = emails;
  }
}

업데이트 2 Jackson의 결과는 다음과 같습니다.

{
  "vendorName":"MAIL",
  "vendorId":45,
  "emails":
  [
    {
      "emailAddress":"dfg",
      "success":false,
      "failure":false,
      "flags":0
    }
  ],
  "vendorType":"DFG",
  "ftpFlag":true,
  "ftpHost":"kdsfjng",
  "ftpPath":"dsfg",
  "ftpUser":"sdfg",
  "ftpPassword":"sdfg",
  "status":"A"
}

다음은 POST에서 반환 할 객체의 구조입니다.

{
  "vendorId":"45",
  "vendorName":"MAIL",
  "vendorType":"DFG",
  "ftpFlag":true,
  "ftpHost":"kdsfjng",
  "ftpUser":"sdfg",
  "ftpPath":"dsfg",
  "ftpPassword":"sdfg",
  "status":"A",
  "emails": 
            [
              {
                "success":"false",
                "failure":"false",
                "emailAddress":"dfg"
              },
              {
                "success":"true",
                "failure":"true",
                "emailAddress":"pfc@sj.org"
              }
            ]
}

나는 www.json.org의 JSON 라이브러리를 사용하여 직렬화를 시도했다. 결과는 위에서 본 것과 정확히 같다. 그러나 해당 데이터를 게시 할 때 컨트롤러에 전달 된 UIVendor 개체의 모든 필드는 null입니다 (개체가 아님).

해결법

  1. ==============================

    1.Update : Spring 3.1부터 @Valid On @RequestBody Controller 메소드 인수를 사용할 수 있습니다.

    Update : Spring 3.1부터 @Valid On @RequestBody Controller 메소드 인수를 사용할 수 있습니다.

    @RequestMapping(value="/ajax/saveVendor.do", method = RequestMethod.POST)
    public @ResponseBody AjaxResponse saveVendor( @Valid @RequestBody UIVendor vendor,
                                                  BindingResult result,
                                                  Locale currentLocale )
    

    많은 시행 착오 끝에 마침내 문제가 무엇인지 알아 냈습니다. 다음 제어기 메소드 서명을 사용할 때 :

    @RequestMapping(value="/ajax/saveVendor.do", method = RequestMethod.POST)
    public @ResponseBody AjaxResponse saveVendor( @Valid UIVendor vendor,
                                                  BindingResult result,
                                                  Locale currentLocale )
    

    클라이언트 스크립트는 객체의 필드를 데이터 후 (일반적으로 "application / x-www-form-urlencoded") 형식으로 전달해야합니다 (즉, field = value & field2 = value2). 이것은 jQuery에서 다음과 같이 수행된다.

    $.post( "mycontroller.do", $.param(object), callback, "json" )
    

    이것은 자식 객체 나 콜렉션을 가지고 있지 않은 간단한 POJO 객체에서 잘 동작하지만 전달 된 객체에 상당한 복잡성이 생기면 jQuery가 객체 데이터를 직렬화하기 위해 사용하는 표기법이 Spring의 매핑 논리에 의해 인식되지 않는다.

    object[0][field]
    

    이 문제를 해결하는 방법은 컨트롤러의 메서드 서명을 다음과 같이 변경하는 것입니다.

    @RequestMapping(value="/ajax/saveVendor.do", method = RequestMethod.POST)
    public @ResponseBody AjaxResponse saveVendor( @RequestBody UIVendor vendor,
                                                  Locale currentLocale )
    

    클라이언트 호출을 다음과 같이 변경하십시오.

        $.ajax(
                {
                  url:"ajax/mycontroller.do", 
                  type: "POST", 
                  data: JSON.stringify( objecdt ), 
                  success: callback, 
                  dataType: "json",
                  contentType: "application/json"
                } );    
    

    이렇게하려면 JSON 자바 스크립트 라이브러리를 사용해야합니다. 또한 Spring이 @RequestBody 어노테이션을 사용할 때 contentType을 "application / json"으로 강제 설정하고 Jackson이 유효한 객체 구조로 역 직렬화 할 수있는 형식으로 객체를 직렬화합니다.

    유일한 부작용은 이제 컨트롤러 메서드 내에서 내 자신의 개체 유효성 검사를 처리해야하지만 비교적 간단합니다.

    BindingResult result = new BeanPropertyBindingResult( object, "MyObject" );
    Validator validator = new MyObjectValidator();
    validator.validate( object, result );
    

    누군가이 과정을 개선하기위한 제안을한다면, 나는 모든 귀입니다.

  2. ==============================

    2.첫째, 가난한 영어에 대한 미안

    첫째, 가난한 영어에 대한 미안

    봄에 param 이름이 object [0] [field] 인 경우 하위 클래스와 같은 클래스 유형으로 간주합니다.

    public class Test {
    
        private List<Map> field;
    
        /**
         * @return the field
         */
        public List<Map> getField() {
            return field;
        }
    
        /**
         * @param field the field to set
         */
        public void setField(List<Map> field) {
            this.field = field;
        }
    }
    

    그래서 봄에 예외가 던져 질 것입니다. "배열도리스트도지도도 아닙니다."

    param 이름이 object [0] .field 인 경우에만 스프링은 클래스의 필드로 취급합니다.

    org.springframework.beans.PropertyAccessor에서 상수 def를 찾을 수 있습니다.

    그래서 내 솔루션은 아래와 같이 jquery를위한 새로운 param 플러그인을 작성합니다.

    (function($) {
      // copy from jquery.js
      var r20 = /%20/g,
      rbracket = /\[\]$/;
    
      $.extend({
        customParam: function( a ) {
          var s = [],
            add = function( key, value ) {
              // If value is a function, invoke it and return its value
              value = jQuery.isFunction( value ) ? value() : value;
              s[ s.length ] = encodeURIComponent( key ) + "=" + encodeURIComponent( value );
            };
    
          // If an array was passed in, assume that it is an array of form elements.
          if ( jQuery.isArray( a ) || ( a.jquery && !jQuery.isPlainObject( a ) ) ) {
            // Serialize the form elements
            jQuery.each( a, function() {
              add( this.name, this.value );
            });
    
          } else {
            for ( var prefix in a ) {
              buildParams( prefix, a[ prefix ], add );
            }
          }
    
          // Return the resulting serialization
          return s.join( "&" ).replace( r20, "+" );
        }
      });
    
    /* private method*/
    function buildParams( prefix, obj, add ) {
      if ( jQuery.isArray( obj ) ) {
        // Serialize array item.
        jQuery.each( obj, function( i, v ) {
          if (rbracket.test( prefix ) ) {
            // Treat each array item as a scalar.
            add( prefix, v );
    
          } else {
            buildParams( prefix + "[" + ( typeof v === "object" || jQuery.isArray(v) ? i : "" ) + "]", v, add );
          }
        });
    
      } else if (obj != null && typeof obj === "object" ) {
        // Serialize object item.
        for ( var name in obj ) {
          buildParams( prefix + "." + name, obj[ name ], add );
        }
    
      } else {
        // Serialize scalar item.
        add( prefix, obj );
      }
    };
    })(jQuery);
    

    실제 나는에서 코드를 변경합니다.

    buildParams( prefix + "[" + name + "]", obj[ name ], traditional, add );
    

    buildParams( prefix + "." + name, obj[ name ], add );
    

    ajax 요청을 할 때 $ .param 대신 $ .customParam을 사용하십시오.

  3. ==============================

    3.다음과 같이해볼 수 있습니다 :

    다음과 같이해볼 수 있습니다 :

    vendor['emails[0].emailAddress'] = "abc123@abc.com";
    vendor['emails[0].flags'] = 3;
    vendor['emails[1].emailAddress'] = "xyz@abc.com";
    vendor['emails[1].flags'] = 3;
    

    :)

  4. ==============================

    4.필드를 List (인터페이스)가 아닌 ArrayList (구체적인 유형)로 정의하십시오.

    필드를 List (인터페이스)가 아닌 ArrayList (구체적인 유형)로 정의하십시오.

    private List emailAddresses = new ArrayList();
    
  5. from https://stackoverflow.com/questions/5900840/post-nested-object-to-spring-mvc-controller-using-json by cc-by-sa and MIT license