2

I have the error in the title when a try to call webApi. This is my class:

public class NotificationDTO
{
    public long idCompanyService { get; set; }

    public DocTypeEnum DocumentType { get; set; }

    public List<Tuple<string, byte[], System.Net.Mime.ContentType>> Attachments { get; set; }
}

This in my object:

NotificationDTO notifDto = new NotificationDTO()
                {
                    idCompanyService = idCompServ,
                    DocumentType = DocType,
                    Attachments = listAttac
                };

And this is the call:

HttpContent content = new StringContent(JsonConvert.SerializeObject(notifDto), Encoding.UTF8, "application/json");
HttpResponseMessage response = client.PostAsync(uri, content).Result;

This is a Json:

{"idCompanyService":1234,"DocumentType":0,"Attachments":[{"Item1":"test.pdf","Item2":"ThisIsABase64Data","Item3":{"Boundary":null,"CharSet":null,"MediaType":"application/pdf","Name":null,"Parameters":[]}}}

Attachments have the problem because is a List<Tuple<string, byte[], ContentType>>.

Error message:

Exception: Newtonsoft.Json.JsonSerializationException: Cannot populate list type System.Net.TrackingStringDictionary. Path 'Attachments.$values[0].Item3.Parameters.$values', line 1, position 105257.
   in Newtonsoft.Json.Serialization.JsonSerializerInternalReader.CreateList(JsonReader reader, Type objectType, JsonContract contract, JsonProperty member, Object existingValue, String id)
   in Newtonsoft.Json.Serialization.JsonSerializerInternalReader.ReadMetadataProperties(JsonReader reader, Type& objectType, JsonContract& contract, JsonProperty member, JsonContainerContract containerContract, JsonProperty containerMember, Object existingValue, Object& newValue, String& id)
   in Newtonsoft.Json.Serialization.JsonSerializerInternalReader.CreateObject(JsonReader reader, Type objectType, JsonContract contract, JsonProperty member, JsonContainerContract containerContract, JsonProperty containerMember, Object existingValue)
   in Newtonsoft.Json.Serialization.JsonSerializerInternalReader.CreateValueInternal(JsonReader reader, Type objectType, JsonContract contract, JsonProperty member, JsonContainerContract containerContract, JsonProperty containerMember, Object existingValue)
   in Newtonsoft.Json.Serialization.JsonSerializerInternalReader.SetPropertyValue(JsonProperty property, JsonConverter propertyConverter, JsonContainerContract containerContract, JsonProperty containerProperty, JsonReader reader, Object target)
   in Newtonsoft.Json.Serialization.JsonSerializerInternalReader.PopulateObject(Object newObject, JsonReader reader, JsonObjectContract contract, JsonProperty member, String id)

Object Example

Try example: You can reproduce the error with a simple web api controller:

[HttpPost]
[Route("api/TestNotification")]
public IHttpActionResult TestNotification(NotificationDTO dto)
{
    return Ok();
}

And a Json like this in a Post Caller:

{"idCompanyService":1234,"DocumentType":1,"Attachments":[{"Item1":"wqere.pdf","Item2":"TWFuIGlzIGRpc3Rpbmd1aXNoZWQsIG5vdCBvbmx5IGJ5IGhpcyByZWFzb24sIGJ1dCBieSB0aGlzIHNpbmd1bGFyIHBhc3Npb24gZnJvbSBvdGhlciBhbmltYWxzLCB3aGljaCBpcyBhIGx1c3Qgb2YgdGhlIG1pbmQsIHRoYXQgYnkgYSBwZXJzZXZlcmFuY2Ugb2YgZGVsaWdodCBpbiB0aGUgY29udGludWVkIGFuZCBpbmRlZmF0aWdhYmxlIGdlbmVyYXRpb24gb2Yga25vd2xlZGdlLCBleGNlZWRzIHRoZSBzaG9ydCB2ZWhlbWVuY2Ugb2YgYW55IGNhcm5hbCBwbGVhc3VyZS4=","Item3":{"Boundary":null,"CharSet":null,"MediaType":"application/pdf","Name":null,"Parameters":[]}},{"Item1":"ewqeqwe.xml","Item2":"TWFuIGlzIGRpc3Rpbmd1aXNoZWQsIG5vdCBvbmx5IGJ5IGhpcyByZWFzb24sIGJ1dCBieSB0aGlzIHNpbmd1bGFyIHBhc3Npb24gZnJvbSBvdGhlciBhbmltYWxzLCB3aGljaCBpcyBhIGx1c3Qgb2YgdGhlIG1pbmQsIHRoYXQgYnkgYSBwZXJzZXZlcmFuY2Ugb2YgZGVsaWdodCBpbiB0aGUgY29udGludWVkIGFuZCBpbmRlZmF0aWdhYmxlIGdlbmVyYXRpb24gb2Yga25vd2xlZGdlLCBleGNlZWRzIHRoZSBzaG9ydCB2ZWhlbWVuY2Ugb2YgYW55IGNhcm5hbCBwbGVhc3VyZS4=","Item3":{"Boundary":null,"CharSet":null,"MediaType":"text/xml","Name":null,"Parameters":[]}}]}

Finally, a .Net fiddle that demonstrates the failure when deserializing the above JSON string to a NotificationDTO directly, via:

var dto = JsonConvert.DeserializeObject<NotificationDTO>(json);

can be found here: https://dotnetfiddle.net/EaPhm9.

dbc
  • 104,963
  • 20
  • 228
  • 340
ricga
  • 79
  • 8
  • Can you please [edit] your question to share a full [mcve]? In this case I think a definition for `NotificationDTO`, your deserialization code, and sample JSON would be sufficient. – dbc Oct 09 '18 at 13:51
  • Your error message seems pretty good, the serialisation is failing on character position 105257 when trying to serialise a `System.Net.TrackingStringDictionary` - not all types can be converted to JSON. As an aside, that's a pretty large number, looks like you are sending out file data? Hard to tell without seeing your structure. – DavidG Oct 09 '18 at 14:46
  • ok I edit my question @dbc – ricga Oct 09 '18 at 14:51
  • That's not really a [mcve] though. At what line does the exception occur? Is it at `HttpContent content = new StringContent(JsonConvert.SerializeObject(notifDto), Encoding.UTF8, "application/json");` or at `HttpResponseMessage response = client.PostAsync(uri, content).Result;`? How can I reproduce the problem in a simple console app? – dbc Oct 09 '18 at 15:02
  • There is no line number in the message. When I call my WebApi, before that arrived on the first line, throw the exception in a log file. – ricga Oct 09 '18 at 15:30
  • I add try example – ricga Oct 09 '18 at 15:59
  • @ricga - thanks for the additional information, but without the class definition for `NotificationDTO` I don't think we can help. Perhaps the "Object Example" screenshot is intended to show its class definition. If so, might you please re-[edit] your question to include your code as **text** rather than as a screenshot? It's policy here not to to use images for this purpose, see [*Discourage screenshots of code and/or errors*](https://meta.stackoverflow.com/a/307500) and [*Why not upload images of code on SO when asking a question*](https://meta.stackoverflow.com/a/285557) for why. – dbc Oct 09 '18 at 16:15
  • @dbc ok sorry, I add my class. The screenshot is to view the object Item3.Parameters.$values – ricga Oct 10 '18 at 09:41
  • And here's a [mcve]: https://dotnetfiddle.net/a49FwW. Looks like the real problem is in deserializing a `System.Net.Mime.ContentType` with Json.NET. – dbc Oct 10 '18 at 12:26

1 Answers1

2

Your real problem here is that your DTO refers to objects of type System.Net.Mime.ContentType which in turn has a member ContentType.Parameters which is of declared type System.Collections.Specialized.StringDictionary (and actual type System.Net.TrackingStringDictionary). Unfortunately this ancient type (from .Net 1.1) doesn't even implement IDictionary. Since it only implements the untyped enumerator System.Collections.IEnumerable, Json.NET has no way to know how to deserialize it, and throws the exception you are seeing when asked to do so.

Thus it will be necessary to write a custom JsonConverter to deserialize objects of or derived from this type:

using System.Collections.Specialized;

public class StringDictionaryConverter : JsonConverter
{
    public override bool CanConvert(Type objectType)
    {
        return typeof(StringDictionary).IsAssignableFrom(objectType);
    }

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        var tokenType = reader.SkipComments().TokenType;
        if (tokenType == JsonToken.Null)
            return null;

        var dictionary = existingValue as StringDictionary ?? (StringDictionary)serializer.ContractResolver.ResolveContract(objectType).DefaultCreator();

        switch (tokenType)
        {
            case JsonToken.StartArray:
                {
                    // StringDictionary serialized without converter
                    var list = serializer.Deserialize<List<KeyValuePair<string, string>>>(reader);
                    foreach (var pair in list)
                        dictionary.Add(pair.Key, pair.Value);
                }
                break;

            case JsonToken.StartObject:
                {
                    // StringDictionary serialized with converter
                    var typedDictionary = serializer.Deserialize<Dictionary<string, string>>(reader);
                    foreach (var pair in typedDictionary)
                        dictionary.Add(pair.Key, pair.Value);
                }
                break;

            default:
                throw new JsonSerializationException(string.Format("Unknown token {0} at path {1}", tokenType, reader.Path));
        }

        return dictionary;
    }

    // Change to false if you want the dictionary written as an array of key/value objects.
    public override bool CanWrite { get { return true; } }

    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        var dictionary = (StringDictionary)value;
        writer.WriteStartObject();
        foreach (DictionaryEntry entry in dictionary)
        {
            writer.WritePropertyName((string)entry.Key);
            writer.WriteValue((string)entry.Value);
        }
        writer.WriteEndObject();
    }
}

public static partial class JsonExtensions
{
    public static JsonReader SkipComments(this JsonReader reader)
    {
        while (reader.TokenType == JsonToken.Comment && reader.Read())
            ;
        return reader;
    }
}

Once written, you will need to use the converter on the server side to deserialize the incoming JSON. You don't specify which version of Web API you are using. To add a converter globally, see

You may also want to use the converter on the client side as well to serialize your JSON. If you do, your content parameters will be serialized more compactly as a JSON object, rather than as a key/value array. (The converter accepts both forms as input.)

Sample fiddle here.

dbc
  • 104,963
  • 20
  • 228
  • 340