Ignore duplicates when serializing array with JSON.Net - c#

Clarification (to anyone in the same situation):
Note that my task is to serialize an existing legacy object. As such, I would prefer to tune the serializer rather than interfere with the data structure.
I believe in most cases it's better to remove the duplicates directly from the data, as indicated by #danny-chen's answer.
As part of my object that I want to serialize with JSON.Net, there is a string[] files property which contains duplicates:
some/path/to/f1.jpg
some/path/to/f1.jpg
some/path/to/f2.jpg
some/path/to/f3.jpg
some/path/to/f2.jpg
And let's suppose these are not necessarily in order (f2, f3, f2).
Is it possible to serialize the array and ignore the duplicates ? Expected result:
{
"files": [
"some/path/to/f1.jpg",
"some/path/to/f2.jpg",
"some/path/to/f3.jpg"
]
}
I have tried the PreserveReferencesHandling setting, but it didn't work as each file in the array is a different object, with a possibly repeated value.

It's not part of the serialization, it's part of the data processing. I suggest you remove the duplicates before serialization.
string[] files = GetFiles();
data.Files = files.Distinct().ToArray();
//serialize data
//instead of data.Files = files; and do tricky things in serialization

The simplest solution is to filter the list before serialization as suggested by #Danny Chen. However, if you absolutely have to do it during serialization you can use a custom JsonConverter.
Here is the code you would need:
public class RemoveDuplicatesConverter<T> : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return typeof(IEnumerable<T>).IsAssignableFrom(objectType);
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
writer.WriteStartArray();
foreach (T item in ((IEnumerable<T>)value).Distinct())
{
serializer.Serialize(writer, item);
}
writer.WriteEndArray();
}
public override bool CanRead
{
get { return false; }
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
throw new NotImplementedException();
}
}
To use the converter, add a [JsonConverter] attribute to the list or array property in your class for which you'd like to remove duplicates, as shown below. Be sure the generic type of the converter matches the type of your list.
class Foo
{
[JsonProperty("files")]
[JsonConverter(typeof(RemoveDuplicatesConverter<string>))]
public string[] Files { get; set; }
}
Then serialize as normal. The list in the JSON will have the duplicates removed, but the original list in your object will be unaffected.
string json = JsonConvert.SerializeObject(your_object, Formatting.Indented);
Fiddle: https://dotnetfiddle.net/vs2oWQ

Related

Only run JsonConverter on string tokens [duplicate]

This question already has answers here:
Best way to upgrade JSON field to a class structure
(1 answer)
Json.NET custom serialization with JsonConverter - how to get the "default" behavior
(1 answer)
Closed 1 year ago.
I'm trying to make a "reference" system, where JSON files can depend and reference other files. I know JSON.net has a built-in system like this but it doesn't fit my use case.
I implemented it like this:
public class ReferenceJsonConverter : JsonConverter<NamedType>
{
private readonly ContentLoader _contentLoader;
public ReferenceJsonConverter(ContentLoader contentLoader)
{
_contentLoader = contentLoader;
}
public override bool CanWrite => false;
public override void WriteJson(JsonWriter writer, NamedType value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
public override NamedType ReadJson(JsonReader reader, Type objectType, NamedType existingValue, bool hasExistingValue,
JsonSerializer serializer)
{
switch (reader.TokenType)
{
case JsonToken.String:
{
string value = reader.Value!.ToString().ToLower();
return _contentLoader.Load<NamedType>(value);
}
default:
{
NamedType namedType = (NamedType?) serializer.Deserialize(reader, objectType);
return namedType;
}
}
}
}
So pretty simple, if the token is a string it gets it from memory instead of deserializing it. The problem is, since the token type is the same as the parent type, this will loop infinitely because it will keep trying to use ReferenceJsonConverter to deserialize non-strings.
I've tried the following:
Re-implement CanConvert to try and "skip" StartObject tokens (janky, didn't work)
Have a different JsonConverter do the StartObject, then deserialize using ReferenceJsonConverter on string tokens (loops infinitely because I can't "skip" the converter)
Re-implement the JObject convert method (wayy too much work, will break on updates, and it's all internal anyways)
For clarity, it would look like this:
{ <--- Serialized NamedType, should use default deserializer
"Name": "Test", <-- Field of NamedType, should use default deserializer
"ReferenceOther": "ReferencedType" <--- Referenced NamedType, should use ReferenceJsonConverter
}

Handling quirky API JSON response body --- singleton versus array [duplicate]

This question already has answers here:
How to handle both a single item and an array for the same property using JSON.net
(9 answers)
Closed 2 years ago.
So my API service provider has inconsistent JSON response body elements. Specifically, a property that can contain one or many records isn't always passed back as an array. If there are multiple records it is. But if there is only one record then the record is passed back as a singleton.
What I am looking to do in my C# app is to programmatically append the [ ] around the singleton record, so that it's correctly defined as an array with a single element. More of a regex experiment, since I am handling the JSON response body as a string. I know it's perhaps less efficient, but the JSON string is what a third-party "black box" is looking for.
Example of the singleton JSON response body.
{
"Transfer":{
"transferID":"3581",
"sent":"true",
"received":"true"
}
}
And now an example of the array JSON body.
{
"Transfer":[
{
"transferID":"3581",
"sent":"true",
"received":"true"
},
{
"transferID":"3582",
"sent":"true",
"received":"true"
}
]
}
Just looking for the quickest and cleanest way to add the [ and the ] around the singleton Transfer record. Would like a reusable method, since the API service provider passes back other record types this way as well.
Apparently the most intuitive way to handle the inconsistency is described here --> How to handle both a single item and an array for the same property using JSON.net.
Specifically, here is the C# class that facilities what's required.
class SingleOrArrayConverter<T> : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return (objectType == typeof(List<T>));
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
JToken token = JToken.Load(reader);
if (token.Type == JTokenType.Array)
{
return token.ToObject<List<T>>();
}
return new List<T> { token.ToObject<T>() };
}
public override bool CanWrite
{
get { return false; }
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
}

Json.NET does not preserve primitive type information in lists or dictionaries of objects. Is there a workaround?

The following example illustrates a fundamental flaw in Json.NET's type handling:
List<object> items = new List<object>() {Guid.NewGuid(),DateTime.Now};
var settings = new JsonSerializerSettings() { TypeNameHandling=TypeNameHandling.All };
var json = JsonConvert.SerializeObject<List<object>>(value,settings);
resulting in the following JSON:
{"$type":"System.Collections.Generic.List`1[[System.Object, mscorlib]], mscorlib","$values":["9d7aa4d3-a340-4cee-baa8-6af0582b8acd","2014-07-28T21:03:17.1287029-04:00"]}
As you can see the list items have lost their type information. Deserializing that same JSON will result in a list containing just strings.
This issue was previously reported on codeplex and perfunctorily closed, stating including the type information would make the JSON too messy. I am surprised we aren't given a separate option to include primitive type information for such scenarios as the round-trip consistency is broken.
https://json.codeplex.com/workitem/23833
I would expect the data to come back with the same type information that it left with.
Does anybody have any suggestions or workarounds to remedy this undesired behavior?
Thanks,
Chris
Here is a solution using a custom JsonConverter:
public sealed class PrimitiveJsonConverter : JsonConverter
{
public PrimitiveJsonConverter()
{
}
public override bool CanRead
{
get
{
return false;
}
}
public override bool CanConvert(Type objectType)
{
return objectType.IsPrimitive;
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
throw new NotImplementedException();
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
switch (serializer.TypeNameHandling)
{
case TypeNameHandling.All:
writer.WriteStartObject();
writer.WritePropertyName("$type", false);
switch (serializer.TypeNameAssemblyFormat)
{
case FormatterAssemblyStyle.Full:
writer.WriteValue(value.GetType().AssemblyQualifiedName);
break;
default:
writer.WriteValue(value.GetType().FullName);
break;
}
writer.WritePropertyName("$value", false);
writer.WriteValue(value);
writer.WriteEndObject();
break;
default:
writer.WriteValue(value);
break;
}
}
}
Here is how to use it:
JsonSerializerSettings settings = new JsonSerializerSettings()
{
TypeNameHandling = TypeNameHandling.All,
};
settings.Converters.Insert(0, new PrimitiveJsonConverter());
return JsonConvert.SerializeObject(myDotNetObject, settings);
I'm currently using this solution to serialize an IDictionary<string, object> instance that can contain primitives.
Hacked this together and tested it out. Obviously this needs unit testing and is more a proof of concept. If you want a dirty solution to get you going this should get one started.
https://github.com/xstos/Newtonsoft.Json/commit/8d3507cbba78f7096a82e42973e56d69c9541c42

JSON.NET: Deserialize a class containig a List of objects derived from an interface

I have troubles deserializing my object. It contains following property:
public List<IShape> Shapes { get; set; };
and JSON.NET deserializer always tells me, that it is not possible to instantiate an interface.
I have several classes which implement interfaces which implement IShape interface, e.g. Polyline -> IPolyline -> IShape. I tried two solutions already:
https://stackoverflow.com/a/8031283/1525505
https://stackoverflow.com/a/12769061/1525505
But I got the same exception, that IShape cannot be instantied, was thrown.
I serialize the object with TypeNameHandling.Auto, TypeNameHandling.All doesn't help too, even when I use the converters mentioned in posts I linked above.
Does anyone know of a solution to this problem? If some code is needed I will gladly post it.
Here is a sample of JSON that is generated.
"$type": "SketchModel.Layer, SketchModel",
"Id": 57865477,
"Shapes": {
"$type": "System.Collections.Generic.List`1[[SketchModel.Shapes.AbstractShapes.IShape, SketchModel]], mscorlib",
"$values": [
{
"$type": "SketchModel.Shapes.Polyline, SketchModel",
This line is responsible for the problem:
"System.Collections.Generic.List`1[[SketchModel.Shapes.AbstractShapes.IShape, SketchModel]], mscorlib"
It simply doesn't know how to instantiate IShape. If I create a custom converter and let it return a Polyline for each IShape, it works, but doesn't create any other shapes (e.g. Ellipses).
In the
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) method which is overriden in the custom converter, if I let me print the full type name of objectType it's always IShape, never anything else...
The exception makes sense because the deserializer doesn't know what concrete type the interface is supposed to represent when hydrating.
During serialization, JSON.NET allows you to configure it to add some meta data to be used in this case. This SO question has an answer explaining how to configure it.
The configuration will add a type property to the JSON that will be used during deserialization.
I had exactly this problem and only solved it by explicitly providing a converter for the Type. It doesn't work if I use annotations - I need to pass the converters in at deserialization - basically like this:
state = JsonConvert.DeserializeObject<StateImpl>((String)stateObject, new JsonConverter[] { new StatePersistenceStateEntryConverter(), new StatePersistenceUserInteractionConverter() });
Where my StateImpl object included these properties:
[DataMember]
[JsonProperty("stateEntries", TypeNameHandling = TypeNameHandling.Auto)]
public List<IStateEntry> StateEntries
{
get;
set;
}
[DataMember]
[JsonProperty("precommitStateEntry", TypeNameHandling = TypeNameHandling.Auto)]
public IPrecommitStateEntry PrecommitStateEntry
{
get;
set;
}
IPrecommitStateEntry extends the IStateEntry interface (just FYI in case you're wondering why the extra logic in the converter).
Also - in my IStateEntry object, I have a similar child problem:
[DataMember]
[JsonProperty("userInteractions", TypeNameHandling = TypeNameHandling.Auto)]
public List<IUserInteraction> UserInteractions
{
get;
set;
}
So my object has child list properties of IStateEntry and an IStateEntry has a further child list of IUserInteraction. My converters are as follows:
public class StatePersistenceStateEntryConverter : CustomCreationConverter<IStateEntry>
{
public override IStateEntry Create(Type objectType)
{
if (objectType == typeof(IPrecommitStateEntry))
{
return new PrecommitStateEntry();
}
else
{
return new StateEntry();
}
}
}
And...
public class StatePersistenceUserInteractionConverter : CustomCreationConverter<IUserInteraction>
{
public override IUserInteraction Create(Type objectType)
{
return new UserInteraction();
}
}
Literally all they're doing is creating an instance of that particular object implementation.
So I don't know why the converters are needed as clearly a List can be instantiated - just not the IStateEntry individually. Clearly there's a bug in the NewtonSoft implementation somewhere - or I'm missing something fundamental.
Hope that helps. It was a frustrating few hours for me, but now working!
Without including concrete types within your Json string, you can use a JsonConverter to convert an IList<SomeInterface> to their concrete type:
Class to deserialize:
public partial class MovieInfo : IMovieInfo
{
~~~~
[JsonProperty("genres")]
[JsonConverter(typeof(ListConverter<IGenre, Genre>))]
public IList<IGenre> Genres { get; set; }
~~~~
}
JsonConverter example:
public class ListConverter<I, T> : JsonConverter
{
public override bool CanWrite => false;
public override bool CanRead => true;
public override bool CanConvert(Type objectType)
{
return objectType == typeof(I);
}
public override void WriteJson(JsonWriter writer,
object value, JsonSerializer serializer)
{
throw new InvalidOperationException("Use default serialization.");
}
public override object ReadJson(JsonReader reader,
Type objectType, object existingValue,
JsonSerializer serializer)
{
JArray jsonArray = JArray.Load(reader);
var deserialized = (List<T>)Activator.CreateInstance(typeof(List<T>));
serializer.Populate(jsonArray.CreateReader(), deserialized);
return deserialized as IList<I>;
}
}

Serializing strings containing apostrophes with JSON.Net

I am using JSON.Net as my serializer for a large MVC 3 web application in c# and the Razor view engine. For the initial page load in one view, there is a large amount of JSON dumped inside a script tag using #Html.Raw(JsonConvert.SerializeObject(myObject)).
The problem is that some values of some objects contain apostrophes (think names like O'Brien), which JSON.Net is not escaping or encoding in any way.
It's not an option to pre-encode the values stored in the database because that vastly complicates various other processes.
Is there a way to force JSON.Net to HTML encode the values of the objects that it serializes, the same way that the built-in JavaScriptSerializer does when you call JavaScriptSerializer.Serialize(myObject)? Or, is there a way to deal with this in the view?
JsonSerializerSettings settings = new JsonSerializerSettings
{
StringEscapeHandling = StringEscapeHandling.EscapeHtml
};
JsonConvert.SerializeObject(obj, settings);
Though there are some cases wherein you might want to drop some JSON into your page as a JavaScript string, or an HTML attribute value, most often what you'd do is simply include it directly into JavaScript source, because JSON is valid JavaScript syntax after all.
You can create custom JsonConverter like this:
public class EscapeQuoteConverter : JsonConverter
{
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
writer.WriteValue(value.ToString().Replace("'", "\\'"));
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
var value = JToken.Load(reader).Value<string>();
return value.Replace("\\'", "'");
}
public override bool CanConvert(Type objectType)
{
return objectType == typeof(string);
}
}
To use this only for Name property specify it by attribute:
public class Person
{
[JsonConverter(typeof(EscapeQuoteConverter))]
public string Name { get; set; }
}
To apply Converter to all strings use:
JsonConvert.SerializeObject(person, Formatting.Indented, new EscapeQuoteConverter());
Use System.Web.HttpUtility.HtmlEncode
HttpUtility.HtmlEncode(JsonConvert.SerializeObject(myObject))

Categories

Resources