I have a large document and I need to query just the SomeProp property that must be deserialized by a custom JsonConverter. I can't seem to find anything relevant to this situation in RavenDB docs, or searching the web.
public class Document
{
public DocId SomeProp { get; set; }
public string Data { get; set; }
// more properties...
}
public class DocId
{
public Guid Id { get; set; }
}
SomeProp deserializes properly for loading a document, but I get a null back if I try to run this query:
var x = session.Query<Document>()
.Select(i => i.SomeProp)
.FirstOrDefault();
When IdConverter.ReadJson() is called in the Query code path the JsonReader looking at the beginning of this JSON object:
{
"SomeProp":"95704f13-fd64-4dcb-a64e-6aa42bc577d6",
"@metadata":{ ... }
}
while the Load path the JsonReader is looking at SomeProp's value in the following JSON
{
"SomeProp":"95704f13-fd64-4dcb-a64e-6aa42bc577d6",
"Data":null ...
}
I would expect the JsonReader to be looking at the SomeProp's value each time. Am I doing something wrong? Or does RavenDB expect me to parse both the object and string value in my converter?
I've created this converter, contract resolver and unit test to reproduce the problem:
public class IdConverter : JsonConverter
{
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
writer.WriteValue(((DocId)value).Id.ToString());
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
return reader.TokenType != JsonToken.String ? null : new DocId { Id = Guid.Parse(reader.Value as string) };
}
public override bool CanConvert(Type type)
{
return typeof(DocId).IsAssignableFrom(type);
}
}
public class LocalResolver : DefaultRavenContractResolver
{
public LocalResolver(ISerializationConventions x) : base(x)
{
}
public override JsonContract ResolveContract(Type type)
{
if (type.IsGenericType)
{
var p = string.Join(", ", type.GenericTypeArguments.Select(x => x.Name));
Debug.WriteLine($"Resolving converter for {type.Name}<{p}>");
}
else
{
Debug.WriteLine($"Resolving converter for {type.Name}");
}
if (type.IsAssignableFrom(typeof(DocId)))
return new JsonObjectContract(type)
{
Converter = new IdConverter()
};
return base.ResolveContract(type);
}
}
[TestMethod]
public void MyTestMethod()
{
var conventions = new NewtonsoftJsonSerializationConventions();
var resolver = new LocalResolver(conventions);
conventions.JsonContractResolver = resolver;
var store = new DocumentStore()
{
Urls = new[] { <your URL here> },
Database = DatabaseConfiguration.Name,
Conventions = new DocumentConventions()
{
Serialization = conventions
}
};
const string g = "95704f13-fd64-4dcb-a64e-6aa42bc577d6";
var id = $"Doc/{g}";
store.Initialize();
using (var session = store.OpenSession())
{
var doc = new Document { SomeProp = new DocId { Id = Guid.Parse(g) } };
Debug.WriteLine("Storing document");
session.Store(doc, id);
session.SaveChanges();
}
using (var session = store.OpenSession())
{
Debug.WriteLine("Loading document");
var x = session.Load<Document>(id);
Assert.AreEqual(g, x.SomeProp.Id.ToString());
}
using (var session = store.OpenSession())
{
Debug.WriteLine("Querying document");
var x = session.Query<Document>()
.Select(i => i.SomeProp)
.FirstOrDefault();
Assert.IsNotNull(x);
Assert.AreEqual(g, x.Id.ToString());
}
}