skip to Main Content

The scenario is the following: I have some classes part of an sdk and in a type hierarchy, I cannot don’t want to mess around with them. I also have a json returned from a service.

The issue (and I considered it actually a bug in the service, but this is out of my control) is that some properties in the json are returned as an object while the property in the class is defined as a string. So for example

class MyClass {
     string? Foo { get; set; }
     string? Bar { get; set; }
}

But the json looks like:

{
     "Foo": "{"SomeProperty": 123 }",
     "Bar": {
               "SomeProperty": 456
            } 
}

The property Foo is correct and give no issue, Bar on the other hand is not correct.

When deserializing the json

var jsonObject = JObject.Load(reader);
var obj = jsonObject.ToObject<MyClass>();

this fails with the error: Error reading string. Unexpected token: StartObject.

And while I totally agree with the error, I would like to make this work. Bar should just contain the serialised object (string).

To be clear: I don’t want a custom serialisation for MyClass. I am looking for some kind of custom convertor that can handle the mismatch for any property defined as a string but encountered as an object.

3

Answers


  1. Conver all expressions to string first then deserialize them to the corresponding object.

    Create Class to the corresponding expression.

    public class MyFoo
    {
        public int? Expression { get; set; }
    }
    

    Add it in MyClass

    public class MyClass
    {
       public string? Foo { get; set; }
       [NotMapped]
       public MyFoo? RealFoo { get; set; }
    
    }
    

    After deserializing to MyClass, deserialize the property Foo to the property RealFoo

    Example:

    string json = @"{
         ""Foo"": ""{""Expression"": 123 }""
    
    }";
    
    JsonTextReader reader = new JsonTextReader(new StringReader(json));
    var jsonObject = JObject.Load(reader);
    var obj = jsonObject.ToObject<MyClass>();
    JsonTextReader newreader = new JsonTextReader(new StringReader(obj?.Foo??""));
    
    var jsonObject2 = JObject.Load(newreader);
    obj.RealFoo = jsonObject2.ToObject<MyFoo>();
    Console.WriteLine(obj?.RealFoo?.Expression??0);
    

    Output => 123

    Note: it’s not the best solution but it’s a work around to deal with the bug in the service you use

    Login or Signup to reply.
  2. Create additional DTO class:

    class MyClassDto
    {
        public string? Foo { get; set; }
        public object? Bar { get; set; }
    }
    

    Load firstly to DTO, then convert Bar object to string.
    After that create MyClass instance from DTO.

    var dto = jsonObject.ToObject<MyClassDto>();
    dto.Bar = JsonConvert.SerializeObject(dto.Bar);
    
    var obj = new MyClass
    {
        Foo = dto.Foo,
        Bar = dto.Bar.ToString()
    };
    

    Is this solution acceptable to you?

    Login or Signup to reply.
  3. Here is a quick implementation for such a StringConverter.

    public class StringConverter : JsonConverter<string>
    {
        public override string ReadJson(JsonReader reader, Type objectType, string existingValue, bool hasExistingValue, JsonSerializer serializer)
        {
            switch (reader.TokenType)
            {
                case JsonToken.String:
                    return existingValue;
                case JsonToken.StartObject:
                    return JObject.ReadFrom(reader).ToString();
                default:
                    throw new NotImplementedException();
            }
        }
    
        public override void WriteJson(JsonWriter writer, string value, JsonSerializer serializer)
        {
            writer.WriteValue(value);
        }
    }
    

    How to use

    var settings = new JsonSerializerSettings();
    var converter = new StringConverter();
    
    settings.Converters.Add(converter);
    
    var json = "{ "data1": "d1", "data2": { "prop": "d2"}, "data3": "d3" }";
    
    var item = JsonConvert.DeserializeObject<Test>(json, settings);
    
    public class Test
    {
        public string data1 { get; set; }
    
        public string data2 { get; set; }
    
        public string data3 { get; set; }
    }
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search