2

Here is my example:

JSON request string:

{
    entity: '09f7cb28-0464-41c8-a20d-1b05eb1cda0a'
}

My request object:

public class Request {

    public Request() { }

    [JsonProperty("entity")]
    private string EntityIdentifier { get; set; }

    public EntityObject Entity { get; set; }

}

I've got this to work so that the string is passed to the EntityIdentifier, which is fine, then in my code I find the actual entity using the entity identifier property, populate the Entity property with the found entity but then this is what I get when I serialize the object:

{
    entity: '09f7cb28-0464-41c8-a20d-1b05eb1cda0a',
    Entity: {
        // my object's properties
    }
}

When all I really want is:

{
    entity: {
        // my object's properties
    }
}

Now, I know I could split this out into two different classes, and I may have to, but if there is a way to keep it all in the same class, it would be awesome and save me a lot of coding time.

To clarify because I seem to not be explaining what I want very well:

Disclaimer: This doesn't exist (AFAIK) but this is what I would like:

public class Request {

    public Request() { }

    [JsonProperty("entity", OnlyWhen=Deserializing)]
    private string EntityIdentifier { get; set; }

    [JsonProperty("entity", OnlyWhen=Serializing)]
    public EntityObject Entity { get; set; }

}

This is what I would really like to achieve, but as far as I can see the only place I could realistically put this type of code is in a custom converter, but unfortunately, I can't seem to be able to determine whether the converter is being used for serialization or deserialization when it is being used.

Anupheaus
  • 3,383
  • 2
  • 24
  • 30
  • Perhaps you just need to identify why JSON.Net can't serialize the EntityObject (such as infinite recursion) – Neil Thompson Aug 04 '14 at 12:17
  • Do you just need to mark your `Entity` property as not serialised? – Richard Aug 04 '14 at 12:20
  • You need to serialize the id if thats how you retrieve correct item. When you deserialize, just return the Entity (object) from result, not whole Request? – Mikko Viitala Aug 04 '14 at 12:52
  • It isn't that I don't want Entity property serialized or that there is an error...to put it really simply what I want is "When deserialising write to [EntityIdentifier] only, when serializing only serialise [Entity], but both properties are read and written as [entity]", does this make more sense? – Anupheaus Aug 04 '14 at 12:55
  • You or me got terms wrong. Use id only as name you de/serialize your objects, maybe, and you don't have to worry about it? – Mikko Viitala Aug 04 '14 at 13:03
  • @MikkoViitala This is a cut-down version, on the request object I will have EntityIdentifier, ClientIdentifier, LanguageIdentifier, and many more, and I want to be able to populate and serialize the found instances rather than the requested ids. – Anupheaus Aug 04 '14 at 13:06

4 Answers4

1

I believe you just need to mark Entity with [ScriptIgnore] and/or [JsonIgnore], like this:

[ScriptIgnore]
[JsonIgnore]
public EntityObject Entity { get; set; }

I have heard of JsonIgnore not working sometimes. Using both is probably your best bet.

Also - I believe your wording is incorrect when describing the problem. You state: "this is what I get when I deserialize the object" - when in fact, I believe you mean to say "serialize".

If you need to populate Entity when EntityIdentifier is set, then replace the code for EntityIdentifier with something like:

string _eId;
[JsonProperty("entity")]
    private string EntityIdentifier 
    { 
        get{return _eId;}
        set
        {
            _eId = value;
            Entity = someMethodToRetrieveTheEntityById(_eId);
        }
    }
