facebook-csharp-sdk / simple-json

JSON library for .NET 2.0+/SL4+/WP7/WindowsStore with optional support for dynamic and DataContract
MIT License
379 stars 143 forks source link

Serialize and deserialize Enum values as strings #64

Open jasonmead opened 9 years ago

jasonmead commented 9 years ago

Fixes #15

amccorma commented 8 years ago

this issue is not working in the current version. working on a fix to the code.

amccorma commented 8 years ago
   public virtual object DeserializeObject(object value, Type type)
        {
            if (type == null) throw new ArgumentNullException("type");
            string str = value as string;

            if (type == typeof (Guid) && string.IsNullOrEmpty(str))
                return default(Guid);

            if (value == null)
                return null;

            object obj = null;

            if (str != null)
            {
                if (str.Length != 0) // We know it can't be null now.
                {
                    if (type == typeof(DateTime) || (ReflectionUtils.IsNullableType(type) && Nullable.GetUnderlyingType(type) == typeof(DateTime)))
                        return DateTime.ParseExact(str, Iso8601Format, CultureInfo.InvariantCulture, DateTimeStyles.AssumeUniversal | DateTimeStyles.AdjustToUniversal);
                    if (type == typeof(DateTimeOffset) || (ReflectionUtils.IsNullableType(type) && Nullable.GetUnderlyingType(type) == typeof(DateTimeOffset)))
                        return DateTimeOffset.ParseExact(str, Iso8601Format, CultureInfo.InvariantCulture, DateTimeStyles.AssumeUniversal | DateTimeStyles.AdjustToUniversal);
                    if (type == typeof(Guid) || (ReflectionUtils.IsNullableType(type) && Nullable.GetUnderlyingType(type) == typeof(Guid)))
                        return new Guid(str);
                    if (type == typeof(Uri))
                    {
                        bool isValid =  Uri.IsWellFormedUriString(str, UriKind.RelativeOrAbsolute);

                        Uri result;
                        if (isValid && Uri.TryCreate(str, UriKind.RelativeOrAbsolute, out result))
                            return result;

                                                return null;
                    }

                                    if (type == typeof(string))  
                                        return str;

                                    return Convert.ChangeType(str, type, CultureInfo.InvariantCulture);
                }
                else
                {
                    if (type == typeof(Guid))
                        obj = default(Guid);
                    else if (ReflectionUtils.IsNullableType(type) && Nullable.GetUnderlyingType(type) == typeof(Guid))
                        obj = null;
                    else
                        obj = str;
                }
                // Empty string case
                if (!ReflectionUtils.IsNullableType(type) && Nullable.GetUnderlyingType(type) == typeof(Guid))
                    return str;
            }
            else if (value is bool)
                return value;

            bool valueIsLong = value is long;
            bool valueIsDouble = value is double;
            if (type.IsEnum)
            {
                if (value is double || value is int || value is long)
                {
                    return Enum.ToObject(type, Convert.ToInt32(value.ToString()));
                }
                else if (value is string)
                {
                    return Enum.Parse(type, value.ToString());
                }
            }
            if ((valueIsLong && type == typeof(long)) || (valueIsDouble && type == typeof(double)))
                return value;
            if ((valueIsDouble && type != typeof(double)) || (valueIsLong && type != typeof(long)))
            {
                obj = type == typeof(int) || type == typeof(long) || type == typeof(double) || type == typeof(float) || type == typeof(bool) || type == typeof(decimal) || type == typeof(byte) || type == typeof(short)
                            ? Convert.ChangeType(value, type, CultureInfo.InvariantCulture)
                            : value;
            }
            else
            {
                IDictionary<string, object> objects = value as IDictionary<string, object>;
                if (objects != null)
                {
                    IDictionary<string, object> jsonObject = objects;

                    if (ReflectionUtils.IsTypeDictionary(type))
                    {
                        // if dictionary then
                        Type[] types = ReflectionUtils.GetGenericTypeArguments(type);
                        Type keyType = types[0];
                        Type valueType = types[1];

                        Type genericType = typeof(Dictionary<,>).MakeGenericType(keyType, valueType);

                        IDictionary dict = (IDictionary)ConstructorCache[genericType]();

                        foreach (KeyValuePair<string, object> kvp in jsonObject)
                            dict.Add(kvp.Key, DeserializeObject(kvp.Value, valueType));

                        obj = dict;
                    }
                    else
                    {
                        if (type == typeof(object))
                            obj = value;
                        else
                        {
                            obj = ConstructorCache[type]();
                            foreach (KeyValuePair<string, KeyValuePair<Type, ReflectionUtils.SetDelegate>> setter in SetCache[type])
                            {
                                object jsonValue;
                                if (jsonObject.TryGetValue(setter.Key, out jsonValue))
                                {
                                    jsonValue = DeserializeObject(jsonValue, setter.Value.Key);
                                    setter.Value.Value(obj, jsonValue);
                                }
                            }
                        }
                    }
                }
                else
                {
                    IList<object> valueAsList = value as IList<object>;
                    if (valueAsList != null)
                    {
                        IList<object> jsonObject = valueAsList;
                        IList list = null;

                        if (type.IsArray)
                        {
                            list = (IList)ConstructorCache[type](jsonObject.Count);
                            int i = 0;
                            foreach (object o in jsonObject)
                                list[i++] = DeserializeObject(o, type.GetElementType());
                        }
                        else if (ReflectionUtils.IsTypeGenericeCollectionInterface(type) || ReflectionUtils.IsAssignableFrom(typeof(IList), type))
                        {
                            Type innerType = ReflectionUtils.GetGenericListElementType(type);
                            list = (IList)(ConstructorCache[type] ?? ConstructorCache[typeof(List<>).MakeGenericType(innerType)])(jsonObject.Count);
                            foreach (object o in jsonObject)
                                list.Add(DeserializeObject(o, innerType));
                        }
                        obj = list;
                    }
                }
                return obj;
            }
            if (ReflectionUtils.IsNullableType(type))
                return ReflectionUtils.ToNullableType(obj, type);
            return obj;
        }
