0

I have following json:

{"EventMessageUId":"ef51b5a3-32b2-e611-baf9-fc3fdb446bd2","Message":
"{\"StoryId\":20500,\"StoryDesc\":\"Test Story
data\"}","ProjectId":"1"}

Below is the class in which I'm trying to map it:

public class Requirments
    {

        public int FileID { get; set; }
        public string EventMessageUId { get; set; }
        public string ProjectId { get; set; }
        public List<Message> Message { get; set; }
        //public object[] Message { get; set; }
        public string error { get; set; }
    }

It is taking message tag as a string

  "Message": "{\"StoryId\":20500,\"StoryDesc\":\"Test Story data\"}"

I want to Map it into List<Message>

 public class Message
    {
        public string StoryID { get; set; }
        public string StoryDesc { get; set; }
    }

What can I do for this without changing json?

In the current scenario it gives me an error when I try it with List<Message>

eol
  • 23,236
  • 5
  • 46
  • 64
C Sharper
  • 8,284
  • 26
  • 88
  • 151
  • 1
    What JSON deserialiser are you using, and where is the code relating to this? – G0dsquad Jan 05 '17 at 07:59
  • requirements objRequirement = JsonObject.ToObject(); I am using Newtonsoft Json @G0dsquad – C Sharper Jan 05 '17 at 08:01
  • your json doesn't have Message as a collection. Collections should be wrapped in square brackets e.g. "Message":[{"field":"value"}, {"field":"value"}] – majita Jan 05 '17 at 08:10

5 Answers5

3

This might do the trick for you

string jsonstr = File.ReadAllText(YourJSONFile);
jsonstr = jsonstr.Replace("\"{", "{");
jsonstr = jsonstr.Replace("}\"", "}");
jsonstr = jsonstr.Replace("\\", "");
var ser = JsonConvert.DeserializeObject<MyMessages>(jsonstr);

The classes would look like

public class Message
{
    [JsonProperty("StoryId")]
    public int StoryId { get; set; }
    [JsonProperty("StoryDesc")]
    public string StoryDesc { get; set; }
}

public class MyMessages
{
    [JsonProperty("Message")]
    public Message Message { get; set; }
}

The problem with the JSON is

"Message": "{\"StoryId\":20500,\"StoryDesc\":\"Test Story data\"}"
           ^                                                   ^  

are these " which is making it a string instead of two different properties of JSON. So we removed that "{ and }" with

jsonstr = jsonstr.Replace("\"{", "{");
jsonstr = jsonstr.Replace("}\"", "}");

and now the remaining JSON string will be

