0

I am struggling with the deserialization of object...I have created same object model both in my application and web api...it looks like below

public class Project
{
    // some propeties of project object..
    public SpecificationCollection Specs { get; set; }
    public Project()
    {
       Specs = new SpecificationCollection();
     }
}

[Serializable()]
public class SpecificationCollection : CollectionBase
{
    public ProjectSpec this[int index]
    {
        get { return (ProjectSpec)List[index]; }
        set { List[index] = value; }
    }
    //other implemented methods
}

public class ProjectSpec
{
   //Properties
   //Some other class object is also in this class but as of now i am not      
  //getting ProjectSpec
}

I have tried the following:

{using newtonsoft refrence here}
Dim settings As New JsonSerializerSettings() 
settings.TypeNameHandling = TypeNameHandling.All //asme in auto typenamehandling
Dim str As Object = Await response.Content.ReadAsStringAsync()
Dim deserializedList As Project = JsonConvert.DeserializeObject(Of Project)(str, settings)

And this also:

Await response.Content.ReadAsAsync(Of Project)()

I am getting the Project object converted fine but not the collection. Please advice how to appraoch this situation. Thanks

SAMPLE JSON

 {"ProjectID":134,"ProjectName":"ABC","LPID":"","DNumber":0,"OrgnCode":"SPt","OrgnDesc":null,"SRatingCode":"AANSub","SRatingDesc":"AASub","ProjectConCode":"","ProjectCon":"desc","OrTCode":"Csity","OrTDesc":"Corsity","Projectsdf":"Miscld","ProjectType":"Miscellaneous","ProjectStatus":"","Street1":"","Street2":"","Street3":"","City":"Princeton","State":"NJ","StateName":"NY","PostalCode":"081","CountyCode":null,"CountyName":null,"CountryCode":"USA            ","CountryName":"UNITED STATES","TCode":"AA03","TName":"A03","IsA":false,"IsF1":false,"IsF2":false,"IsBacked":false,"IsMeeting":false,"IsVerified":true,"HSpec":false,"NumSpecs":0,"BidDate":"1901-01-01T00:00:00","BidStartDate":"0001-01-01T00:00:00","BidEndDate":"0001-01-01T00:00:00","EnteredBy":"User","EnteredDate":"2014-02-26T14:39:00","LockedBy":null,"LockedDate":"0001-01-01T00:00:00","CreatedBy":"dfg","CreatedDate":"2014-02-26T14:39:00","ModifiedBy":"dfgl","ModifiedDate":"2014-05-07T15:03:00","DeletedDate":null,"SysDate":"2016-01-07T07:11:00","TotalRows":0,"MonthsBack":"0001-01-01T00:00:00","SkID":-2147483648,"ArchID":"dfgdfg","AuthoredBy":"0","DModifiedDate":"1901-01-01T00:00:00","DVersion":0,"Flag":0,"OClassCode":null,"ProjectOrClass":null,"StCode":"DEFAULT","StDesc":null,"Specs":[{"SpecId":51993,"ESpecID":"558","Origin":"OS","OrName":"Openings Studio","WriterID":null,"WriterName":null,"DistName":"","ArchitectName":null,"SpecDate":"0001-01-01T00:00:00","SpecEstBidDate":"0001-01-01T00:00:00","ContractorName":null,"ProductLines":null,"CreatedDate":"2014-03-10T11:34:00","CreatedBy":"dfgdfg","ModifiedDate":"2014-03-10T11:34:00","ModifiedBy":"dfgdfg","STProjectName":null,"OwnerType":null,"SRating":null,"StickRating":null,"ProjectValue":0.0},{"SpecId":52000,"ESpecID":"635","Origin":"dfgdfg","OrName":"dfgdfg","WriterID":null,"WriterName":null,"DistName":"","ArchitectName":null,"SpecDate":"0001-01-01T00:00:00","SpecEstBidDate":"0001-01-01T00:00:00","ContractorName":null,"ProductLines":null,"CreatedDate":"2014-03-10T14:08:00","CreatedBy":"SpecTrak","ModifiedDate":"2014-03-10T14:08:00","ModifiedBy":"dfgdfgdfg","STProjectName":null,"OwnerType":null,"SRating":null,"StickRating":null,"ProjectValue":0.0}]}
Sumit Pannu
  • 177
  • 1
  • 15
  • Web API does deserialize objects. Can you check this link to check if your Project object is compatible to your expected JSON. http://jsonutils.com/ – Som Bhattacharyya Mar 23 '16 at 08:39
  • 1
    Put your json string – Krunal Mevada Mar 23 '16 at 09:08
  • see edit update.....i passed the same in above link by Som...its creating IList....i tried that in my app and working fine...can i get it in collection object? as it is already implemented at many places, icant change that – Sumit Pannu Mar 23 '16 at 09:23

