私は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: "[email protected]", flags: 2 },
{ emailAddress: "[email protected]", flags: 3 }
]
}
UIVendor Beanには、適切なセッターとゲッター(getEmails/setEmails)を持つ、ArrayListタイプの「Emails」というフィールドがあります。 NotificationEmailオブジェクトには、適切なパブリックセッター/ゲッターもあります。
次のコードを使用してオブジェクトを投稿しようとすると:
$.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 [[email protected]]
このようにネストされたオブジェクトをSpringコントローラに正しくポストし、適切なオブジェクト構造に正しくデシリアライズするにはどうすればよいですか。
[〜#〜] update [〜#〜] Bohzoのリクエストによると、ここにUIVendorクラスのコンテンツがあります。このクラスは、Webサービスによって生成されたBeanクラスをラップし、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;
}
}
PDATE 2ジャクソンからの出力です。:
{
"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":"[email protected]"
}
]
}
Www.json.orgのJSONライブラリを使用してシリアル化も試みましたが、結果は上記のとおりです。ただし、そのデータを投稿すると、コントローラーに渡されたUIVendorオブジェクトのすべてのフィールドがnullになります(オブジェクトはそうではありません)。
Update:Spring 3.1以降、 @ Valid On @RequestBodyコントローラーメソッドの引数 を使用することができます。
@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 [〜#〜] JavaScriptライブラリを使用する必要があります。また、contentTypeを「application/json」に強制します。これは、@ RequestBodyアノテーションを使用するときにSpringが期待するものであり、Jacksonが有効なオブジェクト構造にデシリアライズできる形式にオブジェクトをシリアル化します。
唯一の副作用は、コントローラーメソッド内で独自のオブジェクト検証を処理する必要があることですが、それは比較的簡単です。
BindingResult result = new BeanPropertyBindingResult( object, "MyObject" );
Validator validator = new MyObjectValidator();
validator.validate( object, result );
誰かがこのプロセスを改善するための提案を持っている場合、私はすべて耳にしています。
まず、英語が下手でごめんなさい
春に、パラメータ名がオブジェクト[0] [フィールド]のようなものである場合、サブクラスのようなクラス型と見なされます
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の場合のみ、Springはそれをクラスのフィールドとして扱います。
あなたは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を使用します。
あなたはこのようなことを試すことができます:
vendor['emails[0].emailAddress'] = "[email protected]";
vendor['emails[0].flags'] = 3;
vendor['emails[1].emailAddress'] = "[email protected]";
vendor['emails[1].flags'] = 3;
:)
フィールドをList
(コンクリートタイプ)ではなくArrayList
(インターフェース)として定義します。
private List emailAddresses = new ArrayList();