428 lines
17 KiB
C#
428 lines
17 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Reflection;
|
|
|
|
namespace ln.json.mapping
|
|
{
|
|
public delegate bool RequestCustomSerialization(object o, out JSONValue json);
|
|
public delegate bool RequestCustomUnserialization(JSONValue json, Type targetType, out object o);
|
|
public delegate bool RequestCustomMapping(Type targetType,out JSONMapping mapping);
|
|
public delegate bool MappingFactory(Type targetType, out JSONMapping mapping);
|
|
|
|
public class JSONMapper
|
|
{
|
|
public static JSONMapper DefaultMapper { get; set; } = new JSONMapper();
|
|
|
|
public event RequestCustomSerialization OnRequestCustomSerialization;
|
|
public event RequestCustomUnserialization OnRequestCustomUnserialization;
|
|
public event RequestCustomMapping OnRequestCustomMapping;
|
|
|
|
public JSONObjectMappingFlags DefaultMappingFlags { get; set; } = JSONObjectMappingFlags.PROPERTIES | JSONObjectMappingFlags.FIELDS;
|
|
public BindingFlags DefaultBindingFlags { get; set; } = BindingFlags.Instance | BindingFlags.Public;
|
|
|
|
public bool RequestCustomSerialization(object o, out JSONValue json)
|
|
{
|
|
foreach (RequestCustomSerialization rcs in OnRequestCustomSerialization?.GetInvocationList() ?? new RequestCustomSerialization[0])
|
|
{
|
|
if (rcs(o, out json))
|
|
return true;
|
|
}
|
|
json = null;
|
|
return false;
|
|
}
|
|
public bool RequestCustomUnserialization(JSONValue json, Type targetType, out object o)
|
|
{
|
|
foreach (RequestCustomUnserialization rcu in OnRequestCustomUnserialization?.GetInvocationList() ?? new RequestCustomUnserialization[0])
|
|
{
|
|
if (rcu(json, targetType, out o))
|
|
return true;
|
|
}
|
|
o = null;
|
|
return false;
|
|
}
|
|
|
|
public bool RequestCustomMapping(Type targetType, out JSONMapping mapping)
|
|
{
|
|
foreach (RequestCustomMapping rcm in OnRequestCustomMapping?.GetInvocationList() ?? new RequestCustomMapping[0])
|
|
{
|
|
if (rcm(targetType, out mapping))
|
|
return true;
|
|
}
|
|
mapping = null;
|
|
return false;
|
|
}
|
|
|
|
|
|
Dictionary<Type, JSONMapping> mappings = new Dictionary<Type, JSONMapping>();
|
|
public virtual void Add(JSONMapping mapping) => mappings[mapping.TargetType] = mapping;
|
|
|
|
Dictionary<Type, MappingFactory> mappingFactories = new Dictionary<Type, MappingFactory>();
|
|
public virtual void AddMappingFactory(Type targetType, MappingFactory mappingFactory) => mappingFactories.Add(targetType, mappingFactory);
|
|
|
|
public virtual bool GetOrBuildMapping(Type nativeType, out JSONMapping mapping) => TryGetMapping(nativeType, out mapping) || TryBuildRememberedMapping(nativeType, out mapping);
|
|
|
|
public virtual bool TryBuildRememberedMapping(Type nativeType,out JSONMapping mapping)
|
|
{
|
|
if (TryBuildMapping(nativeType, out mapping))
|
|
{
|
|
mappings.Add(nativeType, mapping);
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
public virtual bool TryBuildMapping(Type nativeType,out JSONMapping mapping)
|
|
{
|
|
if (nativeType.IsPrimitive)
|
|
{
|
|
mapping = null;
|
|
return false;
|
|
}
|
|
if (nativeType.IsArray)
|
|
{
|
|
mapping = new JSONArrayMapping(nativeType);
|
|
return true;
|
|
}
|
|
|
|
if (nativeType.IsSubclassOf(typeof(Exception)))
|
|
{
|
|
mapping = new JSONExceptionMapping(nativeType);
|
|
return true;
|
|
}
|
|
|
|
if ((mappingFactories.TryGetValue(nativeType, out MappingFactory mappingFactory)) && mappingFactory(nativeType, out mapping))
|
|
return true;
|
|
|
|
|
|
if (nativeType.IsGenericType)
|
|
{
|
|
Type genericTypeDefinition = nativeType.GetGenericTypeDefinition();
|
|
|
|
if (mappingFactories.TryGetValue(genericTypeDefinition, out mappingFactory) && mappingFactory(nativeType, out mapping))
|
|
return true;
|
|
|
|
if (genericTypeDefinition.Equals(typeof(IEnumerable<>)))
|
|
{
|
|
mapping = (JSONMapping)Activator.CreateInstance(typeof(JSONEnumerableMapping<>).MakeGenericType(nativeType.GetGenericArguments()[0]));
|
|
return true;
|
|
}
|
|
|
|
if (genericTypeDefinition.Equals(typeof(Dictionary<,>)))
|
|
{
|
|
mapping = (JSONMapping)Activator.CreateInstance(typeof(JSONDictionaryMapping<,>).MakeGenericType(nativeType.GetGenericArguments()));
|
|
return true;
|
|
}
|
|
}
|
|
|
|
mapping = new JSONObjectMapping(nativeType, DefaultMappingFlags, DefaultBindingFlags);
|
|
return true;
|
|
}
|
|
|
|
public virtual bool TryGetMapping(Type nativeType,out JSONMapping mapping)
|
|
{
|
|
if (mappings.TryGetValue(nativeType, out mapping))
|
|
return true;
|
|
|
|
if (RequestCustomMapping(nativeType, out mapping))
|
|
return true;
|
|
|
|
if ((this != DefaultMapper) && DefaultMapper.TryGetMapping(nativeType, out mapping))
|
|
return true;
|
|
|
|
return false;
|
|
}
|
|
|
|
|
|
|
|
public virtual bool Serialize(object o, out JSONValue json)
|
|
{
|
|
if (object.ReferenceEquals(null, o))
|
|
{
|
|
json = JSONNull.Instance;
|
|
return true;
|
|
}
|
|
|
|
Type type = o.GetType();
|
|
|
|
if (RequestCustomSerialization(o, out json))
|
|
return true;
|
|
|
|
if (TryGetMapping(type, out JSONMapping mapping))
|
|
{
|
|
json = mapping.ToJson(this, o);
|
|
return true;
|
|
}
|
|
|
|
if (type.IsEnum)
|
|
{
|
|
if (type.GetCustomAttribute<FlagsAttribute>() != null)
|
|
{
|
|
Type enumBaseType = type.GetEnumUnderlyingType();
|
|
o = Convert.ChangeType(o, enumBaseType);
|
|
ConstructorInfo constructor = typeof(JSONNumber).GetConstructor(new Type[]{ enumBaseType });
|
|
json = (JSONNumber)constructor.Invoke(new object[]{ o });
|
|
}
|
|
else
|
|
{
|
|
json = new JSONString(Enum.GetName(type, o));
|
|
}
|
|
return true;
|
|
}
|
|
|
|
if (type.IsPrimitive)
|
|
{
|
|
throw new NotSupportedException(String.Format("JSONMapperBase: Unsupported primitive type found: {0}", type));
|
|
}
|
|
|
|
if (TryBuildRememberedMapping(type,out mapping))
|
|
{
|
|
json = mapping.ToJson(this, o);
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
public virtual bool Deserialize<T>(string jsonSource, out T v) => Deserialize(JSONParser.Parse(jsonSource), out v);
|
|
public virtual bool Deserialize<T>(JSONValue json, out T v)
|
|
{
|
|
if (Deserialize(json, typeof(T), out object o))
|
|
{
|
|
v = (T)o;
|
|
return true;
|
|
}
|
|
v = default;
|
|
return false;
|
|
}
|
|
|
|
public virtual bool Deserialize(string jsonSource, Type nativeType, out object o) => Deserialize(JSONParser.Parse(jsonSource), nativeType, out o);
|
|
public virtual bool Deserialize(JSONValue json, Type nativeType, out object o)
|
|
{
|
|
o = null;
|
|
|
|
if (JSONNull.Instance.Equals(json))
|
|
return true;
|
|
|
|
if (RequestCustomUnserialization(json, nativeType, out o))
|
|
return true;
|
|
|
|
if (TryGetMapping(nativeType, out JSONMapping mapping))
|
|
{
|
|
o = mapping.FromJson(this, json);
|
|
return true;
|
|
}
|
|
|
|
if (nativeType.IsEnum)
|
|
{
|
|
if (json is JSONNumber jsonNumber)
|
|
o = Enum.ToObject(nativeType, jsonNumber.ToNative());
|
|
else if (json is JSONString jsonString)
|
|
o = Enum.Parse(nativeType, jsonString.Value);
|
|
else
|
|
throw new NotSupportedException(String.Format("Mapping from {0} to {1} is not supported", json.GetType().Name, nativeType.Name));
|
|
|
|
return true;
|
|
}
|
|
|
|
if (nativeType.IsPrimitive)
|
|
{
|
|
throw new NotSupportedException(string.Format("JSONMapperBase: Unsupported primitive type found: {0}", nativeType));
|
|
}
|
|
|
|
if (TryBuildRememberedMapping(nativeType, out mapping))
|
|
{
|
|
o = mapping.FromJson(this, json);
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
public void Apply(string jsonString, object o) => Apply((JSONObject)JSONParser.Parse(jsonString), o);
|
|
public virtual bool Apply(JSONObject json, object o)
|
|
{
|
|
Type nativeType = o.GetType();
|
|
|
|
if (TryGetMapping(nativeType,out JSONMapping mapping) || TryBuildRememberedMapping(nativeType, out mapping))
|
|
{
|
|
JSONObjectMapping objectMapping = mapping as JSONObjectMapping;
|
|
objectMapping.Apply(json, o);
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
|
|
public virtual JSONValue ToJson(object o)
|
|
{
|
|
if (Serialize(o,out JSONValue json))
|
|
return json;
|
|
throw new NotSupportedException();
|
|
}
|
|
public virtual T FromJson<T>(JSONValue json) => (T)FromJson(json, typeof(T));
|
|
public virtual object FromJson(JSONValue json,Type targetType)
|
|
{
|
|
if (Deserialize(json, targetType, out object o))
|
|
return o;
|
|
throw new NotSupportedException();
|
|
}
|
|
|
|
|
|
|
|
public bool MapMethodParameters(MethodInfo methodInfo, JSONValue jsonArguments, out object[] methodArguments)
|
|
{
|
|
if (jsonArguments is JSONArray jsonArrayArguments)
|
|
return MapMethodParameters(methodInfo.GetParameters(), jsonArrayArguments, out methodArguments);
|
|
if (jsonArguments is JSONObject jsonNamedArguments)
|
|
return MapMethodParameters(methodInfo.GetParameters(), jsonNamedArguments, out methodArguments);
|
|
|
|
throw new ArgumentException(nameof(jsonArguments));
|
|
}
|
|
public bool MapMethodParameters(ParameterInfo[] parameterInfos, JSONValue jsonArguments, out object[] methodArguments)
|
|
{
|
|
if (jsonArguments is JSONArray jsonArrayArguments)
|
|
return MapMethodParameters(parameterInfos, jsonArrayArguments, out methodArguments);
|
|
if (jsonArguments is JSONObject jsonNamedArguments)
|
|
return MapMethodParameters(parameterInfos, jsonNamedArguments, out methodArguments);
|
|
|
|
throw new ArgumentException(nameof(jsonArguments));
|
|
}
|
|
|
|
public bool MapMethodParameters(MethodInfo methodInfo, JSONObject namedArguments, out object[] methodArguments) => MapMethodParameters(methodInfo.GetParameters(), namedArguments, out methodArguments);
|
|
|
|
public bool MapMethodParameters(ParameterInfo[] parameterInfos, JSONObject namedArguments, out object[] methodArguments)
|
|
{
|
|
methodArguments = new object[parameterInfos.Length];
|
|
|
|
for (int n=0; n < methodArguments.Length; n++)
|
|
{
|
|
if (namedArguments.ContainsKey(parameterInfos[n].Name))
|
|
{
|
|
if (!JSONMapper.DefaultMapper.Deserialize(namedArguments[parameterInfos[n].Name], parameterInfos[n].ParameterType, out methodArguments[n]))
|
|
return false;
|
|
} else if (parameterInfos[n].IsOptional)
|
|
{
|
|
methodArguments[n] = parameterInfos[n].DefaultValue;
|
|
} else
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
public bool MapMethodParameters(MethodInfo methodInfo, JSONArray jsonArguments, out object[] methodArguments) => MapMethodParameters(methodInfo.GetParameters(), jsonArguments, out methodArguments);
|
|
public bool MapMethodParameters(ParameterInfo[] parameterInfos, JSONArray jsonArguments, out object[] methodArguments)
|
|
{
|
|
methodArguments = new object[parameterInfos.Length];
|
|
|
|
if (jsonArguments.Count != methodArguments.Length)
|
|
return false;
|
|
|
|
for (int n=0; n < methodArguments.Length; n++)
|
|
{
|
|
if (!JSONMapper.DefaultMapper.Deserialize(jsonArguments[n], parameterInfos[n].ParameterType, out methodArguments[n]))
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
|
|
static JSONMapper()
|
|
{
|
|
DefaultMapper.Add(new JSONMapping(
|
|
typeof(byte),
|
|
(JSONMapper arg1, object arg2) => new JSONNumber((int)(byte)arg2),
|
|
(JSONMapper arg1, JSONValue arg2) => Decimal.ToByte(((JSONNumber)arg2).Decimal)
|
|
));
|
|
DefaultMapper.Add(new JSONMapping(
|
|
typeof(sbyte),
|
|
(JSONMapper arg1, object arg2) => new JSONNumber((int)(sbyte)arg2),
|
|
(JSONMapper arg1, JSONValue arg2) => Decimal.ToSByte(((JSONNumber)arg2).Decimal)
|
|
));
|
|
DefaultMapper.Add(new JSONMapping(
|
|
typeof(short),
|
|
(JSONMapper arg1, object arg2) => new JSONNumber((int)(short)arg2),
|
|
(JSONMapper arg1, JSONValue arg2) => Decimal.ToInt16(((JSONNumber)arg2).Decimal)
|
|
));
|
|
DefaultMapper.Add(new JSONMapping(
|
|
typeof(int),
|
|
(JSONMapper arg1, object arg2) => new JSONNumber((int)arg2),
|
|
(JSONMapper arg1, JSONValue arg2) => Decimal.ToInt32(((JSONNumber)arg2).Decimal)
|
|
));
|
|
DefaultMapper.Add(new JSONMapping(
|
|
typeof(long),
|
|
(JSONMapper arg1, object arg2) => new JSONNumber((long)arg2),
|
|
(JSONMapper arg1, JSONValue arg2) => Decimal.ToInt64(((JSONNumber)arg2).Decimal)
|
|
));
|
|
DefaultMapper.Add(new JSONMapping(
|
|
typeof(ushort),
|
|
(JSONMapper arg1, object arg2) => new JSONNumber((uint)(ushort)arg2),
|
|
(JSONMapper arg1, JSONValue arg2) => Decimal.ToUInt16(((JSONNumber)arg2).Decimal)
|
|
));
|
|
DefaultMapper.Add(new JSONMapping(
|
|
typeof(uint),
|
|
(JSONMapper arg1, object arg2) => new JSONNumber((uint)(uint)arg2),
|
|
(JSONMapper arg1, JSONValue arg2) => Decimal.ToUInt32(((JSONNumber)arg2).Decimal)
|
|
));
|
|
DefaultMapper.Add(new JSONMapping(
|
|
typeof(ulong),
|
|
(JSONMapper arg1, object arg2) => new JSONNumber((ulong)arg2),
|
|
(JSONMapper arg1, JSONValue arg2) => Decimal.ToUInt64(((JSONNumber)arg2).Decimal)
|
|
));
|
|
|
|
/**
|
|
* Float
|
|
**/
|
|
DefaultMapper.Add(new JSONMapping(
|
|
typeof(float),
|
|
(JSONMapper arg1, object arg2) => new JSONNumber((float)arg2),
|
|
(JSONMapper arg1, JSONValue arg2) => (float)Decimal.ToDouble(((JSONNumber)arg2).Decimal)
|
|
));
|
|
DefaultMapper.Add(new JSONMapping(
|
|
typeof(double),
|
|
(JSONMapper arg1, object arg2) => new JSONNumber((double)arg2),
|
|
(JSONMapper arg1, JSONValue arg2) => Decimal.ToDouble(((JSONNumber)arg2).Decimal)
|
|
));
|
|
|
|
/**
|
|
* Strings
|
|
**/
|
|
|
|
DefaultMapper.Add(new JSONMapping(
|
|
typeof(string),
|
|
(JSONMapper arg1, object arg2) => new JSONString((string)arg2),
|
|
(JSONMapper arg1, JSONValue arg2) => ((JSONString)arg2).Value
|
|
));
|
|
|
|
/**
|
|
* Others
|
|
**/
|
|
|
|
DefaultMapper.Add(new JSONMapping(
|
|
typeof(char),
|
|
(JSONMapper arg1, object arg2) => new JSONString(new String(new char[] { (char)arg2 })),
|
|
(JSONMapper arg1, JSONValue arg2) => ((JSONString)arg2).Value[0]
|
|
));
|
|
|
|
DefaultMapper.Add(new JSONMapping(
|
|
typeof(bool),
|
|
(JSONMapper arg1, object arg2) => ((bool)arg2) ? (JSONValue)JSONTrue.Instance : (JSONValue)JSONFalse.Instance,
|
|
(JSONMapper arg1, JSONValue arg2) => (arg2.ValueType == JSONValueType.TRUE) || (arg2.ValueType == JSONValueType.FALSE) ? false : throw new NotSupportedException()
|
|
));
|
|
|
|
DefaultMapper.Add(new JSONByteArrayMapping());
|
|
|
|
DefaultMapper.Add(new JSONDateTimeMapping());
|
|
DefaultMapper.Add(new JSONDateTimeOffsetMapping());
|
|
DefaultMapper.Add(new JSONGuidMapping());
|
|
DefaultMapper.Add(new JSONTimeSpanMapping());
|
|
|
|
DefaultMapper.Add(new JSONIPv6Mapping());
|
|
|
|
DefaultMapper.Add(new JSONRPCCallMapping());
|
|
DefaultMapper.Add(new JSONRPCResultMapping());
|
|
}
|
|
}
|
|
}
|