[SPRING] JSON을 사용하여 중첩 된 객체를 Spring MVC 컨트롤러에 게시
SPRINGJSON을 사용하여 중첩 된 객체를 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.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.첫째, 가난한 영어에 대한 미안
첫째, 가난한 영어에 대한 미안
봄에 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.다음과 같이해볼 수 있습니다 :
다음과 같이해볼 수 있습니다 :
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.필드를 List (인터페이스)가 아닌 ArrayList (구체적인 유형)로 정의하십시오.
필드를 List (인터페이스)가 아닌 ArrayList (구체적인 유형)로 정의하십시오.
private List emailAddresses = new ArrayList();
from https://stackoverflow.com/questions/5900840/post-nested-object-to-spring-mvc-controller-using-json by cc-by-sa and MIT license
'SPRING' 카테고리의 다른 글
[SPRING] Spring에서 예약 된 작업을 조건부로 활성화 또는 비활성화하는 방법은 무엇입니까? (0) | 2018.12.14 |
---|---|
[SPRING] 봄 부팅 및 SQLite (0) | 2018.12.14 |
[SPRING] Spring 캐시 @ 동일한 클래스에서 호출 할 때 캐시 가능한 메소드가 무시됩니다. (0) | 2018.12.14 |
[SPRING] 메서드 봄에 게시물 mvc에서 param을 얻는 방법? (0) | 2018.12.14 |
[SPRING] Spring 3.0.5에서 매개 변수 바인딩이 쉼표를 해석하지 못하게하는 방법은 무엇입니까? (0) | 2018.12.14 |