1 Answers1

3

The problem is that CollectionBase is a non-generic, untyped collection, and thus Json.NET has no way to know how to deserialize its items unless the $type metadata properties are present in the incoming JSON -- which they apparently are not. In fact, this class is considered to be obsolete. Microsoft's guideline When to Use Generic Collections state:

Using generic collections is generally recommended, because you gain the immediate benefit of type safety without having to derive from a base collection type and implement type-specific members. Generic collection types also generally perform better than the corresponding nongeneric collection types (and better than types that are derived from nongeneric base collection types) when the collection elements are value types, because with generics there is no need to box the elements.

So it is recommended instead to inherit from System.Collections.ObjectModel.Collection<T>:

public class SpecificationCollection : Collection<ProjectSpec>
{
}

That being said, if you must continue to use CollectionBase, you could make your SpecificationCollection implement ICollection<ProjectSpec> despite being untyped underneath. If you do that, Json.NET will be able to deserialize it successfully:

[Serializable()]
public class SpecificationCollection : TypedCollectionBase<ProjectSpec>
{
}

[Serializable()]
public class TypedCollectionBase<TItem> : CollectionBase, IList<TItem>
{
    #region IList<TItem> Members

    public int IndexOf(TItem item)
    {
        return List.IndexOf(item);
    }

    public void Insert(int index, TItem item)
    {
        List.Insert(index, item);
    }

    public TItem this[int index]
    {
        get { return (TItem)List[index]; }
        set { List[index] = value; }
    }

    #endregion

    #region ICollection<TItem> Members

    public void Add(TItem spec)
    {
        List.Add(spec);
    }

    public bool Contains(TItem item)
    {
        return List.Contains(item);
    }

    public void CopyTo(TItem[] array, int arrayIndex)
    {
        foreach (var item in this)
            array[arrayIndex++] = item;
    }

    public bool IsReadOnly { get { return List.IsReadOnly; } }

    public bool Remove(TItem item)
    {
        int index = IndexOf(item);
        if (index >= 0)
            RemoveAt(index);
        return index >= 0;
    }

    #endregion

    #region IEnumerable<TItem> Members

    public new IEnumerator<TItem> GetEnumerator()
    {
        return List.Cast<TItem>().GetEnumerator();
    }

    #endregion
}

And if you cannot modify the design of your SpecificationCollection in any way, you will need to write your own JsonConverter to deserialize it:

public class CollectionBaseConverter<TCollection, TItem> : JsonConverter where TCollection : CollectionBase
{
    public override bool CanConvert(Type objectType)
    {
        return typeof(TCollection).IsAssignableFrom(objectType);
    }

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        if (reader.TokenType == JsonToken.Null)
            return null;
        TCollection collection = (TCollection)(existingValue ?? serializer.ContractResolver.ResolveContract(objectType).DefaultCreator());
        var wrapper = new CollectionBaseWrapper<TCollection, TItem>(collection);
        serializer.Populate(reader, wrapper);
        return collection;
    }

    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        var collection = (TCollection)value;
        serializer.Serialize(writer, collection.Cast<TItem>());
    }
}

class CollectionBaseWrapper<TCollection, TItem> : ICollection<TItem> where TCollection : CollectionBase
{
    readonly IList collection;

    public CollectionBaseWrapper(TCollection collection)
    {
        if (collection == null)
            throw new ArgumentNullException();
        this.collection = collection;
    }

    public void Add(TItem item)
    {
        collection.Add(item);
    }

    public void Clear()
    {
        collection.Clear();
    }

    public bool Contains(TItem item)
    {
        return collection.Contains(item);
    }

    public void CopyTo(TItem[] array, int arrayIndex)
    {
        foreach (var item in this)
            array[arrayIndex++] = item;
    }

    public int Count { get { return collection.Count; } }

    public bool IsReadOnly { get { return collection.IsReadOnly; } }

    public bool Remove(TItem item)
    {
        bool found = collection.Contains(item);
        if (found)
            collection.Remove(item);
        return found;
    }

    public IEnumerator<TItem> GetEnumerator()
    {
        return collection.Cast<TItem>().GetEnumerator();
    }

    IEnumerator IEnumerable.GetEnumerator()
    {
        return GetEnumerator();
    }
}

Then use it in settings as follows:

var settings = new JsonSerializerSettings { TypeNameHandling = TypeNameHandling.Auto, Converters = new[] { new CollectionBaseConverter<SpecificationCollection, ProjectSpec>() } };
var deserializedList = JsonConvert.DeserializeObject<Project>(str, settings);
dbc
  • 104,963
  • 20
  • 228
  • 340