Serialize class that provides ToString() [duplicate] - c#

This question already has answers here:
How to make JSON.Net serializer to call ToString() when serializing a particular type?
(4 answers)
Closed 3 years ago.
Suppose I have some class that defines ToString():
class Person {
public override string ToString() { /* ... */ }
}
And suppose an instance is contained in some model:
public Person Person { get; }
Then it is serialized like this:
"person": {
"value": "Foo Bar"
}
But what I expected was this:
"person": "Foo Bar"
Can I do this somehow, or must I use a custom converter?
UPDATE
No this is not a dupe of that linked question. That shows how to do two-way conversion to/from a type. I want to do one-way conversion, given my type already has a ToString method - i.e. serialization only, not deserialization.
The question is not how to write a type converter - it is whether this one-way serialization is possible without a type converter.

I recently ran into this on a project, and the only thing my team came to, was having to write a type converter.

I want a one-way conversion which relies on my type's ToString() overload. This is for data serialized on the server and used in a REST response.
This is what I did:
public class StringConverter : JsonConverter {
public override bool CanConvert(Type objectType) {
return true;
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) {
throw new NotSupportedException("This one-way converter cannot be used for deserialization.");
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) {
writer.WriteValue(value.ToString());
}
}
Used like this:
[JsonConverter(typeof(StringConverter))]
public Person Person { get; } // assumes Person has overloaded ToString()
And the serialized result is this:
"person": "Foo Bar"
(instead of "person": { "value": "Foo Bar" }.)
Maybe a better name is ToStringSerializer or OneWayStringConverter.

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
}

serialzie c# object to json array with different types [duplicate]

This question already has answers here:
JSON deserialization - Map array indices to properties with JSON.NET
(3 answers)
Serialize an object's properties to separate JSON objects in an array
(2 answers)
How to deserialize an array of values with a fixed schema to a strongly typed data class?
(3 answers)
Closed 2 years ago.
I have trouble to convert an c# object so an json array.
public class ChartValue
{
public DateTime Timestamp { get; set; }
public float Value { get; set; }
}
This object should be serialized like this for example:
[
"2020-03-03T13:27:45",
52.2
]
I need to stick to this json structure so adjusting is not possible.
For serializian the Newtonsoft.json class is used.
Using a string array failed for me because the value is then "52.2" which is not accepted.
Any ideas how to adjust the serialization or to create a "multitype" array
thanks for help!
I'm not sure that there is a build in way, but you can write your own JsonConverter using some reflection magic:
public class ChartValueToArrayJsonConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return objectType == typeof(ChartValue);
}
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)
{
if (value == null) return;
writer.WriteStartArray();
var properties = value.GetType().GetProperties();
foreach (var property in properties)
writer.WriteValue(value.GetType().GetProperty(property.Name).GetValue(value));
writer.WriteEndArray();
}
}
Usage:
JsonConvert.SerializeObject(new ChartValue(), new ChartValueToArrayJsonConverter()) // results in ["0001-01-01T00:00:00",0.0]
or mark your ChartValue class with [JsonConverterAttribute(typeof(ChartValueToArrayJsonConverter))] attribute if you want this behavior globally.

JsonConverter to handle dynamic key names [duplicate]

This question already has answers here:
How can I parse a JSON string that would cause illegal C# identifiers?
(3 answers)
Closed 3 years ago.
I am working on code that processes responses from OpenLibrary. This is a rest service that returns books based on a passed in ISBN. An example URL is this:
https://openlibrary.org/api/books?bibkeys=ISBN:9780596005405&jscmd=data&format=json
{
ISBN:9780596005405: {
publishers: [
{
name: "O'Reilly"
}
],
pagination: "xxxii, 854 p. :",
identifiers: {
lccn: [
"2006281089"
],
openlibrary: [
"OL17924716M"
],
isbn_10: [
"0596005407"
],
goodreads: [
"58129"
],
librarything: [
"187028"
],
},
.... other properties omitted for brevity
}
I have these objects:
public class OLResult
{
Publishers Publishers { get; set; }
// other properties
}
public class Publishers
{
// properties go here
}
I created C# objects that are identical to what OpenLibrary returns. However, if you look at the response, you'll notice it's a JSON with a root element with a weird key: ISBN: followed by the passed in ISBN number. Newtonsoft.Json does not know how to map this dynamic key to my OLResult object.
I have created a simple converter to manually convert this JSON to the correct object:
public class OpenLibraryResultConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return true;
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
OLResult result = new OLResult();
// Perform magic to copy JSON results to result object.
return result;
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
}
I have also added an attribute [JsonConverter(typeof(OpenLibraryResultConverter))] to my OLResult class. When I call JsonConvert.DeserializeObject<OLResult>(jsonStringResult);, and use the debugger to check the ReadJson method, I can not seem to find the JSON data of the jsonStringResult parameter I passed in to the converter. Most properties of the parameters passed into the ReadJson method appear null or empty.
My question: how do I successfully read out the JSON string in my new JsonConverter?
In case you don't need that dynamic property number: ISBN:XXXXXX you can go with something simple like using partial JSON serialization that you can check out here
It's not clear from the post., but the first part is actually a label, so the overall structure is like this:
{
"ISBN:9780596005405": {...}
}
Note that the label contains a colon, which makes it more difficult to translate directly into a C# object.
But either way, you'll need to either define an object class for that and parse it, or as #Ernestas suggests, skip it.

