Post Nested Object to Spring MVC controller using JSON

后端 未结 4 1373
一生所求
一生所求 2020-11-30 02:08

I have a controller with the POST handler defined like so:

@RequestMapping(value=\"/ajax/saveVendor.do\", method = RequestMethod.POST)
public @ResponseBody A         


        
相关标签:
4条回答
  • 2020-11-30 02:24

    first, sorry for my poor english

    in spring, if the param name is like object[0][field], they will consider it as a class type like sub

    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;
        }
    }
    

    that's why the spring will throw an exception said something "is neither an array nor a List nor a Map".

    only when the param name is object[0].field, spring will treat it as a class's field.

    you could find the constants def in org.springframework.beans.PropertyAccessor

    so my solution is write a new param plugin for jquery, like below:

    (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);
    

    actual I just change the code from

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

    to

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

    and use $.customParam instead of $.param when do ajax request.

    0 讨论(0)
  • 2020-11-30 02:33

    Define the field to be List (interface), not ArrayList (concrete type):

    private List emailAddresses = new ArrayList();
    
    0 讨论(0)
  • 2020-11-30 02:44

    Update: since Spring 3.1, it's possible to use @Valid On @RequestBody Controller Method Arguments.

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

    After much trial and error, I've finally figured out, as well as I can, what the problem is. When using the following controller method signature:

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

    The client script has to pass the field in the object in post-data (typically "application/x-www-form-urlencoded") format (i.e., field=value&field2=value2). This is done in jQuery like this:

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

    This works fine for simple POJO objects that don't have child objects or collections, but once you introduce significant complexity to the object being passed, the notation used by jQuery to serialize the object data is not recognized by Spring's mapping logic:

    object[0][field]
    

    The way that I solved this problem was to change the method signature in the controller to:

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

    And change the call from client to:

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

    This requires the use of the JSON javascript library. It also forces the contentType to "application/json", which is what Spring expects when using the @RequestBody annotation, and serializes the object to a format that Jackson can deserialize into a valid object structure.

    The only side effect is that now I have to handle my own object validation inside of the controller method, but that's relatively simple:

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

    If anyone has any suggestions to improve upon this process, I'm all ears.

    0 讨论(0)
  • 2020-11-30 02:49

    You can try something like this:

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

    :)

    0 讨论(0)
提交回复
热议问题