"Message": {\"StoryId\":20500,\"StoryDesc\":\"Test Story data\"}
            ^        ^        ^          ^  ^                ^

in which we have \ back slash in the JSON string which will again creates issue while deseralizing the JSON string. So

jsonstr = jsonstr.Replace("\\", "");
Mohit S
  • 13,723
  • 6
  • 34
  • 69
  • This looks promising.. let me try – C Sharper Jan 05 '17 at 08:11
  • 1
    this should do the trick, but keep in mind that the replacement of \", \{, }\ is for the complete String not only the message part. The cleanest option would be changing the dataformat. – Florian Jan 05 '17 at 08:14
2

You have two different problems in deserializing your JSON to your Requirments class:

  1. The Message property contains nested double-serialized data. I.e. the sending system serialized the original Message object to a JSON string then included that in an outer container object that was subsequently itself serialized, causing the inner JSON data to be escaped.

  2. The nested JSON represents a single object - a collection of name/value pairs surrounded by braces. But you want to deserialize to a List<Message>, and all JSON serializers will map a List<T> to a JSON array rather than to a JSON object.

Both these problems can be overcome by using combined with a custom JsonConverter for the Message property. However, the conversion will require two independent steps:

  1. You will need to unwrap the nested double-serialized JSON into a string.

  2. And then map the JSON object thereby unwrapped with a converter similar to SingleOrArrayConverter<T> from How to handle both a single item and an array for the same property using JSON.net.

The following set of converters performs this chained conversion:

public class SingleOrArrayConverter<TCollection, TItem> : SingleOrArrayConverter where TCollection : ICollection<TItem>
{
    public override bool CanConvert(Type objectType)
    {
        if (!base.CanConvert(objectType))
            return false;
        return typeof(TCollection).IsAssignableFrom(objectType);
    }
}

public class SingleOrArrayConverter : JsonConverter
{
    public override bool CanConvert(Type objectType)
    {
        if (objectType.IsArray || objectType == typeof(string) || objectType.IsPrimitive)
            return false;
        Type elementType = null;
        foreach (var type in objectType.GetCollectItemTypes())
        {
            if (elementType == null)
                elementType = type;
            else
                return false;
        }
        return elementType != null;
    }

    object ReadJsonGeneric<TItem>(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        if (reader.TokenType == JsonToken.Null)
            return null;
        var collection = (ICollection<TItem>)(existingValue ?? serializer.ContractResolver.ResolveContract(objectType).DefaultCreator());
        if (reader.TokenType == JsonToken.StartArray)
            serializer.Populate(reader, collection);
        else
            collection.Add(serializer.Deserialize<TItem>(reader));
        return collection;
    }

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        if (reader.TokenType == JsonToken.Null)
            return null;
        if (objectType.IsArray)
            throw new JsonSerializationException("Read-only collections such as arrays are not supported");
        try
        {
            var elementType = objectType.GetCollectItemTypes().SingleOrDefault();
            if (elementType == null)
                throw new JsonSerializationException(string.Format("{0} is not an ICollection<T> for some T", objectType));
            var method = typeof(SingleOrArrayConverter).GetMethod("ReadJsonGeneric", BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Public);
            return method.MakeGenericMethod(new[] { elementType }).Invoke(this, new object[] { reader, objectType, existingValue, serializer });
        }
        catch (Exception ex)
        {
            // Wrap the TargetInvocationException in a JsonSerializerException
            throw new JsonSerializationException("Failed to deserialize " + objectType, ex);
        }
    }

    void WriteJsonGeneric<TItem>(JsonWriter writer, object value, JsonSerializer serializer)
    {
        var list = (ICollection<TItem>)value;
        if (list.Count == 1)
        {
            foreach (object item in list)
            {
                serializer.Serialize(writer, item);
                break;
            }
        }
        else
        {
            writer.WriteStartArray();
            foreach (var item in list)
            {
                serializer.Serialize(writer, item);
            }
            writer.WriteEndArray();
        }
    }

    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        var objectType = value.GetType();
        try
        {
            var elementType = objectType.GetCollectItemTypes().SingleOrDefault();
            if (elementType == null)
                throw new JsonSerializationException(string.Format("{0} is not an ICollection<T> for some T", objectType));
            var method = typeof(SingleOrArrayConverter).GetMethod("WriteJsonGeneric", BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Public);
            method.MakeGenericMethod(new[] { elementType }).Invoke(this, new object[] { writer, value, serializer });
        }
        catch (Exception ex)
        {
            // Wrap the TargetInvocationException in a JsonSerializerException
            throw new JsonSerializationException("Failed to serialize " + objectType, ex);
        }
    }
}

public static class TypeExtensions
{
    public static IEnumerable<Type> GetInterfacesAndSelf(this Type type)
    {
        if (type == null)
            throw new ArgumentNullException();
        if (type.IsInterface)
            return new[] { type }.Concat(type.GetInterfaces());
        else
            return type.GetInterfaces();
    }

    public static IEnumerable<Type> GetCollectItemTypes(this Type type)
    {
        foreach (Type intType in type.GetInterfacesAndSelf())
        {
            if (intType.IsGenericType
                && intType.GetGenericTypeDefinition() == typeof(ICollection<>))
            {
                yield return intType.GetGenericArguments()[0];
            }
        }
    }
}

public class StringConverterDecorator : JsonConverterDecorator
{
    public StringConverterDecorator(Type jsonConverterType) : base(jsonConverterType) { }

    public StringConverterDecorator(JsonConverter converter) : base(converter) { }

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        if (reader.TokenType == JsonToken.Null)
            return null;
        // Unwrap the double-serialized string.
        var s = JToken.Load(reader).ToString();
        var token = JToken.Parse(s);
        // Then convert the revealed JSON to its final form.
        using (var subReader = token.CreateReader())
        {
            while (subReader.TokenType == JsonToken.None)
                subReader.Read();
            return base.ReadJson(subReader, objectType, existingValue, serializer);
        }
    }

    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        string s;

        // Serialize the value to an intermediate JSON string.
        using (var textWriter = new StringWriter())
        {
            using (var subWriter = new JsonTextWriter(textWriter))
            {
                base.WriteJson(subWriter, value, serializer);
            }
            s = textWriter.ToString();
        }
        // Then double-serialize the value by writing the JSON as a string literal to the output stream.
        writer.WriteValue(s);
    }
}

public abstract class JsonConverterDecorator : JsonConverter
{
    readonly JsonConverter converter;

    public JsonConverterDecorator(Type jsonConverterType) : this((JsonConverter)Activator.CreateInstance(jsonConverterType)) { }

