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>

3
  • 1
    What JSON deserialiser are you using, and where is the code relating to this? Commented Jan 5, 2017 at 7:59
  • requirements objRequirement = JsonObject.ToObject<requirements>(); I am using Newtonsoft Json @G0dsquad Commented Jan 5, 2017 at 8:01
  • your json doesn't have Message as a collection. Collections should be wrapped in square brackets e.g. "Message":[{"field":"value"}, {"field":"value"}] Commented Jan 5, 2017 at 8:10

5 Answers 5

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("\\", "");
Sign up to request clarification or add additional context in comments.

2 Comments

This looks promising.. let me try
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.
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.

5 Comments

Can it be implemented with NewtonSoft Json ??
@CSharper - yes, the above implementation uses NewtonSoft Json's json.net.
When I remove line :- [JsonConverter(typeof(StringConverterDecorator), typeof(SingleOrArrayConverter<Message>))] 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'
@CSharper Why did you remove the [JsonConverter] attribute? That is required to allow this solution to work correctly.
@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: dotnetfiddle.net/EuxaEV
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; }
}

3 Comments

public Message Message { get; set; } this does not work
please change the Message class name to something else. The problem is the PropertyName and class both are same
Aah your json Message property is escaping the double quotes. From where are you getting the Json response?
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; }
}

1 Comment

I have already mentioned in question :- What can I do for this without changing json?
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!

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.