Mike
  • 643
  • 3
  • 10
  • Sorry yes I did mean serialize, will change it now. – Anupheaus Aug 04 '14 at 12:36
  • Won't work because when I serialise it, entity will be a string not an object, this isn't satisfying what I have specified under "When all I really want is:". If you read my comment under the initial post this might clarify this for you too. – Anupheaus Aug 04 '14 at 12:58
  • 1
    So - when deserializing, you only want to look at the Entity ID, but when serializing, you want to output all entity properties? This doesn't make much sense. Realistically, the Entity should know it's own ID - and having a separate property for it in Request shouldn't be necessary. – Mike Aug 04 '14 at 13:27
  • Please see my edit for this, the Entity ID is on the request object, the Entity object is on the request object. It is not that I'm specifying the entity ID on the entity object, I'm specifying the Entity ID on the request object then running some code to populate the Entity object using that Entity ID on the request object. Hope this makes sense. – Anupheaus Aug 04 '14 at 13:36
1

How about using the JsonIgnore attribute?

    public class Request
    {

        public Request() { }

        [JsonIgnore]
        private string EntityIdentifier { get; set; }

        [JsonProperty("entity")]
        public EntityObject Entity { get; set; }

    }
JFM
  • 753
  • 12
  • 16
  • But this won't work when I deserialize the string now because the string can't be converted to an EntityObject. – Anupheaus Aug 04 '14 at 12:37
1

The problem here could be that you're trying to force one class do the job of two. Why not do:

// Deserialize requests into this.
public class EntityRequest
{
    [JsonProperty("entity")]
    private string EntityIdentifier { get; set; }
}

// Serialize these to file/etc.
public class EntityData
{
    [JsonProperty("entity")]
    public EntityObject Entity { get; set; }
}

Then you deserialize requests into EntityRequest objects, load EntityData objects using the EntityRequest and some other logic, then serialize the EntityData objects to file. The JsonProperty attributes here mean that both the request and the output entity are both called 'entity' just like in your OP. This seems to be the workflow you're after.

Cramming everything into one class is making this problem more complicated than it needs to be.

jmsb
  • 4,846
  • 5
  • 29
  • 38
  • Thanks but I already said in my initial post that I could split these out but if there was another way then that would be better (for me in my circumstances), otherwise I'll have to go down this route. – Anupheaus Aug 04 '14 at 13:34
  • Splitting the entities like this would be a clean, quick, readable, and maintainable way to have the data you want go in and out of your system. Your OP is a good question since it highlights what can and cannot be done using JSON.NET in a common use-case, but it feels like you are trying to fit a square peg that into a round hole, when you have a round peg available as well. – jmsb Aug 04 '14 at 13:42
0

You can solve this problem with a custom JsonConverter similar to this:

public class RequestConverter : JsonConverter
{
    public override bool CanConvert(Type objectType)
    {
        return (objectType == typeof(Request));
    }

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        // On deserialization, the JSON has an entity identifier (GUID)
        // so use it to retrieve the actual object from the database.
        JToken token = JToken.Load(reader);
        Request req = new Request();
        req.Entity = RetrieveFromDatabase(token["entity"].ToString());
        return req;
    }

    private EntityObject RetrieveFromDatabase(string entityIdentifier)
    {
        // Implement this method to retrieve the actual EntityObject from the DB.
        // (Return null if the object is not found.)
    }

    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        // On serialization, write out just the entity object properties
        Request req = (Request)value;
        JObject obj = new JObject();
        obj.Add("entity", JToken.FromObject(req.Entity));
        obj.WriteTo(writer);
    }
}

To use the converter, simply decorate your Request class with a [JsonConverter] attribute as shown below. Note that you can remove the private EntityIdentifier property, as it will no longer be needed. The converter has responsibility for retrieving the EntityObject from the database based on the identifier in the JSON.

[JsonConverter(typeof(RequestConverter))]
public class Request
{
    public Request() { }

    public EntityObject Entity { get; set; }
}

Note, if you don't want the converter to have responsibility for populating the entity object, you can still use the converter idea. In that case, make the ReadJson method set the EntityIdentifier property on the request. However, since you have made this property private, you will either need to use reflection to do this, make the property public, or make a constructor for the Request that the converter can use to instantiate it with the identifier.

Brian Rogers
  • 125,747
  • 31
  • 299
  • 300