Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Deserializing Class with 2 non-default constructors

Tags:

json

c#

I've a class, that tries to serialize and deserialize objects, given to it, using Json.net

public class JsonSerializer
{
    public string Serialize(object toSerialaze)
    {
        return JsonConvert.SerializeObject(toSerialaze);
    }

    public T Deserialize<T>(string toDeserialaze)
    {
        return JsonConvert.DeserializeObject<T>(toDeserialaze);
    }
}

And giving to it anobject of such a class

public class Isbn
{
    private readonly int _groupCode;
    private readonly int _publisherCode;
    private readonly int _titleCode;
    private readonly int _checkCode;
    private static readonly Regex Regex = new Regex(@"^\s*\d*\s*-\s*\d*\s*-\s*\d*\s*-\s*\d*\s*$");

    public Isbn(int groupCode, int publisherCode, int titleCode, int checkCode)
    {
        _groupCode = groupCode;
        _publisherCode = publisherCode;
        _titleCode = titleCode;
        _checkCode = checkCode;
    }

    public Isbn(string isbn)
    {
        if (isbn == null)
            throw new ArgumentNullException("isbn");
        if (isbn == "") return;
        if (!IsValid(isbn)) return;
        var isbnStrings = isbn.Split(new[] {'-', ' '}, StringSplitOptions.RemoveEmptyEntries);
        _groupCode = Convert.ToInt32(isbnStrings[0]);
        _publisherCode = Convert.ToInt32(isbnStrings[1]);
        _titleCode = Convert.ToInt32(isbnStrings[2]);
        _checkCode = Convert.ToInt32(isbnStrings[3]);
    }
}

I get the following exception:

Additional information: Unable to find a constructor to use for type Library.Isbn. A class should either have a default constructor, one constructor with arguments or a constructor marked with the JsonConstructor attribute.

I know, I may put [JsonConstructor] before the constructor I need to use while deserialization, but I don't want class Isbn to know about Json. How may I achieve the same behavior in another way? How may I let JsonConverter know, which of 2 constructors to use?

like image 836
Sergey Z. Avatar asked Jun 27 '14 07:06

Sergey Z.


2 Answers

I think this answer is a little bit late but someone may want to use it.

You can do it by creating a custom JsonConverter

public class IsbnConverter : JsonConverter
{
    public override bool CanConvert(Type objectType)
    {
        return objectType == typeof(Isbn);
    }

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        if (reader.TokenType == JsonToken.StartObject)
        {
            var dict = new Dictionary<string, int>();
            serializer.Populate(reader, dict);
            return new Isbn(dict["groupCode"], dict["publisherCode"], dict["titleCode"], dict["checkCode"]);
        }

        if (reader.TokenType == JsonToken.String)
        {
            return new Isbn((string)reader.Value);
        }

        return null;
    }

    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        throw new NotImplementedException();
    }
}

The only thing you need is to pass this Converter to JsonConvert.DeserializeObject

var yourobj = JsonConvert.DeserializeObject<T>(json, new IsbnConverter());

Now deserializetion can work for both json's

{ .... , isbn:{groupCode:1,publisherCode:2,titleCode:3,checkCode:4}, ...... }
{ .... , isbn:"1-2-3-4", .... }

For ex;

public class Book
{
    public string Title { get; set; }
    public Isbn isbn { get; set; }
}

string json1 = @"{Title:""Title 1"", isbn:{groupCode:1,publisherCode:2,titleCode:3,checkCode:4}}";
string json2 = @"{Title:""Title 2"", isbn:""1-2-3-4""}";

var book1 = JsonConvert.DeserializeObject<Book>(json1, new IsbnConverter());
var book2 = JsonConvert.DeserializeObject<Book>(json2, new IsbnConverter());
like image 62
L.B Avatar answered Nov 16 '22 10:11

L.B


One option is to have a different class just for serialisation. Then map to your original, either manually or using AutoMapper.

As a bonus you will find your business object is then free to be refactored without worrying about what that does to the serialisation. Because you are right it shouldn't know about json.

Replace public constructors with static methods

Another option is to reduce the number of constructors, I try not to have more than one, often having none (by which I mean no public constructors).

Example:

public Isbn(int groupCode, int publisherCode, int titleCode, int checkCode)
{
    _groupCode = groupCode;
    _publisherCode = publisherCode;
    _titleCode = titleCode;
    _checkCode = checkCode;
}

public static Isbn FromString(string isbn)
{
    if (isbn == null)
        throw new ArgumentNullException("isbn");
    if (isbn == "") return;
    if (!IsValid(isbn)) return;
    var isbnStrings = isbn.Split(new[] {'-', ' '}, StringSplitOptions.RemoveEmptyEntries);
    var groupCode = Convert.ToInt32(isbnStrings[0]);
    var publisherCode = Convert.ToInt32(isbnStrings[1]);
    var titleCode = Convert.ToInt32(isbnStrings[2]);
    var checkCode = Convert.ToInt32(isbnStrings[3]);
    return new Isbn(groupCode, publisherCode, titleCode, checkCode);
}
like image 20
weston Avatar answered Nov 16 '22 11:11

weston