Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
129 views
in Technique[技术] by (71.8m points)

c# - Custom conversion of specific objects in JSON.NET

I'm using JSON.NET to serialize some of my objects, and i'd like to know if there is a simple way to override the default json.net converter only for a specific object?

Currently I have the following class:

public class ChannelContext : IDataContext
{
    public int Id { get; set; }
    public string Name { get; set; }
    public IEnumerable<INewsItem> Items { get; set; }
}

JSON.NET currently serializes the above like:

{
    "Id": 2,
    "Name": "name value",
    "Items": [ item_data_here ]
}

Is it possible just for that specific class to format it this way instead:

"Id_2":
{
    "Name": "name value",
    "Items": [ item data here ]
}

I'm kinda new to JSON.NET.. I was wondering if the above has something to do with writing a custom converter. I wasn't able to find any concrete examples on how to write one, If anyone can point me out to a specific source, I'll really appreciate it.

I need to find a solution which makes that specific class always convert the same, because the above context is a part of an even bigger context which the JSON.NET default converter converts just fine.

Hope my question is clear enough...

UPDATE:

I've found how to create a new custom converter (by creating a new class which inherits from JsonConverter and override it's abstract methods), I overriden the WriteJson method as follows:

public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        ChannelContext contextObj = value as ChannelContext;

        writer.WriteStartObject();
        writer.WritePropertyName("id_" + contextObj.Id);
        writer.WriteStartObject();
        writer.WritePropertyName("Name");
        serializer.Serialize(writer, contextObj.Name);

        writer.WritePropertyName("Items");
        serializer.Serialize(writer, contextObj.Items);
        writer.WriteEndObject();
        writer.WriteEndObject();
    }

This indeed does the job successfully, but... I'm intrigued if there's a way to serialize the rest of the object properties by reusing the default JsonSerializer (or converter for that matter) instead of manually "Writing" the object using the jsonwriter methods.

UPDATE 2: I'm trying to get a more generic solution and came up with the following:

public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        writer.WriteStartObject();

        // Write associative array field name
        writer.WritePropertyName(m_FieldNameResolver.ResolveFieldName(value));

        // Remove this converter from serializer converters collection
        serializer.Converters.Remove(this);

        // Serialize the object data using the rest of the converters
        serializer.Serialize(writer, value);

        writer.WriteEndObject();
    }

This works fine when adding the converter manually to the serializer, like this:

jsonSerializer.Converters.Add(new AssociativeArraysConverter<DefaultFieldNameResolver>());
jsonSerializer.Serialize(writer, channelContextObj);

But doesn't work when using the [JsonConverter()] attribute set to my custom coverter above the ChannelContext class because of a self reference loop that occurs when executing:

serializer.Serialize(writer, value)

This is obviously because my custom converter is now considered the default converter for the class once it is set with the JsonConverterAttribute, so I get an inifinite loop. The only thing I can think of, in order to solve this problem is inheriting from a basic, jsonconverter class, and calling the base.serialize() method instead... But is such a JsonConverter class even exists?

Thanks a lot!

Mikey

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

If anyone's interested in my solution:

When serializing certain collections, I wanted to create an associative json array instead of a standard json array, so my colleague client side developer can reach those fields efficiently, using their name (or key for that matter) instead of iterating through them.

consider the following:

public class ResponseContext
{
    private List<ChannelContext> m_Channels;

    public ResponseContext()
    {
        m_Channels = new List<ChannelContext>();
    }

    public HeaderContext Header { get; set; }

    [JsonConverter(
        typeof(AssociativeArraysConverter<ChannelContextFieldNameResolver>))]
    public List<ChannelContext> Channels
    {
        get { return m_Channels; }
    }

}

[JsonObject(MemberSerialization = MemberSerialization.OptOut)]
public class ChannelContext : IDataContext
{
    [JsonIgnore]
    public int Id { get; set; }

    [JsonIgnore]
    public string NominalId { get; set; }

    public string Name { get; set; }

    public IEnumerable<Item> Items { get; set; }
}

Response context contains the whole response which is written back to the client, like you can see, it includes a section called "channels", And instead of outputting the channelcontexts in a normal array, I'd like to be able to output in the following way:

"Channels"
{
"channelNominalId1":
{
  "Name": "name value1"
  "Items": [ item data here ]
},
"channelNominalId2":
{
  "Name": "name value2"
  "Items": [ item data here ]
}
}

Since I wanted to use the above for other contexts as well, and I might decide to use a different property as their "key", or might even choose to create my own unique name, which doesn't have to do with any property, I needed some sort of a generic solution, therefore I wrote a generic class called AssociativeArraysConverter, which inherits from JsonConverter in the following manner:

public class AssociativeArraysConverter<T> : JsonConverter
    where T : IAssociateFieldNameResolver, new()
{
    private T m_FieldNameResolver;

    public AssociativeArraysConverter()
    {
        m_FieldNameResolver = new T();
    }

    public override bool CanConvert(Type objectType)
    {
        return typeof(IEnumerable).IsAssignableFrom(objectType) &&
                !typeof(string).IsAssignableFrom(objectType);
    }

    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        IEnumerable collectionObj = value as IEnumerable;

        writer.WriteStartObject();

        foreach (object currObj in collectionObj)
        {
            writer.WritePropertyName(m_FieldNameResolver.ResolveFieldName(currObj));
            serializer.Serialize(writer, currObj);
        }

        writer.WriteEndObject();
    }
}

And declared the following Interface:

public interface IAssociateFieldNameResolver
{
    string ResolveFieldName(object i_Object);
}

Now all is left to do, is create a class which implements IAssociateFieldNameResolver's single function, which accepts each item in the collection, and returns a string based on that object, which will act as the item's associative object's key.

Example for such a class is:

public class ChannelContextFieldNameResolver : IAssociateFieldNameResolver
{
    public string ResolveFieldName(object i_Object)
    {
        return (i_Object as ChannelContext).NominalId;
    }
}

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

1.4m articles

1.4m replys

5 comments

57.0k users

...