19

NOTE: I have provided the solution at the bottom of this feed.

I have a C# Win 8 app where I'm de-serializing some json that looks like this:

{
    'Unit': [
        {
            'name':'House 123',
            isAvailable:'no'
        },
        {
            'name':'House 456',
            isAvailable:'yes'
        }]
}

into a class that uses this interface:

public interface IUnit
{
    string Name { get; }
    bool isAvailable { get; }
}

But Newtonsoft throws an error:

Unexpected character encountered while parsing value: n. Path 'Unit[0].isAvailable, line 1, position 42.

Is there a way to extend Newtonsoft to parse yes/no or 1/0 based on the resulting object property type of bool? Right now it only works for true/false.

There are several posts on custom converters for classes, but not a primitive type like bool.

Any suggestions?

Brendan Green
  • 11,676
  • 5
  • 44
  • 76
CodeChops
  • 1,980
  • 1
  • 20
  • 27

5 Answers5

25
public class MyBooleanConverter : JsonConverter
{
    public override bool CanWrite { get { return false; } }

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

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        var value = reader.Value;

        if (value == null || String.IsNullOrWhiteSpace(value.ToString()))
        {
            return false;
        }

        if ("yes".Equals(value, StringComparison.OrdinalIgnoreCase))
        {
            return true;
        }

        return false;
    }

    public override bool CanConvert(Type objectType)
    {
        if (objectType == typeof(String) || objectType == typeof(Boolean))
        {
            return true;
        }
        return false;
    }
}


public interface IUnit
{
    string Name { get; }

    [JsonConverter(typeof(MyBooleanConverter))]
    bool isAvailable { get; }
}
John
  • 29,788
  • 18
  • 89
  • 130
Craig Stuntz
  • 125,891
  • 12
  • 252
  • 273
  • Thanks for the fast response Craig. This looks better than the solution I came up with but I am having problems getting it to work with my code: I've posted it in the next answer... – CodeChops Jan 25 '13 at 16:24
  • Newtonsoft.Json.JsonSerializer serializer = new Newtonsoft.Json.JsonSerializer(); serializer.Converters.Add(new MyBooleanConverter()) string json = "{'Unit':[{'name':'Apartment 123',isSingleUnit:'no'},{'name':'House 456',isSingleUnit:'yes'}]}".Replace('\'', '\"'); var obj = serializer.Deserialize(new StringReader(json), typeof(bool)); Console.WriteLine(obj); It just returns "false". – CodeChops Jan 25 '13 at 16:28
  • 1
    +1 for the basics; your code can be seriously trimmed down though :) – Sten Petrov Sep 18 '13 at 01:50
  • Consider this: https://stackoverflow.com/a/809558/820068 And then consider changing the comparison to : "if ("yes".Equals(value.ToString() ..." – Wesley Long Aug 28 '19 at 18:12
13

i would suggest this approach

using System;
using Newtonsoft.Json;

namespace JsonConverters
{
    public class BooleanJsonConverter : JsonConverter
    {
        public override bool CanConvert( Type objectType )
        {
            return objectType == typeof( bool );
        }

        public override object ReadJson( JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer )
        {
            switch ( reader.Value.ToString().ToLower().Trim() )
            {
                case "true":
                case "yes":
                case "y":
                case "1":
                    return true;
                case "false":
                case "no":
                case "n":
                case "0":
                    return false;
            }

            // If we reach here, we're pretty much going to throw an error so let's let Json.NET throw it's pretty-fied error message.
            return new JsonSerializer().Deserialize( reader, objectType );
        }

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

    }
}
harishr
  • 17,807
  • 9
  • 78
  • 125
6

This is what I came up with.

public class JsonBooleanConverter : JsonConverter
{
    public override bool CanWrite { get { return false; } }

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

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        var value = reader.Value.ToString().ToLower().Trim();
        switch (value)
        {
            case "true":
            case "yes":
            case "y":
            case "1":
                return true;
        }
        return false;
    }

    public override bool CanConvert(Type objectType)
    {
        if (objectType == typeof(Boolean))
        {
            return true;
        }
        return false;
    }
}

Usage:

var myObj = JsonConvert.DeserializeObject<T>(json, new JsonBooleanConverter());
Pete
  • 267
  • 3
  • 8
  • This does work. https://github.com/petekapakos/JsonBooleanConverterTest – Pete Dec 21 '18 at 05:07
  • 2
    Yes, it does, for your specific and well-crafted test case. Try this: replace everything in your `main()` function with two calls: `JsonConvert.DeserializeObject("true", new JsonBooleanConverter()); JsonConvert.DeserializeObject("yes", new JsonBooleanConverter());` The first will succeed, the latter will fail with the following exception: `{"Unexpected character encountered while parsing value: y. Path '', line 0, position 0."}`. This is because you are misusing Converter: it is not a pre-parse fixup mechanism for malformed JSON, which is how it is being utilized here. – rpj Dec 26 '18 at 23:16
1

