* JustTest.cs: chore: 优化 AVObject 编解码
* AVClient.cs: * AVObject.cs: * AVObject2.cs: * ObjectControllerTests.cs: * AVDecoder.cs: * AVEncoder.cs: * MutableObjectState.cs:
parent
1823dd974b
commit
e5aa736805
|
@ -1,28 +1,41 @@
|
|||
using System;
|
||||
using NUnit.Framework;
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace LeanCloud.Test {
|
||||
public class JustTest {
|
||||
public class Animal {
|
||||
[Test]
|
||||
public void Concat() {
|
||||
Dictionary<string, string> d1 = new Dictionary<string, string> {
|
||||
{ "aaa", "111" }
|
||||
};
|
||||
Dictionary<string, string> d2 = new Dictionary<string, string> {
|
||||
{ "aaa", "222" },
|
||||
{ "ccc", "333" }
|
||||
};
|
||||
IEnumerable<KeyValuePair<string, string>> d = d1.Concat(d2);
|
||||
foreach (var e in d) {
|
||||
TestContext.Out.WriteLine($"{e.Key} : {e.Value}");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public class Dog : Animal {
|
||||
|
||||
}
|
||||
|
||||
public class Walk<T> where T : Animal {
|
||||
public virtual T Do() {
|
||||
return default;
|
||||
List<string> l1 = new List<string> { "aaa" };
|
||||
List<string> l2 = new List<string> { "aaa", "bbb" };
|
||||
IEnumerable<string> l = l1.Concat(l2);
|
||||
foreach (var e in l) {
|
||||
TestContext.Out.WriteLine($"{e}");
|
||||
}
|
||||
}
|
||||
|
||||
public class Run : Walk<Dog> {
|
||||
public override Dog Do() {
|
||||
return base.Do();
|
||||
}
|
||||
}
|
||||
|
||||
public JustTest() {
|
||||
[Test]
|
||||
public void GenericType() {
|
||||
List<int> list = new List<int> { 1, 1, 2, 3, 5, 8 };
|
||||
Type type = list.GetType();
|
||||
TestContext.Out.WriteLine(type);
|
||||
Type genericType = type.GetGenericTypeDefinition();
|
||||
TestContext.Out.WriteLine(genericType);
|
||||
TestContext.Out.WriteLine(typeof(IList<>));
|
||||
TestContext.Out.WriteLine(typeof(List<>));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -15,6 +15,11 @@ namespace LeanCloud.Test {
|
|||
public async Task Save() {
|
||||
AVObject obj = AVObject.Create("Foo");
|
||||
obj["content"] = "hello, world";
|
||||
obj["list"] = new List<int> { 1, 1, 2, 3, 5, 8 };
|
||||
obj["dict"] = new Dictionary<string, int> {
|
||||
{ "hello", 1 },
|
||||
{ "world", 2 }
|
||||
};
|
||||
await obj.SaveAsync();
|
||||
Assert.NotNull(obj.ObjectId);
|
||||
Assert.NotNull(obj.CreatedAt);
|
||||
|
|
|
@ -4,17 +4,13 @@ using System.Collections.Generic;
|
|||
using System.Globalization;
|
||||
using LeanCloud.Utilities;
|
||||
|
||||
namespace LeanCloud.Storage.Internal
|
||||
{
|
||||
public class AVDecoder
|
||||
{
|
||||
namespace LeanCloud.Storage.Internal {
|
||||
public class AVDecoder {
|
||||
// This class isn't really a Singleton, but since it has no state, it's more efficient to get
|
||||
// the default instance.
|
||||
private static readonly AVDecoder instance = new AVDecoder();
|
||||
public static AVDecoder Instance
|
||||
{
|
||||
get
|
||||
{
|
||||
public static AVDecoder Instance {
|
||||
get {
|
||||
return instance;
|
||||
}
|
||||
}
|
||||
|
@ -22,143 +18,78 @@ namespace LeanCloud.Storage.Internal
|
|||
// Prevent default constructor.
|
||||
private AVDecoder() { }
|
||||
|
||||
public object Decode(object data)
|
||||
{
|
||||
if (data == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var dict = data as IDictionary<string, object>;
|
||||
if (dict != null)
|
||||
{
|
||||
if (dict.ContainsKey("__op"))
|
||||
{
|
||||
public object Decode(object data) {
|
||||
// 如果是字典类型
|
||||
if (data is IDictionary<string, object> dict) {
|
||||
if (dict.ContainsKey("__op")) {
|
||||
return AVFieldOperations.Decode(dict);
|
||||
}
|
||||
|
||||
object type;
|
||||
dict.TryGetValue("__type", out type);
|
||||
var typeString = type as string;
|
||||
|
||||
if (typeString == null)
|
||||
{
|
||||
var newDict = new Dictionary<string, object>();
|
||||
foreach (var pair in dict)
|
||||
{
|
||||
newDict[pair.Key] = Decode(pair.Value);
|
||||
if (dict.TryGetValue("__type", out object type)) {
|
||||
string typeString = type as string;
|
||||
switch (typeString) {
|
||||
case "Date":
|
||||
return ParseDate(dict["iso"] as string);
|
||||
case "Bytes":
|
||||
return Convert.FromBase64String(dict["base64"] as string);
|
||||
case "Pointer": {
|
||||
if (dict.Keys.Count > 3) {
|
||||
return DecodeAVObject(dict);
|
||||
}
|
||||
return DecodePointer(dict["className"] as string, dict["objectId"] as string);
|
||||
}
|
||||
case "GeoPoint":
|
||||
return new AVGeoPoint(Conversion.To<double>(dict["latitude"]),
|
||||
Conversion.To<double>(dict["longitude"]));
|
||||
case "Object":
|
||||
return DecodeAVObject(dict);
|
||||
case "Relation":
|
||||
return AVRelationBase.CreateRelation(null, null, dict["className"] as string);
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return newDict;
|
||||
}
|
||||
|
||||
if (typeString == "Date")
|
||||
{
|
||||
return ParseDate(dict["iso"] as string);
|
||||
}
|
||||
|
||||
if (typeString == "Bytes")
|
||||
{
|
||||
return Convert.FromBase64String(dict["base64"] as string);
|
||||
}
|
||||
|
||||
if (typeString == "Pointer")
|
||||
{
|
||||
//set a include key to fetch or query.
|
||||
if (dict.Keys.Count > 3)
|
||||
{
|
||||
return DecodeAVObject(dict);
|
||||
}
|
||||
return DecodePointer(dict["className"] as string, dict["objectId"] as string);
|
||||
}
|
||||
|
||||
if (typeString == "File")
|
||||
{
|
||||
return DecodeAVFile(dict);
|
||||
}
|
||||
|
||||
if (typeString == "GeoPoint")
|
||||
{
|
||||
return new AVGeoPoint(Conversion.To<double>(dict["latitude"]),
|
||||
Conversion.To<double>(dict["longitude"]));
|
||||
}
|
||||
|
||||
if (typeString == "Object")
|
||||
{
|
||||
return DecodeAVObject(dict);
|
||||
}
|
||||
|
||||
if (typeString == "Relation")
|
||||
{
|
||||
return AVRelationBase.CreateRelation(null, null, dict["className"] as string);
|
||||
}
|
||||
|
||||
var converted = new Dictionary<string, object>();
|
||||
foreach (var pair in dict)
|
||||
{
|
||||
foreach (var pair in dict) {
|
||||
converted[pair.Key] = Decode(pair.Value);
|
||||
}
|
||||
return converted;
|
||||
}
|
||||
|
||||
var list = data as IList<object>;
|
||||
if (list != null)
|
||||
{
|
||||
// 如果是数组类型
|
||||
if (data is IList<object> list) {
|
||||
return (from item in list
|
||||
select Decode(item)).ToList();
|
||||
}
|
||||
|
||||
// 原样返回
|
||||
return data;
|
||||
}
|
||||
|
||||
protected virtual object DecodePointer(string className, string objectId)
|
||||
{
|
||||
if (className == "_File")
|
||||
{
|
||||
return AVFile.CreateWithoutData("_File", objectId);
|
||||
}
|
||||
protected virtual object DecodePointer(string className, string objectId) {
|
||||
return AVObject.CreateWithoutData(className, objectId);
|
||||
}
|
||||
protected virtual object DecodeAVObject(IDictionary<string, object> dict)
|
||||
{
|
||||
|
||||
protected virtual object DecodeAVObject(IDictionary<string, object> dict) {
|
||||
var className = dict["className"] as string;
|
||||
if (className == "_File")
|
||||
{
|
||||
return DecodeAVFile(dict);
|
||||
}
|
||||
var state = AVObjectCoder.Instance.Decode(dict, this);
|
||||
return AVObject.FromState<AVObject>(state, dict["className"] as string);
|
||||
}
|
||||
protected virtual object DecodeAVFile(IDictionary<string, object> dict)
|
||||
{
|
||||
var objectId = dict["objectId"] as string;
|
||||
var file = AVFile.CreateWithoutData("_File", objectId);
|
||||
//file.MergeFromJSON(dict);
|
||||
return file;
|
||||
return AVObject.FromState<AVObject>(state, className);
|
||||
}
|
||||
|
||||
|
||||
public virtual IList<T> DecodeList<T>(object data)
|
||||
{
|
||||
public virtual IList<T> DecodeList<T>(object data) {
|
||||
IList<T> rtn = null;
|
||||
var list = (IList<object>)data;
|
||||
if (list != null)
|
||||
{
|
||||
if (list != null) {
|
||||
rtn = new List<T>();
|
||||
foreach (var item in list)
|
||||
{
|
||||
foreach (var item in list) {
|
||||
rtn.Add((T)item);
|
||||
}
|
||||
}
|
||||
return rtn;
|
||||
}
|
||||
|
||||
public static DateTime ParseDate(string input)
|
||||
{
|
||||
var rtn = DateTime.ParseExact(input,
|
||||
public static DateTime ParseDate(string input) {
|
||||
return DateTime.ParseExact(input,
|
||||
AVClient.DateFormatStrings,
|
||||
CultureInfo.InvariantCulture,
|
||||
DateTimeStyles.AssumeUniversal);
|
||||
return rtn;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,29 +5,19 @@ using System.Linq;
|
|||
using LeanCloud.Utilities;
|
||||
using LeanCloud.Storage.Internal;
|
||||
|
||||
namespace LeanCloud.Storage.Internal
|
||||
{
|
||||
namespace LeanCloud.Storage.Internal {
|
||||
/// <summary>
|
||||
/// A <c>AVEncoder</c> can be used to transform objects such as <see cref="AVObject"/> into JSON
|
||||
/// data structures.
|
||||
/// </summary>
|
||||
/// <seealso cref="AVDecoder"/>
|
||||
public abstract class AVEncoder
|
||||
{
|
||||
#if UNITY
|
||||
private static readonly bool isCompiledByIL2CPP = AppDomain.CurrentDomain.FriendlyName.Equals("IL2CPP Root Domain");
|
||||
#else
|
||||
private static readonly bool isCompiledByIL2CPP = false;
|
||||
#endif
|
||||
|
||||
public static bool IsValidType(object value)
|
||||
{
|
||||
public abstract class AVEncoder {
|
||||
public static bool IsValidType(object value) {
|
||||
return value == null ||
|
||||
ReflectionHelpers.IsPrimitive(value.GetType()) ||
|
||||
value is string ||
|
||||
value is AVObject ||
|
||||
value is AVACL ||
|
||||
value is AVFile ||
|
||||
value is AVGeoPoint ||
|
||||
value is AVRelationBase ||
|
||||
value is DateTime ||
|
||||
|
@ -36,77 +26,46 @@ namespace LeanCloud.Storage.Internal
|
|||
Conversion.As<IList<object>>(value) != null;
|
||||
}
|
||||
|
||||
public object Encode(object value)
|
||||
{
|
||||
public object Encode(object value) {
|
||||
// If this object has a special encoding, encode it and return the
|
||||
// encoded object. Otherwise, just return the original object.
|
||||
if (value is DateTime)
|
||||
{
|
||||
return new Dictionary<string, object>
|
||||
{
|
||||
{
|
||||
"iso", ((DateTime)value).ToUniversalTime().ToString(AVClient.DateFormatStrings.First(), CultureInfo.InvariantCulture)
|
||||
},
|
||||
{
|
||||
"__type", "Date"
|
||||
}
|
||||
if (value is DateTime) {
|
||||
return new Dictionary<string, object> {
|
||||
{ "__type", "Date" },
|
||||
{ "iso", ((DateTime)value).ToUniversalTime().ToString(AVClient.DateFormatStrings.First(), CultureInfo.InvariantCulture) }
|
||||
};
|
||||
}
|
||||
|
||||
if (value is AVFile)
|
||||
{
|
||||
var file = value as AVFile;
|
||||
return new Dictionary<string, object>
|
||||
{
|
||||
{"__type", "Pointer"},
|
||||
{ "className", "_File"},
|
||||
{ "objectId", file.ObjectId}
|
||||
if (value is byte[] bytes) {
|
||||
return new Dictionary<string, object> {
|
||||
{ "__type", "Bytes" },
|
||||
{ "base64", Convert.ToBase64String(bytes) }
|
||||
};
|
||||
}
|
||||
|
||||
var bytes = value as byte[];
|
||||
if (bytes != null)
|
||||
{
|
||||
return new Dictionary<string, object>
|
||||
{
|
||||
{ "__type", "Bytes"},
|
||||
{ "base64", Convert.ToBase64String(bytes)}
|
||||
};
|
||||
}
|
||||
|
||||
var obj = value as AVObject;
|
||||
if (obj != null)
|
||||
{
|
||||
if (value is AVObject obj) {
|
||||
return EncodeAVObject(obj);
|
||||
}
|
||||
|
||||
var jsonConvertible = value as IJsonConvertible;
|
||||
if (jsonConvertible != null)
|
||||
{
|
||||
if (value is IJsonConvertible jsonConvertible) {
|
||||
return jsonConvertible.ToJSON();
|
||||
}
|
||||
|
||||
var dict = Conversion.As<IDictionary<string, object>>(value);
|
||||
if (dict != null)
|
||||
{
|
||||
var json = new Dictionary<string, object>();
|
||||
foreach (var pair in dict)
|
||||
{
|
||||
json[pair.Key] = Encode(pair.Value);
|
||||
}
|
||||
return json;
|
||||
}
|
||||
//var dict = Conversion.As<IDictionary<string, object>>(value);
|
||||
//if (dict != null) {
|
||||
// var json = new Dictionary<string, object>();
|
||||
// foreach (var pair in dict) {
|
||||
// json[pair.Key] = Encode(pair.Value);
|
||||
// }
|
||||
// return json;
|
||||
//}
|
||||
|
||||
var list = Conversion.As<IList<object>>(value);
|
||||
if (list != null)
|
||||
{
|
||||
return EncodeList(list);
|
||||
}
|
||||
//var list = Conversion.As<IList<object>>(value);
|
||||
//if (list != null) {
|
||||
// return EncodeList(list);
|
||||
//}
|
||||
|
||||
// TODO (hallucinogen): convert IAVFieldOperation to IJsonConvertible
|
||||
var operation = value as IAVFieldOperation;
|
||||
if (operation != null)
|
||||
{
|
||||
if (value is IAVFieldOperation operation) {
|
||||
return operation.Encode();
|
||||
}
|
||||
|
||||
|
@ -115,19 +74,10 @@ namespace LeanCloud.Storage.Internal
|
|||
|
||||
protected abstract IDictionary<string, object> EncodeAVObject(AVObject value);
|
||||
|
||||
private object EncodeList(IList<object> list)
|
||||
{
|
||||
var newArray = new List<object>();
|
||||
// We need to explicitly cast `list` to `List<object>` rather than
|
||||
// `IList<object>` because IL2CPP is stricter than the usual Unity AOT compiler pipeline.
|
||||
if (isCompiledByIL2CPP && list.GetType().IsArray)
|
||||
{
|
||||
list = new List<object>(list);
|
||||
}
|
||||
foreach (var item in list)
|
||||
{
|
||||
if (!IsValidType(item))
|
||||
{
|
||||
private object EncodeList(IList<object> list) {
|
||||
List<object> newArray = new List<object>();
|
||||
foreach (object item in list) {
|
||||
if (!IsValidType(item)) {
|
||||
throw new ArgumentException("Invalid type for value in an array");
|
||||
}
|
||||
newArray.Add(Encode(item));
|
||||
|
|
|
@ -10,17 +10,9 @@ namespace LeanCloud.Storage.Internal {
|
|||
public DateTime? UpdatedAt { get; set; }
|
||||
public DateTime? CreatedAt { get; set; }
|
||||
|
||||
// Initialize serverData to avoid further null checking.
|
||||
private IDictionary<string, object> serverData = new Dictionary<string, object>();
|
||||
public IDictionary<string, object> ServerData {
|
||||
get {
|
||||
return serverData;
|
||||
}
|
||||
|
||||
set {
|
||||
serverData = value;
|
||||
}
|
||||
}
|
||||
get; set;
|
||||
} = new Dictionary<string, object>();
|
||||
|
||||
public object this[string key] {
|
||||
get {
|
||||
|
@ -35,8 +27,7 @@ namespace LeanCloud.Storage.Internal {
|
|||
public void Apply(IDictionary<string, IAVFieldOperation> operationSet) {
|
||||
// Apply operationSet
|
||||
foreach (var pair in operationSet) {
|
||||
object oldValue;
|
||||
ServerData.TryGetValue(pair.Key, out oldValue);
|
||||
ServerData.TryGetValue(pair.Key, out object oldValue);
|
||||
var newValue = pair.Value.Apply(oldValue, pair.Key);
|
||||
if (newValue != AVDeleteOperation.DeleteToken) {
|
||||
ServerData[pair.Key] = newValue;
|
||||
|
|
|
@ -118,10 +118,14 @@ namespace LeanCloud {
|
|||
});
|
||||
}
|
||||
|
||||
public static void SetServerURL(string serverUrl) {
|
||||
CurrentConfiguration.ApiServer = serverUrl;
|
||||
CurrentConfiguration.EngineServer = serverUrl;
|
||||
CurrentConfiguration.RTMServer = serverUrl;
|
||||
public static void Initialize(string applicationId, string applicationKey, string serverUrl) {
|
||||
Initialize(new Configuration {
|
||||
ApplicationId = applicationId,
|
||||
ApplicationKey = applicationKey,
|
||||
ApiServer = serverUrl,
|
||||
EngineServer = serverUrl,
|
||||
RTMServer = serverUrl
|
||||
});
|
||||
}
|
||||
|
||||
internal static Action<string> LogTracker { get; private set; }
|
||||
|
|
|
@ -8,6 +8,7 @@ using System.Runtime.CompilerServices;
|
|||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using System.Collections;
|
||||
using System.Collections.Concurrent;
|
||||
|
||||
namespace LeanCloud {
|
||||
/// <summary>
|
||||
|
@ -971,8 +972,7 @@ string propertyName
|
|||
List<string> appliedKeys = new List<string>();
|
||||
lock (mutex) {
|
||||
foreach (var pair in operations) {
|
||||
object oldValue;
|
||||
map.TryGetValue(pair.Key, out oldValue);
|
||||
map.TryGetValue(pair.Key, out object oldValue);
|
||||
var newValue = pair.Value.Apply(oldValue, pair.Key);
|
||||
if (newValue != AVDeleteOperation.DeleteToken) {
|
||||
map[pair.Key] = newValue;
|
||||
|
|
|
@ -0,0 +1,64 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections;
|
||||
|
||||
namespace Storage.Public {
|
||||
public class AVObject2 : IDictionary<string, object> {
|
||||
ConcurrentDictionary<string, object> data;
|
||||
|
||||
public object this[string key] { get => ((IDictionary<string, object>)data)[key]; set => ((IDictionary<string, object>)data)[key] = value; }
|
||||
|
||||
public ICollection<string> Keys => ((IDictionary<string, object>)data).Keys;
|
||||
|
||||
public ICollection<object> Values => ((IDictionary<string, object>)data).Values;
|
||||
|
||||
public int Count => ((IDictionary<string, object>)data).Count;
|
||||
|
||||
public bool IsReadOnly => ((IDictionary<string, object>)data).IsReadOnly;
|
||||
|
||||
public void Add(string key, object value) {
|
||||
((IDictionary<string, object>)data).Add(key, value);
|
||||
}
|
||||
|
||||
public void Add(KeyValuePair<string, object> item) {
|
||||
((IDictionary<string, object>)data).Add(item);
|
||||
}
|
||||
|
||||
public void Clear() {
|
||||
((IDictionary<string, object>)data).Clear();
|
||||
}
|
||||
|
||||
public bool Contains(KeyValuePair<string, object> item) {
|
||||
return ((IDictionary<string, object>)data).Contains(item);
|
||||
}
|
||||
|
||||
public bool ContainsKey(string key) {
|
||||
return ((IDictionary<string, object>)data).ContainsKey(key);
|
||||
}
|
||||
|
||||
public void CopyTo(KeyValuePair<string, object>[] array, int arrayIndex) {
|
||||
((IDictionary<string, object>)data).CopyTo(array, arrayIndex);
|
||||
}
|
||||
|
||||
public IEnumerator<KeyValuePair<string, object>> GetEnumerator() {
|
||||
return ((IDictionary<string, object>)data).GetEnumerator();
|
||||
}
|
||||
|
||||
public bool Remove(string key) {
|
||||
return ((IDictionary<string, object>)data).Remove(key);
|
||||
}
|
||||
|
||||
public bool Remove(KeyValuePair<string, object> item) {
|
||||
return ((IDictionary<string, object>)data).Remove(item);
|
||||
}
|
||||
|
||||
public bool TryGetValue(string key, out object value) {
|
||||
return ((IDictionary<string, object>)data).TryGetValue(key, out value);
|
||||
}
|
||||
|
||||
IEnumerator IEnumerable.GetEnumerator() {
|
||||
return ((IDictionary<string, object>)data).GetEnumerator();
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue