i have a problem and want to know if it is or if it will be possible with json.net
i have a superclass called A and two classes which inherit from it, B1 and B2
when i made a list of type A and then add some B1 and B2, after serialize and deserialize they are all of type A.
Is it possible that each class which inherit will be casted to its former class type like B1 or B2?
Yes, it should work as long as you inform JSON.Net that you want to use TypeNameHandling.All and use appropriate casting. The following works in my project:
public class MembaseJsonSerializer<T>
{
private IContractResolver resolver;
public MembaseJsonSerializer(IContractResolver resolver)
{
this.resolver = resolver;
}
public R FromJson<R>(object json)
{
if (typeof(T).IsAssignableFrom(typeof(R)))
{
object res = JsonConvert.DeserializeObject(json.ToString(), new JsonSerializerSettings() {
ContractResolver = resolver,
TypeNameHandling = TypeNameHandling.All
});
return (R)res;
}
throw new NotImplementedException("Type is not assignable.");
}
public string ToJson(object obj)
{
if (typeof(T).IsAssignableFrom(obj.GetType()))
{
string json = JsonConvert.SerializeObject(obj, Formatting.None, new JsonSerializerSettings() {
ContractResolver = resolver,
TypeNameHandling = TypeNameHandling.All
});
return json;
}
throw new NotImplementedException("Type is not assignable.");
}
}
Where T is the base class and R is the sub class.
Related
This might be a duplicate. But I cannot find a solution to my Problem.
I have a class
public class MyResponse implements Serializable {
private boolean isSuccess;
public boolean isSuccess() {
return isSuccess;
}
public void setSuccess(boolean isSuccess) {
this.isSuccess = isSuccess;
}
}
Getters and setters are generated by Eclipse.
In another class, I set the value to true, and write it as a JSON string.
System.out.println(new ObjectMapper().writeValueAsString(myResponse));
In JSON, the key is coming as {"success": true}.
I want the key as isSuccess itself. Is Jackson using the setter method while serializing? How do I make the key the field name itself?
This is a slightly late answer, but may be useful for anyone else coming to this page.
A simple solution to changing the name that Jackson will use for when serializing to JSON is to use the #JsonProperty annotation, so your example would become:
public class MyResponse implements Serializable {
private boolean isSuccess;
#JsonProperty(value="isSuccess")
public boolean isSuccess() {
return isSuccess;
}
public void setSuccess(boolean isSuccess) {
this.isSuccess = isSuccess;
}
}
This would then be serialised to JSON as {"isSuccess":true}, but has the advantage of not having to modify your getter method name.
Note that in this case you could also write the annotation as #JsonProperty("isSuccess") as it only has the single value element
I recently ran into this issue and this is what I found. Jackson will inspect any class that you pass to it for getters and setters, and use those methods for serialization and deserialization. What follows "get", "is" and "set" in those methods will be used as the key for the JSON field ("isValid" for getIsValid and setIsValid).
public class JacksonExample {
private boolean isValid = false;
public boolean getIsValid() {
return isValid;
}
public void setIsValid(boolean isValid) {
this.isValid = isValid;
}
}
Similarly "isSuccess" will become "success", unless renamed to "isIsSuccess" or "getIsSuccess"
Read more here: http://www.citrine.io/blog/2015/5/20/jackson-json-processor
Using both annotations below, forces the output JSON to include is_xxx:
#get:JsonProperty("is_something")
#param:JsonProperty("is_something")
When you are using Kotlin and data classes:
data class Dto(
#get:JsonProperty("isSuccess") val isSuccess: Boolean
)
You might need to add #param:JsonProperty("isSuccess") if you are going to deserialize JSON as well.
EDIT: If you are using swagger-annotations to generate documentation, the property will be marked as readOnly when using #get:JsonProperty. In order to solve this, you can do:
#JsonAutoDetect(isGetterVisibility = JsonAutoDetect.Visibility.NONE)
data class Dto(
#field:JsonProperty(value = "isSuccess") val isSuccess: Boolean
)
You can configure your ObjectMapper as follows:
mapper.setPropertyNamingStrategy(new PropertyNamingStrategy() {
#Override
public String nameForGetterMethod(MapperConfig<?> config, AnnotatedMethod method, String defaultName)
{
if(method.hasReturnType() && (method.getRawReturnType() == Boolean.class || method.getRawReturnType() == boolean.class)
&& method.getName().startsWith("is")) {
return method.getName();
}
return super.nameForGetterMethod(config, method, defaultName);
}
});
I didn't want to mess with some custom naming strategies, nor re-creating some accessors.
The less code, the happier I am.
This did the trick for us :
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonProperty;
#JsonIgnoreProperties({"success", "deleted"}) // <- Prevents serialization duplicates
public class MyResponse {
private String id;
private #JsonProperty("isSuccess") boolean isSuccess; // <- Forces field name
private #JsonProperty("isDeleted") boolean isDeleted;
}
Building upon Utkarsh's answer..
Getter names minus get/is is used as the JSON name.
public class Example{
private String radcliffe;
public getHarryPotter(){
return radcliffe;
}
}
is stored as { "harryPotter" : "whateverYouGaveHere" }
For Deserialization, Jackson checks against both the setter and the field name.
For the Json String { "word1" : "example" }, both the below are valid.
public class Example{
private String word1;
public setword2( String pqr){
this.word1 = pqr;
}
}
public class Example2{
private String word2;
public setWord1(String pqr){
this.word2 = pqr ;
}
}
A more interesting question is which order Jackson considers for deserialization. If i try to deserialize { "word1" : "myName" } with
public class Example3{
private String word1;
private String word2;
public setWord1( String parameter){
this.word2 = parameter ;
}
}
I did not test the above case, but it would be interesting to see the values of word1 & word2 ...
Note: I used drastically different names to emphasize which fields are required to be same.
You can change primitive boolean to java.lang.Boolean (+ use #JsonPropery)
#JsonProperty("isA")
private Boolean isA = false;
public Boolean getA() {
return this.isA;
}
public void setA(Boolean a) {
this.isA = a;
}
Worked excellent for me.
If you are interested in handling 3rd party classes not under your control (like #edmundpie mentioned in a comment) then you add Mixin classes to your ObjectMapper where the property/field names should match the ones from your 3rd party class:
public class MyStack32270422 {
public static void main(String[] args) {
ObjectMapper om3rdParty = new ObjectMapper();
om3rdParty .addMixIn(My3rdPartyResponse.class, MixinMyResponse.class);
// add further mixins if required
String jsonString = om3rdParty.writeValueAsString(new My3rdPartyResponse());
System.out.println(jsonString);
}
}
class MixinMyResponse {
// add all jackson annotations here you want to be used when handling My3rdPartyResponse classes
#JsonProperty("isSuccess")
private boolean isSuccess;
}
class My3rdPartyResponse{
private boolean isSuccess = true;
// getter and setter here if desired
}
Basically you add all your Jackson annotations to your Mixin classes as if you would own the class. In my opinion quite a nice solution as you don't have to mess around with checking method names starting with "is.." and so on.
there is another method for this problem.
just define a new sub-class extends PropertyNamingStrategy and pass it to ObjectMapper instance.
here is a code snippet may be help more:
mapper.setPropertyNamingStrategy(new PropertyNamingStrategy() {
#Override
public String nameForGetterMethod(MapperConfig<?> config, AnnotatedMethod method, String defaultName) {
String input = defaultName;
if(method.getName().startsWith("is")){
input = method.getName();
}
//copy from LowerCaseWithUnderscoresStrategy
if (input == null) return input; // garbage in, garbage out
int length = input.length();
StringBuilder result = new StringBuilder(length * 2);
int resultLength = 0;
boolean wasPrevTranslated = false;
for (int i = 0; i < length; i++)
{
char c = input.charAt(i);
if (i > 0 || c != '_') // skip first starting underscore
{
if (Character.isUpperCase(c))
{
if (!wasPrevTranslated && resultLength > 0 && result.charAt(resultLength - 1) != '_')
{
result.append('_');
resultLength++;
}
c = Character.toLowerCase(c);
wasPrevTranslated = true;
}
else
{
wasPrevTranslated = false;
}
result.append(c);
resultLength++;
}
}
return resultLength > 0 ? result.toString() : input;
}
});
The accepted answer won't work for my case.
In my case, the class is not owned by me. The problematic class comes from 3rd party dependencies, so I can't just add #JsonProperty annotation in it.
To solve it, inspired by #burak answer above, I created a custom PropertyNamingStrategy as follow:
mapper.setPropertyNamingStrategy(new PropertyNamingStrategy() {
#Override
public String nameForSetterMethod(MapperConfig<?> config, AnnotatedMethod method, String defaultName)
{
if (method.getParameterCount() == 1 &&
(method.getRawParameterType(0) == Boolean.class || method.getRawParameterType(0) == boolean.class) &&
method.getName().startsWith("set")) {
Class<?> containingClass = method.getDeclaringClass();
String potentialFieldName = "is" + method.getName().substring(3);
try {
containingClass.getDeclaredField(potentialFieldName);
return potentialFieldName;
} catch (NoSuchFieldException e) {
// do nothing and fall through
}
}
return super.nameForSetterMethod(config, method, defaultName);
}
#Override
public String nameForGetterMethod(MapperConfig<?> config, AnnotatedMethod method, String defaultName)
{
if(method.hasReturnType() && (method.getRawReturnType() == Boolean.class || method.getRawReturnType() == boolean.class)
&& method.getName().startsWith("is")) {
Class<?> containingClass = method.getDeclaringClass();
String potentialFieldName = method.getName();
try {
containingClass.getDeclaredField(potentialFieldName);
return potentialFieldName;
} catch (NoSuchFieldException e) {
// do nothing and fall through
}
}
return super.nameForGetterMethod(config, method, defaultName);
}
});
Basically what this does is, before serializing and deserializing, it checks in the target/source class which property name is present in the class, whether it is isEnabled or enabled property.
Based on that, the mapper will serialize and deserialize to the property name that is exist.
I'm trying to serialize an object (Root), with some duplicated entries of MyObject. Just want store the whole objects one, I'm using #JsonIdentityReference, which works pretty well.
However, I realize that it will generate un-deserializable object, if there're equal objects with different reference. I wonder if there's a configuration in Jackson to change this behavior, thanks!
#Value
#AllArgsConstructor
#NoArgsConstructor(force = true)
class Root {
private List<MyObject> allObjects;
private Map<String, MyObject> objectMap;
}
#Value
#AllArgsConstructor
#NoArgsConstructor(force = true)
#JsonIdentityReference
#JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class, property = "id")
class MyObject {
private String id;
private int value;
}
public class Main {
public static void main() throws JsonProcessingException {
// Constructing equal objects
val obj1 = new MyObject("a", 1);
val obj2 = new MyObject("a", 1);
assert obj1.equals(obj2);
val root = new Root(
Lists.newArrayList(obj1),
ImmutableMap.of(
"lorem", obj2
)
);
val objectMapper = new ObjectMapper();
val json = objectMapper.writeValueAsString(root);
// {"allObjects":[{"id":"a","value":1}],"objectMap":{"lorem":{"id":"a","value":1}}}
// Note here both obj1 and obj2 are expanded.
// Exception: Already had POJO for id
val deserialized = objectMapper.readValue(json, Root.class);
assert root.equals(deserialized);
}
}
I'm using Jackson 2.10.
Full stacktrace:
Exception in thread "main" com.fasterxml.jackson.databind.JsonMappingException: Already had POJO for id (java.lang.String) [[ObjectId: key=a, type=com.fasterxml.jackson.databind.deser.impl.PropertyBasedObjectIdGenerator, scope=java.lang.Object]] (through reference chain: Root["objectMap"]->java.util.LinkedHashMap["lorem"]->MyObject["id"])
at com.fasterxml.jackson.databind.JsonMappingException.wrapWithPath(JsonMappingException.java:394)
at com.fasterxml.jackson.databind.JsonMappingException.wrapWithPath(JsonMappingException.java:353)
at com.fasterxml.jackson.databind.deser.BeanDeserializerBase.wrapAndThrow(BeanDeserializerBase.java:1714)
at com.fasterxml.jackson.databind.deser.BeanDeserializer.deserializeFromObject(BeanDeserializer.java:371)
at com.fasterxml.jackson.databind.deser.BeanDeserializerBase.deserializeWithObjectId(BeanDeserializerBase.java:1257)
at com.fasterxml.jackson.databind.deser.BeanDeserializer.deserialize(BeanDeserializer.java:157)
at com.fasterxml.jackson.databind.deser.std.MapDeserializer._readAndBindStringKeyMap(MapDeserializer.java:527)
at com.fasterxml.jackson.databind.deser.std.MapDeserializer.deserialize(MapDeserializer.java:364)
at com.fasterxml.jackson.databind.deser.std.MapDeserializer.deserialize(MapDeserializer.java:29)
at com.fasterxml.jackson.databind.deser.impl.FieldProperty.deserializeAndSet(FieldProperty.java:138)
at com.fasterxml.jackson.databind.deser.BeanDeserializer.vanillaDeserialize(BeanDeserializer.java:288)
at com.fasterxml.jackson.databind.deser.BeanDeserializer.deserialize(BeanDeserializer.java:151)
at com.fasterxml.jackson.databind.ObjectMapper._readMapAndClose(ObjectMapper.java:4202)
at com.fasterxml.jackson.databind.ObjectMapper.readValue(ObjectMapper.java:3205)
at com.fasterxml.jackson.databind.ObjectMapper.readValue(ObjectMapper.java:3173)
at Main.main(Main.java:53)
Caused by: java.lang.IllegalStateException: Already had POJO for id (java.lang.String) [[ObjectId: key=a, type=com.fasterxml.jackson.databind.deser.impl.PropertyBasedObjectIdGenerator, scope=java.lang.Object]]
at com.fasterxml.jackson.annotation.SimpleObjectIdResolver.bindItem(SimpleObjectIdResolver.java:24)
at com.fasterxml.jackson.databind.deser.impl.ReadableObjectId.bindItem(ReadableObjectId.java:57)
at com.fasterxml.jackson.databind.deser.impl.ObjectIdValueProperty.deserializeSetAndReturn(ObjectIdValueProperty.java:101)
at com.fasterxml.jackson.databind.deser.impl.ObjectIdValueProperty.deserializeAndSet(ObjectIdValueProperty.java:83)
at com.fasterxml.jackson.databind.deser.BeanDeserializer.deserializeFromObject(BeanDeserializer.java:369)
... 14 more
As I mentioned earlier, this setup only works if obj1 == obj2, as the two objects with same ID should be identity-equal. In that case, the second object would also net get expanded during serialization (alwaysAsId = false only expands the first object).
However, if you want to have this setup and are fine with the serialization, you could use a custom Resolver for deserialization that stores a single instance per key:
#JsonIdentityReference(alwaysAsId = false)
#JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class, property = "id", resolver = CustomScopeResolver.class)
static class MyObject {
private String id;
// ...
}
class CustomScopeResolver implements ObjectIdResolver {
Map<String, MyObject> data = new HashMap<>();
#Override
public void bindItem(final IdKey id, final Object pojo) {
data.put(id.key.toString(), (MyObject) pojo);
}
#Override
public Object resolveId(final IdKey id) {
return data.get(id.key);
}
#Override
public ObjectIdResolver newForDeserialization(final Object context) {
return new CustomScopeResolver();
}
#Override
public boolean canUseFor(final ObjectIdResolver resolverType) {
return false;
}
}
NEW EDIT: Apparently, its very easy: Just turn on objectMapper.configure(SerializationFeature.USE_EQUALITY_FOR_OBJECT_ID, true); so that the DefaultSerializerProvider uses a regular Hashmap instead of an IdentityHashMap to manage the serialized beans.
DEPRECATED: Update for Serialization: It is possible to achieve this by adding a custom SerializationProvider:
class CustomEqualObjectsSerializerProvider extends DefaultSerializerProvider {
private final Collection<MyObject> data = new HashSet<>();
private final SerializerProvider src;
private final SerializationConfig config;
private final SerializerFactory f;
public CustomEqualObjectsSerializerProvider(
final SerializerProvider src,
final SerializationConfig config,
final SerializerFactory f) {
super(src, config, f);
this.src = src;
this.config = config;
this.f = f;
}
#Override
public DefaultSerializerProvider createInstance(final SerializationConfig config, final SerializerFactory jsf) {
return new CustomEqualObjectsSerializerProvider(src, this.config, f);
}
#Override
public WritableObjectId findObjectId(final Object forPojo, final ObjectIdGenerator<?> generatorType) {
// check if there is an equivalent pojo, use it if exists
final Optional<MyObject> equivalentObject = data.stream()
.filter(forPojo::equals)
.findFirst();
if (equivalentObject.isPresent()) {
return super.findObjectId(equivalentObject.get(), generatorType);
} else {
if (forPojo instanceof MyObject) {
data.add((MyObject) forPojo);
}
return super.findObjectId(forPojo, generatorType);
}
}
}
#Test
public void main() throws IOException {
// Constructing equal objects
final MyObject obj1 = new MyObject();
obj1.setId("a");
final MyObject obj2 = new MyObject();
obj2.setId("a");
assert obj1.equals(obj2);
final Root root = new Root();
root.setAllObjects(Collections.singletonList(obj1));
root.setObjectMap(Collections.singletonMap(
"lorem", obj2));
final ObjectMapper objectMapper = new ObjectMapper();
objectMapper.setSerializerProvider(
new CustomEqualObjectsSerializerProvider(
objectMapper.getSerializerProvider(),
objectMapper.getSerializationConfig(),
objectMapper.getSerializerFactory()));
final String json = objectMapper.writeValueAsString(root);
System.out.println(json); // second object is not expanded!
}
I have a web interface with a lot of data grids that allow the user passing a search criteria to the server.
I don't want to actually build an explicit method that will handle each grid individually, so I thought to allow passing JSON criteria from the client to the server.
This code
string res = JsonConvert.SerializeObject(Restrictions.Eq("id", "1"));
will return will return {"PropertyName":"id","Value":"1"} which is exactly what I wanted to pass from the client - perfect.
but this code with or statement:
string res = JsonConvert.SerializeObject(Restrictions.Or(Restrictions.Eq("id", "1"),Restrictions.Eq("id", "2")))
will return {}
What is the way to convert JSON into a Criteria and back in nHibernate.
To be more accurate, I have something like this in the client, and I want my server to support it. I can serialize the client ui to json.
Thanks
The empty json serialization happens because newtonsoft can only serialize public fields.
The code below will serialize all fields of a class regardless of its visibility.
public class MyContractResolver : DefaultContractResolver
{
protected override IList<JsonProperty> CreateProperties(Type type, MemberSerialization memberSerialization)
{
var props = type.GetProperties(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance)
.Select(p => base.CreateProperty(p, memberSerialization))
.Union(type.GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance)
.Select(f => base.CreateProperty(f, memberSerialization)))
.ToList();
props.ForEach(p => { p.Writable = true; p.Readable = true; });
return props;
}
}
...
var myOrs = Restrictions.Or(Restrictions.Eq("id", "1"), Restrictions.Eq("id", "2"));
var settings = new JsonSerializerSettings()
{
ContractResolver = new MyContractResolver()
};
string json = JsonConvert.SerializeObject(myOrs, settings);
Now to deserialize this json we have a problem, the NHibernate classes are either abstract or don't have a public empty constructor.
You will need to create a custom class to deserialize.
public class MyConverter : JsonConverter
{
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException("Not implemented yet");
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
JObject obj = JObject.Load(reader);
if (obj.Value<string>("Op") == "or")
{
var leftJObject = obj["LeftHandSide"];
var rightJObject = obj["RightHandSide"];
var left = new SimpleExpression(leftJObject.Value<string>("PropertyName"), GetJTokenValue(leftJObject["Value"]), leftJObject.Value<string>("Op"));
var right = new SimpleExpression(rightJObject.Value<string>("PropertyName"), GetJTokenValue(rightJObject["Value"]), rightJObject.Value<string>("Op"));
return Restrictions.Or(left, right);
}
//TODO: The rest of the restrictions
return null;
}
public override bool CanWrite
{
get { return false; }
}
public override bool CanConvert(Type objectType)
{
return true;
}
private object GetJTokenValue(JToken token)
{
if (token.Type == JTokenType.String)
{
return token.Value<string>();
}
else if (token.Type == JTokenType.Integer)
{
return token.Value<Int32>();
}
//TODO: The rest of the types
return null;
}
}
...
var result = JsonConvert.DeserializeObject<AbstractCriterion>(json, new MyConverter());
I am trying to use reflection and ninject on the same project. Here is my code :
Type type = Type.GetType("MySolution.Project.Web.App_Code.DataClass");
MethodInfo theMethod = type.GetMethod("Events_ListAll");
object classInstance = Activator.CreateInstance(type, null);
And here is my class that contains that method:
public class DataClass
{
private IEventService eventService;
public DataClass(IEventService eventService)
{
this.eventService = eventService;
}
public String Events_ListAll()
{
List<Event> lstEvents = eventService.GetEvents().ToList<Event>();
return "";
}
}
I get an error saying that there is no constructor found. The solution to that would be to ad an empty default constructor, but that wont inject class I want. Is there any workaround to solve this?
You will need a concrete instance of IEventService to pass as parameter to ctor of DataClass, like this Activator.CreateInstance(type, instance);, so you got many approaches to do that, see 2 of :
1st - class has a concrete IEventService
That class where you doing the reflection has a concrete instance of IEventService and then you just pass as param to the Activator:
public class Foo
{
public Foo(IEventService eventService)
{
Type type = Type.GetType("MySolution.Project.Web.App_Code.DataClass");
MethodInfo theMethod = type.GetMethod("Events_ListAll");
object classInstance = Activator.CreateInstance(type, eventService);
}
}
2nd - Get IKernel implementation of Ninject
If you are using NinjectWebCommom you can just change the bootstrapper prop to public and get the kernel like this NinjectWebCommom.bootstrapper.Kernel.get<IEventService>()
Type type = Type.GetType("MySolution.Project.Web.App_Code.DataClass");
MethodInfo theMethod = type.GetMethod("Events_ListAll");
object classInstance = Activator.CreateInstance(type, Kernel.Get<IEventService>());
How can I bind InitializerForXXX (non-generic implementation) to IInitializer<XXX> (generic interface) using Ninject Conventions so that requests for an IInitializer<T> resolve a non-generic implementation whose name starts with InitializerFor and end with typeof(T).Name like:
initializerFactory.CreateFor<Blue>(); //resolves InitializerOfBlue
initializerFactory.CreateFor<ShadeOfBlue>(); //resolves InitializerOfShadeOfBlue
where no non-abstract class directly implement IInitializer<T>, and some implementations inherit from other implementations:
InitializerForShadeOfBlue inherits from InitializerForBlue
InitializerForBlue inherits from abstract Initializer<Blue>
abstract Initializer<T> directly implements IInitializer<T>
I'm hoping I can use a .EndsWith(typeof(T).Name) for a given IInitializer<T> convention I can use, because there are literally hundreds of initializers in the ShadeOfxxx vein. If I have to map all of them, I'm better off finding a way to resolve with reflection at runtime.
Given the following:
UPDATE: bindings with custom binding generator (see my answer below for implementation)
void Bootstrap(IBindingRoot kernel)
{
kernel.Bind<IInitializerFactory>()
.To<InitializerFactory>()
.InSingletonScope();
kernel.Bind(scanner =>
scanner.FromThisAssembly().SelectAllClasses()
.WhichAreNotGeneric()
.InheritedFrom(typeof(IComplexContent))
.BindAllInterfaces());
kernel.Bind(scanner =>
scanner.FromThisAssembly().SelectAllClasses()
.WhichAreNotGeneric()
.InheritedFrom(typeof(IInitializer<>))
.BindWith<FirstTypeParameterNameMatchesEndOfBoundClassNameGenerator>());
}
main method
void Main(IEnumerable<string> values)
{
// setup bindings
var kernel = new StandardKernel();
Bootstrap(kernel);
IInitializerFactory initializerFactory =
kernel.Get<IInitializerFactory>();
IInitializer<ShadeOfBlueComplexContent> initializer =
initializerFactory.CreateFor<ShadeOfBlueComplexContent>();
initializer.Initialize(values);
}
initializer factory
interface IInitializerFactory
{
IInitializer<T> CreateFor<T>() where T : class, IComplexContent, new();
}
class InitializerFactory : IInitializerFactory
{
public IInitializer<T> CreateFor<T>() where T : class, IComplexContent, new()
{
return MagicallyGetInitializer<T>();
}
//behind the curtain, whirring noises are heard as 't' is resolved...
private static IInitializer<T> MagicallyGetInitializer<T>()
where T : class, IComplexContent, new()
{
IInitializer<T> i = null;
return i;
}
}
initializers
interface IInitializer<out T> where T : IComplexContent
{
T Initialize(IEnumerable<string> values);
}
abstract class Initializer<T> : IInitializer<T> where T : IComplexContent
{
public abstract T Initialize(IEnumerable<string> values);
}
class InitializerOfBlue : Initializer<Blue>
{
private readonly Blue _content;
public InitializerOfBlue(Blue content) {_content = content;}
public override Blue Initialize(IEnumerable<string> values)
{
_content.BlueSpecificProperty = values.ElementAt(0);
//... populate other blue-specific properties like this
return _content;
}
}
class InitializerOfShadeOfBlue : InitializerOfBlue
{
public InitializerOfShadeOfBlue(ShadeOfBlue content) : base(content){}
}
content models
interface IComplexContent
{
string OneBasicProperty { get; set; }
// other properties are specific to implementation
string UniqueOperation();
}
abstract class BaseComplexContent : IComplexContent
{
public string OneBasicProperty { get; set; }
public abstract string UniqueOperation();
}
class Blue : BaseComplexContent
{
// initializer sets this
public string PropertyForAllKindsOfBlue { get; set; }
// initializer doesn't interact with this
public override string UniqueOperation() {return "I'm plain.";}
}
class ShadeOfBlue : Blue
{
// initializer doesn't interact with this
public override string UniqueOperation() {return "I'm fabulous!";}
}
You are over specifying the class selection
kernel.Bind(scanner =>
scanner.FromThisAssembly().SelectAllClasses()
.WhichAreNotGeneric()
.InheritedFrom(typeof (IInitializer<>))
This is already enough. What you need to do though is to add a custom Binding Generator. That selects IInitializer<Blue> for InitializerForBlue and IInitializer<ShadeOfBlue> for InitializerForShadeOfBlue
https://github.com/ninject/ninject.extensions.conventions/wiki/Projecting-Services-to-Bind
BEGIN SOLUTION CANDIDATE - custom binding generator:
custom binding generator
Thanks for the advice, #RemoGloor and #RubenBartelink. I'm stumped though - the problem is that I wind up binding the IInitializer<Blue> to InitializerOfShadeOfBlue. I need to be able to somehow change the generic type argument from Blue to ShadeOfBlue in the IInitializer<Blue> binding candidate, since IInitializer<ShadeOfBlue> is what will be requested from the factory method at runtime.
Is there a way to modify the generic type argument list of the binding candidate? Or am I barking up the wrong implementation? Any edit suggestions to my OP or this answer are appreciated.
/// <summary>Creates bindings on open generic types where bound implementations'
/// names end with the name of the generic type argument</summary>
public class FirstTypeParameterNameMatchesEndOfBoundClassNameGenerator : IBindingGenerator
{
public IEnumerable<IBindingWhenInNamedWithOrOnSyntax<object>> CreateBindings(Type type, IBindingRoot bindingRoot)
{
if (type == null) throw new ArgumentNullException("type");
if (bindingRoot == null) throw new ArgumentNullException("bindingRoot");
// only consider concrete, non-abstract classes
if (type.IsInterface || type.IsAbstract) yield break;
var bindingType = GetBindingType(type);
if (bindingType != null)
yield return bindingRoot.Bind(bindingType).To(type);
// ARGH! bindingType == IInitializer`1[[Blue]] but I want
// IInitializer`1[[ShadeOfBlue]] for type == ShadeOfBlue
}
private static Type GetBindingType(Type type)
{
Type goodMatch = null;
foreach (var candidate in type.GetInterfaces())
{
// skip non-generic interfaces
if (!candidate.IsGenericType) continue;
// assumption: using argument in first position
var firstArg = candidate.GetGenericArguments().First();
if (!type.Name.EndsWith(firstArg.Name)) continue;
// IInitializer<XXX> matches InitializerOfXXX
goodMatch = candidate;
break;
}
if (goodMatch == null)
{
// if no match on interfaces, walk through the ancestor types
foreach (var candidate in type.GetAllAncestors())
{
goodMatch = GetBindingType(candidate);
if (goodMatch != null) break;
}
}
return goodMatch;
}
Type Extension helper
public static class TypeExtensions
{
// returns all ancestor types starting with the parent
public static IEnumerable<Type> GetAllAncestors(this Type type)
{
for (var current = type.BaseType; current != null; current = current.BaseType)
yield return current;
}
}
END SOLUTION CANDIDATE - custom binding generator