//This is what I came up with...

   using System;
 using System.Collections.Generic;
 using System.IO;
using System.Linq;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;

namespace NewtonTest
{

internal class NewtonTest
{
    public class Data
    {
        public IEnumerable<IUnit> Unit { get; set; }

        public override string ToString()
        {
            return string.Format("Data{{Unit=[{0}]}}",
                string.Join(", ", Unit.Select(c =>
                                string.Format("{0} - Single Unit: {1}", 
                                    c.Name,
                                    c.isSingleUnit.ToString()))));
        }
    }

    public interface IUnit
    {
        string Name { get; }

        // [JsonConverter(typeof(Converter))]
        bool isSingleUnit { get; }
    }

    public class House : IUnit
    {
        public House(string name, bool isSingle)
        {
            this.Name = name;
            this.isSingleUnit = isSingle;
        }

        public string Name { get; private set; }

        public bool isSingleUnit { get; private set; }
    }

    public class Apartment : IUnit
    {
        public Apartment(string name, bool isSingle)
        {
            this.Name = name;
            this.isSingleUnit = isSingle;
        }

        public string Name { get; private set; }

        public bool isSingleUnit { get; private set; }
    }

    private static bool ConvertToBool(string value)
    {
        value =
            value.ToUpper().
                  Replace("YES", "TRUE").
                  Replace("Y", "TRUE").
                  Replace("1", "TRUE").
                  Replace("NO", "FALSE").
                  Replace("N", "FALSE").
                  Replace("0", "FALSE");

        bool result = false;

        bool.TryParse(value, out result);

        return result;
    }


    private class UnitConverter : Newtonsoft.Json.JsonConverter
    {
        public override bool CanConvert(Type objectType)
        {
            return typeof (NewtonTest.IUnit).IsAssignableFrom(objectType);
        }

        public override object ReadJson(Newtonsoft.Json.JsonReader reader, Type objectType, object existingValue,
                                        Newtonsoft.Json.JsonSerializer serializer)
        {
            JObject obj = serializer.Deserialize<JToken>(reader) as JObject;

            if (obj != null)
            {
                string result = obj["isSingleUnit"].ToObject<string>();

                bool isSingleUnit = ConvertToBool(result);

                string name = obj["name"].ToObject<string>();

                if (isSingleUnit)
                {
                    return new NewtonTest.House(name, isSingleUnit);
                }
                else
                {
                    return new NewtonTest.Apartment(name, isSingleUnit);
                }
            }
            else
            {
                return null;
            }
        }

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


    public static void Main()
    {
        Newtonsoft.Json.JsonSerializer serializer = new Newtonsoft.Json.JsonSerializer();
        serializer.Converters.Add(new UnitConverter());
        string json =
            "{'Unit':[{'name':'Apartment 123',isSingleUnit:'no'},{'name':'House 456',isSingleUnit:'yes'}]}".Replace(
                '\'', '\"');
        var obj = serializer.Deserialize(new StringReader(json), typeof (Data));
        Console.WriteLine(obj);
        Console.ReadKey();
    }
}
}
CodeChops
  • 1,980
  • 1
  • 20
  • 27
  • I don't see where you've told the deserializer that you expect an `IUnit`. It won't know to apply the right converter. – Craig Stuntz Jan 25 '13 at 17:26
  • Sorry, the code is hard to read the way it nests it. It's in this method: public override bool CanConvert(Type objectType) { return typeof (NewtonTest.IUnit).IsAssignableFrom(objectType); } – CodeChops Jan 30 '13 at 16:03
  • Not enough. You need a reference to `IUnit` in `Main()`. – Craig Stuntz Jan 30 '13 at 17:40
0

Here's a version of @John's solution in vb for anyone that needs that. It handles Boolean and nullable Boolean. On Write it converts to 0/1 to save some bytes in transfer (rather than true/false):

Imports Newtonsoft.Json

Public Class MyBooleanConverter
    Inherits JsonConverter

Public Overrides ReadOnly Property CanWrite As Boolean
    Get
        Return True
    End Get
End Property

Public Overrides Sub WriteJson(writer As JsonWriter, value As Object, serializer As JsonSerializer)
    Dim boolVal As Boolean = value
    writer.WriteValue(If(boolVal, 1, 0))
End Sub

Public Overrides Function ReadJson(reader As JsonReader, objectType As Type, existingValue As Object, serializer As JsonSerializer) As Object
    Dim value = reader.Value
    If IsNothing(value) OrElse String.IsNullOrWhiteSpace(value.ToString()) OrElse "0" = value Then
        Return False
    End If
    If 0 = String.Compare("yes", value, True) OrElse 0 = String.Compare("true", value, True) Then
        Return True
    End If
    Return False
End Function

Public Overrides Function CanConvert(objectType As Type) As Boolean
    Return objectType = GetType(Boolean) OrElse objectType = GetType(Boolean?) 'OrElse objectType = GetType(String)
End Function
End Class
Gary
  • 3,254
  • 2
  • 27
  • 30