    public JsonConverterDecorator(JsonConverter converter)
    {
        if (converter == null)
            throw new ArgumentNullException();
        this.converter = converter;
    }

    public override bool CanConvert(Type objectType)
    {
        return converter.CanConvert(objectType);
    }

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        return converter.ReadJson(reader, objectType, existingValue, serializer);
    }

    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        converter.WriteJson(writer, value, serializer);
    }

    public override bool CanRead { get { return converter.CanRead; } }

    public override bool CanWrite { get { return converter.CanWrite; } }
}

Then apply the chained converter to your Message property using a [JsonConverter(typeof(TConverter), ...)] attribute as follows:

public class Requirments
{
    public int FileID { get; set; }
    public string EventMessageUId { get; set; }
    public string ProjectId { get; set; }

    [JsonConverter(typeof(StringConverterDecorator), typeof(SingleOrArrayConverter))]
    public List<Message> Message { get; set; }

    public string error { get; set; }
}

Then deserialize with JsonConvert.DeserializeObject<T>:

var requirement = JsonConvert.DeserializeObject<Requirments>(jsonString);

Or, if you do not want to apply the converter to directly to your type, you can add it to JsonSerializerSettings.Converters and deserialize as follows:

var settings = new JsonSerializerSettings
{
    Converters = { new StringConverterDecorator(new SingleOrArrayConverter<List<Message>, Message>()) },
};
var requirement = JsonConvert.DeserializeObject<Requirments>(json, settings);

Note the generic SingleOrArrayConverter<List<Message>, Message> is required here to prevent the converter from applying to all types of collection.

Sample fiddle.

dbc
  • 104,963
  • 20
  • 228
  • 340
  • Can it be implemented with NewtonSoft Json ?? – C Sharper Jan 05 '17 at 09:23
  • @CSharper - yes, the above implementation uses NewtonSoft Json's [tag:json.net]. – dbc Jan 05 '17 at 09:26
  • When I remove line :- [JsonConverter(typeof(StringConverterDecorator), typeof(SingleOrArrayConverter))] It gices me error :- Error converting value "{"StoryId":20500,"StoryDesc":"Test Story data"}" to type 'System.Collections.Generic.List`1[ATL.Trebuchet.Requirement.Message]'. Path 'Message' – C Sharper Jan 05 '17 at 10:21
  • 1
    @CSharper Why did you remove the `[JsonConverter]` attribute? That is required to allow this solution to work correctly. – Brian Rogers Jan 05 '17 at 15:28
  • @CSharper - OK, there was a problem with the original answer if you added the converter to `JsonSerializerSettings.Converters` rather than applying directly to the property. I updated the answer to make both options work. New sample fiddle: https://dotnetfiddle.net/EuxaEV – dbc Jan 05 '17 at 20:02
0

The definition of the Message class is right. However the Json body for message property is not an array. So the class should be

public class Requirments
{

    public int FileID { get; set; }
    public string EventMessageUId { get; set; }
    public string ProjectId { get; set; }
    public Message Message { get; set; }
    //public object[] Message { get; set; }
    public string error { get; set; }
}
Rohi_Dev_1.0
  • 372
  • 1
  • 2
  • 19
0

Main problem is with your JSON, it should look like this

{"EventMessageUId":"ef51b5a3-32b2-e611-baf9-fc3fdb446bd2","Message":
[{"StoryId":20500,"StoryDesc":"Test Story
data"}],"ProjectId":"1"}

After that you will get "Message" as a list, and also you can easily map it to class.

public class Message
{
    public int StoryId { get; set; }
    public string StoryDesc { get; set; }
}

public class Requirments
{
    public string EventMessageUId { get; set; }
    public List<Message> Message { get; set; }
    public string ProjectId { get; set; }
}
JigsK
  • 81
  • 5
0

I have successfully parsed list of class type Message from your Json but for that you will slightly need to change your class Requirements:

public class Requirments
    {    
        public int FileID { get; set; }
        public string EventMessageUId { get; set; }
        public string ProjectId { get; set; }
        public string  Message { get; set; } 
        //public List<Message> Message { get; set; } // **need to change property type to "string"**
        //public object[] Message { get; set; }
        public string error { get; set; }
    }

You can try below code:

Requirments mainResult = JsonConvert.DeserializeObject<Requirments>("YOUR JSON STING");
List<Message> MessageList = JsonConvert.DeserializeObject<List<Message>>(mainResult.Message.ToString());

Note: you will need to include using Newtonsoft.Json; in your class.

This will give you list of class type Message in MessageList

Hope, this will help!