amccorma commented 8 years ago

Here the code change:

Method:

\ public virtual object DeserializeObject(object value, Type type)**

line: 1386

Current version at time of change is 0.38.0 or copy method above and replace. tested with 4 enums.

 if (type.IsEnum)
            {
                if (value is double || value is int || value is long)
                {
                    return Enum.ToObject(type, Convert.ToInt32(value.ToString()));
                }
                else if (value is string)
                {
                    return Enum.Parse(type, value.ToString());
                }
            }
jasonmead commented 8 years ago

Submit a PR with tests, please.

amccorma commented 8 years ago

I have never done a PR request. reading up on google and going try it.

amccorma commented 8 years ago

I followed this: https://guides.github.com/activities/contributing-to-open-source/

I did the fork. made a branch (enumfix). compiled solution. updated source. committed branch. added tests (EnumTest). unsure what do to next. the code been committed to the branch.

Everything compiles. test worked ok. checked in.

jasonmead commented 8 years ago

The next step would be to open a pull request from your forked branch.

jchannon commented 8 years ago

@jasonmead this is great, i'm not sure what the diff between your PR and @amccorma PR #72 although that PR I think has line ending issues as the whole simplejson file has changed. I'd like to get this into Nancy https://github.com/NancyFx/Nancy for the JSON work you did.

Including @prabirshrestha in this too.

What needs to be done to get this feature in ASAP?

jasonmead commented 8 years ago

This will have to be implemented in Nancy directly. Unfortunately, we had to modify SimpleJson.cs in Nancy to get it to work correctly.

prabirshrestha commented 8 years ago

@jchannon I will be out of town. will have to look at this next week.

Deepscorn commented 7 years ago

What is the status? I need enum support to use SimpleJson in a Unity game (last stable version of which sadly provides us, programmers, with C# 4 and .NET 2.0-3.5 at the time of writing which don't make much of choise when handling json). It's the only thing, that stops me from using SimpleJson. I need something like this to work:

[DataContract]
    public enum Keeper
    {
        [DataMember(Name = "no keeper")]
        None,
        [DataMember(Name = "Mary Rose")]
        Mary,
        [DataMember(Name = "Tom The Third")]
        Tom,
        [DataMember(Name = "Simply, Todd")]
        Todd
    }

[DataContract]
    public class Animal : JsonResponse
    {
        [NotNull]
        [DataMember(Name = "animal color")] // works ok
        public string Color { get; private set; }

        [DataMember(Name = "animal keeper")] // fails, enum serialized as number ( 0 for None, 1 for Mary and so on)
        public Keeper Keeper { get; private set; }
}

So, needed serialized example of animal: { "animal color" : "red", "animal keeper" : "Mary Rose" }

Actual serialized example of animal: { "animal color" : "red", "animal keeper" : 1 }

I came from java and there we successfully used (for example) com.google.api.client.util.Value attribute to specify [de]serialized string value for each enum member:

public enum CardStatus {
    @Value("active")
    ACTIVE,
    @Value("expired")
    EXPIRED,
    @Value("blocked")
    BLOCKED
}

public class Card {
    @Key("card id")
    private String id;
    @Key("status")
    private CardStatus status;
}

So, serialized example of Card: { "card id" : "101010", "status" : "expired" }

PS com.google.api.client.util.Value is in com.google.http-client:google-http-client-android:1.22.0 or com.google.http-client:google-http-client-jackson2:1.22.0 - don't remember exactly, allways imported both at the same time

PS I tested code of that merge request and it gave me: { "animal color" : "red", "animal keeper" : "Mary" } As you see, sadly, DataMember attribute ignored