Search code examples
c#.netjsonjson-serialization

Projecting list of objects into another type by JsonProperty attribute value


I have the following models:

class CheckResponse
{
    public ICollection<CheckModel> Checks { get; set; }
}

public class CheckModel
{
    [JsonConverter(typeof(StringEnumConverter))]
    public CheckCodes CheckCode { get; set; }

    [JsonConverter(typeof(StringEnumConverter))]
    public ResultCode ResultCode { get; set; }
}

public enum CheckCodes
{
    FirstCheck,
    SecondCheck,
    ThirdCheck,
}

public enum ResultCode
{
    Failure,
    Success,
    Warning
}

I need to convert Checks into CheckList:

class CheckList
{
    [JsonProperty(nameof(CheckCodes.FirstCheck))]
    public bool FirstCheckPassed { get; set; }

    [JsonProperty(nameof(CheckCodes.SecondCheck))]
    public bool SecondCheckPassed { get; set; }

    [JsonProperty(nameof(CheckCodes.ThirdCheck))]
    public bool ThirdCheckPassed { get; set; }
}

Example:

class Program
{
    static void Main(string[] args)
    {
        var response = GetResponse();

        var checkResponse = JsonConvert.DeserializeObject<CheckResponse>(response);

        // var checkList = ?
    }

    static string GetResponse() => @"{
'checks': [
    {
        'checkCode': 0,
        'resultCode': 1
    },
    {
        'checkCode': 1,
        'resultCode': 2
    },
    {
        'checkCode': 2,
        'resultCode': 0
    }
]}";
}

If resultCode equals 2 (ResultCode.Warning) then check should be passed.

So, the checkList should have following properties values:

FirstCheckPassed = true

SecondCheckPassed = true

ThirdCheckPassed = false

UPDATED:

My solution as follows:

static void Main(string[] args)
{
    Test();
}

static IEnumerable<string> Yield(ICollection<CheckModel> checks)
{
    foreach (var check in checks)
    {
        var success = check.ResultCode == ResultCode.Success || check.ResultCode == ResultCode.Warning;
        yield return "'" + check.CheckCode.ToString() + "': '" + success.ToString() + "'";
    }
}

static void Test()
{
    var response = GetResponse();

    var checkResponse = JsonConvert.DeserializeObject<CheckResponse>(response);

    var o = "{" + string.Join(",", Yield(checkResponse.Checks)) + "}";
    var checkList = JsonConvert.DeserializeObject<CheckList>(o);
}

But I'm not sure that this is the best one (Actually, I consider it a little bit ugly). Are there any better approaches?


Solution

  • You can create your own JsonConverter, with additional change to the POCO you can Deserialize to the exact object you want.

    Little change into CheckResponse

    class CheckResponse
    {
        [JsonConverter(typeof(CheckModelConverter))]
        public CheckList Checks { get; set; }
    }
    

    Adding custom converter

    public class CheckModelConverter : JsonConverter
    {
        public override bool CanConvert(Type objectType)
        {
            throw new NotImplementedException();
        }
    
        public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
        {
            var obj = serializer.Deserialize<CheckModel[]>(reader);
            var result = new CheckList();
            foreach (var item in obj)
            {
                bool resultValue = item.ResultCode != ResultCode.Failure;
                switch (item.CheckCode)
                {
                    case CheckCodes.FirstCheck:
                        result.FirstCheckPassed = resultValue;
                        break;
                    case CheckCodes.SecondCheck:
                        result.SecondCheckPassed = resultValue;
                        break;
                    case CheckCodes.ThirdCheck:
                        result.ThirdCheckPassed = resultValue;
                        break;
                    default:
                        throw new Exception("No checkcode of " + item.CheckCode);
                }
            }
            return result;  
        }
    
        public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
        {
            throw new NotImplementedException();
        }
    }
    

    Disclosure: I will love more to actually deserialize it to the exact model, and only then Map it (or part of it) into the type i want, this give me more future flexibility and preserve the actual model structure.

    Edit: Fixed spelling in Disclosure.