Can I parse json either into a string or another concrete type as object?

I'd like to have property of type object that can be either a string or Template type.
Is it possible to tell Json.NET to parse something into one of several specified types?
class MyClass
{
public object Template { get; set; }
}
where Template = "TemplateName"
{
"Template": "TemplateName"
}
or Template = new Template()
{
"Template": { "Name": "OtherTamplate", ... }
}
UPDATE:
I tried to follow #krillgar' advice and create a custom JsonConverter but unfortunatelly the CanConvert method receives only the target type, in this case object. This information is not enough to tell wheter it can be deserialized (in case I had other object properties). I guess I need it to be a Template after all or create a derived type like TemplateReference or something:
class myconverter : JsonConverter
{
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
throw new NotImplementedException();
}
public override bool CanConvert(Type objectType)
{
// objectType = typeof(object)
throw new NotImplementedException();
}
}
Configuration = JsonConvert.DeserializeObject<MyClass>(text, new myconverter());
Disclaimer
This question has once been closed as a duplicate of How to deserialize a JSON property that can be two different data types using Json.NET. Because at the time of writing my question I hadn't known that there already was a similar one I'd like to clarify the difference between them to prevent it from being closed in future:
The other question is about how to deserialize different values into a concrete type whereas mine is about deserializing different values into an object. It might seem to be the same at the first look because in both examples only the type of the property is different but it has a huge impact on the overall application design. It's important for me that I can use an object to store different specialized types rather then one type having multiple responsibilities.
This problem can be solved by using a custom JsonConverter. Here is a generic version that should work for this situation:
class ObjectOrStringConverter<T> : JsonConverter
{
public override bool CanConvert(Type objectType)
{
// CanConvert is not called when the [JsonConverter] attribute is used
return false;
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
JToken token = JToken.Load(reader);
if (token.Type == JTokenType.Object)
{
return token.ToObject<T>(serializer);
}
return token.ToString();
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
serializer.Serialize(writer, value);
}
}
To use the converter, all you need to do is add a [JsonConverter] attribute to the property in your class that can be either a string or an object. The generic type parameter must match the type of non-string object you are expecting.
class MyClass
{
[JsonConverter(typeof(ObjectOrStringConverter<Template>))]
public object Template { get; set; }
}
Below is a demonstration of the converter in action:
class Program
{
static void Main(string[] args)
{
Console.WriteLine("--- first run ---");
string json = #"
{
""Template"": ""TemplateName""
}";
DeserializeAndDump(json);
Console.WriteLine("--- second run ---");
json = #"
{
""Template"": { ""Name"": ""OtherTemplate"" }
}";
DeserializeAndDump(json);
}
static void DeserializeAndDump(string json)
{
MyClass obj = JsonConvert.DeserializeObject<MyClass>(json);
if (obj.Template == null)
{
Console.WriteLine("Template property is null");
}
else
{
Console.WriteLine("Template property is a " + obj.Template.GetType().Name);
string name = "(unknown)";
if (obj.Template is Template) name = ((Template)obj.Template).Name;
else if (obj.Template is string) name = (string)obj.Template;
Console.WriteLine("Template name is \"" + name + "\"");
}
Console.WriteLine();
}
}
class Template
{
public string Name { get; set; }
}
And here is the output from the above:
--- first run ---
Template property is a String
Template name is "TemplateName"
--- second run ---
Template property is a Template
Template name is "OtherTemplate"
Fiddle: https://dotnetfiddle.net/Lw3RaN
I don't know if you can do that, but you could go another way. Change your "Template" property to be a Template instead of an object and use a custom property of the Template class to know weither you want to serialize it as a Template or a string.
class MyClass
{
[JsonConverter(typeof(TemplateConverter))]
public Template Template { get; set; }
}
class Template
{
/* Your Template class */
public Type TypeToSerializeInto { get; private set; }
}
public class TemplateConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return objectType == typeof(Template);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) { }
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
Template val = value as Template;
writer.WriteStartObject();
writer.WritePropertyName("Template");
if (val.TypeToSerializeInto == typeof(Template))
serializer.Serialize(writer, val);
else if (val.TypeToSerializeInto == typeof(string))
serializer.Serialize(writer, val.Name);
writer.WriteEndObject();
}
}
If you have a property, which type is an abstract type - like object, on de-serialization, you can know the specific type that was serialized by serializing somewhere also the name of that specific type.
So your json should look like this:
{
"MyClass": {
"Template": "some name",
"type": "System.String"
}
}
This way on deserialization you can check what type was that property before the serialization (in this case String)
Another way to determine the type is by checking the json structure as you can see here:
C#: Deserializing JSON when one field can be different types

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>;
}
}

Categories

Resources