I'm trying to post a object that includes an id (int) and a array, but I get a http 400 Bad request response from the server side. This is what I have so far...
Java Bean Object for Request:
public class GuardarVentaRequest {
private Integer idCliente;
private List<Venta> venta; ... (Getters and Setters code)
Java Object:
public class Venta {
private Integer id;
private String nombre;
private Integer precio;
private Integer cantidad;
private Integer total; ... (Getters and Setters code)
Java Controller:
#RequestMapping(value = "/guardarVenta", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE)
public #ResponseBody void venta(#RequestBody GuardarVentaRequest factura){
System.out.println(factura);
}
AngularJS Service:
function guardarVenta(array){
let factura = {
idCliente : parseInt($('#cliente').val()),
venta : array,
};
console.log(factura);
$http({
method: 'POST',
url: '/blue/guardarVenta',
contentType: 'application/json',
data: factura
}).then(
function successCallback(response){
console.log(response.statusText);
},
function errorCallback(response){
console.log(response.statusText);
}
);
}
Array:
$scope.productos = new Array();
let productoInfo = {
id: $scope.producto.id,
nombre: $scope.producto.nombre,
precio: $scope.producto.precio,
cantidad: $scope.cantidadProducto,
total: $scope.producto.precio * $scope.cantidadProducto
}
$scope.productos.push(productoInfo);
Output:
ADVERTENCIA: Failed to read HTTP message:
org.springframework.http.converter.HttpMessageNotReadableException: Could
not read document: Can not construct instance of com.blue.beans.Venta: no
suitable constructor found, can not deserialize from Object value (missing
default constructor or creator, or perhaps need to add/enable type
information?)
at [Source: java.io.PushbackInputStream#663359f3; line: 1, column: 28]
(through reference chain: com.blue.beans.GuardarVentaRequest["venta"]-
>java.util.ArrayList[0]); nested exception is
com.fasterxml.jackson.databind.JsonMappingException: Can not construct
instance of com.blue.beans.Venta: no suitable constructor found, can not
deserialize from Object value (missing default constructor or creator, or
perhaps need to add/enable type information?)
at [Source: java.io.PushbackInputStream#663359f3; line: 1, column: 28]
(through reference chain: com.blue.beans.GuardarVentaRequest["venta"]-
>java.util.ArrayList[0])
Chrome's Network Tab Output
Any ideas?
Try these 3 things.
Add the default constructor to GuardarVentaRequest and Venta.
GuardarVentaRequest(){} &
Venta(){}
Check if a HTTPMessageConverter has been added to your spring config. Eg: MappingJackson2MessageConverter (Check for compatibility with your spring version).
Try serializing the request payload by using angular.toJson
Hope that helps!
Related
I am having a little problem while sending data from my React app to my Spring Boot Controller, I am sending the data via a put method, but I get 400, error, and an error in eclipse pops up, so What I did is :
export const changeContratTypes = (idContrat, items, declaration) => {
const endpoint = template(CONTRAT_TYPES_CHANGE);
return instance // just an axios instance
.put(endpoint({ idContrat }), { items, declaration })
.then(values => values)
.catch(err => err.response);
};
My endpoint constant is the url, simple is that, and I send declaration which is an integer and items which is an array of object, my object structure is :
{
id: 1, // or 2, 3, ....
isSelected: true, // or false
title: "a String here"
}
To get this in Spring boot I created this method in my controller :
#CrossOrigin(origins = "*")
#ApiOperation(value = "${contrat.recuperation}", notes = "${contrat.recuperation.notes}", response = ContratDetailDto.class)
#PutMapping(value="/{idContrat}/trtype")
#ApiModelProperty(example = "4000004")
#ApiResponses(value = {
#ApiResponse(code = 200, message = "Enrigistrer Les types de contrats ") })
public ResponseEntity enrigistrerTypesDeContrat(#ApiParam(value = "${contrat.recuperation.param.id}") #PathVariable long idContrat, #RequestBody TypesConformites tcf) {
if (log.isDebugEnabled()) {
log.debug("appel de la méthode enrigistrerTypesDeContrat");
}
System.out.println("Voila "+tcf.getDeclaration());
return ResponseEntity.ok(HttpStatus.OK);
}
This controller is well mapped and other methods in it works fine, but all methods I used are Get Methods.
What I did before that is creating a class used as a RequestBody :
#Getter #Setter
public class TypesConformites {
private int declaration;
private ArrayList<Item> items;
public TypesConformites() {
}
}
and Here is my Item class :
#Getter #Setter
public class Item {
private int id;
private String title;
private boolean isSelected;
public Item() {
}
}
I get this error in Java :
Blockquote
JSON parse error: Unrecognized field "isSelected" (class com.apicil.cosy.contrat.controller.api.impl.external.Item), not marked as ignorable; nested exception is com.fasterxml.jackson.databind.exc.UnrecognizedPropertyException: Unrecognized field "isSelected" (class com.apicil.cosy.contrat.controller.api.impl.external.Item), not marked as ignorable (3 known properties: "title", "id", "selected"])
at [Source: (PushbackInputStream); line: 1, column: 66] (through reference chain: com.apicil.cosy.contrat.controller.api.impl.external.TypesConformites["items"]->java.util.ArrayList[0]->com.apicil.cosy.contrat.controller.api.impl.external.Item["isSelected"])
What's wrong with that code, Any help would be much appreciated.
Generally the Item is deserialised by jackson like this :-
public void setId(String firstName) {
public void setTitle(String lastName) {
public void setSelected(boolean isActive) {
To avoid this you can just changed the mapping name in the Item and request body.. or annotated your isSelected with #JsonProperty
I'm new to Typescript and started using it to play with Ionic. I come from a Java background and I'm experiencing some trouble getting accustomed to it's syntax and way of doing things. Specifically creating new object instances and getting them initialized.
Recently I came across this issue which I cannot explain. I'm sending a HTTP GET request and getting it's response in a Javascript Object.
To work with this response, I'm trying to map this Javascript Object I received to a typescriptclass of mine which I called HttpResponse, creating a new instance of it when I receive the response. To make things easier I created a simple fiddle which ilustrates my problem.
You can find the quick written fiddle on this link
As you can see on the output, object data is there but when I invoke the HttpResponse it just creates an empty instance, it contains nothing, and I don't understand why it does so.
Any help appreciated! Here's the code:
class HttpResponse {
constructor(status: number = 0,
data: string = '',
headers: Object = '',
error: string = ''){}
}
class Page {
response: HttpResponse;
retrieveData(): void {
document.body.innerHTML += 'inside retrieveData()<br>';
let data = {
status: 200, data: "this is some fake data",
headers: { foo: 'foo', bar: 'bar', baz: 'baz' }
}
document.body.innerHTML +='Data: ' + JSON.stringify(data) + '<br>';
this.response = new HttpResponse(data.status, data.data, data.headers);
document.body.innerHTML += JSON.stringify(this.response);
}
}
new Page().retrieveData();
In the constructor of your HttpResponse class, you are not setting the properties of the class with the passed arguments. You can do so by using the public qualifier in the parameters.
class HttpResponse {
constructor(public status: number = 0,
public data: string = '',
public headers: Object = '',
public error: string = ''){ }
}
See the updated Fiddle.
Alternatively, you can declare the properties in your class and set them in the constructor like this.data = data; assignments.
Because your HttpResponse does not actually have any properties. You only have constructor parameters and nothing else. If you want to automatically convert your constructor parameters into properties, you can do so by using parameter properties:
class HttpResponse {
constructor(
public status: number = 0,
public data: string = '',
public headers: Object = '',
public error: string = ''){}
}
You must have to declare constructor parameters using qualifiers i.e. private public protected
class HttpResponse {
constructor(
private status: number = 0,
private data: string = '',
private headers: Object = '',
private error: string = ''){}
}
My requirement is to pass multiple class object(which is going to map with request param or request body in rest controller) in angular $http service.
I can't pass class object in request body since one http call has only one request body.
When i'm trying to pass class object in $http param I'm getting class cast exception.
is there any way to pass multiple object in single $http call ??
#RequestMapping(value = "CommitObject", method = RequestMethod.POST)
public void Commit(#RequestParam(value = "clazz") final String clazz,
#RequestParam(value = "Id") final String modelId,#RequestBody LinkedHashMap<String, Object> obj) {
mService.Commit(Id,clazz workingmodelObj);
}
I tried it many times but always failed.So to overcome this I use to create a Java Bean and encapsulate both objects into it at then accepting it in #RequestBody.
Your Wrapper should look like this:
public class SampleWrapper {
private Object1 object1;
private Object2 object2;
//getters setters and constructors go here
}
And your constructor should look like this:
#RequestMapping(value = "/url",method= RequestMethod.POST)
public String getRequest(#RequestBody SampleWrapper wrapper) {
Object1 o1 = wrapper.getObject1();
Object2 o2 = wrapper.getObject2();
}
JSON request should look like this:
{
"object1" : {//object1 body },
"object2" : {//object2 body }
}
If you're trying to make a $http.post request you can make an array of the objects you are trying to send and then attach it in the request.
$http.put('ROUTE HERE', Array Of Objects);
You can pass Objects by appending it in formData
Angular side
var formData=new FormData();
formData.append("obj1",new Blob([JSON.stringify(obj1)],{type: "application/json"}));
formData.append("obj2",new Blob([JSON.stringify(obj2)],{type: "application/json"}));
http({
method: 'POST',
url: '/MyProject/requestUrl',
headers: { 'Content-Type': undefined},//to set boundary value by default
data: formData,
})
Spring side
#RequestMapping(value = { "/requestUrl" }, consumes = { "multipart/form-data" }, method = RequestMethod.POST)
#ResponseBody
public String getRequest(#RequestPart("obj1") Object1 object1,
#RequestPart("obj2") Object2 Object2,HttpSession session)
throws IOException {
//implementation
}
I try to send a JSON object from JavaScript to Controller. I get 400 status code when sending the object. I tried many methods, but my last approach is below. Do I miss something? Why do I keep getting 400?
ajax post call:
$.ajax({
type: "POST",
url: "http://localhost:8080/services/saveReservation",
cache: false,
data: saveReservation,
contentType: "application/json; charset=utf-8",
dataType: "json",
success: function(data) {
console.debug("After success");
}
});
my object looks like this:
{
"accommodaion":"a0d8185c-a238-5qe7-aa48-5c196b108aba",
"totalOutputPrice":90,
"bookerInfo":{
"country":"xa",
"homeAddress":"",
"phoneNumber":"0019382663773",
"contactChoice":"phone",
"name":"test name",
"id":"87"
},
"creditCard":{
"holderName":"holder test",
"cardType":"discover",
"cardNumber":"6011303031648258",
"expMonth":"01",
"expYear":"2017",
"cvc":"123"
},
"checkIn":"2016-11-16 06:43:19.77",
"checkOut":"2017-03-16 06:43:19.77",
"totalTax":4,
"totalVat":13,
"roomOutputPrice":"77"
}
Controller:
#RequestMapping(value = "/saveReservation", method = RequestMethod.POST)
public test saveReservation(
#RequestBody test saveReservation) {
System.out.println(saveReservation.getAccommodaion());
System.out.println(saveReservation.getBookerInfo().getName());
System.out.println(saveReservation);
return saveReservation;
}
Classes:
private class bookerInfo {
private String country;
private String homeAddress;
private String phoneNumber;
private String contactChoice;
private String name;
private String id;
//getters setters
}
private class creditCard {
private String holderName;
private String cardType;
private String expMonth;
private String expYear;
private String cvc;
//getters setters
}
private class test {
private String accommodaion;
private Float totalOutputPrice;
private bookerInfo bookerInfo;
private creditCard creditCard;
private String checkIn;
private String checkOut;
private Float totalTax;
private Float totalVat;
private Float roomOutputPrice;
//getters setters
}
Error in framework:
16:12:14,096 DEBUG ExceptionHandlerExceptionResolver:134 - Resolving exception from handler [public ba.projectService.controller.test ba.projectService.controller.G2BController.saveReservation(ba.projectService.controller.test)]: org.springframework.http.converter.HttpMessageNotReadableException: Could not read JSON: Unrecognized field "cardNumber" (class ba.projectService.controller.creditCard), not marked as ignorable (5 known properties: , "cvc", "expMonth", "holderName", "cardType", "expYear"])
at [Source: org.apache.catalina.connector.CoyoteInputStream#793ea01d; line: 1, column: 287] (through reference chain: ba.projectService.controller.test["creditCard"]->ba.projectService.controller.creditCard["cardNumber"]); nested exception is com.fasterxml.jackson.databind.exc.UnrecognizedPropertyException: Unrecognized field "cardNumber" (class ba.projectService.controller.creditCard), not marked as ignorable (5 known properties: , "cvc", "expMonth", "holderName", "cardType", "expYear"])
at [Source: org.apache.catalina.connector.CoyoteInputStream#793ea01d; line: 1, column: 287] (through reference chain: ba.projectService.controller.test["creditCard"]->ba.projectService.controller.creditCard["cardNumber"])
16:12:14,099 DEBUG ResponseStatusExceptionResolver:134 - Resolving exception from handler [public ba.projectService.controller.test ba.projectService.controller.G2BController.saveReservation(ba.projectService.controller.test)]: org.springframework.http.converter.HttpMessageNotReadableException: Could not read JSON: Unrecognized field "cardNumber" (class ba.projectService.controller.creditCard), not marked as ignorable (5 known properties: , "cvc", "expMonth", "holderName", "cardType", "expYear"])
at [Source: org.apache.catalina.connector.CoyoteInputStream#793ea01d; line: 1, column: 287] (through reference chain: ba.projectService.controller.test["creditCard"]->ba.projectService.controller.creditCard["cardNumber"]); nested exception is com.fasterxml.jackson.databind.exc.UnrecognizedPropertyException: Unrecognized field "cardNumber" (class ba.projectService.controller.creditCard), not marked as ignorable (5 known properties: , "cvc", "expMonth", "holderName", "cardType", "expYear"])
at [Source: org.apache.catalina.connector.CoyoteInputStream#793ea01d; line: 1, column: 287] (through reference chain: ba.projectService.controller.test["creditCard"]->ba.projectService.controller.creditCard["cardNumber"])
16:12:14,099 DEBUG DefaultHandlerExceptionResolver:134 - Resolving exception from handler [public ba.projectService.controller.test ba.projectService.controller.G2BController.saveReservation(ba.projectService.controller.test)]: org.springframework.http.converter.HttpMessageNotReadableException: Could not read JSON: Unrecognized field "cardNumber" (class ba.projectService.controller.creditCard), not marked as ignorable (5 known properties: , "cvc", "expMonth", "holderName", "cardType", "expYear"])
at [Source: org.apache.catalina.connector.CoyoteInputStream#793ea01d; line: 1, column: 287] (through reference chain: ba.projectService.controller.test["creditCard"]->ba.projectService.controller.creditCard["cardNumber"]); nested exception is com.fasterxml.jackson.databind.exc.UnrecognizedPropertyException: Unrecognized field "cardNumber" (class ba.projectService.controller.creditCard), not marked as ignorable (5 known properties: , "cvc", "expMonth", "holderName", "cardType", "expYear"])
at [Source: org.apache.catalina.connector.CoyoteInputStream#793ea01d; line: 1, column: 287] (through reference chain: ba.projectService.controller.test["creditCard"]->ba.projectService.controller.creditCard["cardNumber"])
You need to serialize your javascript object with JSON.stringify.
Try:
$.ajax({
type: "POST",
url: "http://localhost:8080/services/saveReservation",
cache: false,
data: JSON.stringify(saveReservation),
contentType: "application/json; charset=utf-8",
dataType: "json",
processData: false,
success: function(data) {
console.debug("After success");
}
});
And add the content type to the request mappging:
#RequestMapping(value = "/saveReservation", method = RequestMethod.POST, consumes=MediaType.APPLICATION_JSON_VALUE)
Make sure you have defined the json message converters. If you have Jackson as dependecy Spring do it for you.
Your new message is clear. You are sending a property from javascript that does not exist in your class. I am not sure whether you made a mistake or it is what you want. The property "cardNumber" does not exist at your CreditCard bean.
You can configure Jackson to ignore the unknown properties without throwing an exception using the annotation #JsonIgnoreProperties at the top of your class.
#JsonIgnoreProperties(ignoreUnknown = true)
private class creditCard {
private String holderName;
private String cardType;
private String expMonth;
private String expYear;
private String cvc;
//getters setters
}
If you want to send more than one credit card you could use a list. So, just changing your test bean to be able to work with a list of cards
private class test {
...
private List<creditCard> creditCards;
...
//getters setters
}
and at your javascript object, it should be now an array
{
... here all your properties
"creditCards":[{
"holderName":"card 1",
"cardType":"discover",
"cardNumber":"6011303031648258",
"expMonth":"01",
"expYear":"2017",
"cvc":"123"
},{
"holderName":"card 2",
"cardType":"discover",
"cardNumber":"6011303031648258",
"expMonth":"01",
"expYear":"2017",
"cvc":"123"
},{
"holderName":"card 3",
"cardType":"discover",
"cardNumber":"6011303031648258",
"expMonth":"01",
"expYear":"2017",
"cvc":"123"
}],
... more properties
}
should be enough. I hope you get the idea.
I'm having a class like the following:
using System;
using System.Collections.Generic;
using System.Runtime.Serialization;
[DataContract()]
public class TestCol : List<Test> { }
[DataContract()]
public class MainTest
{
public TestCol Components { get; set; }
}
[DataContract()]
public class Test
{
public Test() { }
public String Name { get; set; }
}
And a webservice with the following webmethod like this:
[WebMethod]
public String Test(MainTest input)
{
String rtrn = String.Empty;
foreach (Test test in input.Components)
rtrn += test.Name;
return rtrn;
}
Which is called by AJAX with the following method:
var Test = {};
Test.Name = "Test";
var MainTest = {};
MainTest.Components = [];
MainTest.Components.push(Test);
$.ajax({
type: "POST",
url: "WebService/WSTest.asmx/Test",
contentType: "application/json; charset=utf-8",
dataType: "json",
data: JSON.stringify({
"input": MainTest
}),
success: function(data, textStatus) {
console.log("success");
},
error: function(XMLHttpRequest, textStatus, errorThrown) {
window.console && console.log && console.log(XMLHttpRequest.responseText + " || " + textStatus + " || " + errorThrown);
}
});
When executing the AJAX call, it will return errors. I found out that the error is with the typed class TestCol, which has no properties.
Now do I have found 2 solutions that require changes in the C# classes:
Remove the TestCol class and change the Components property to List<Test> datatype:
[DataContract()]
public class MainTest
{
public List<Test> Components { get; set; }
}
[DataContract()]
public class Test
{
public Test() { }
public String Name { get; set; }
}
Or add an extra property to the TestCol class and change the webmethod:
[DataContract()]
public class TestCol : List<Test>
{
public List<Test> Components { get; set; }
}
[DataContract()]
public class MainTest
{
public TestCol Components { get; set; }
}
[DataContract()]
public class Test
{
public Test() { }
public String Name { get; set; }
}
&
[WebMethod]
public String Test(MainTest input)
{
String rtrn = String.Empty;
foreach (Test test in input.Components.Components)
rtrn += test.Name;
return rtrn;
}
Both solutions require changes in the C# classes, which I prefer not to, as other code is depended on it. Does anyone know a solution for this problem?
Edit: I've uploaded a test solution, containing above code: http://jeroenvanwarmerdam.nl/content/temp/JSONtoClassWebservice.zip
So this solution changes the List to Object instead of Test. I hoped to change as little code as possible (i dislike having to do casts in foreach loops). The below code does so with two function additions and the previously mentioned inheritance change.
public class TestCol : List<object>
{
public new IEnumerator<Test> GetEnumerator()
{
return this.ConvertAll<Test>(
dict => ConvertDictionaryTo<Test>(
(Dictionary<string, object>) dict
)
).GetEnumerator();
}
private T ConvertDictionaryTo<T>(IDictionary<string, object> dictionary) where T : new()
{
Type type = typeof(T);
T ret = new T();
foreach (var keyValue in dictionary)
{
type.GetProperty(keyValue.Key).SetValue(ret, keyValue.Value, null);
}
return ret;
}
}
Convert function courtesy TurBas
Mapping object to dictionary and vice versa
JavaScriptSerializer serialization: IEnumerable -> JavaScript Array
When the JavaScriptSerializer is used, it automatically converts an IEnumerable (without IDictionary) type -- that covers List<> or anything derived from it -- into an array.
Deserialization: JavaScript Array -> IEnumerable -> Collection Object
Now, upon deserialization from JSON, the JavaScriptSerializer must take the array, create an IEnumerable, then create an object for the field by passing that IEnumerable into its constructor.
Constructing Collection object via Constructor
Now, for List<> you have a constructor overload that takes an IEnumerable. So if you put List<Test> as the type of your component it creates it fine.
Constructors not inherited
However, TestCol does NOT have such a constructor! The reason why it worked with List<Test> and not with TestCol (which derives from List<Test>) is that the only thing that is not inherited between classes are constructors!
Therefore, the JavaScriptSerializer does not have any way to construct a TestCol from an IEnumerable. So it fails silently.
Deserialize Array by Creating List, then Casting to Type
Now the JavaScriptSerializer may then attempt to create a List<Test> from this IEnumerable<Test>, and then try to cast it into a TestCol.
Possible Solution
Solution: Try putting in:
public TestCol () {} // Need this when you have another constructor
public TestCol (IEnumerable<Test> list) : base(list) {} // Constructor that takes an IEnumerable
public TestCol (IList<Test> list) : base(list) {} // Constructor that takes an IList
as your TestCol's constructors.
And if it still doesn't work, implement an explicit type cast from List<Test> to TestCol.
public static explicit operator TestCol(IList<Test> list) { return new TestCol(list); }
hmmm this didn't work in the web method?
foreach (Test test in input.Components.TestCol)
Re comment below, does this work then?
foreach (Test test in (List<Test>)input.Components.TestCol)
It should work because a class can be enumerated...
If you're expecting JSON, you'll need to return JSON.
Check with the System.Web.Script.Serialization.JavaScriptSerializer
http://msdn.microsoft.com/en-us/library/system.web.script.serialization.javascriptserializer.aspx
If you use ASMX services the JavaScriptSerializer will be responsible for the data conversion and not the DataContractJsonSerializer. So all DataContract attributes which you use will not work.
You are write that classes like public class TestCol : List<Test> { } are bad for the JavaScriptSerializer, but classes having List<Test> as the property (public class MainTest { public List<Test> Components { get; set; }}) have no problem.
So I suggest to simplify you code to the following. The classes used as the parameters can be defines as
public class Test {
public String Name { get; set; }
}
public class MainTest {
public List<Test> Components { get; set; }
}
The WebMethod Test will be
[WebMethod]
public String Test(MainTest input)
{
StringBuilder rtrn = new StringBuilder();
foreach (Test test in input.Components) {
rtrn.AppendLine (test.Name);
}
return rtrn.ToString ();
}
and the ajax call can be
var tests = {
Components: [
{Name:"Test1"},
{Name:"Test2"},
{Name:"Test3"}
]
};
$.ajax({
type: "POST",
url: "WebService1.asmx/Test",
contentType: "application/json; charset=utf-8",
dataType: "json",
data: JSON.stringify({
"input": tests
}),
success: function (data, textStatus) {
alert("success:\n" + data.d);
},
error: function (XMLHttpRequest, textStatus, errorThrown) {
alert(XMLHttpRequest.responseText+" || "+textStatus+" || "+errorThrown);
}
});
How you can see all will be very simple and it's work. For more details how you can send complex data I recommend you to read another answer and this.
You seem to be using ASMX (not WCF) because you have omitted [DataMember] attributes on all your public properties and still get serialized. WCF is "opt-in", so you shouldn't be seeing any serialization of any properly.
As a result, all [DataContract] attributes are useless.
ASMX defaults to the JavaScriptSerializer if you are using ScriptManger and outputing JSON. The JavaScriptSerializer is "opt-out" (which means that all public properties are automatically serialized unless marked with [ScriptIgnoreAttribute]).
The JavaScriptSerializer supports serializing List<>'s. You should not be having problems serializing your TestCol property because JavaScriptSerializer automatically supports serializing all types that implement IEnumerable (but not IDictionary) -- which includes List<> -- into JSON arrays.
Your error seems to be that the JavaScriptSerializer does not properly handle classes that inherit from List<> (or from a class implementing IEnumerable). In your first work-around, you eliminated the class that inherited from List<>. In your second work-around, you skipped all functionalities of the base class, but re-implemented the List<> in a property.
Your JSON post data currently looks like:
{ Components: [
{ Name:"foo" },
{ Name:"bar" },
:
] }
However, you have one extra level or redirection in the serializer (inheriting from List<Test> -> TestCol). It is possible that the serializer is looking for:
{ Components: {
Items: [
{ Name:"foo" },
{ Name:"bar" },
:
] }
}
because you are essentially serializing the "Items" property of List<>. So, your JSON post data is just feeding Test objects to the wrong place, and your TestCol Components property ends up empty.
I'd suggest that you add a web service method to output a test MainTest object to see what it serializes into. You'll probably find that it puts in an additional level.