Created
July 24, 2017 12:55
-
-
Save anonymous/82055eb397fc65068f9987df7160df0a to your computer and use it in GitHub Desktop.
Serialize/Deserialize complex objects into string fields in Solr
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System.Collections.Generic; | |
public class SearchResponse<T> | |
{ | |
public int NumFound { get; set; } | |
public int Start { get; set; } | |
public IEnumerable<T> Docs { get; set; } | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using Newtonsoft.Json; | |
using Newtonsoft.Json.Linq; | |
using System; | |
using System.Collections.Generic; | |
using System.Linq; | |
using System.Net.Http; | |
using System.Threading.Tasks; | |
public class SolrClient | |
{ | |
public SolrClient(string solrUrl) | |
{ | |
this.solrUrl = solrUrl; | |
} | |
public async Task<SearchResponse<T>> Search<T>(string pathAndQuery) | |
{ | |
var settings = new JsonSerializerSettings { ContractResolver = new SolrContractResolver(SolrContractResolverAction.Deserialize) }; | |
var serializer = JsonSerializer.Create(settings); | |
using (var http = SolrHttpClient(solrUrl)) | |
{ | |
var response = await http.GetAsync(pathAndQuery).ConfigureAwait(false); | |
if (!response.IsSuccessStatusCode) | |
throw new Exception(await response.Content.ReadAsStringAsync()); | |
var content = await response.Content.ReadAsStringAsync().ConfigureAwait(false); | |
return JObject.Parse(content)["response"].ToObject<SearchResponse<T>>(serializer); | |
} | |
} | |
public async Task Execute(string pathAndQuery) | |
{ | |
using (var http = SolrHttpClient(solrUrl)) | |
{ | |
var response = await http.GetAsync(pathAndQuery); | |
if (!response.IsSuccessStatusCode) | |
throw new Exception(await response.Content.ReadAsStringAsync()); | |
} | |
} | |
public async Task Delete(string index, Commit commit, string query) | |
{ | |
var json = JsonConvert.SerializeObject(new { delete = new { query } }); | |
using (var http = SolrHttpClient(solrUrl)) | |
{ | |
var response = await http.PostAsync($"{index}/update/json?commit={commit.ToLower()}", new StringContent(json)); | |
if (!response.IsSuccessStatusCode) | |
throw new Exception(await response.Content.ReadAsStringAsync()); | |
} | |
} | |
public async Task Delete<T>(string index, Commit commit, IEnumerable<T> ids) | |
{ | |
if (ids == null) | |
return; | |
var ids_ = ids.ToList(); | |
if (!ids_.Any()) | |
return; | |
var json = JsonConvert.SerializeObject(new { delete = ids_ }); | |
using (var http = SolrHttpClient(solrUrl)) | |
{ | |
var response = await http.PostAsync($"{index}/update/json?commit={commit.ToLower()}", new StringContent(json)); | |
if (!response.IsSuccessStatusCode) | |
throw new Exception(await response.Content.ReadAsStringAsync()); | |
} | |
} | |
public async Task Update<T>(string index, Commit commit, IEnumerable<T> entries) | |
{ | |
if (entries == null) | |
return; | |
var entries_ = entries.ToList(); | |
if (!entries_.Any()) | |
return; | |
var settings = new JsonSerializerSettings { | |
ContractResolver = new SolrContractResolver(SolrContractResolverAction.Serialize), | |
NullValueHandling = NullValueHandling.Ignore | |
}; | |
var json = JsonConvert.SerializeObject(entries_, settings); | |
using (var http = SolrHttpClient(solrUrl)) | |
{ | |
var response = await http.PostAsync($"{index}/update/json?commit={commit.ToLower()}", new StringContent(json)); | |
if (!response.IsSuccessStatusCode) | |
throw new Exception(await response.Content.ReadAsStringAsync()); | |
} | |
} | |
string solrUrl; | |
HttpClient SolrHttpClient(string solrUrl) => new HttpClient { BaseAddress = new Uri(solrUrl + "solr/") }; | |
} | |
public enum Commit | |
{ | |
True, | |
False | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using Newtonsoft.Json; | |
using Newtonsoft.Json.Linq; | |
using Newtonsoft.Json.Serialization; | |
using System; | |
using System.Collections; | |
using System.Collections.Generic; | |
using System.Linq; | |
using System.Reflection; | |
public enum SolrContractResolverAction | |
{ | |
Serialize, | |
Deserialize | |
} | |
/// <summary> | |
/// Transforms complex objects into something Solr understands (raw strings) and back again (C# objects). | |
/// </summary> | |
public class SolrContractResolver : DefaultContractResolver | |
{ | |
SolrContractResolverAction action; | |
public SolrContractResolver(SolrContractResolverAction action) | |
{ | |
this.action = action; | |
} | |
// Solr supports conversion of simple types when dealing with json. If you find a type Solr supports without | |
// custom conversion, feel free to add it here. | |
HashSet<Type> typesNotRequiredToConvert = new HashSet<Type> | |
{ | |
typeof(bool), | |
typeof(int), | |
typeof(long), | |
typeof(float), | |
typeof(double), | |
typeof(decimal), | |
typeof(string), | |
typeof(DateTime), | |
typeof(Guid) | |
}; | |
protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization) | |
{ | |
var property = base.CreateProperty(member, memberSerialization); | |
var propertyInfo = property.DeclaringType.GetProperty(property.UnderlyingName); | |
if (propertyInfo == null) | |
throw new Exception("Fields are unsupported. Please only use properties or feel free to add support for fields."); | |
var propertyType = propertyInfo.PropertyType; | |
if (propertyType.FullName.StartsWith("System.Nullable`1")) | |
// No custom logic for Nullables. You're safe as long you adhere to the types in 'typesNotRequiredToConvert'. | |
return property; | |
if (propertyType.IsEnum) | |
return property; | |
if (typesNotRequiredToConvert.Contains(propertyType)) | |
return property; | |
if (IsCollection(propertyInfo) && typesNotRequiredToConvert.Contains(GetItemType(propertyType))) | |
return property; | |
if (action == SolrContractResolverAction.Serialize) | |
// Object needs to be converted into a json string | |
property.ValueProvider = new ObjectToJsonStringValueProvider(propertyInfo); | |
else if (action == SolrContractResolverAction.Deserialize) | |
// Object needs to be converted from a json string back to an object | |
property.MemberConverter = new JsonStringToObjectConverter(); | |
return property; | |
} | |
bool IsCollection(PropertyInfo propertyInfo) => | |
propertyInfo.PropertyType.GetInterface(typeof(IEnumerable<>).FullName) != null || | |
propertyInfo.PropertyType.GetInterface(typeof(IEnumerable).FullName) != null; | |
/// <summary> | |
/// Get item type for arrays/lists. Returns null if no item type is found | |
/// </summary> | |
Type GetItemType(Type type) | |
{ | |
if (type.IsArray) | |
return type.GetElementType(); | |
// type is IEnumerable<T>; | |
if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(IEnumerable<>)) | |
return type.GetGenericArguments()[0]; | |
// type implements/extends IEnumerable<T>; | |
return type.GetInterfaces() | |
.Where(t => t.IsGenericType && t.GetGenericTypeDefinition() == typeof(IEnumerable<>)) | |
.Select(t => t.GenericTypeArguments[0]).FirstOrDefault(); | |
} | |
} | |
class ObjectToJsonStringValueProvider : IValueProvider | |
{ | |
PropertyInfo propertyInfo; | |
public ObjectToJsonStringValueProvider(PropertyInfo propertyInfo) | |
{ | |
this.propertyInfo = propertyInfo; | |
} | |
public object GetValue(object target) | |
{ | |
var value = propertyInfo.GetValue(target); | |
if (value == null) | |
return null; | |
// Turn complex object into a raw json string, since Solr doesn't support nested objects (it does, but in a weird way) | |
return JsonConvert.SerializeObject(value).ToString(); | |
} | |
public void SetValue(object target, object value) | |
{ | |
throw new NotImplementedException(); | |
} | |
} | |
class JsonStringToObjectConverter : JsonConverter | |
{ | |
public override object ReadJson(JsonReader reader, Type type, object _, JsonSerializer serializer) => | |
// Solr returns nested json as a raw string, but one can apply a json transformer, turning the result into actual json | |
// Both scenarios are supported here. https://cwiki.apache.org/confluence/display/solr/Transforming+Result+Documents | |
reader.TokenType == JsonToken.String | |
? JsonConvert.DeserializeObject(reader.Value.ToString(), type) | |
: JToken.Load(reader).ToObject(type, serializer); | |
public override void WriteJson(JsonWriter writer, object value, JsonSerializer _) | |
{ | |
throw new NotImplementedException(); | |
} | |
public override bool CanWrite => false; | |
public override bool CanRead => true; | |
public override bool CanConvert(Type _) => true; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment